From 184487c8083e4e3958f9efe6fb6a9b1d4865fdf5 Mon Sep 17 00:00:00 2001 From: "edisonn@google.com" Date: Fri, 8 Mar 2013 18:00:16 +0000 Subject: [PATCH] Collect minidump and print callstack if an app chrashes. Review URL: https://codereview.chromium.org/12387018 git-svn-id: http://skia.googlecode.com/svn/trunk@8044 2bbb7eff-a529-9590-31e7-b0007b416f81 --- gyp/common.gypi | 7 ++ gyp/common_variables.gypi | 2 + gyp/tools.gyp | 46 +++++++++ tools/render_pdfs_main.cpp | 27 ++++- tools/win_dbghelp.cpp | 247 +++++++++++++++++++++++++++++++++++++++++++++ tools/win_dbghelp.h | 35 +++++++ 6 files changed, 361 insertions(+), 3 deletions(-) create mode 100644 tools/win_dbghelp.cpp create mode 100644 tools/win_dbghelp.h diff --git a/gyp/common.gypi b/gyp/common.gypi index 810a0f8..d3e1498 100644 --- a/gyp/common.gypi +++ b/gyp/common.gypi @@ -76,6 +76,13 @@ ], }, }], + [ 'skia_win_debuggers_path and skia_os == "win"', + { + 'defines': [ + 'SK_USE_CDB', + ], + }, + ], ], 'configurations': { 'Debug': { diff --git a/gyp/common_variables.gypi b/gyp/common_variables.gypi index 13e681d..8ddbbb4 100644 --- a/gyp/common_variables.gypi +++ b/gyp/common_variables.gypi @@ -92,6 +92,7 @@ # Note: This is currently only turned on for linux and android. # TODO: Turn on for Win and Mac as well. 'skia_warnings_as_errors%': 0, + 'skia_win_debuggers_path%': '', }, # Re-define all variables defined within the level-2 'variables' dict, @@ -116,6 +117,7 @@ 'skia_warnings_as_errors%': '<(skia_warnings_as_errors)', 'skia_static_initializers%': '<(skia_static_initializers)', 'ios_sdk_version%': '6.0', + 'skia_win_debuggers_path%': '<(skia_win_debuggers_path)', # These are referenced by our .gypi files that list files (e.g. core.gypi) # diff --git a/gyp/tools.gyp b/gyp/tools.gyp index e5d6cd3..5ae9e1c 100644 --- a/gyp/tools.gyp +++ b/gyp/tools.gyp @@ -184,6 +184,41 @@ 'ports.gyp:ports', 'tools.gyp:picture_utils', ], + 'conditions': [ + ['skia_win_debuggers_path and skia_os == "win"', + { + 'dependencies': [ + 'tools.gyp:win_dbghelp', + ], + }, + ], + # VS static libraries don't have a linker option. We must set a global + # project linker option, or add it to each executable. + ['skia_win_debuggers_path and skia_os == "win" and ' + 'skia_arch_width == 64', + { + 'msvs_settings': { + 'VCLinkerTool': { + 'AdditionalDependencies': [ + '<(skia_win_debuggers_path)/x64/DbgHelp.lib', + ], + }, + }, + }, + ], + ['skia_win_debuggers_path and skia_os == "win" and ' + 'skia_arch_width == 32', + { + 'msvs_settings': { + 'VCLinkerTool': { + 'AdditionalDependencies': [ + '<(skia_win_debuggers_path)/DbgHelp.lib', + ], + }, + }, + }, + ], + ], }, { 'target_name': 'picture_utils', @@ -233,6 +268,17 @@ 'tools.gyp:picture_utils', ], }, + { + 'target_name': 'win_dbghelp', + 'type': 'static_library', + 'defines': [ + 'SK_CDB_PATH="<(skia_win_debuggers_path)"', + ], + 'sources': [ + '../tools/win_dbghelp.h', + '../tools/win_dbghelp.cpp', + ], + }, ], } diff --git a/tools/render_pdfs_main.cpp b/tools/render_pdfs_main.cpp index 6c2543b..1ac02d3 100644 --- a/tools/render_pdfs_main.cpp +++ b/tools/render_pdfs_main.cpp @@ -16,6 +16,10 @@ #include "PdfRenderer.h" #include "picture_utils.h" +#ifdef SK_USE_CDB +#include "win_dbghelp.h" +#endif + /** * render_pdfs * @@ -207,9 +211,8 @@ static void parse_commandline(int argc, char* const argv[], } } -int tool_main(int argc, char** argv); -int tool_main(int argc, char** argv) { - +int tool_main_core(int argc, char** argv); +int tool_main_core(int argc, char** argv) { SkAutoGraphics ag; SkTArray inputs; @@ -229,6 +232,24 @@ int tool_main(int argc, char** argv) { SkDebugf("Failed to render %i PDFs.\n", failures); return 1; } + + return 0; +} + +int tool_main(int argc, char** argv); +int tool_main(int argc, char** argv) { +#ifdef SK_USE_CDB + setUpDebuggingFromArgs(argv[0]); + __try { +#endif + return tool_main_core(argc, argv); +#ifdef SK_USE_CDB + } + __except(GenerateDumpAndPrintCallstack(GetExceptionInformation())) + { + return -1; + } +#endif return 0; } diff --git a/tools/win_dbghelp.cpp b/tools/win_dbghelp.cpp new file mode 100644 index 0000000..cd215ff --- /dev/null +++ b/tools/win_dbghelp.cpp @@ -0,0 +1,247 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifdef SK_BUILD_FOR_WIN32 + +#include "windows.h" +#include "win_dbghelp.h" +#include +#include +#include +#include + +// Remove prefix addresses. 18 = 2 * (8 digit hexa + 1 space). +// e.g. "abcd1234 abcd1234 render_pdf!processInput +#define CDB_CALLSTACK_PREFIX (18) + +// CDB.EXE prints a lot of garbage and there is no argument to pass which +// would remove all that noise. +// Using eval feature that evaluates a number in hex and prints it to stdout +// to mark where the callstack is printed. +// For example, each thread's callstack will be marked with "12340000" at +// the beginning and "12340001" at the end. +// We just made up these numbers; they could be anything, as long as they +// match up with their decimal equivalents. + +#define MARKER_THREAD_CALLSTACK_START_NUMBER "12340000" +#define MARKER_THREAD_CALLSTACK_START "Evaluate expression: 305397760 = 12340000" + +#define MARKER_THREAD_CALLSTACK_STOP_NUMBER "12340001" +#define MARKER_THREAD_CALLSTACK_STOP "Evaluate expression: 305397761 = 12340001" + +#define MARKER_EXCEPTION_CALLSTACK_START_NUMBER "12340002" +#define MARKER_EXCEPTION_CALLSTACK_START "Evaluate expression: 305397762 = 12340002" + +#define MARKER_EXCEPTION_CALLSTACK_STOP_NUMBER "12340003" +#define MARKER_EXCEPTION_CALLSTACK_STOP "Evaluate expression: 305397763 = 12340003" + +// k - print stack +// ? val - evaluate expression. Used to mark the log file. +// .ecxr - load exception, if exception was thrown. +// k - print the resolved stack by .ecxr +// q - quit cdb.exe +#define CDB_PRINT_CALLSTACK_CURRENT_THREAD "? " MARKER_THREAD_CALLSTACK_START_NUMBER "; k; ? " MARKER_THREAD_CALLSTACK_STOP_NUMBER "; .ecxr; ? " MARKER_EXCEPTION_CALLSTACK_START_NUMBER "; k; ? " MARKER_EXCEPTION_CALLSTACK_STOP_NUMBER "; q" + +static void strncpyOrSetBlank(char* dest, const char* src, size_t len) { + const char* srcOrEmptyString = (NULL == src) ? "" : src; + strncpy(dest, srcOrEmptyString, len); +} + +char debug_app_name[MAX_PATH] = ""; +void setAppName(const char* app_name) { + strncpyOrSetBlank(debug_app_name, app_name, sizeof(debug_app_name)); +} + +const char* getAppName() { + return debug_app_name; +} + +char debug_binaries_path[MAX_PATH] = ""; +void setBinariesPath(const char* binaries_path) { + strncpyOrSetBlank(debug_binaries_path, binaries_path, + sizeof(debug_binaries_path)); +} + +const char* getBinariesPath() { + return debug_binaries_path; +} + +char debug_app_version[100] = ""; +void setAppVersion(const char* version) { + strncpyOrSetBlank(debug_app_version, version, sizeof(debug_app_version)); +} + +const char* getAppVersion() { + return debug_app_version; +} + +char debug_cdb_path[MAX_PATH] = ""; +void setCdbPath(const char* path) { + strncpyOrSetBlank(debug_cdb_path, path, sizeof(debug_cdb_path)); +} + +const char* getCdbPath() { + return debug_cdb_path; +} + +/** Print all the lines of a CDB k command whicha are callstacks. + * Callstack lines are marked by start and stop markers and they are prefixed + * byt 2 hex adresses, which will not be reported. + */ +static void printCallstack(const char* filename, + const char* start, + const char* stop) { + FILE* file = fopen(filename, "rt"); + char line[1000]; + bool started = false; + // Not the most performant code, but this will be used only to collect + // the callstack from a log files, only when the application had failed. + while (fgets(line, sizeof(line), file)) { + if (!started && strncmp(start, line, strlen(start)) == 0) { + started = true; + } else if (started && strncmp(stop, line, strlen(stop)) == 0) { + break; + } else if (started) { + // Filter messages. Calstack lines contain "exe/dll!function" + if (strchr(line, '!') != NULL && strlen(line) > CDB_CALLSTACK_PREFIX) { + printf("%s", line + CDB_CALLSTACK_PREFIX); // fgets includes \n already. + } + } + } + fclose(file); +} + +#define BUILD_UNIQUE_FILENAME(var, ext, szPath, szAppName, szVersion, stLocalTime) \ + sprintf(szFileName, "%s%s\\%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld" ext, \ + szPath, szAppName, szVersion, \ + stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, \ + stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, \ + GetCurrentProcessId(), GetCurrentThreadId()); + +// Exception execution handler. Exception is recognized. Transfer control to +// the exception handler by executing the __except compound statement, +// then continue execution after the __except block. +int GenerateDumpAndPrintCallstack(EXCEPTION_POINTERS* pExceptionPointers) { + BOOL bMiniDumpSuccessful; + char szPath[MAX_PATH]; + char szFileName[MAX_PATH]; + char szFileNameOutput[MAX_PATH]; + const char* szAppName = getAppName(); + const char* szVersion = getAppVersion(); + DWORD dwBufferSize = MAX_PATH; + HANDLE hDumpFile; + SYSTEMTIME stLocalTime; + MINIDUMP_EXCEPTION_INFORMATION ExpParam; + + GetLocalTime( &stLocalTime ); + GetTempPath( dwBufferSize, szPath ); + + sprintf( szFileName, "%s%s", szPath, szAppName ); + CreateDirectory( szFileName, NULL ); + + BUILD_UNIQUE_FILENAME(szFileName, ".dmp", szPath, szAppName, szVersion, stLocalTime); + BUILD_UNIQUE_FILENAME(szFileNameOutput, ".out", szPath, szAppName, szVersion, stLocalTime); + + hDumpFile = CreateFile(szFileName, + GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_WRITE|FILE_SHARE_READ, + 0, + CREATE_ALWAYS, + 0, + 0); + + ExpParam.ThreadId = GetCurrentThreadId(); + ExpParam.ExceptionPointers = pExceptionPointers; + ExpParam.ClientPointers = TRUE; + + bMiniDumpSuccessful = MiniDumpWriteDump(GetCurrentProcess(), + GetCurrentProcessId(), + hDumpFile, + MiniDumpWithDataSegs, + &ExpParam, + NULL, + NULL); + + printf("MiniDump file: %s\n", szFileName); + printf("App exe and pdb: %s\n", getBinariesPath()); + + const char* cdbExePath = getCdbPath(); + if (cdbExePath && *cdbExePath != '\0') { + printf("Cdb exe: %s\n", cdbExePath); + + char command[MAX_PATH * 4]; + sprintf(command, "%s -y \"%s\" -i \"%s\" -z \"%s\" -c \"%s\" -kqm >\"%s\"", + cdbExePath, + getBinariesPath(), + getBinariesPath(), + szFileName, + CDB_PRINT_CALLSTACK_CURRENT_THREAD, + szFileNameOutput); + system(command); + + printf("\nThread Callstack:\n"); + printCallstack(szFileNameOutput, + MARKER_THREAD_CALLSTACK_START, + MARKER_THREAD_CALLSTACK_STOP); + + printf("\nException Callstack:\n"); + printCallstack(szFileNameOutput, + MARKER_EXCEPTION_CALLSTACK_START, + MARKER_EXCEPTION_CALLSTACK_STOP); + } else { + printf("Warning: CDB path not set up.\n"); + } + + return EXCEPTION_EXECUTE_HANDLER; +} + +/** Sets the debugging variables. Input parameter is app location. + * e.g out\Debug\render_pdfs.exe + * This function expects the .pdb file to be in the same directory. + */ +void setUpDebuggingFromArgs(const char* vargs0) { + int i = strlen(vargs0); + + if (i >= 4 && stricmp(vargs0 - 4, ".exe") == 0) { + // Ignore .exe + i -= 4; + } + + int pos_period = i; + + // Find last \ in path - this is Windows! + while (i >= 0 && vargs0[i] != '\\') { + i--; + } + + int pos_last_slash = i; + + char app_name[MAX_PATH]; + strncpy(app_name, vargs0 + pos_last_slash + 1, pos_period - pos_last_slash - 1); + app_name[pos_period - pos_last_slash] = '\0'; + setAppName(app_name); + + char binaries_path[MAX_PATH]; + strncpy(binaries_path, vargs0, pos_last_slash); + binaries_path[pos_last_slash] = '\0'; + setBinariesPath(binaries_path); + + setAppVersion("1.0"); // Dummy for now, but use revision instead if we use + // the minidump for anything else other than + // collecting the callstack. + + // cdb.exe is the app used to load the minidump which prints the callstack. + char cdbExePath[MAX_PATH]; +#ifdef _WIN64 + sprintf(cdbExePath, "%s\\x64\\cdb.exe", SK_CDB_PATH); +#else + sprintf(cdbExePath, "%s\\cdb.exe", SK_CDB_PATH); +#endif + setCdbPath(cdbExePath); +} + +#endif // SK_BUILD_FOR_WIN32 diff --git a/tools/win_dbghelp.h b/tools/win_dbghelp.h new file mode 100644 index 0000000..d334318 --- /dev/null +++ b/tools/win_dbghelp.h @@ -0,0 +1,35 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef win_dbghelp_DEFINED +#define win_dbghelp_DEFINED + +#ifdef SK_BUILD_FOR_WIN32 + +#include +#include +#include + +void setAppName(const char* app_name); +const char* getAppName(); + +void setBinariesPath(const char* binaries_path); +const char* getBinariesPath(); + +void setAppVersion(const char* version); +const char* getAppVersion(); + +void setCdbPath(const char* path); +const char* getCdbPath(); + +void setUpDebuggingFromArgs(const char* vargs0); + +int GenerateDumpAndPrintCallstack(EXCEPTION_POINTERS* pExceptionPointers); + +#endif // SK_BUILD_FOR_WIN32 + +#endif // win_dbghelp_DEFINED -- 2.7.4