duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

crash_handler.cpp (11823B)


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