1 /* 2 * Copyright 2013 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include <windows.h> 9 #include "tools/win_dbghelp.h" 10 11 #include <process.h> 12 #include <string.h> 13 #include <stdlib.h> 14 #include <stdio.h> 15 16 // Remove prefix addresses. 18 = 2 * (8 digit hexa + 1 space). 17 // e.g. "abcd1234 abcd1234 render_pdf!processInput 18 #define CDB_CALLSTACK_PREFIX (18) 19 20 // CDB.EXE prints a lot of garbage and there is no argument to pass which 21 // would remove all that noise. 22 // Using eval feature that evaluates a number in hex and prints it to stdout 23 // to mark where the callstack is printed. 24 // For example, each thread's callstack will be marked with "12340000" at 25 // the beginning and "12340001" at the end. 26 // We just made up these numbers; they could be anything, as long as they 27 // match up with their decimal equivalents. 28 29 #define MARKER_THREAD_CALLSTACK_START_NUMBER "12340000" 30 #define MARKER_THREAD_CALLSTACK_START "Evaluate expression: 305397760 = 12340000" 31 32 #define MARKER_THREAD_CALLSTACK_STOP_NUMBER "12340001" 33 #define MARKER_THREAD_CALLSTACK_STOP "Evaluate expression: 305397761 = 12340001" 34 35 #define MARKER_EXCEPTION_CALLSTACK_START_NUMBER "12340002" 36 #define MARKER_EXCEPTION_CALLSTACK_START "Evaluate expression: 305397762 = 12340002" 37 38 #define MARKER_EXCEPTION_CALLSTACK_STOP_NUMBER "12340003" 39 #define MARKER_EXCEPTION_CALLSTACK_STOP "Evaluate expression: 305397763 = 12340003" 40 41 // k - print stack 42 // ? val - evaluate expression. Used to mark the log file. 43 // .ecxr - load exception, if exception was thrown. 44 // k - print the resolved stack by .ecxr 45 // q - quit cdb.exe 46 #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" 47 strncpyOrSetBlank(char * dest,const char * src,size_t len)48 static void strncpyOrSetBlank(char* dest, const char* src, size_t len) { 49 const char* srcOrEmptyString = (nullptr == src) ? "" : src; 50 strncpy(dest, srcOrEmptyString, len); 51 } 52 53 char debug_app_name[MAX_PATH] = ""; setAppName(const char * app_name)54 void setAppName(const char* app_name) { 55 strncpyOrSetBlank(debug_app_name, app_name, sizeof(debug_app_name)); 56 } 57 getAppName()58 const char* getAppName() { 59 return debug_app_name; 60 } 61 62 char debug_binaries_path[MAX_PATH] = ""; setBinariesPath(const char * binaries_path)63 void setBinariesPath(const char* binaries_path) { 64 strncpyOrSetBlank(debug_binaries_path, binaries_path, 65 sizeof(debug_binaries_path)); 66 } 67 getBinariesPath()68 const char* getBinariesPath() { 69 return debug_binaries_path; 70 } 71 72 char debug_app_version[100] = ""; setAppVersion(const char * version)73 void setAppVersion(const char* version) { 74 strncpyOrSetBlank(debug_app_version, version, sizeof(debug_app_version)); 75 } 76 getAppVersion()77 const char* getAppVersion() { 78 return debug_app_version; 79 } 80 81 char debug_cdb_path[MAX_PATH] = ""; setCdbPath(const char * path)82 void setCdbPath(const char* path) { 83 strncpyOrSetBlank(debug_cdb_path, path, sizeof(debug_cdb_path)); 84 } 85 getCdbPath()86 const char* getCdbPath() { 87 return debug_cdb_path; 88 } 89 90 /** Print all the lines of a CDB k command whicha are callstacks. 91 * Callstack lines are marked by start and stop markers and they are prefixed 92 * byt 2 hex adresses, which will not be reported. 93 */ printCallstack(const char * filename,const char * start,const char * stop)94 static void printCallstack(const char* filename, 95 const char* start, 96 const char* stop) { 97 FILE* file = fopen(filename, "rt"); 98 char line[1000]; 99 bool started = false; 100 // Not the most performant code, but this will be used only to collect 101 // the callstack from a log files, only when the application had failed. 102 while (fgets(line, sizeof(line), file)) { 103 if (!started && strncmp(start, line, strlen(start)) == 0) { 104 started = true; 105 } else if (started && strncmp(stop, line, strlen(stop)) == 0) { 106 break; 107 } else if (started) { 108 // Filter messages. Calstack lines contain "exe/dll!function" 109 if (strchr(line, '!') != nullptr && strlen(line) > CDB_CALLSTACK_PREFIX) { 110 printf("%s", line + CDB_CALLSTACK_PREFIX); // fgets includes \n already. 111 } 112 } 113 } 114 fclose(file); 115 } 116 117 #define BUILD_UNIQUE_FILENAME(var, ext, szPath, szAppName, szVersion, stLocalTime) \ 118 sprintf(szFileName, "%s%s\\%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld" ext, \ 119 szPath, szAppName, szVersion, \ 120 stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, \ 121 stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, \ 122 GetCurrentProcessId(), GetCurrentThreadId()); 123 124 // Exception execution handler. Exception is recognized. Transfer control to 125 // the exception handler by executing the __except compound statement, 126 // then continue execution after the __except block. GenerateDumpAndPrintCallstack(EXCEPTION_POINTERS * pExceptionPointers)127 int GenerateDumpAndPrintCallstack(EXCEPTION_POINTERS* pExceptionPointers) { 128 BOOL bMiniDumpSuccessful; 129 char szPath[MAX_PATH]; 130 char szFileName[MAX_PATH]; 131 char szFileNameOutput[MAX_PATH]; 132 const char* szAppName = getAppName(); 133 const char* szVersion = getAppVersion(); 134 DWORD dwBufferSize = MAX_PATH; 135 HANDLE hDumpFile; 136 SYSTEMTIME stLocalTime; 137 MINIDUMP_EXCEPTION_INFORMATION ExpParam; 138 139 GetLocalTime( &stLocalTime ); 140 GetTempPath( dwBufferSize, szPath ); 141 142 sprintf( szFileName, "%s%s", szPath, szAppName ); 143 CreateDirectory( szFileName, nullptr ); 144 145 BUILD_UNIQUE_FILENAME(szFileName, ".dmp", szPath, szAppName, szVersion, stLocalTime); 146 BUILD_UNIQUE_FILENAME(szFileNameOutput, ".out", szPath, szAppName, szVersion, stLocalTime); 147 148 hDumpFile = CreateFile(szFileName, 149 GENERIC_READ|GENERIC_WRITE, 150 FILE_SHARE_WRITE|FILE_SHARE_READ, 151 0, 152 CREATE_ALWAYS, 153 0, 154 0); 155 156 ExpParam.ThreadId = GetCurrentThreadId(); 157 ExpParam.ExceptionPointers = pExceptionPointers; 158 ExpParam.ClientPointers = TRUE; 159 160 bMiniDumpSuccessful = MiniDumpWriteDump(GetCurrentProcess(), 161 GetCurrentProcessId(), 162 hDumpFile, 163 MiniDumpWithDataSegs, 164 &ExpParam, 165 nullptr, 166 nullptr); 167 168 printf("MiniDump file: %s\n", szFileName); 169 printf("App exe and pdb: %s\n", getBinariesPath()); 170 171 const char* cdbExePath = getCdbPath(); 172 if (cdbExePath && *cdbExePath != '\0') { 173 printf("Cdb exe: %s\n", cdbExePath); 174 175 char command[MAX_PATH * 4]; 176 sprintf(command, "%s -y \"%s\" -i \"%s\" -z \"%s\" -c \"%s\" -kqm >\"%s\"", 177 cdbExePath, 178 getBinariesPath(), 179 getBinariesPath(), 180 szFileName, 181 CDB_PRINT_CALLSTACK_CURRENT_THREAD, 182 szFileNameOutput); 183 system(command); 184 185 printf("\nThread Callstack:\n"); 186 printCallstack(szFileNameOutput, 187 MARKER_THREAD_CALLSTACK_START, 188 MARKER_THREAD_CALLSTACK_STOP); 189 190 printf("\nException Callstack:\n"); 191 printCallstack(szFileNameOutput, 192 MARKER_EXCEPTION_CALLSTACK_START, 193 MARKER_EXCEPTION_CALLSTACK_STOP); 194 } else { 195 printf("Warning: CDB path not set up.\n"); 196 } 197 198 return EXCEPTION_EXECUTE_HANDLER; 199 } 200 201 /** Sets the debugging variables. Input parameter is app location. 202 * e.g out\Debug\render_pdfs.exe 203 * This function expects the .pdb file to be in the same directory. 204 */ setUpDebuggingFromArgs(const char * vargs0)205 void setUpDebuggingFromArgs(const char* vargs0) { 206 size_t i = strlen(vargs0); 207 208 if (i >= 4 && _stricmp(vargs0 - 4, ".exe") == 0) { 209 // Ignore .exe 210 i -= 4; 211 } 212 213 size_t pos_period = i; 214 215 // Find last \ in path - this is Windows! 216 while (i >= 0 && vargs0[i] != '\\') { 217 i--; 218 } 219 220 size_t pos_last_slash = i; 221 222 char app_name[MAX_PATH]; 223 strncpy(app_name, vargs0 + pos_last_slash + 1, pos_period - pos_last_slash - 1); 224 app_name[pos_period - pos_last_slash] = '\0'; 225 setAppName(app_name); 226 227 char binaries_path[MAX_PATH]; 228 strncpy(binaries_path, vargs0, pos_last_slash); 229 binaries_path[pos_last_slash] = '\0'; 230 setBinariesPath(binaries_path); 231 232 setAppVersion("1.0"); // Placeholder for now, but use revision instead if we use 233 // the minidump for anything else other than 234 // collecting the callstack. 235 236 // cdb.exe is the app used to load the minidump which prints the callstack. 237 char cdbExePath[MAX_PATH]; 238 #ifdef _WIN64 239 sprintf(cdbExePath, "%s\\x64\\cdb.exe", SK_CDB_PATH); 240 #else 241 sprintf(cdbExePath, "%s\\cdb.exe", SK_CDB_PATH); 242 #endif 243 setCdbPath(cdbExePath); 244 } 245