error_handler.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  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. std::free(functions);
  87. return stacktrace.str();
  88. #else
  89. return "";
  90. #endif
  91. }
  92. std::string
  93. ErrorHandler::get_system_info()
  94. {
  95. #ifdef WIN32
  96. std::stringstream info;
  97. /*
  98. // This method reports Windows 8 on my
  99. // Windows 10 PC. Disabled.
  100. if (IsWindows10OrGreater())
  101. info << "Windows 10/11";
  102. else if (IsWindows8Point1OrGreater())
  103. info << "Windows 8.1";
  104. else if (IsWindows8OrGreater())
  105. info << "Windows 8";
  106. else if (IsWindows7OrGreater())
  107. info << "Windows 7";
  108. else if (IsWindowsVistaOrGreater())
  109. info << "Windows Vista";
  110. else if (IsWindowsXPOrGreater())
  111. info << "Windows XP";
  112. else
  113. info << "Windows";
  114. info << " ";
  115. */
  116. info << "Windows ";
  117. SYSTEM_INFO sysinfo;
  118. GetSystemInfo(&sysinfo);
  119. switch (sysinfo.wProcessorArchitecture) {
  120. case PROCESSOR_ARCHITECTURE_AMD64:
  121. info << "x64";
  122. break;
  123. case PROCESSOR_ARCHITECTURE_ARM:
  124. info << "ARM";
  125. break;
  126. case PROCESSOR_ARCHITECTURE_ARM64:
  127. info << "ARM64";
  128. break;
  129. case PROCESSOR_ARCHITECTURE_IA64:
  130. info << "Intel Itanium-based";
  131. break;
  132. case PROCESSOR_ARCHITECTURE_INTEL:
  133. info << "x86";
  134. break;
  135. default:
  136. break;
  137. }
  138. return info.str();
  139. #elif defined(UNIX)
  140. struct utsname uts;
  141. uname(&uts);
  142. std::stringstream info;
  143. info << uts.sysname << " "
  144. << uts.release << " "
  145. << uts.version << " "
  146. << uts.machine;
  147. return info.str();
  148. #else
  149. return "";
  150. #endif
  151. }
  152. [[ noreturn ]] void
  153. ErrorHandler::handle_error(int sig)
  154. {
  155. if (m_handing_error)
  156. {
  157. // Error happened again while handling another segfault. Abort now.
  158. close_program();
  159. }
  160. else
  161. {
  162. m_handing_error = true;
  163. // Do not use external stuff (like log_fatal) to limit the risk of causing
  164. // another error, which would restart the handler again.
  165. std::cerr << "\nError: signal " << sig << ":\n";
  166. error_dialog_crash(get_stacktrace());
  167. close_program();
  168. }
  169. }
  170. void
  171. ErrorHandler::error_dialog_crash(const std::string& stacktrace)
  172. {
  173. char msg[] = "SuperTux has encountered an unrecoverable error!";
  174. std::cerr << msg << "\n" << stacktrace << std::endl;
  175. SDL_MessageBoxButtonData btns[] = {
  176. {
  177. 0, // flags
  178. 0, // buttonid
  179. "Report" // text
  180. },
  181. {
  182. 0, // flags
  183. 1, // buttonid
  184. "Details" // text
  185. },
  186. {
  187. SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, // flags
  188. 2, // buttonid
  189. "OK" // text
  190. }
  191. };
  192. SDL_MessageBoxData data = {
  193. SDL_MESSAGEBOX_ERROR, // flags
  194. nullptr, // window
  195. "Error", // title
  196. msg, // message
  197. SDL_arraysize(btns), // numbuttons
  198. btns, // buttons
  199. nullptr // colorscheme
  200. };
  201. int resultbtn;
  202. SDL_ShowMessageBox(&data, &resultbtn);
  203. switch (resultbtn)
  204. {
  205. case 0:
  206. report_error(stacktrace);
  207. break;
  208. case 1:
  209. {
  210. SDL_MessageBoxButtonData detailsbtns[] = {
  211. {
  212. 0, // flags
  213. 0, // buttonid
  214. "Report" // text
  215. },
  216. {
  217. SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, // flags
  218. 1, // buttonid
  219. "OK" // text
  220. }
  221. };
  222. data = {
  223. SDL_MESSAGEBOX_ERROR, // flags
  224. nullptr, // window
  225. "Error details", // title
  226. stacktrace.c_str(), // message
  227. SDL_arraysize(detailsbtns), // numbuttons
  228. detailsbtns, // buttons
  229. nullptr // colorscheme
  230. };
  231. SDL_ShowMessageBox(&data, &resultbtn);
  232. if (resultbtn == 0)
  233. report_error(stacktrace);
  234. break;
  235. }
  236. default:
  237. break;
  238. }
  239. }
  240. void
  241. ErrorHandler::error_dialog_exception(const std::string& exception)
  242. {
  243. std::stringstream stream;
  244. stream << "SuperTux has encountered a fatal exception!";
  245. if (!exception.empty())
  246. {
  247. stream << "\n\n" << exception;
  248. }
  249. std::string msg = stream.str();
  250. SDL_MessageBoxButtonData btns[] = {
  251. #ifdef WIN32
  252. {
  253. 0, // flags
  254. 0, // buttonid
  255. "Open log" // text
  256. },
  257. #endif
  258. {
  259. SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, // flags
  260. 1, // buttonid
  261. "OK" // text
  262. }
  263. };
  264. SDL_MessageBoxData data = {
  265. SDL_MESSAGEBOX_ERROR, // flags
  266. nullptr, // window
  267. "Error", // title
  268. msg.c_str(), // message
  269. SDL_arraysize(btns), // numbuttons
  270. btns, // buttons
  271. nullptr // colorscheme
  272. };
  273. int resultbtn;
  274. SDL_ShowMessageBox(&data, &resultbtn);
  275. #ifdef WIN32
  276. if (resultbtn == 0)
  277. {
  278. // Repurpose the stream.
  279. stream.str("");
  280. stream << SDL_GetPrefPath("SuperTux", "supertux2")
  281. << "/console.err";
  282. FileSystem::open_path(stream.str());
  283. }
  284. #endif
  285. }
  286. void
  287. ErrorHandler::report_error(const std::string& details)
  288. {
  289. std::stringstream url;
  290. // cppcheck-suppress unknownMacro
  291. url << "https://github.com/supertux/supertux/issues/new"
  292. "?template=crash.yml"
  293. "&labels=type:crash,status:needs-confirmation"
  294. "&supertux-version=" << FileSystem::escape_url(PACKAGE_VERSION) <<
  295. "&system-info=" << FileSystem::escape_url(get_system_info()) <<
  296. "&debug-stacktrace=" << FileSystem::escape_url(details);
  297. FileSystem::open_url(url.str());
  298. }
  299. [[ noreturn ]] void
  300. ErrorHandler::close_program()
  301. {
  302. _Exit(10);
  303. }
  304. /* EOF */