error_handler.cpp 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  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. #define WIN32_LEAN_AND_MEAN
  33. #include <windows.h>
  34. #include <DbgHelp.h>
  35. //#include <VersionHelpers.h>
  36. #pragma comment(lib, "DbgHelp.lib")
  37. #elif defined(UNIX)
  38. #include <sys/utsname.h>
  39. #include <execinfo.h>
  40. #include <unistd.h>
  41. #endif
  42. bool ErrorHandler::m_handing_error = false;
  43. void
  44. ErrorHandler::set_handlers()
  45. {
  46. signal(SIGSEGV, handle_error);
  47. signal(SIGABRT, handle_error);
  48. }
  49. std::string
  50. ErrorHandler::get_stacktrace()
  51. {
  52. #ifdef WIN32
  53. std::stringstream stacktrace;
  54. // Initialize symbols
  55. SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);
  56. if (!SymInitialize(GetCurrentProcess(), NULL, TRUE))
  57. {
  58. return "";
  59. }
  60. // Get current stack frame
  61. void* stack[100];
  62. WORD frames = CaptureStackBackTrace(0, 100, stack, NULL);
  63. // Get symbols for each frame
  64. SYMBOL_INFO* symbol = static_cast<SYMBOL_INFO*>(std::calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1));
  65. symbol->MaxNameLen = 255;
  66. symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
  67. for (int i = 0; i < frames; i++)
  68. {
  69. SymFromAddr(GetCurrentProcess(), (DWORD64) stack[i], 0, symbol);
  70. stacktrace << symbol->Name << " - 0x" << std::hex << symbol->Address << "\n";
  71. }
  72. std::free(symbol);
  73. SymCleanup(GetCurrentProcess());
  74. return stacktrace.str();
  75. #elif defined(UNIX)
  76. void* array[128];
  77. size_t size;
  78. // Get void*'s for all entries on the stack.
  79. size = backtrace(array, 127);
  80. char** functions = backtrace_symbols(array, static_cast<int>(size));
  81. if (functions == nullptr)
  82. return "";
  83. std::stringstream stacktrace;
  84. for (size_t i = 0; i < size; i++)
  85. stacktrace << functions[i] << "\n";
  86. return stacktrace.str();
  87. #else
  88. return "";
  89. #endif
  90. }
  91. std::string
  92. ErrorHandler::get_system_info()
  93. {
  94. #ifdef WIN32
  95. std::stringstream info;
  96. /*
  97. // This method reports Windows 8 on my
  98. // Windows 10 PC. Disabled.
  99. if (IsWindows10OrGreater())
  100. info << "Windows 10/11";
  101. else if (IsWindows8Point1OrGreater())
  102. info << "Windows 8.1";
  103. else if (IsWindows8OrGreater())
  104. info << "Windows 8";
  105. else if (IsWindows7OrGreater())
  106. info << "Windows 7";
  107. else if (IsWindowsVistaOrGreater())
  108. info << "Windows Vista";
  109. else if (IsWindowsXPOrGreater())
  110. info << "Windows XP";
  111. else
  112. info << "Windows";
  113. info << " ";
  114. */
  115. info << "Windows ";
  116. SYSTEM_INFO sysinfo;
  117. GetSystemInfo(&sysinfo);
  118. switch (sysinfo.wProcessorArchitecture) {
  119. case PROCESSOR_ARCHITECTURE_AMD64:
  120. info << "x64";
  121. break;
  122. case PROCESSOR_ARCHITECTURE_ARM:
  123. info << "ARM";
  124. break;
  125. case PROCESSOR_ARCHITECTURE_ARM64:
  126. info << "ARM64";
  127. break;
  128. case PROCESSOR_ARCHITECTURE_IA64:
  129. info << "Intel Itanium-based";
  130. break;
  131. case PROCESSOR_ARCHITECTURE_INTEL:
  132. info << "x86";
  133. break;
  134. default:
  135. break;
  136. }
  137. return info.str();
  138. #elif defined(UNIX)
  139. struct utsname uts;
  140. uname(&uts);
  141. std::stringstream info;
  142. info << uts.sysname << " "
  143. << uts.release << " "
  144. << uts.version << " "
  145. << uts.machine;
  146. return info.str();
  147. #else
  148. return "";
  149. #endif
  150. }
  151. [[ noreturn ]] void
  152. ErrorHandler::handle_error(int sig)
  153. {
  154. if (m_handing_error)
  155. {
  156. // Error happened again while handling another segfault. Abort now.
  157. close_program();
  158. }
  159. else
  160. {
  161. m_handing_error = true;
  162. // Do not use external stuff (like log_fatal) to limit the risk of causing
  163. // another error, which would restart the handler again.
  164. std::cerr << "\nError: signal " << sig << ":\n";
  165. error_dialog_crash(get_stacktrace());
  166. close_program();
  167. }
  168. }
  169. void
  170. ErrorHandler::error_dialog_crash(const std::string& stacktrace)
  171. {
  172. char msg[] = "SuperTux has encountered an unrecoverable error!";
  173. std::cerr << msg << "\n" << stacktrace << std::endl;
  174. SDL_MessageBoxButtonData btns[] = {
  175. {
  176. 0, // flags
  177. 0, // buttonid
  178. "Report" // text
  179. },
  180. {
  181. 0, // flags
  182. 1, // buttonid
  183. "Details" // text
  184. },
  185. {
  186. SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, // flags
  187. 2, // buttonid
  188. "OK" // text
  189. }
  190. };
  191. SDL_MessageBoxData data = {
  192. SDL_MESSAGEBOX_ERROR, // flags
  193. nullptr, // window
  194. "Error", // title
  195. msg, // message
  196. SDL_arraysize(btns), // numbuttons
  197. btns, // buttons
  198. nullptr // colorscheme
  199. };
  200. int resultbtn;
  201. SDL_ShowMessageBox(&data, &resultbtn);
  202. switch (resultbtn)
  203. {
  204. case 0:
  205. report_error(stacktrace);
  206. break;
  207. case 1:
  208. {
  209. SDL_MessageBoxButtonData detailsbtns[] = {
  210. {
  211. 0, // flags
  212. 0, // buttonid
  213. "Report" // text
  214. },
  215. {
  216. SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, // flags
  217. 1, // buttonid
  218. "OK" // text
  219. }
  220. };
  221. data = {
  222. SDL_MESSAGEBOX_ERROR, // flags
  223. nullptr, // window
  224. "Error details", // title
  225. stacktrace.c_str(), // message
  226. SDL_arraysize(detailsbtns), // numbuttons
  227. detailsbtns, // buttons
  228. nullptr // colorscheme
  229. };
  230. SDL_ShowMessageBox(&data, &resultbtn);
  231. if (resultbtn == 0)
  232. report_error(stacktrace);
  233. break;
  234. }
  235. default:
  236. break;
  237. }
  238. }
  239. void
  240. ErrorHandler::error_dialog_exception(const std::string& exception)
  241. {
  242. std::stringstream stream;
  243. stream << "SuperTux has encountered a fatal exception!";
  244. if (!exception.empty())
  245. {
  246. stream << "\n\n" << exception;
  247. }
  248. std::string msg = stream.str();
  249. SDL_MessageBoxButtonData btns[] = {
  250. #ifdef WIN32
  251. {
  252. 0, // flags
  253. 0, // buttonid
  254. "Open log" // text
  255. },
  256. #endif
  257. {
  258. SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, // flags
  259. 1, // buttonid
  260. "OK" // text
  261. }
  262. };
  263. SDL_MessageBoxData data = {
  264. SDL_MESSAGEBOX_ERROR, // flags
  265. nullptr, // window
  266. "Error", // title
  267. msg.c_str(), // message
  268. SDL_arraysize(btns), // numbuttons
  269. btns, // buttons
  270. nullptr // colorscheme
  271. };
  272. int resultbtn;
  273. SDL_ShowMessageBox(&data, &resultbtn);
  274. #ifdef WIN32
  275. if (resultbtn == 0)
  276. {
  277. // Repurpose the stream.
  278. stream.str("");
  279. stream << SDL_GetPrefPath("SuperTux", "supertux2")
  280. << "/console.err";
  281. FileSystem::open_path(stream.str());
  282. }
  283. #endif
  284. }
  285. void
  286. ErrorHandler::report_error(const std::string& details)
  287. {
  288. std::stringstream url;
  289. // cppcheck-suppress unknownMacro
  290. url << "https://github.com/supertux/supertux/issues/new"
  291. "?template=crash.yml"
  292. "&labels=type:crash,status:needs-confirmation"
  293. "&supertux-version=" << FileSystem::escape_url(PACKAGE_VERSION) <<
  294. "&system-info=" << FileSystem::escape_url(get_system_info()) <<
  295. "&debug-stacktrace=" << FileSystem::escape_url(details);
  296. FileSystem::open_url(url.str());
  297. }
  298. [[ noreturn ]] void
  299. ErrorHandler::close_program()
  300. {
  301. _Exit(10);
  302. }
  303. /* EOF */