forked from PCSX2/pcsx2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCrashHandler.cpp
380 lines (310 loc) · 10.6 KB
/
CrashHandler.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "Pcsx2Defs.h"
#include "CrashHandler.h"
#include "DynamicLibrary.h"
#include "FileSystem.h"
#include "StringUtil.h"
#include <cinttypes>
#include <cstdio>
#include <ctime>
#if defined(_WIN32)
#include "RedtapeWindows.h"
#include "StackWalker.h"
#include <DbgHelp.h>
class CrashHandlerStackWalker : public StackWalker
{
public:
explicit CrashHandlerStackWalker(HANDLE out_file);
~CrashHandlerStackWalker();
protected:
void OnOutput(LPCSTR szText) override;
private:
HANDLE m_out_file;
};
CrashHandlerStackWalker::CrashHandlerStackWalker(HANDLE out_file)
: StackWalker(RetrieveVerbose, nullptr, GetCurrentProcessId(), GetCurrentProcess())
, m_out_file(out_file)
{
}
CrashHandlerStackWalker::~CrashHandlerStackWalker() = default;
void CrashHandlerStackWalker::OnOutput(LPCSTR szText)
{
if (m_out_file)
{
DWORD written;
WriteFile(m_out_file, szText, static_cast<DWORD>(std::strlen(szText)), &written, nullptr);
}
OutputDebugStringA(szText);
}
static bool WriteMinidump(HMODULE hDbgHelp, HANDLE hFile, HANDLE hProcess, DWORD process_id, DWORD thread_id,
PEXCEPTION_POINTERS exception, MINIDUMP_TYPE type)
{
using PFNMINIDUMPWRITEDUMP =
BOOL(WINAPI*)(HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType,
PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
PFNMINIDUMPWRITEDUMP minidump_write_dump =
hDbgHelp ? reinterpret_cast<PFNMINIDUMPWRITEDUMP>(GetProcAddress(hDbgHelp, "MiniDumpWriteDump")) : nullptr;
if (!minidump_write_dump)
return false;
MINIDUMP_EXCEPTION_INFORMATION mei = {};
if (exception)
{
mei.ThreadId = thread_id;
mei.ExceptionPointers = exception;
mei.ClientPointers = FALSE;
return minidump_write_dump(hProcess, process_id, hFile, type, &mei, nullptr, nullptr);
}
__try
{
RaiseException(EXCEPTION_INVALID_HANDLE, 0, 0, nullptr);
}
__except (WriteMinidump(hDbgHelp, hFile, GetCurrentProcess(), GetCurrentProcessId(), GetCurrentThreadId(),
GetExceptionInformation(), type),
EXCEPTION_EXECUTE_HANDLER)
{
}
return true;
}
static std::wstring s_write_directory;
static DynamicLibrary s_dbghelp_module;
static bool s_in_crash_handler = false;
static void GenerateCrashFilename(wchar_t* buf, size_t len, const wchar_t* prefix, const wchar_t* extension)
{
SYSTEMTIME st = {};
GetLocalTime(&st);
_snwprintf_s(buf, len, _TRUNCATE, L"%s%scrash-%04u-%02u-%02u-%02u-%02u-%02u-%03u.%s",
prefix ? prefix : L"", prefix ? L"\\" : L"",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, extension);
}
static void WriteMinidumpAndCallstack(PEXCEPTION_POINTERS exi)
{
s_in_crash_handler = true;
wchar_t filename[1024] = {};
GenerateCrashFilename(filename, std::size(filename), s_write_directory.empty() ? nullptr : s_write_directory.c_str(), L"txt");
// might fail
HANDLE hFile = CreateFileW(filename, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
if (exi && hFile != INVALID_HANDLE_VALUE)
{
char line[1024];
DWORD written;
std::snprintf(line, std::size(line), "Exception 0x%08X at 0x%p\n", static_cast<unsigned>(exi->ExceptionRecord->ExceptionCode),
exi->ExceptionRecord->ExceptionAddress);
WriteFile(hFile, line, static_cast<DWORD>(std::strlen(line)), &written, nullptr);
}
GenerateCrashFilename(filename, std::size(filename), s_write_directory.empty() ? nullptr : s_write_directory.c_str(), L"dmp");
const MINIDUMP_TYPE minidump_type =
static_cast<MINIDUMP_TYPE>(MiniDumpNormal | MiniDumpWithHandleData | MiniDumpWithProcessThreadData |
MiniDumpWithThreadInfo | MiniDumpWithIndirectlyReferencedMemory);
const HANDLE hMinidumpFile = CreateFileW(filename, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
if (hMinidumpFile == INVALID_HANDLE_VALUE ||
!WriteMinidump(static_cast<HMODULE>(s_dbghelp_module.GetHandle()), hMinidumpFile, GetCurrentProcess(), GetCurrentProcessId(),
GetCurrentThreadId(), exi, minidump_type))
{
static const char error_message[] = "Failed to write minidump file.\n";
if (hFile != INVALID_HANDLE_VALUE)
{
DWORD written;
WriteFile(hFile, error_message, sizeof(error_message) - 1, &written, nullptr);
}
}
if (hMinidumpFile != INVALID_HANDLE_VALUE)
CloseHandle(hMinidumpFile);
CrashHandlerStackWalker sw(hFile);
sw.ShowCallstack(GetCurrentThread(), exi ? exi->ContextRecord : nullptr);
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
}
static LONG NTAPI ExceptionHandler(PEXCEPTION_POINTERS exi)
{
// if the debugger is attached, or we're recursively crashing, let it take care of it.
if (!s_in_crash_handler)
WriteMinidumpAndCallstack(exi);
// returning EXCEPTION_CONTINUE_SEARCH makes sense, except for the fact that it seems to leave zombie processes
// around. instead, force ourselves to terminate.
TerminateProcess(GetCurrentProcess(), 0xFEFEFEFEu);
return EXCEPTION_CONTINUE_SEARCH;
}
bool CrashHandler::Install()
{
// load dbghelp at install/startup, that way we're not LoadLibrary()'ing after a crash
// .. because that probably wouldn't go down well.
HMODULE mod = StackWalker::LoadDbgHelpLibrary();
if (mod)
s_dbghelp_module.Adopt(mod);
SetUnhandledExceptionFilter(ExceptionHandler);
return true;
}
void CrashHandler::SetWriteDirectory(std::string_view dump_directory)
{
s_write_directory = FileSystem::GetWin32Path(dump_directory);
}
void CrashHandler::WriteDumpForCaller()
{
WriteMinidumpAndCallstack(nullptr);
}
#elif !defined(__APPLE__)
#include "FileSystem.h"
#include <backtrace.h>
#include <signal.h>
#include <sys/mman.h>
#include <unistd.h>
#include <cstdarg>
#include <cstdio>
#include <mutex>
namespace CrashHandler
{
struct BacktraceBuffer
{
char* buffer;
size_t used;
size_t size;
};
static const char* GetSignalName(int signal_no);
static void AllocateBuffer(BacktraceBuffer* buf);
static void FreeBuffer(BacktraceBuffer* buf);
static void AppendToBuffer(BacktraceBuffer* buf, const char* format, ...);
static int BacktraceFullCallback(void* data, uintptr_t pc, const char* filename, int lineno, const char* function);
static void LogCallstack(int signal, const void* exception_pc);
static std::recursive_mutex s_crash_mutex;
static bool s_in_signal_handler = false;
static backtrace_state* s_backtrace_state = nullptr;
} // namespace CrashHandler
const char* CrashHandler::GetSignalName(int signal_no)
{
switch (signal_no)
{
// Don't need to list all of them, there's only a couple we register.
// clang-format off
case SIGSEGV: return "SIGSEGV";
case SIGBUS: return "SIGBUS";
default: return "UNKNOWN";
// clang-format on
}
}
void CrashHandler::AllocateBuffer(BacktraceBuffer* buf)
{
buf->used = 0;
buf->size = __pagesize;
buf->buffer = static_cast<char*>(mmap(nullptr, buf->size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
if (buf->buffer == static_cast<char*>(MAP_FAILED))
{
buf->buffer = nullptr;
buf->size = 0;
}
}
void CrashHandler::FreeBuffer(BacktraceBuffer* buf)
{
if (buf->buffer)
munmap(buf->buffer, buf->size);
}
void CrashHandler::AppendToBuffer(BacktraceBuffer* buf, const char* format, ...)
{
std::va_list ap;
va_start(ap, format);
// Hope this doesn't allocate memory... it *can*, but hopefully unlikely since
// it won't be the first call, and we're providing the buffer.
if (buf->size > 0 && buf->used < (buf->size - 1))
{
const int written = std::vsnprintf(buf->buffer + buf->used, buf->size - buf->used, format, ap);
if (written > 0)
buf->used += static_cast<size_t>(written);
}
va_end(ap);
}
int CrashHandler::BacktraceFullCallback(void* data, uintptr_t pc, const char* filename, int lineno,
const char* function)
{
BacktraceBuffer* buf = static_cast<BacktraceBuffer*>(data);
AppendToBuffer(buf, " %016p", pc);
if (function)
AppendToBuffer(buf, " %s", function);
if (filename)
AppendToBuffer(buf, " [%s:%d]", filename, lineno);
AppendToBuffer(buf, "\n");
return 0;
}
void CrashHandler::LogCallstack(int signal, const void* exception_pc)
{
BacktraceBuffer buf;
AllocateBuffer(&buf);
if (signal != 0 || exception_pc)
AppendToBuffer(&buf, "*************** Unhandled %s at %p ***************\n", GetSignalName(signal), exception_pc);
else
AppendToBuffer(&buf, "*******************************************************************\n");
const int rc = backtrace_full(s_backtrace_state, 0, BacktraceFullCallback, nullptr, &buf);
if (rc != 0)
AppendToBuffer(&buf, " backtrace_full() failed: %d\n");
AppendToBuffer(&buf, "*******************************************************************\n");
if (buf.used > 0)
write(STDERR_FILENO, buf.buffer, buf.used);
FreeBuffer(&buf);
}
void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx)
{
std::unique_lock lock(s_crash_mutex);
// If we crash somewhere in libbacktrace, don't bother trying again.
if (!s_in_signal_handler)
{
s_in_signal_handler = true;
#if defined(__APPLE__) && defined(__x86_64__)
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__ss.__rip);
#elif defined(__FreeBSD__) && defined(__x86_64__)
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.mc_rip);
#elif defined(__x86_64__)
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.gregs[REG_RIP]);
#else
void* const exception_pc = nullptr;
#endif
LogCallstack(signal, exception_pc);
s_in_signal_handler = false;
}
lock.unlock();
// We can't continue from here. Just bail out and dump core.
std::fputs("Aborting application.\n", stderr);
std::fflush(stderr);
std::abort();
}
bool CrashHandler::Install()
{
const std::string progpath = FileSystem::GetProgramPath();
s_backtrace_state = backtrace_create_state(progpath.empty() ? nullptr : progpath.c_str(), 0, nullptr, nullptr);
if (!s_backtrace_state)
return false;
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO | SA_NODEFER;
sa.sa_sigaction = CrashSignalHandler;
if (sigaction(SIGBUS, &sa, nullptr) != 0)
return false;
if (sigaction(SIGSEGV, &sa, nullptr) != 0)
return false;
return true;
}
void CrashHandler::SetWriteDirectory(std::string_view dump_directory)
{
}
void CrashHandler::WriteDumpForCaller()
{
LogCallstack(0, nullptr);
}
#else
bool CrashHandler::Install()
{
return false;
}
void CrashHandler::SetWriteDirectory(std::string_view dump_directory)
{
}
void CrashHandler::WriteDumpForCaller()
{
}
void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx)
{
// We can't continue from here. Just bail out and dump core.
std::fputs("Aborting application.\n", stderr);
std::fflush(stderr);
std::abort();
}
#endif