/ src / common / utils / UnhandledExceptionHandler.h
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  }