error_handler.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. // SuperTux
  2. // Copyright (C) 2020 A. Semphris <semphris@protonmail.com>
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. #include "supertux/error_handler.hpp"
  17. // execinfo.h as a built-in libc feature is exclusive to glibc as of 2020.
  18. // On FreeBSD and musl systems, an external libexecinfo is available, but
  19. // it has to be explicitly linked into the final executable.
  20. // This is a *libc* feature, not a compiler one; furthermore, it's possible
  21. // to verify its availability in CMakeLists.txt, if one is so inclined.
  22. #include <csignal>
  23. #include <sstream>
  24. #include <string>
  25. #include <SDL.h>
  26. #include <version.h>
  27. #include "util/file_system.hpp"
  28. #if (defined(__unix__) || defined(__APPLE__)) && !(defined(__EMSCRIPTEN__))
  29. #define UNIX
  30. #endif
  31. #ifdef WIN32
  32. #include <DbgHelp.h>
  33. //#include <VersionHelpers.h>
  34. #pragma comment(lib, "DbgHelp.lib")
  35. #elif defined(UNIX)
  36. #include <sys/utsname.h>
  37. #include <execinfo.h>
  38. #include <unistd.h>
  39. #endif
  40. void
  41. ErrorHandler::set_handlers()
  42. {
  43. #ifdef WIN32
  44. SetUnhandledExceptionFilter(supertux_seh_handler);
  45. #elif defined(UNIX)
  46. signal(SIGSEGV, handle_error);
  47. signal(SIGABRT, handle_error);
  48. #endif
  49. }
  50. #ifdef WIN32
  51. static PCONTEXT pcontext = NULL;
  52. #endif
  53. std::string
  54. ErrorHandler::get_stacktrace()
  55. {
  56. #ifdef WIN32
  57. // Adapted from SuperTuxKart, (C) 2013-2015 Lionel Fuentes, GPLv3
  58. if (pcontext == NULL)
  59. {
  60. CONTEXT context;
  61. std::memset(&context, 0, sizeof(CONTEXT));
  62. context.ContextFlags = CONTEXT_FULL;
  63. RtlCaptureContext(&context);
  64. pcontext = &context;
  65. }
  66. const HANDLE hProcess = GetCurrentProcess();
  67. const HANDLE hThread = GetCurrentThread();
  68. // Since the stack trace can also be used for leak checks, don't
  69. // initialise this all the time.
  70. static bool first_time = true;
  71. // Initialize the symbol hander for the process
  72. if (first_time)
  73. {
  74. // Get the file path of the executable
  75. std::string path(MAX_PATH, 0);
  76. GetModuleFileName(NULL, &path[0], MAX_PATH);
  77. int size_needed = MultiByteToWideChar(CP_UTF8, 0, &path[0], (int) path.size(), NULL, 0);
  78. std::wstring wpath(size_needed, 0);
  79. MultiByteToWideChar(CP_UTF8, 0, &path[0], (int) path.size(), &wpath[0], size_needed);
  80. // Finally initialize the symbol handler.
  81. BOOL bOk = SymInitializeW(hProcess, wpath.empty() ? NULL : wpath.c_str(), TRUE);
  82. if (!bOk)
  83. {
  84. return "";
  85. }
  86. SymSetOptions(SYMOPT_LOAD_LINES);
  87. first_time = false;
  88. }
  89. std::stringstream callstack;
  90. // Get the stack trace
  91. {
  92. // Initialize the STACKFRAME structure so that it
  93. // corresponds to the current function call
  94. STACKFRAME64 stackframe;
  95. std::memset(&stackframe, 0, sizeof(stackframe));
  96. stackframe.AddrPC.Mode = AddrModeFlat;
  97. stackframe.AddrStack.Mode = AddrModeFlat;
  98. stackframe.AddrFrame.Mode = AddrModeFlat;
  99. #if defined(_M_ARM)
  100. stackframe.AddrPC.Offset = pcontext->Pc;
  101. stackframe.AddrStack.Offset = pcontext->Sp;
  102. stackframe.AddrFrame.Offset = pcontext->R11;
  103. const DWORD machine_type = IMAGE_FILE_MACHINE_ARM;
  104. #elif defined(_M_ARM64)
  105. stackframe.AddrPC.Offset = pcontext->Pc;
  106. stackframe.AddrStack.Offset = pcontext->Sp;
  107. stackframe.AddrFrame.Offset = pcontext->Fp;
  108. const DWORD machine_type = IMAGE_FILE_MACHINE_ARM64;
  109. #elif defined(_WIN64)
  110. stackframe.AddrPC.Offset = pcontext->Rip;
  111. stackframe.AddrStack.Offset = pcontext->Rsp;
  112. stackframe.AddrFrame.Offset = pcontext->Rbp;
  113. const DWORD machine_type = IMAGE_FILE_MACHINE_AMD64;
  114. #else
  115. stackframe.AddrPC.Offset = pcontext->Eip;
  116. stackframe.AddrStack.Offset = pcontext->Esp;
  117. stackframe.AddrFrame.Offset = pcontext->Ebp;
  118. const DWORD machine_type = IMAGE_FILE_MACHINE_I386;
  119. #endif
  120. // Walk the stack
  121. const int max_nb_calls = 32;
  122. for (int i = 0; i < max_nb_calls ; i++)
  123. {
  124. const BOOL stackframe_ok = StackWalk64(machine_type, hProcess, hThread,
  125. &stackframe, pcontext, NULL,
  126. SymFunctionTableAccess64,
  127. SymGetModuleBase64, NULL);
  128. if (!stackframe_ok) break;
  129. // Decode the symbol and add it to the call stack
  130. DWORD64 sym_displacement;
  131. char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; // cppcheck-suppress unassignedVariable
  132. PSYMBOL_INFO symbol = (PSYMBOL_INFO) buffer;
  133. symbol->MaxNameLen = MAX_SYM_NAME;
  134. symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
  135. if (!SymFromAddr(hProcess, stackframe.AddrPC.Offset,
  136. &sym_displacement, symbol))
  137. {
  138. callstack << "<no symbol available>\n";
  139. continue;
  140. }
  141. IMAGEHLP_LINE64 line64;
  142. DWORD dwDisplacement = (DWORD) sym_displacement;
  143. bool result = SymGetLineFromAddr64(hProcess,
  144. stackframe.AddrPC.Offset,
  145. &dwDisplacement, &line64);
  146. if (result)
  147. {
  148. std::string s(line64.FileName);
  149. callstack << symbol->Name << " ("
  150. << FileSystem::basename(s) << ":"
  151. << line64.LineNumber << ")\n";
  152. }
  153. else
  154. {
  155. callstack << symbol->Name << "\n";
  156. }
  157. }
  158. }
  159. return callstack.str();
  160. #elif defined(UNIX)
  161. void* array[128];
  162. size_t size;
  163. // Get void*'s for all entries on the stack.
  164. size = backtrace(array, 127);
  165. char** functions = backtrace_symbols(array, static_cast<int>(size));
  166. if (functions == nullptr)
  167. return "";
  168. std::stringstream stacktrace;
  169. for (size_t i = 0; i < size; i++)
  170. stacktrace << functions[i] << "\n";
  171. std::free(functions);
  172. return stacktrace.str();
  173. #else
  174. return "";
  175. #endif
  176. }
  177. std::string
  178. ErrorHandler::get_system_info()
  179. {
  180. #ifdef WIN32
  181. std::stringstream info;
  182. /*
  183. // This method reports Windows 8 on my
  184. // Windows 10 PC. Disabled.
  185. if (IsWindows10OrGreater())
  186. info << "Windows 10/11";
  187. else if (IsWindows8Point1OrGreater())
  188. info << "Windows 8.1";
  189. else if (IsWindows8OrGreater())
  190. info << "Windows 8";
  191. else if (IsWindows7OrGreater())
  192. info << "Windows 7";
  193. else if (IsWindowsVistaOrGreater())
  194. info << "Windows Vista";
  195. else if (IsWindowsXPOrGreater())
  196. info << "Windows XP";
  197. else
  198. info << "Windows";
  199. info << " ";
  200. */
  201. info << "Windows ";
  202. SYSTEM_INFO sysinfo;
  203. GetSystemInfo(&sysinfo);
  204. switch (sysinfo.wProcessorArchitecture) {
  205. case PROCESSOR_ARCHITECTURE_AMD64:
  206. info << "x64";
  207. break;
  208. case PROCESSOR_ARCHITECTURE_ARM:
  209. info << "ARM";
  210. break;
  211. case PROCESSOR_ARCHITECTURE_ARM64:
  212. info << "ARM64";
  213. break;
  214. case PROCESSOR_ARCHITECTURE_IA64:
  215. info << "Intel Itanium-based";
  216. break;
  217. case PROCESSOR_ARCHITECTURE_INTEL:
  218. info << "x86";
  219. break;
  220. default:
  221. break;
  222. }
  223. return info.str();
  224. #elif defined(UNIX)
  225. struct utsname uts;
  226. uname(&uts);
  227. std::stringstream info;
  228. info << uts.sysname << " "
  229. << uts.release << " "
  230. << uts.version << " "
  231. << uts.machine;
  232. return info.str();
  233. #else
  234. return "";
  235. #endif
  236. }
  237. #ifdef WIN32
  238. LONG WINAPI
  239. ErrorHandler::supertux_seh_handler(_EXCEPTION_POINTERS* ExceptionInfo)
  240. {
  241. pcontext = ExceptionInfo->ContextRecord;
  242. error_dialog_crash(get_stacktrace());
  243. return EXCEPTION_EXECUTE_HANDLER;
  244. }
  245. #else
  246. [[ noreturn ]] void
  247. ErrorHandler::handle_error(int sig)
  248. {
  249. static bool handling_error = false;
  250. if (handling_error)
  251. {
  252. // Error happened again while handling another segfault. Abort now.
  253. close_program();
  254. }
  255. else
  256. {
  257. handling_error = true;
  258. // Do not use external stuff (like log_fatal) to limit the risk of causing
  259. // another error, which would restart the handler again.
  260. std::cerr << "\nError: signal " << sig << ":\n";
  261. error_dialog_crash(get_stacktrace());
  262. close_program();
  263. }
  264. }
  265. #endif
  266. void
  267. ErrorHandler::error_dialog_crash(const std::string& stacktrace)
  268. {
  269. char msg[] = "SuperTux has encountered an unrecoverable error!";
  270. std::cerr << msg << "\n" << stacktrace << std::endl;
  271. SDL_MessageBoxButtonData btns[] = {
  272. {
  273. 0, // flags
  274. 0, // buttonid
  275. "Report" // text
  276. },
  277. {
  278. 0, // flags
  279. 1, // buttonid
  280. "Details" // text
  281. },
  282. {
  283. SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, // flags
  284. 2, // buttonid
  285. "OK" // text
  286. }
  287. };
  288. SDL_MessageBoxData data = {
  289. SDL_MESSAGEBOX_ERROR, // flags
  290. nullptr, // window
  291. "Error", // title
  292. msg, // message
  293. SDL_arraysize(btns), // numbuttons
  294. btns, // buttons
  295. nullptr // colorscheme
  296. };
  297. int resultbtn;
  298. SDL_ShowMessageBox(&data, &resultbtn);
  299. switch (resultbtn)
  300. {
  301. case 0:
  302. report_error(stacktrace);
  303. break;
  304. case 1:
  305. {
  306. SDL_MessageBoxButtonData detailsbtns[] = {
  307. {
  308. 0, // flags
  309. 0, // buttonid
  310. "Report" // text
  311. },
  312. {
  313. SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, // flags
  314. 1, // buttonid
  315. "OK" // text
  316. }
  317. };
  318. data = {
  319. SDL_MESSAGEBOX_ERROR, // flags
  320. nullptr, // window
  321. "Error details", // title
  322. stacktrace.c_str(), // message
  323. SDL_arraysize(detailsbtns), // numbuttons
  324. detailsbtns, // buttons
  325. nullptr // colorscheme
  326. };
  327. SDL_ShowMessageBox(&data, &resultbtn);
  328. if (resultbtn == 0)
  329. report_error(stacktrace);
  330. break;
  331. }
  332. default:
  333. break;
  334. }
  335. }
  336. void
  337. ErrorHandler::error_dialog_exception(const std::string& exception)
  338. {
  339. std::stringstream stream;
  340. stream << "SuperTux has encountered a fatal exception!";
  341. if (!exception.empty())
  342. {
  343. stream << "\n\n" << exception;
  344. }
  345. std::string msg = stream.str();
  346. std::cerr << msg << std::endl;
  347. SDL_MessageBoxButtonData btns[] = {
  348. #ifdef WIN32
  349. {
  350. 0, // flags
  351. 0, // buttonid
  352. "Open log" // text
  353. },
  354. #endif
  355. {
  356. SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, // flags
  357. 1, // buttonid
  358. "OK" // text
  359. }
  360. };
  361. SDL_MessageBoxData data = {
  362. SDL_MESSAGEBOX_ERROR, // flags
  363. nullptr, // window
  364. "Error", // title
  365. msg.c_str(), // message
  366. SDL_arraysize(btns), // numbuttons
  367. btns, // buttons
  368. nullptr // colorscheme
  369. };
  370. int resultbtn;
  371. SDL_ShowMessageBox(&data, &resultbtn);
  372. #ifdef WIN32
  373. if (resultbtn == 0)
  374. {
  375. // Repurpose the stream.
  376. stream.str("");
  377. stream << SDL_GetPrefPath("SuperTux", "supertux2")
  378. << "/console.err";
  379. FileSystem::open_path(stream.str());
  380. }
  381. #endif
  382. }
  383. void
  384. ErrorHandler::report_error(const std::string& details)
  385. {
  386. std::stringstream url;
  387. // cppcheck-suppress unknownMacro
  388. url << "https://github.com/supertux/supertux/issues/new"
  389. "?template=crash.yml"
  390. "&labels=type:crash,status:needs-confirmation"
  391. "&supertux-version=" << FileSystem::escape_url(PACKAGE_VERSION) <<
  392. "&system-info=" << FileSystem::escape_url(get_system_info()) <<
  393. "&debug-stacktrace=" << FileSystem::escape_url(details);
  394. FileSystem::open_url(url.str());
  395. }
  396. [[ noreturn ]] void
  397. ErrorHandler::close_program()
  398. {
  399. _Exit(10);
  400. }
  401. /* EOF */