Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/common/crash_handler.cpp
4802 views
1
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "crash_handler.h"
5
#include "dynamic_library.h"
6
#include "file_system.h"
7
#include "string_util.h"
8
#include <cinttypes>
9
#include <csignal>
10
#include <cstdio>
11
#include <ctime>
12
13
#if defined(_WIN32)
14
#include "windows_headers.h"
15
16
#include "thirdparty/StackWalker.h"
17
#include <DbgHelp.h>
18
#include <exception>
19
20
namespace {
21
class CrashHandlerStackWalker : public StackWalker
22
{
23
public:
24
explicit CrashHandlerStackWalker(HANDLE out_file);
25
~CrashHandlerStackWalker();
26
27
protected:
28
void OnOutput(LPCSTR szText) override;
29
30
private:
31
HANDLE m_out_file;
32
};
33
} // namespace
34
35
CrashHandlerStackWalker::CrashHandlerStackWalker(HANDLE out_file)
36
: StackWalker(RetrieveVerbose, nullptr, GetCurrentProcessId(), GetCurrentProcess()), m_out_file(out_file)
37
{
38
}
39
40
CrashHandlerStackWalker::~CrashHandlerStackWalker() = default;
41
42
void CrashHandlerStackWalker::OnOutput(LPCSTR szText)
43
{
44
if (m_out_file)
45
{
46
DWORD written;
47
WriteFile(m_out_file, szText, static_cast<DWORD>(std::strlen(szText)), &written, nullptr);
48
}
49
50
OutputDebugStringA(szText);
51
}
52
53
static bool WriteMinidump(HMODULE hDbgHelp, HANDLE hFile, HANDLE hProcess, DWORD process_id, DWORD thread_id,
54
PEXCEPTION_POINTERS exception, MINIDUMP_TYPE type)
55
{
56
using PFNMINIDUMPWRITEDUMP =
57
BOOL(WINAPI*)(HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType,
58
PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
59
PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
60
61
PFNMINIDUMPWRITEDUMP minidump_write_dump =
62
hDbgHelp ?
63
reinterpret_cast<PFNMINIDUMPWRITEDUMP>(reinterpret_cast<void*>(GetProcAddress(hDbgHelp, "MiniDumpWriteDump"))) :
64
nullptr;
65
if (!minidump_write_dump)
66
return false;
67
68
MINIDUMP_EXCEPTION_INFORMATION mei = {};
69
if (exception)
70
{
71
mei.ThreadId = thread_id;
72
mei.ExceptionPointers = exception;
73
mei.ClientPointers = FALSE;
74
return minidump_write_dump(hProcess, process_id, hFile, type, &mei, nullptr, nullptr);
75
}
76
77
__try
78
{
79
RaiseException(EXCEPTION_INVALID_HANDLE, 0, 0, nullptr);
80
}
81
__except (WriteMinidump(hDbgHelp, hFile, GetCurrentProcess(), GetCurrentProcessId(), GetCurrentThreadId(),
82
GetExceptionInformation(), type),
83
EXCEPTION_EXECUTE_HANDLER)
84
{
85
}
86
87
return true;
88
}
89
90
static std::wstring s_write_directory;
91
static DynamicLibrary s_dbghelp_module;
92
static CrashHandler::CleanupHandler s_cleanup_handler;
93
static bool s_in_crash_handler = false;
94
95
static void GenerateCrashFilename(wchar_t* buf, size_t len, const wchar_t* prefix, const wchar_t* extension)
96
{
97
SYSTEMTIME st = {};
98
GetLocalTime(&st);
99
100
_snwprintf_s(buf, len, _TRUNCATE, L"%s%scrash-%04u-%02u-%02u-%02u-%02u-%02u-%03u.%s", prefix ? prefix : L"",
101
prefix ? L"\\" : L"", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds,
102
extension);
103
}
104
105
static void WriteMinidumpAndCallstack(PEXCEPTION_POINTERS exi, const std::string_view message)
106
{
107
wchar_t filename[1024] = {};
108
GenerateCrashFilename(filename, std::size(filename), s_write_directory.empty() ? nullptr : s_write_directory.c_str(),
109
L"txt");
110
111
// might fail
112
HANDLE hFile = CreateFileW(filename, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
113
DWORD written;
114
115
if (!message.empty() && hFile != INVALID_HANDLE_VALUE)
116
{
117
const char newline = '\n';
118
WriteFile(hFile, message.data(), static_cast<DWORD>(message.length()), &written, nullptr);
119
WriteFile(hFile, &newline, sizeof(newline), &written, nullptr);
120
}
121
122
GenerateCrashFilename(filename, std::size(filename), s_write_directory.empty() ? nullptr : s_write_directory.c_str(),
123
L"dmp");
124
125
const MINIDUMP_TYPE minidump_type =
126
static_cast<MINIDUMP_TYPE>(MiniDumpNormal | MiniDumpWithHandleData | MiniDumpWithProcessThreadData |
127
MiniDumpWithThreadInfo | MiniDumpWithIndirectlyReferencedMemory);
128
const HANDLE hMinidumpFile = CreateFileW(filename, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
129
if (hMinidumpFile == INVALID_HANDLE_VALUE ||
130
!WriteMinidump(static_cast<HMODULE>(s_dbghelp_module.GetHandle()), hMinidumpFile, GetCurrentProcess(),
131
GetCurrentProcessId(), GetCurrentThreadId(), exi, minidump_type))
132
{
133
static const char error_message[] = "Failed to write minidump file.\n";
134
if (hFile != INVALID_HANDLE_VALUE)
135
WriteFile(hFile, error_message, sizeof(error_message) - 1, &written, nullptr);
136
}
137
if (hMinidumpFile != INVALID_HANDLE_VALUE)
138
CloseHandle(hMinidumpFile);
139
140
CrashHandlerStackWalker sw(hFile);
141
sw.ShowCallstack(GetCurrentThread(), exi ? exi->ContextRecord : nullptr);
142
143
if (hFile != INVALID_HANDLE_VALUE)
144
CloseHandle(hFile);
145
}
146
147
static LONG NTAPI ExceptionHandler(PEXCEPTION_POINTERS exi)
148
{
149
// if the debugger is attached, or we're recursively crashing, let it take care of it.
150
if (!s_in_crash_handler)
151
{
152
s_in_crash_handler = true;
153
if (s_cleanup_handler)
154
s_cleanup_handler();
155
156
char message[128];
157
std::snprintf(message, std::size(message), "Exception 0x%08X at 0x%p",
158
static_cast<unsigned>(exi->ExceptionRecord->ExceptionCode), exi->ExceptionRecord->ExceptionAddress);
159
160
WriteMinidumpAndCallstack(exi, message);
161
}
162
163
// returning EXCEPTION_CONTINUE_SEARCH makes sense, except for the fact that it seems to leave zombie processes
164
// around. instead, force ourselves to terminate.
165
TerminateProcess(GetCurrentProcess(), 0xFEFEFEFEu);
166
return EXCEPTION_CONTINUE_SEARCH;
167
}
168
169
static void InvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file,
170
unsigned int line, uintptr_t pReserved)
171
{
172
// if the debugger is attached, or we're recursively crashing, let it take care of it.
173
if (!s_in_crash_handler && !IsDebuggerPresent())
174
{
175
s_in_crash_handler = true;
176
if (s_cleanup_handler)
177
s_cleanup_handler();
178
179
WriteMinidumpAndCallstack(nullptr, "Invalid parameter handler invoked");
180
}
181
182
__fastfail(FAST_FAIL_INVALID_ARG);
183
}
184
185
static void RaiseHandler(const std::exception& ex)
186
{
187
// if the debugger is attached, or we're recursively crashing, let it take care of it.
188
if (!s_in_crash_handler && !IsDebuggerPresent())
189
{
190
s_in_crash_handler = true;
191
if (s_cleanup_handler)
192
s_cleanup_handler();
193
194
WriteMinidumpAndCallstack(nullptr, ex.what());
195
}
196
}
197
198
static void PureCallHandler()
199
{
200
// if the debugger is attached, or we're recursively crashing, let it take care of it.
201
if (!s_in_crash_handler && !IsDebuggerPresent())
202
{
203
s_in_crash_handler = true;
204
if (s_cleanup_handler)
205
s_cleanup_handler();
206
207
WriteMinidumpAndCallstack(nullptr, "Pure call handler invoked");
208
}
209
210
__fastfail(FAST_FAIL_INVALID_ARG);
211
}
212
213
static void TerminateHandler()
214
{
215
if (!s_in_crash_handler && !IsDebuggerPresent())
216
{
217
s_in_crash_handler = true;
218
if (s_cleanup_handler)
219
s_cleanup_handler();
220
221
WriteMinidumpAndCallstack(nullptr, "Terminate handler invoked");
222
}
223
224
if (IsDebuggerPresent())
225
__debugbreak();
226
227
TerminateProcess(GetCurrentProcess(), 0xFBFBFBFBu);
228
}
229
230
static void AbortSignalHandler(int signal)
231
{
232
// if the debugger is attached, or we're recursively crashing, let it take care of it.
233
if (!s_in_crash_handler && !IsDebuggerPresent())
234
{
235
s_in_crash_handler = true;
236
if (s_cleanup_handler)
237
s_cleanup_handler();
238
239
WriteMinidumpAndCallstack(nullptr, "Pure call handler invoked");
240
}
241
242
if (IsDebuggerPresent())
243
__debugbreak();
244
245
TerminateProcess(GetCurrentProcess(), 0xFAFAFAFAu);
246
}
247
248
bool CrashHandler::Install(CleanupHandler cleanup_handler)
249
{
250
// load dbghelp at install/startup, that way we're not LoadLibrary()'ing after a crash
251
// .. because that probably wouldn't go down well.
252
HMODULE mod = StackWalker::LoadDbgHelpLibrary();
253
if (mod)
254
s_dbghelp_module.Adopt(mod);
255
256
s_cleanup_handler = cleanup_handler;
257
258
SetUnhandledExceptionFilter(ExceptionHandler);
259
_set_invalid_parameter_handler(InvalidParameterHandler);
260
_set_purecall_handler(PureCallHandler);
261
std::exception::_Set_raise_handler(RaiseHandler);
262
std::set_terminate(TerminateHandler);
263
#ifdef _DEBUG
264
_set_abort_behavior(_WRITE_ABORT_MSG, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
265
#else
266
_set_abort_behavior(_WRITE_ABORT_MSG | _CALL_REPORTFAULT, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
267
#endif
268
signal(SIGABRT, AbortSignalHandler);
269
return true;
270
}
271
272
void CrashHandler::SetWriteDirectory(std::string_view dump_directory)
273
{
274
s_write_directory = StringUtil::UTF8StringToWideString(dump_directory);
275
}
276
277
void CrashHandler::WriteDumpForCaller(std::string_view message)
278
{
279
WriteMinidumpAndCallstack(nullptr, message);
280
}
281
282
#elif !defined(__APPLE__) && !defined(__ANDROID__)
283
284
#include <backtrace.h>
285
#include <cstdarg>
286
#include <cstdio>
287
#include <mutex>
288
#include <signal.h>
289
#include <sys/mman.h>
290
#include <unistd.h>
291
292
namespace CrashHandler {
293
namespace {
294
struct BacktraceBuffer
295
{
296
char* buffer;
297
size_t used;
298
size_t size;
299
};
300
} // namespace
301
302
static const char* GetSignalName(int signal_no);
303
static void AllocateBuffer(BacktraceBuffer* buf);
304
static void FreeBuffer(BacktraceBuffer* buf);
305
static void AppendToBuffer(BacktraceBuffer* buf, const char* format, ...);
306
static int BacktraceFullCallback(void* data, uintptr_t pc, const char* filename, int lineno, const char* function);
307
static void LogCallstack(int signal, const void* exception_pc);
308
309
static std::recursive_mutex s_crash_mutex;
310
static bool s_in_signal_handler = false;
311
312
static CleanupHandler s_cleanup_handler;
313
static backtrace_state* s_backtrace_state = nullptr;
314
} // namespace CrashHandler
315
316
const char* CrashHandler::GetSignalName(int signal_no)
317
{
318
switch (signal_no)
319
{
320
// Don't need to list all of them, there's only a couple we register.
321
// clang-format off
322
case SIGSEGV: return "SIGSEGV";
323
case SIGBUS: return "SIGBUS";
324
case SIGABRT: return "SIGABRT";
325
default: return "UNKNOWN";
326
// clang-format on
327
}
328
}
329
330
void CrashHandler::AllocateBuffer(BacktraceBuffer* buf)
331
{
332
buf->used = 0;
333
buf->size = sysconf(_SC_PAGESIZE);
334
buf->buffer =
335
static_cast<char*>(mmap(nullptr, buf->size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
336
if (buf->buffer == static_cast<char*>(MAP_FAILED))
337
{
338
buf->buffer = nullptr;
339
buf->size = 0;
340
}
341
}
342
343
void CrashHandler::FreeBuffer(BacktraceBuffer* buf)
344
{
345
if (buf->buffer)
346
munmap(buf->buffer, buf->size);
347
}
348
349
void CrashHandler::AppendToBuffer(BacktraceBuffer* buf, const char* format, ...)
350
{
351
std::va_list ap;
352
va_start(ap, format);
353
354
// Hope this doesn't allocate memory... it *can*, but hopefully unlikely since
355
// it won't be the first call, and we're providing the buffer.
356
if (buf->size > 0 && buf->used < (buf->size - 1))
357
{
358
const int written = std::vsnprintf(buf->buffer + buf->used, buf->size - buf->used, format, ap);
359
if (written > 0)
360
buf->used += static_cast<size_t>(written);
361
}
362
363
va_end(ap);
364
}
365
366
int CrashHandler::BacktraceFullCallback(void* data, uintptr_t pc, const char* filename, int lineno,
367
const char* function)
368
{
369
BacktraceBuffer* buf = static_cast<BacktraceBuffer*>(data);
370
AppendToBuffer(buf, " %016p", pc);
371
if (function)
372
AppendToBuffer(buf, " %s", function);
373
if (filename)
374
AppendToBuffer(buf, " [%s:%d]", filename, lineno);
375
376
AppendToBuffer(buf, "\n");
377
return 0;
378
}
379
380
void CrashHandler::LogCallstack(int signal, const void* exception_pc)
381
{
382
BacktraceBuffer buf;
383
AllocateBuffer(&buf);
384
if (signal != 0 || exception_pc)
385
AppendToBuffer(&buf, "*************** Unhandled %s at %p ***************\n", GetSignalName(signal), exception_pc);
386
else
387
AppendToBuffer(&buf, "*******************************************************************\n");
388
389
const int rc = backtrace_full(s_backtrace_state, 0, BacktraceFullCallback, nullptr, &buf);
390
if (rc != 0)
391
AppendToBuffer(&buf, " backtrace_full() failed: %d\n");
392
393
AppendToBuffer(&buf, "*******************************************************************\n");
394
395
if (buf.used > 0)
396
write(STDERR_FILENO, buf.buffer, buf.used);
397
398
FreeBuffer(&buf);
399
}
400
401
void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx)
402
{
403
std::unique_lock lock(s_crash_mutex);
404
405
// If we crash somewhere in libbacktrace, don't bother trying again.
406
if (!s_in_signal_handler)
407
{
408
s_in_signal_handler = true;
409
410
if (s_cleanup_handler)
411
s_cleanup_handler();
412
413
#if defined(__APPLE__) && defined(__x86_64__)
414
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__ss.__rip);
415
#elif defined(__FreeBSD__) && defined(__x86_64__)
416
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.mc_rip);
417
#elif defined(__x86_64__)
418
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.gregs[REG_RIP]);
419
#else
420
void* const exception_pc = nullptr;
421
#endif
422
423
LogCallstack(signal, exception_pc);
424
425
s_in_signal_handler = false;
426
}
427
428
lock.unlock();
429
430
// We can't continue from here. Just bail out and dump core.
431
static const char abort_message[] = "Aborting application.\n";
432
write(STDERR_FILENO, abort_message, sizeof(abort_message) - 1);
433
434
// Call default abort signal handler, regardless of whether this was SIGSEGV or SIGABRT.
435
lock.lock();
436
std::signal(SIGABRT, SIG_DFL);
437
raise(SIGABRT);
438
}
439
440
bool CrashHandler::Install(CleanupHandler cleanup_handler)
441
{
442
const std::string progpath = FileSystem::GetProgramPath();
443
s_backtrace_state = backtrace_create_state(progpath.empty() ? nullptr : progpath.c_str(), 0, nullptr, nullptr);
444
if (!s_backtrace_state)
445
return false;
446
447
struct sigaction sa;
448
449
sigemptyset(&sa.sa_mask);
450
sa.sa_flags = SA_SIGINFO | SA_NODEFER;
451
sa.sa_sigaction = CrashSignalHandler;
452
if (sigaction(SIGBUS, &sa, nullptr) != 0)
453
return false;
454
if (sigaction(SIGSEGV, &sa, nullptr) != 0)
455
return false;
456
if (sigaction(SIGABRT, &sa, nullptr) != 0)
457
return false;
458
459
s_cleanup_handler = cleanup_handler;
460
return true;
461
}
462
463
void CrashHandler::SetWriteDirectory(std::string_view dump_directory)
464
{
465
}
466
467
void CrashHandler::WriteDumpForCaller(std::string_view message)
468
{
469
LogCallstack(0, nullptr);
470
}
471
472
#elif !defined(__ANDROID__)
473
474
bool CrashHandler::Install(CleanupHandler cleanup_handler)
475
{
476
return false;
477
}
478
479
void CrashHandler::SetWriteDirectory(std::string_view dump_directory)
480
{
481
}
482
483
void CrashHandler::WriteDumpForCaller(std::string_view message)
484
{
485
}
486
487
void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx)
488
{
489
// We can't continue from here. Just bail out and dump core.
490
std::fputs("Aborting application.\n", stderr);
491
std::fflush(stderr);
492
std::abort();
493
}
494
495
#endif
496
497