From 54119264739aa5fd6a3a79e9f8f4fb21461d551d Mon Sep 17 00:00:00 2001 From: justburner Date: Aug 12 2022 10:22:05 +0000 Subject: Add minidump and modules to win platform --- diff --git a/toonz/sources/toonz/crashhandler.cpp b/toonz/sources/toonz/crashhandler.cpp index 9ee1b07..3e5db27 100644 --- a/toonz/sources/toonz/crashhandler.cpp +++ b/toonz/sources/toonz/crashhandler.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #else #include #include @@ -41,38 +42,65 @@ //----------------------------------------------------------------------------- -#ifndef _WIN32 - -static bool sh(std::string &out, const char *cmd) { - char buffer[128]; - FILE *p = popen(cmd, "r"); - if (p == NULL) return false; - while (fgets(buffer, 128, p)) out.append(buffer); - pclose(p); - return true; +static const char *filenameOnly(const char *path) { + for (int i = strlen(path); i >= 0; --i) { + if (path[i] == '\\' || path[i] == '/') return path + i + 1; + } + return path; } -static bool addr2line(std::string &out, const char *exepath, const char *addr) { - char cmd[512]; -#ifdef OSX - sprintf(cmd, "atos -o \"%.400s\" %s 2>&1", exepath, addr); -#else - sprintf(cmd, "addr2line -f -p -e \"%.400s\" %s 2>&1", exepath, addr); -#endif - return sh(out, cmd); +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// Windows platform functions + +#ifdef _WIN32 + +#define HAS_MINIDUMP +static bool generateMinidump(TFilePath dumpFile) { + HANDLE hDumpFile = CreateFileW(dumpFile.getWideString().c_str(), + GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); + if (hDumpFile == INVALID_HANDLE_VALUE) return false; + + MINIDUMP_EXCEPTION_INFORMATION mdei; + mdei.ThreadId = GetCurrentThreadId(); + mdei.ExceptionPointers = NULL; + mdei.ClientPointers = FALSE; + + if (MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, + MiniDumpNormal, &mdei, 0, NULL)) { + CloseHandle(hDumpFile); + return true; + } + + return false; } -#endif +#define HAS_MODULES +static void printModules(std::string &out) { + HANDLE hProcess = GetCurrentProcess(); -static void print_backtrace(std::string &out) { + HMODULE modules[1024]; + DWORD size; + if (EnumProcessModules(hProcess, modules, sizeof(modules), &size)) { + for (unsigned int i = 0; i < size / sizeof(HMODULE); i++) { + char moduleName[512]; + GetModuleFileNameA(modules[i], moduleName, 512); + out.append(moduleName); + out.append("\n"); + } + } +} + +#define HAS_BACKTRACE +static void printBacktrace(std::string &out) { int frameStack = 0; - int frameSkip = 2; + int frameSkip = 3; -#ifdef _WIN32 HANDLE hProcess = GetCurrentProcess(); SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_INCLUDE_32BIT_MODULES | - SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); + SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_LOAD_LINES | + SYMOPT_UNDNAME); SymInitialize(hProcess, NULL, TRUE); @@ -87,6 +115,10 @@ static void print_backtrace(std::string &out) { memset(&sourceInfo, 0, sizeof(IMAGEHLP_LINE64)); sourceInfo.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + IMAGEHLP_MODULE64 moduleInfo; + memset(&moduleInfo, 0, sizeof(moduleInfo)); + moduleInfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE64); + STACKFRAME64 stackframe; memset(&stackframe, 0, sizeof(STACKFRAME64)); @@ -112,7 +144,6 @@ static void print_backtrace(std::string &out) { while (StackWalk64(machineType, hProcess, hThread, &stackframe, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) { - // Skip first frames since they point to this function if (frameStack++ < frameSkip) continue; char numStr[32]; @@ -129,12 +160,18 @@ static void print_backtrace(std::string &out) { &displacement64, sourceSym)) { out.append(sourceSym->Name); + // Get module filename + char moduleFile[512]; + MEMORY_BASIC_INFORMATION mbi; + VirtualQuery((LPCVOID)stackframe.AddrPC.Offset, &mbi, sizeof(mbi)); + GetModuleFileNameA((HMODULE)mbi.AllocationBase, moduleFile, 512); + // Get source filename and line DWORD displacement32; if (SymGetLineFromAddr64(hProcess, stackframe.AddrPC.Offset, &displacement32, &sourceInfo) != FALSE) { out.append(" {"); - out.append(sourceInfo.FileName); + out.append(filenameOnly(sourceInfo.FileName)); out.append(":"); out.append(std::to_string(sourceInfo.LineNumber)); out.append("}"); @@ -143,14 +180,136 @@ static void print_backtrace(std::string &out) { sprintf(numStr, " [0x%" PRIx64 "]", stackframe.AddrPC.Offset); out.append(numStr); } + out.append(" <"); + out.append(filenameOnly(moduleFile)); + out.append(">"); } out.append("\n"); } SymCleanup(hProcess); +} + +//----------------------------------------------------------------------------- + +LONG WINAPI exceptionHandler(PEXCEPTION_POINTERS info) { + const char *reason = "Unknown"; + int accessType; + + switch (info->ExceptionRecord->ExceptionCode) { + case EXCEPTION_ACCESS_VIOLATION: + reason = "EXCEPTION_ACCESS_VIOLATION"; + break; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + reason = "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; + break; + //case EXCEPTION_BREAKPOINT: + // reason = "EXCEPTION_BREAKPOINT"; + // break; + case EXCEPTION_DATATYPE_MISALIGNMENT: + reason = "EXCEPTION_DATATYPE_MISALIGNMENT"; + break; + case EXCEPTION_FLT_DENORMAL_OPERAND: + reason = "EXCEPTION_FLT_DENORMAL_OPERAND"; + break; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + reason = "EXCEPTION_FLT_DIVIDE_BY_ZERO"; + break; + case EXCEPTION_FLT_INEXACT_RESULT: + reason = "EXCEPTION_FLT_INEXACT_RESULT"; + break; + case EXCEPTION_FLT_INVALID_OPERATION: + reason = "EXCEPTION_FLT_INVALID_OPERATION"; + break; + case EXCEPTION_FLT_OVERFLOW: + reason = "EXCEPTION_FLT_OVERFLOW"; + break; + case EXCEPTION_FLT_STACK_CHECK: + reason = "EXCEPTION_FLT_STACK_CHECK"; + break; + case EXCEPTION_FLT_UNDERFLOW: + reason = "EXCEPTION_FLT_UNDERFLOW"; + break; + case EXCEPTION_ILLEGAL_INSTRUCTION: + reason = "EXCEPTION_ILLEGAL_INSTRUCTION"; + break; + case EXCEPTION_IN_PAGE_ERROR: + reason = "EXCEPTION_IN_PAGE_ERROR"; + break; + case EXCEPTION_INT_DIVIDE_BY_ZERO: + reason = "EXCEPTION_INT_DIVIDE_BY_ZERO"; + break; + //case EXCEPTION_INT_OVERFLOW: + // reason = "EXCEPTION_INT_OVERFLOW"; + // break; + case EXCEPTION_INVALID_DISPOSITION: + reason = "EXCEPTION_INVALID_DISPOSITION"; + break; + case EXCEPTION_NONCONTINUABLE_EXCEPTION: + reason = "EXCEPTION_NONCONTINUABLE_EXCEPTION"; + break; + case EXCEPTION_PRIV_INSTRUCTION: + reason = "EXCEPTION_PRIV_INSTRUCTION"; + break; + //case EXCEPTION_SINGLE_STEP: + // reason = "EXCEPTION_SINGLE_STEP"; + // break; + case EXCEPTION_STACK_OVERFLOW: + reason = "EXCEPTION_STACK_OVERFLOW"; + break; + default: + return EXCEPTION_CONTINUE_EXECUTION; + } + + if (CrashHandler::trigger(reason, true)) _Exit(1); + + return EXCEPTION_CONTINUE_EXECUTION; +} + +#endif + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// Linux and Mac OS X platform functions + +#ifndef _WIN32 + +static bool sh(std::string &out, const char *cmd) { + char buffer[128]; + FILE *p = popen(cmd, "r"); + if (p == NULL) return false; + while (fgets(buffer, 128, p)) out.append(buffer); + pclose(p); + return true; +} +//----------------------------------------------------------------------------- + +static bool addr2line(std::string &out, const char *exepath, const char *addr) { + char cmd[512]; +#ifdef OSX + sprintf(cmd, "atos -o \"%.400s\" %s 2>&1", exepath, addr); #else + sprintf(cmd, "addr2line -f -p -e \"%.400s\" %s 2>&1", exepath, addr); +#endif + return sh(out, cmd); +} + +//----------------------------------------------------------------------------- + +static bool generateMinidump(TFilePath dumpFile) { return false; } + +//----------------------------------------------------------------------------- + +static void printModules(std::string &out) {} + +//----------------------------------------------------------------------------- + +#define HAS_BACKTRACE +static void printBacktrace(std::string &out) { + int frameStack = 0; + int frameSkip = 3; const int size = 256; void *buffer[size]; @@ -158,7 +317,8 @@ static void print_backtrace(std::string &out) { // Get executable path char exepath[512]; memset(exepath, 0, 512); - if (readlink("/proc/self/exe", exepath, 512) < 0) throw TException(); + if (readlink("/proc/self/exe", exepath, 512) < 0) + fprintf(stderr, "Couldn't get exe path\n"); // Back trace int nptrs = backtrace(buffer, size); @@ -190,34 +350,9 @@ static void print_backtrace(std::string &out) { } free(bts); - -#endif - -} - -//----------------------------------------------------------------------------- - -static void print_sysinfo(std::string &out) { - out.append("Build ABI: " + QSysInfo::buildAbi().toStdString() + "\n"); - out.append("Operating System: " + QSysInfo::prettyProductName().toStdString() + "\n"); - out.append("OS Kernel: " + QSysInfo::kernelVersion().toStdString() + "\n"); - out.append("CPU Threads: " + std::to_string(QThread::idealThreadCount()) + "\n"); -} - -//----------------------------------------------------------------------------- - -static void print_gpuinfo(std::string &out) { - std::string gpuVendorName = (const char *)glGetString(GL_VENDOR); - std::string gpuModelName = (const char *)glGetString(GL_RENDERER); - std::string gpuVersion = (const char *)glGetString(GL_VERSION); - out.append("GPU Vendor: " + gpuVendorName + "\n"); - out.append("GPU Model: " + gpuModelName + "\n"); - out.append("GPU Version: " + gpuVersion + "\n"); } -//----------------------------------------------------------------------------- - -static void signal_handler(int sig) { +void signalHandler(int sig) { QString reason = "Unknown"; switch (sig) { @@ -244,6 +379,29 @@ static void signal_handler(int sig) { if (CrashHandler::trigger(reason, true)) _Exit(1); } +#endif + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +static void printSysInfo(std::string &out) { + out.append("Build ABI: " + QSysInfo::buildAbi().toStdString() + "\n"); + out.append("Operating System: " + QSysInfo::prettyProductName().toStdString() + "\n"); + out.append("OS Kernel: " + QSysInfo::kernelVersion().toStdString() + "\n"); + out.append("CPU Threads: " + std::to_string(QThread::idealThreadCount()) + "\n"); +} + +//----------------------------------------------------------------------------- + +static void printGPUInfo(std::string &out) { + std::string gpuVendorName = (const char *)glGetString(GL_VENDOR); + std::string gpuModelName = (const char *)glGetString(GL_RENDERER); + std::string gpuVersion = (const char *)glGetString(GL_VERSION); + out.append("GPU Vendor: " + gpuVendorName + "\n"); + out.append("GPU Model: " + gpuModelName + "\n"); + out.append("GPU Version: " + gpuVersion + "\n"); +} + //----------------------------------------------------------------------------- CrashHandler::CrashHandler(QWidget *parent, TFilePath crashFile, QString crashReport) @@ -334,19 +492,29 @@ void CrashHandler::openFolder() { //----------------------------------------------------------------------------- void CrashHandler::install() { - signal(SIGABRT, signal_handler); - signal(SIGFPE, signal_handler); - signal(SIGILL, signal_handler); - signal(SIGINT, signal_handler); - signal(SIGSEGV, signal_handler); - signal(SIGTERM, signal_handler); +#ifdef _WIN32 + // std library seems to override this + //SetUnhandledExceptionFilter(exceptionHandler); + + void *handler = AddVectoredExceptionHandler(0, exceptionHandler); + assert(handler != NULL); + //RemoveVectoredExceptionHandler(handler); +#else + signal(SIGABRT, signalHandler); + signal(SIGFPE, signalHandler); + signal(SIGILL, signalHandler); + signal(SIGINT, signalHandler); + signal(SIGSEGV, signalHandler); + signal(SIGTERM, signalHandler); +#endif } //----------------------------------------------------------------------------- bool CrashHandler::trigger(const QString reason, bool showDialog) { - char fileName[1024]; - char dateName[256]; + char fileName[128]; + char dumpName[128]; + char dateName[128]; std::string out; // Get time and build filename @@ -355,7 +523,12 @@ bool CrashHandler::trigger(const QString reason, bool showDialog) { struct tm *tm = localtime(&acc_time); strftime(dateName, 128, "%Y-%m-%d %H:%M:%S", tm); strftime(fileName, 128, "Crash-%Y%m%d-%H%M%S.log", tm); - TFilePath fp = ToonzFolder::getCrashReportFolder() + fileName; + strftime(dumpName, 128, "Crash-%Y%m%d-%H%M%S.dmp", tm); + TFilePath fpCrsh = ToonzFolder::getCrashReportFolder() + fileName; + TFilePath fpDump = ToonzFolder::getCrashReportFolder() + dumpName; + + // Generate minidump + bool minidump = generateMinidump(fpDump); TProjectManager *pm = TProjectManager::instance(); TProjectP currentProject = pm->getCurrentProject(); @@ -371,11 +544,18 @@ bool CrashHandler::trigger(const QString reason, bool showDialog) { out.append("\nCrash Reason: "); out.append(reason.toStdString()); out.append("\n\n"); - print_sysinfo(out); + printSysInfo(out); out.append("\n"); - print_gpuinfo(out); + printGPUInfo(out); out.append("\nCrash File: "); - out.append(fp.getQString().toStdString()); + out.append(fpCrsh.getQString().toStdString()); +#ifdef HAS_MINIDUMP + out.append("\nMini Dump File: "); + if (minidump) + out.append(fpDump.getQString().toStdString()); + else + out.append("Failed"); +#endif out.append("\nApplication Dir: "); out.append(QCoreApplication::applicationDirPath().toStdString()); out.append("\nStuff Dir: "); @@ -389,14 +569,22 @@ bool CrashHandler::trigger(const QString reason, bool showDialog) { out.append(projectPath.getQString().toStdString()); out.append("\nScene Path: "); out.append(currentScene->getScenePath().getQString().toStdString()); - out.append("\n\n==== Backtrace ====\n"); - print_backtrace(out); + out.append("\n"); +#ifdef HAS_MODULES + out.append("\n==== Modules ====\n"); + printModules(out); + out.append("==== End ====\n"); +#endif +#ifdef HAS_BACKTRACE + out.append("\n==== Backtrace ====\n"); + printBacktrace(out); out.append("==== End ====\n"); +#endif } catch (...) { } // Save to crash information to file - FILE *fw = fopen(fp, "w"); + FILE *fw = fopen(fpCrsh, "w"); if (fw != NULL) { fwrite(out.c_str(), 1, out.size(), fw); fclose(fw); @@ -404,7 +592,7 @@ bool CrashHandler::trigger(const QString reason, bool showDialog) { if (showDialog) { // Show crash handler dialog - CrashHandler crashdialog(TApp::instance()->getMainWindow(), fp, + CrashHandler crashdialog(TApp::instance()->getMainWindow(), fpCrsh, QString::fromStdString(out)); return crashdialog.exec() != QDialog::Rejected; }