UnhandledExceptionHandler.h
1 #pragma once 2 3 #include <Windows.h> 4 #include <DbgHelp.h> 5 #include <signal.h> 6 #include <sstream> 7 #include <stdio.h> 8 9 #include "winapi_error.h" 10 #include "../logger/logger.h" 11 12 static BOOLEAN processingException = FALSE; 13 14 static inline const char* exceptionDescription(const DWORD& code) 15 { 16 switch (code) 17 { 18 case EXCEPTION_ACCESS_VIOLATION: 19 return "EXCEPTION_ACCESS_VIOLATION"; 20 case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: 21 return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; 22 case EXCEPTION_BREAKPOINT: 23 return "EXCEPTION_BREAKPOINT"; 24 case EXCEPTION_DATATYPE_MISALIGNMENT: 25 return "EXCEPTION_DATATYPE_MISALIGNMENT"; 26 case EXCEPTION_FLT_DENORMAL_OPERAND: 27 return "EXCEPTION_FLT_DENORMAL_OPERAND"; 28 case EXCEPTION_FLT_DIVIDE_BY_ZERO: 29 return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; 30 case EXCEPTION_FLT_INEXACT_RESULT: 31 return "EXCEPTION_FLT_INEXACT_RESULT"; 32 case EXCEPTION_FLT_INVALID_OPERATION: 33 return "EXCEPTION_FLT_INVALID_OPERATION"; 34 case EXCEPTION_FLT_OVERFLOW: 35 return "EXCEPTION_FLT_OVERFLOW"; 36 case EXCEPTION_FLT_STACK_CHECK: 37 return "EXCEPTION_FLT_STACK_CHECK"; 38 case EXCEPTION_FLT_UNDERFLOW: 39 return "EXCEPTION_FLT_UNDERFLOW"; 40 case EXCEPTION_ILLEGAL_INSTRUCTION: 41 return "EXCEPTION_ILLEGAL_INSTRUCTION"; 42 case EXCEPTION_IN_PAGE_ERROR: 43 return "EXCEPTION_IN_PAGE_ERROR"; 44 case EXCEPTION_INT_DIVIDE_BY_ZERO: 45 return "EXCEPTION_INT_DIVIDE_BY_ZERO"; 46 case EXCEPTION_INT_OVERFLOW: 47 return "EXCEPTION_INT_OVERFLOW"; 48 case EXCEPTION_INVALID_DISPOSITION: 49 return "EXCEPTION_INVALID_DISPOSITION"; 50 case EXCEPTION_NONCONTINUABLE_EXCEPTION: 51 return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; 52 case EXCEPTION_PRIV_INSTRUCTION: 53 return "EXCEPTION_PRIV_INSTRUCTION"; 54 case EXCEPTION_SINGLE_STEP: 55 return "EXCEPTION_SINGLE_STEP"; 56 case EXCEPTION_STACK_OVERFLOW: 57 return "EXCEPTION_STACK_OVERFLOW"; 58 default: 59 return "UNKNOWN EXCEPTION"; 60 } 61 } 62 63 /* Returns the index of the last backslash in the file path */ 64 inline int GetFilenameStart(wchar_t* path) 65 { 66 int pos = 0; 67 int found = 0; 68 if (path != NULL) 69 { 70 while (path[pos] != L'\0' && pos < MAX_PATH) 71 { 72 if (path[pos] == L'\\') 73 { 74 found = pos + 1; 75 } 76 ++pos; 77 } 78 } 79 80 return found; 81 } 82 83 inline std::wstring GetModuleName(HANDLE process, const STACKFRAME64& stack) 84 { 85 static wchar_t modulePath[MAX_PATH]{}; 86 const size_t size = sizeof(modulePath); 87 memset(&modulePath[0], '\0', size); 88 89 DWORD64 moduleBase = SymGetModuleBase64(process, stack.AddrPC.Offset); 90 if (!moduleBase) 91 { 92 Logger::error(L"Failed to get a module. {}", get_last_error_or_default(GetLastError())); 93 return std::wstring(); 94 } 95 96 if (!GetModuleFileNameW(reinterpret_cast<HINSTANCE>(moduleBase), modulePath, MAX_PATH)) 97 { 98 Logger::error(L"Failed to get a module path. {}", get_last_error_or_default(GetLastError())); 99 return std::wstring(); 100 } 101 102 const int start = GetFilenameStart(modulePath); 103 return std::wstring(modulePath, start); 104 } 105 106 inline std::wstring GetName(HANDLE process, const STACKFRAME64& stack) 107 { 108 static IMAGEHLP_SYMBOL64* pSymbol = static_cast<IMAGEHLP_SYMBOL64*>(malloc(sizeof(IMAGEHLP_SYMBOL64) + MAX_PATH * sizeof(TCHAR))); 109 if (!pSymbol) 110 { 111 return std::wstring(); 112 } 113 114 memset(pSymbol, '\0', sizeof(*pSymbol) + MAX_PATH); 115 pSymbol->MaxNameLength = MAX_PATH; 116 pSymbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); 117 118 DWORD64 dw64Displacement = 0; 119 if (!SymGetSymFromAddr64(process, stack.AddrPC.Offset, &dw64Displacement, pSymbol)) 120 { 121 Logger::error(L"Failed to get a symbol. {}", get_last_error_or_default(GetLastError())); 122 return std::wstring(); 123 } 124 125 std::string str = pSymbol->Name; 126 return std::wstring(str.begin(), str.end()); 127 } 128 129 inline std::wstring GetLine(HANDLE process, const STACKFRAME64& stack) 130 { 131 static IMAGEHLP_LINE64 line{}; 132 133 memset(&line, '\0', sizeof(IMAGEHLP_LINE64)); 134 line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); 135 line.LineNumber = 0; 136 137 DWORD dwDisplacement = 0; 138 if (!SymGetLineFromAddr64(process, stack.AddrPC.Offset, &dwDisplacement, &line)) 139 { 140 return std::wstring(); 141 } 142 143 std::string fileName(line.FileName); 144 return L"(" + std::wstring(fileName.begin(), fileName.end()) + L":" + std::to_wstring(line.LineNumber) + L")"; 145 } 146 147 inline void LogStackTrace() 148 { 149 CONTEXT context; 150 try 151 { 152 RtlCaptureContext(&context); 153 } 154 catch (...) 155 { 156 Logger::error(L"Failed to capture context. {}", get_last_error_or_default(GetLastError())); 157 return; 158 } 159 160 STACKFRAME64 stack; 161 memset(&stack, 0, sizeof(STACKFRAME64)); 162 163 HANDLE process = GetCurrentProcess(); 164 HANDLE thread = GetCurrentThread(); 165 166 #ifdef _M_ARM64 167 stack.AddrPC.Offset = context.Pc; 168 stack.AddrStack.Offset = context.Sp; 169 stack.AddrFrame.Offset = context.Fp; 170 #else 171 stack.AddrPC.Offset = context.Rip; 172 stack.AddrStack.Offset = context.Rsp; 173 stack.AddrFrame.Offset = context.Rbp; 174 #endif 175 stack.AddrPC.Mode = AddrModeFlat; 176 stack.AddrStack.Mode = AddrModeFlat; 177 stack.AddrFrame.Mode = AddrModeFlat; 178 179 BOOL result = false; 180 std::wstringstream ss; 181 for (;;) 182 { 183 result = StackWalk64( 184 #ifdef _M_ARM64 185 IMAGE_FILE_MACHINE_ARM64, 186 #else 187 IMAGE_FILE_MACHINE_AMD64, 188 #endif 189 process, 190 thread, 191 &stack, 192 &context, 193 NULL, 194 SymFunctionTableAccess64, 195 SymGetModuleBase64, 196 NULL); 197 198 if (!result) 199 { 200 break; 201 } 202 203 ss << GetModuleName(process, stack) << "!" << GetName(process, stack) << GetLine(process, stack) << std::endl; 204 } 205 206 Logger::error(L"STACK TRACE\r\n{}", ss.str()); 207 Logger::flush(); 208 } 209 210 inline LONG WINAPI UnhandledExceptionHandler(PEXCEPTION_POINTERS info) 211 { 212 if (!processingException) 213 { 214 bool headerLogged = false; 215 try 216 { 217 const char* exDescription = "Exception code not available"; 218 processingException = true; 219 if (info != NULL && info->ExceptionRecord != NULL && info->ExceptionRecord->ExceptionCode != NULL) 220 { 221 exDescription = exceptionDescription(info->ExceptionRecord->ExceptionCode); 222 } 223 224 headerLogged = true; 225 Logger::error(exDescription); 226 LogStackTrace(); 227 } 228 catch (...) 229 { 230 Logger::error("Failed to log stack trace"); 231 Logger::flush(); 232 } 233 234 processingException = false; 235 } 236 237 return EXCEPTION_CONTINUE_SEARCH; 238 } 239 240 /* Handler to trap abort() calls */ 241 inline void AbortHandler(int /*signal_number*/) 242 { 243 Logger::error("--- ABORT"); 244 try 245 { 246 LogStackTrace(); 247 } 248 catch (...) 249 { 250 Logger::error("Failed to log stack trace on abort"); 251 Logger::flush(); 252 } 253 } 254 255 inline void InitSymbols() 256 { 257 // Preload symbols so they will be available in case of out-of-memory exception 258 SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); 259 HANDLE process = GetCurrentProcess(); 260 if (!SymInitialize(process, NULL, TRUE)) 261 { 262 Logger::error(L"Failed to initialize symbol handler. {}", get_last_error_or_default(GetLastError())); 263 } 264 } 265 266 inline void InitUnhandledExceptionHandler(void) 267 { 268 try 269 { 270 InitSymbols(); 271 // Global handler for unhandled exceptions 272 SetUnhandledExceptionFilter(UnhandledExceptionHandler); 273 // Handler for abort() 274 signal(SIGABRT, &AbortHandler); 275 } 276 catch (...) 277 { 278 Logger::error("Failed to init global unhandled exception handler"); 279 } 280 }