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