#include "crash-logger.hpp" #include #include #include #include #include #include "compiler.hpp" #include "consts.hpp" // TODO: Double-check which includes are necessary #ifdef WINDOWS // Check if this is necessary or lets me remove any windows includes // also check if it's needed in Linux #include #include #include #include #include "FileStackWalker.h" // The windows analogues to the unix open, close, and write functions // are _open, _close, and _write. These #defines let me use the same name in all cases. #define open _open #define close _close #define write _write #define STDERR_FILENO 2 bool handleException(unsigned int expCode, EXCEPTION_POINTERS* pExp, HANDLE thread); #else #include #include #include // CHeck if these are needed in Linux //#include //#include void abortHandler(int signum); static inline void printStackTrace(int fd_out = STDERR_FILENO); #endif void printInfo(int fd_out); CrashLogger::CrashLogger(int(*mainFunc)(int, char*[]), int argc, char* argv[]) { write(STDERR_FILENO, "Calling main\n", 13); #ifdef WINDOWS __try { mainFunc(argc, argv); // maybe do this and call a function inside CrashLogger // In that case, also pass GetCurrentThread() as a parameter to then pass to handleException // I could also move almost all of this into CrashLogger by creating a function in CrashLogger that takes a reference // to the effective main function and, for Windows, wraps it in all this error-handling stuff } __except (handleException( GetExceptionCode(), GetExceptionInformation(), GetCurrentThread()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER) { } #else // Apparently, sigaction should be used instead for Linux // It gives more info. Check if I should bother switching signal(SIGABRT, abortHandler); signal(SIGSEGV, abortHandler); signal(SIGILL, abortHandler); signal(SIGFPE, abortHandler); write(STDERR_FILENO, "Handlers attached\n", 18); mainFunc(argc, argv); #endif } CrashLogger::~CrashLogger() { } #ifdef WINDOWS bool handleException(unsigned int expCode, EXCEPTION_POINTERS* pExp, HANDLE thread) { int crash_log = open(CRASH_LOG_FILE, O_RDWR | O_CREAT | O_APPEND, _S_IREAD | _S_IWRITE); if (crash_log == -1) { // TODO: Figure out exactly what perror does and if I should use it perror("opening crash.log"); // TODO: Figure out exactly what perror does and if I should use it } printInfo(crash_log); FileStackWalker sw(crash_log == -1 ? STDERR_FILENO : crash_log); if (pExp != NULL) { sw.ShowCallstack(thread, pExp->ContextRecord); } else { write(crash_log, "Could not get the stack trace\n", 30); } close(crash_log); if (expCode == EXCEPTION_ACCESS_VIOLATION) { write(STDERR_FILENO, "ACCESS VIOLATION\n", 17); } return true; } #else void abortHandler(int signum) { int crash_log = open(CRASH_LOG_FILE, O_RDWR | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR); if (crash_log == -1) { // TODO: Figure out exactly what perror does and if I should use it perror("opening crash.log"); } printInfo(crash_log); printStackTrace(crash_log); close(crash_log); write(STDERR_FILENO, "The game has crashed. Check crash.log for more info\n", 52); exit(signum); } static inline void printStackTrace(int fd_out) { write(fd_out, "stack trace:\n", 13); // storage array for stack trace address data void* addrlist[64]; // retrieve current stack addresses unsigned int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*)); if (addrlen == 0) { write(fd_out, " \n", 3); return; } // create readable strings to each frame. char** symbollist = backtrace_symbols(addrlist, addrlen); size_t funcnamesize = 1024; char* funcname = (char*)malloc(sizeof(char) * funcnamesize); // iterate over the returned symbol lines // skip the first few, since those are printStackTrace, abortHandler, // and a couple others called after the crash for (unsigned int i = 0; i < addrlen; i++) { char* begin_name = NULL; char* begin_offset = NULL; char* end_offset = NULL; #ifdef MAC for (char *p = symbollist[i]; *p; p++) { if ((*p == '_') && (*(p-1) == ' ')) { begin_name = p-1; } else if (*p == '+') { begin_offset = p-1; } } write(fd_out, " ", 2); if (begin_name && begin_offset && (begin_name < begin_offset)) { *begin_name++ = '\0'; *begin_offset++ = '\0'; // mangled name is now in [begin_name, begin_offset) and caller // offset in [begin_offset, end_offset). now apply // __cxa_demangle(): int status; char* ret = abi::__cxa_demangle(begin_name, funcname, &funcnamesize, &status); if (status == 0) { funcname = ret; // use possibly realloc()-ed string write(fd_out, symbollist[i], strlen(symbollist[i])); write(fd_out, " ", 1); write(fd_out, funcname, strlen(funcname)); write(fd_out, " ", 1); } else { // demangling failed. Output function name as a C function with no arguments. write(fd_out, symbollist[i], strlen(symbollist[i])); write(fd_out, " ", 1); write(fd_out, begin_name, strlen(begin_name)); write(fd_out, "() ", 3); } write(fd_out, begin_offset, strlen(begin_offset)); } else { // couldn't parse the line? print the whole line. write(fd_out, symbollist[i], strlen(symbollist[i])); } write(fd_out, "\n", 1); #else for (char *p = symbollist[i]; *p; p++) { if (*p == '(') { begin_name = p; } else if (*p == '+') { begin_offset = p; } else if (*p == ')' && (begin_offset || begin_name)) { end_offset = p; } } write(fd_out, " ", 2); if (begin_name && end_offset && (begin_name < end_offset)) { *begin_name++ = '\0'; *end_offset++ = '\0'; if (begin_offset) { *begin_offset++ = '\0'; } // mangled name is now in [begin_name, begin_offset) and caller // offset in [begin_offset, end_offset). now apply // __cxa_demangle(): int status; char* ret = abi::__cxa_demangle(begin_name, funcname, &funcnamesize, &status); write(fd_out, symbollist[i], strlen(symbollist[i])); write(fd_out, " ( ", 3); if (status == 0) { write(fd_out, ret, strlen(ret)); } else { write(fd_out, begin_name, strlen(begin_name)); } if (begin_offset) { write(fd_out, " + ", 3); write(fd_out, begin_offset, strlen(begin_offset)); } else { write(fd_out, " ", 9); } write(fd_out, ") ", 1); write(fd_out, end_offset, strlen(end_offset)); } else { // couldn't parse the line? print the whole line. write(fd_out, symbollist[i], strlen(symbollist[i])); } write(fd_out, "\n", 1); #endif } free(funcname); free(symbollist); write(fd_out, "End of stack trace\n", 19); } #endif void printInfo(int fd_out) { write(fd_out, "Game Version: ", 14); write(fd_out, GAME_VERSION, strlen(GAME_VERSION)); write(fd_out, "\n", 1); write(fd_out, "OS: ", 4); #if defined WINDOWS write(fd_out, "Windows", 7); #elif defined LINUX write(fd_out, "Linux", 5); #elif defined MAC write(fd_out, "Mac", 3); #else write(fd_out, "Unknown", 7); #endif write(fd_out, "\n", 1); write(fd_out, "\n", 1); }