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