1// Copyright (c) 2010 Google Inc. 2// All rights reserved. 3// 4// Redistribution and use in source and binary forms, with or without 5// modification, are permitted provided that the following conditions are 6// met: 7// 8// * Redistributions of source code must retain the above copyright 9// notice, this list of conditions and the following disclaimer. 10// * Redistributions in binary form must reproduce the above 11// copyright notice, this list of conditions and the following disclaimer 12// in the documentation and/or other materials provided with the 13// distribution. 14// * Neither the name of Google Inc. nor the names of its 15// contributors may be used to endorse or promote products derived from 16// this software without specific prior written permission. 17// 18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 30// crash_report.mm: Convert the contents of a minidump into a format that 31// looks more like Apple's CrashReporter format 32 33#include <unistd.h> 34 35#include <mach/machine.h> 36#include <mach-o/arch.h> 37 38#include <string> 39 40#include <Foundation/Foundation.h> 41 42#include "common/scoped_ptr.h" 43#include "google_breakpad/processor/basic_source_line_resolver.h" 44#include "google_breakpad/processor/call_stack.h" 45#include "google_breakpad/processor/code_module.h" 46#include "google_breakpad/processor/minidump.h" 47#include "google_breakpad/processor/minidump_processor.h" 48#include "google_breakpad/processor/process_state.h" 49#include "google_breakpad/processor/stack_frame_cpu.h" 50#include "google_breakpad/processor/system_info.h" 51#include "processor/pathname_stripper.h" 52#include "processor/simple_symbol_supplier.h" 53 54#include "on_demand_symbol_supplier.h" 55 56using std::string; 57 58using google_breakpad::BasicSourceLineResolver; 59using google_breakpad::CallStack; 60using google_breakpad::CodeModule; 61using google_breakpad::CodeModules; 62using google_breakpad::Minidump; 63using google_breakpad::MinidumpProcessor; 64using google_breakpad::OnDemandSymbolSupplier; 65using google_breakpad::PathnameStripper; 66using google_breakpad::ProcessState; 67using google_breakpad::scoped_ptr; 68using google_breakpad::StackFrame; 69using google_breakpad::StackFramePPC; 70using google_breakpad::StackFrameX86; 71using google_breakpad::SystemInfo; 72 73typedef struct { 74 NSString *minidumpPath; 75 NSString *searchDir; 76 NSString *symbolSearchDir; 77 BOOL printThreadMemory; 78} Options; 79 80//============================================================================= 81static int PrintRegister(const char *name, u_int32_t value, int sequence) { 82 if (sequence % 4 == 0) { 83 printf("\n"); 84 } 85 printf("%6s = 0x%08x ", name, value); 86 return ++sequence; 87} 88 89//============================================================================= 90static void PrintStack(const CallStack *stack, const string &cpu) { 91 size_t frame_count = stack->frames()->size(); 92 char buffer[1024]; 93 for (size_t frame_index = 0; frame_index < frame_count; ++frame_index) { 94 const StackFrame *frame = stack->frames()->at(frame_index); 95 const CodeModule *module = frame->module; 96 printf("%2zu ", frame_index); 97 98 if (module) { 99 // Module name (20 chars max) 100 strcpy(buffer, PathnameStripper::File(module->code_file()).c_str()); 101 int maxStr = 20; 102 buffer[maxStr] = 0; 103 printf("%-*s", maxStr, buffer); 104 105 strcpy(buffer, module->version().c_str()); 106 buffer[maxStr] = 0; 107 108 printf("%-*s",maxStr, buffer); 109 110 u_int64_t instruction = frame->instruction; 111 112 // PPC only: Adjust the instruction to match that of Crash reporter. The 113 // instruction listed is actually the return address. See the detailed 114 // comments in stackwalker_ppc.cc for more information. 115 if (cpu == "ppc" && frame_index) 116 instruction += 4; 117 118 printf(" 0x%08llx ", instruction); 119 120 // Function name 121 if (!frame->function_name.empty()) { 122 printf("%s", frame->function_name.c_str()); 123 if (!frame->source_file_name.empty()) { 124 string source_file = PathnameStripper::File(frame->source_file_name); 125 printf(" + 0x%llx (%s:%d)", 126 instruction - frame->source_line_base, 127 source_file.c_str(), frame->source_line); 128 } else { 129 printf(" + 0x%llx", instruction - frame->function_base); 130 } 131 } 132 } 133 printf("\n"); 134 } 135} 136 137//============================================================================= 138static void PrintRegisters(const CallStack *stack, const string &cpu) { 139 int sequence = 0; 140 const StackFrame *frame = stack->frames()->at(0); 141 if (cpu == "x86") { 142 const StackFrameX86 *frame_x86 = 143 reinterpret_cast<const StackFrameX86*>(frame); 144 145 if (frame_x86->context_validity & StackFrameX86::CONTEXT_VALID_EIP) 146 sequence = PrintRegister("eip", frame_x86->context.eip, sequence); 147 if (frame_x86->context_validity & StackFrameX86::CONTEXT_VALID_ESP) 148 sequence = PrintRegister("esp", frame_x86->context.esp, sequence); 149 if (frame_x86->context_validity & StackFrameX86::CONTEXT_VALID_EBP) 150 sequence = PrintRegister("ebp", frame_x86->context.ebp, sequence); 151 if (frame_x86->context_validity & StackFrameX86::CONTEXT_VALID_EBX) 152 sequence = PrintRegister("ebx", frame_x86->context.ebx, sequence); 153 if (frame_x86->context_validity & StackFrameX86::CONTEXT_VALID_ESI) 154 sequence = PrintRegister("esi", frame_x86->context.esi, sequence); 155 if (frame_x86->context_validity & StackFrameX86::CONTEXT_VALID_EDI) 156 sequence = PrintRegister("edi", frame_x86->context.edi, sequence); 157 if (frame_x86->context_validity == StackFrameX86::CONTEXT_VALID_ALL) { 158 sequence = PrintRegister("eax", frame_x86->context.eax, sequence); 159 sequence = PrintRegister("ecx", frame_x86->context.ecx, sequence); 160 sequence = PrintRegister("edx", frame_x86->context.edx, sequence); 161 sequence = PrintRegister("efl", frame_x86->context.eflags, sequence); 162 } 163 } else if (cpu == "ppc") { 164 const StackFramePPC *frame_ppc = 165 reinterpret_cast<const StackFramePPC*>(frame); 166 167 if ((frame_ppc->context_validity & StackFramePPC::CONTEXT_VALID_ALL) == 168 StackFramePPC::CONTEXT_VALID_ALL) { 169 sequence = PrintRegister("srr0", frame_ppc->context.srr0, sequence); 170 sequence = PrintRegister("srr1", frame_ppc->context.srr1, sequence); 171 sequence = PrintRegister("cr", frame_ppc->context.cr, sequence); 172 sequence = PrintRegister("xer", frame_ppc->context.xer, sequence); 173 sequence = PrintRegister("lr", frame_ppc->context.lr, sequence); 174 sequence = PrintRegister("ctr", frame_ppc->context.ctr, sequence); 175 sequence = PrintRegister("mq", frame_ppc->context.mq, sequence); 176 sequence = PrintRegister("vrsave", frame_ppc->context.vrsave, sequence); 177 178 sequence = 0; 179 char buffer[5]; 180 for (int i = 0; i < MD_CONTEXT_PPC_GPR_COUNT; ++i) { 181 sprintf(buffer, "r%d", i); 182 sequence = PrintRegister(buffer, frame_ppc->context.gpr[i], sequence); 183 } 184 } else { 185 if (frame_ppc->context_validity & StackFramePPC::CONTEXT_VALID_SRR0) 186 sequence = PrintRegister("srr0", frame_ppc->context.srr0, sequence); 187 if (frame_ppc->context_validity & StackFramePPC::CONTEXT_VALID_GPR1) 188 sequence = PrintRegister("r1", frame_ppc->context.gpr[1], sequence); 189 } 190 } 191 192 printf("\n"); 193} 194 195static void PrintModules(const CodeModules *modules) { 196 if (!modules) 197 return; 198 199 printf("\n"); 200 printf("Loaded modules:\n"); 201 202 u_int64_t main_address = 0; 203 const CodeModule *main_module = modules->GetMainModule(); 204 if (main_module) { 205 main_address = main_module->base_address(); 206 } 207 208 unsigned int module_count = modules->module_count(); 209 for (unsigned int module_sequence = 0; 210 module_sequence < module_count; 211 ++module_sequence) { 212 const CodeModule *module = modules->GetModuleAtSequence(module_sequence); 213 assert(module); 214 u_int64_t base_address = module->base_address(); 215 printf("0x%08llx - 0x%08llx %s %s%s %s\n", 216 base_address, base_address + module->size() - 1, 217 PathnameStripper::File(module->code_file()).c_str(), 218 module->version().empty() ? "???" : module->version().c_str(), 219 main_module != NULL && base_address == main_address ? 220 " (main)" : "", 221 module->code_file().c_str()); 222 } 223} 224 225static void ProcessSingleReport(Options *options, NSString *file_path) { 226 string minidump_file([file_path fileSystemRepresentation]); 227 BasicSourceLineResolver resolver; 228 string search_dir = options->searchDir ? 229 [options->searchDir fileSystemRepresentation] : ""; 230 string symbol_search_dir = options->symbolSearchDir ? 231 [options->symbolSearchDir fileSystemRepresentation] : ""; 232 scoped_ptr<OnDemandSymbolSupplier> symbol_supplier( 233 new OnDemandSymbolSupplier(search_dir, symbol_search_dir)); 234 scoped_ptr<MinidumpProcessor> 235 minidump_processor(new MinidumpProcessor(symbol_supplier.get(), &resolver)); 236 ProcessState process_state; 237 scoped_ptr<Minidump> dump(new google_breakpad::Minidump(minidump_file)); 238 239 if (!dump->Read()) { 240 fprintf(stderr, "Minidump %s could not be read\n", dump->path().c_str()); 241 return; 242 } 243 if (minidump_processor->Process(dump.get(), &process_state) != 244 google_breakpad::PROCESS_OK) { 245 fprintf(stderr, "MinidumpProcessor::Process failed\n"); 246 return; 247 } 248 249 const SystemInfo *system_info = process_state.system_info(); 250 string cpu = system_info->cpu; 251 252 // Convert the time to a string 253 u_int32_t time_date_stamp = process_state.time_date_stamp(); 254 struct tm timestruct; 255 gmtime_r(reinterpret_cast<time_t*>(&time_date_stamp), ×truct); 256 char timestr[20]; 257 strftime(timestr, 20, "%Y-%m-%d %H:%M:%S", ×truct); 258 printf("Date: %s GMT\n", timestr); 259 260 printf("Operating system: %s (%s)\n", system_info->os.c_str(), 261 system_info->os_version.c_str()); 262 printf("Architecture: %s\n", cpu.c_str()); 263 264 if (process_state.crashed()) { 265 printf("Crash reason: %s\n", process_state.crash_reason().c_str()); 266 printf("Crash address: 0x%llx\n", process_state.crash_address()); 267 } else { 268 printf("No crash\n"); 269 } 270 271 int requesting_thread = process_state.requesting_thread(); 272 if (requesting_thread != -1) { 273 printf("\n"); 274 printf("Thread %d (%s)\n", 275 requesting_thread, 276 process_state.crashed() ? "crashed" : 277 "requested dump, did not crash"); 278 PrintStack(process_state.threads()->at(requesting_thread), cpu); 279 } 280 281 // Print all of the threads in the dump. 282 int thread_count = static_cast<int>(process_state.threads()->size()); 283 const std::vector<google_breakpad::MemoryRegion*> 284 *thread_memory_regions = process_state.thread_memory_regions(); 285 286 for (int thread_index = 0; thread_index < thread_count; ++thread_index) { 287 if (thread_index != requesting_thread) { 288 // Don't print the crash thread again, it was already printed. 289 printf("\n"); 290 printf("Thread %d\n", thread_index); 291 PrintStack(process_state.threads()->at(thread_index), cpu); 292 google_breakpad::MemoryRegion *thread_stack_bytes = 293 thread_memory_regions->at(thread_index); 294 if (options->printThreadMemory) { 295 thread_stack_bytes->Print(); 296 } 297 } 298 } 299 300 // Print the crashed registers 301 if (requesting_thread != -1) { 302 printf("\nThread %d:", requesting_thread); 303 PrintRegisters(process_state.threads()->at(requesting_thread), cpu); 304 } 305 306 // Print information about modules 307 PrintModules(process_state.modules()); 308} 309 310//============================================================================= 311static void Start(Options *options) { 312 NSFileManager *manager = [NSFileManager defaultManager]; 313 NSString *minidump_path = options->minidumpPath; 314 BOOL is_dir = NO; 315 BOOL file_exists = [manager fileExistsAtPath:minidump_path 316 isDirectory:&is_dir]; 317 if (file_exists && is_dir) { 318 NSDirectoryEnumerator *enumerator = 319 [manager enumeratorAtPath:minidump_path]; 320 NSString *current_file = nil; 321 while ((current_file = [enumerator nextObject])) { 322 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 323 if ([[current_file pathExtension] isEqualTo:@"dmp"]) { 324 printf("Attempting to process report: %s\n", 325 [current_file cStringUsingEncoding:NSASCIIStringEncoding]); 326 NSString *full_path = 327 [minidump_path stringByAppendingPathComponent:current_file]; 328 ProcessSingleReport(options, full_path); 329 } 330 [pool release]; 331 } 332 } else if (file_exists) { 333 ProcessSingleReport(options, minidump_path); 334 } 335} 336 337//============================================================================= 338static void Usage(int argc, const char *argv[]) { 339 fprintf(stderr, "Convert a minidump to a crash report. Breakpad symbol " 340 "files will be used (or created if missing) in /tmp.\n" 341 "If a symbol-file-search-dir is specified, any symbol " 342 "files in it will be used instead of being loaded from " 343 "modules on disk.\n" 344 "If modules cannot be found at the paths stored in the " 345 "minidump file, they will be searched for at " 346 "<module-search-dir>/<path-in-minidump-file>.\n"); 347 fprintf(stderr, "Usage: %s [-s module-search-dir] [-S symbol-file-search-dir] " 348 "minidump-file\n", argv[0]); 349 fprintf(stderr, "\t-s: Specify a search directory to use for missing modules\n" 350 "\t-S: Specify a search directory to use for symbol files\n" 351 "\t-t: Print thread stack memory in hex\n" 352 "\t-h: Usage\n" 353 "\t-?: Usage\n"); 354} 355 356//============================================================================= 357static void SetupOptions(int argc, const char *argv[], Options *options) { 358 extern int optind; 359 char ch; 360 361 while ((ch = getopt(argc, (char * const *)argv, "S:s:ht?")) != -1) { 362 switch (ch) { 363 case 's': 364 options->searchDir = [[NSFileManager defaultManager] 365 stringWithFileSystemRepresentation:optarg 366 length:strlen(optarg)]; 367 break; 368 369 case 'S': 370 options->symbolSearchDir = [[NSFileManager defaultManager] 371 stringWithFileSystemRepresentation:optarg 372 length:strlen(optarg)]; 373 break; 374 375 case 't': 376 options->printThreadMemory = YES; 377 break; 378 case 'h': 379 case '?': 380 Usage(argc, argv); 381 exit(1); 382 break; 383 } 384 } 385 386 if ((argc - optind) != 1) { 387 fprintf(stderr, "%s: Missing minidump file\n", argv[0]); 388 Usage(argc, argv); 389 exit(1); 390 } 391 392 options->minidumpPath = [[NSFileManager defaultManager] 393 stringWithFileSystemRepresentation:argv[optind] 394 length:strlen(argv[optind])]; 395} 396 397//============================================================================= 398int main (int argc, const char * argv[]) { 399 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 400 Options options; 401 402 bzero(&options, sizeof(Options)); 403 SetupOptions(argc, argv, &options); 404 Start(&options); 405 [pool release]; 406 407 return 0; 408} 409