1#!/usr/bin/python 2 3#---------------------------------------------------------------------- 4# Be sure to add the python path that points to the LLDB shared library. 5# 6# To use this in the embedded python interpreter using "lldb": 7# 8# cd /path/containing/crashlog.py 9# lldb 10# (lldb) script import crashlog 11# "crashlog" command installed, type "crashlog --help" for detailed help 12# (lldb) crashlog ~/Library/Logs/DiagnosticReports/a.crash 13# 14# The benefit of running the crashlog command inside lldb in the 15# embedded python interpreter is when the command completes, there 16# will be a target with all of the files loaded at the locations 17# described in the crash log. Only the files that have stack frames 18# in the backtrace will be loaded unless the "--load-all" option 19# has been specified. This allows users to explore the program in the 20# state it was in right at crash time. 21# 22# On MacOSX csh, tcsh: 23# ( setenv PYTHONPATH /path/to/LLDB.framework/Resources/Python ; ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash ) 24# 25# On MacOSX sh, bash: 26# PYTHONPATH=/path/to/LLDB.framework/Resources/Python ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash 27#---------------------------------------------------------------------- 28 29import lldb 30import commands 31import cmd 32import datetime 33import glob 34import optparse 35import os 36import platform 37import plistlib 38import pprint # pp = pprint.PrettyPrinter(indent=4); pp.pprint(command_args) 39import re 40import shlex 41import string 42import sys 43import time 44import uuid 45from lldb.utils import symbolication 46 47PARSE_MODE_NORMAL = 0 48PARSE_MODE_THREAD = 1 49PARSE_MODE_IMAGES = 2 50PARSE_MODE_THREGS = 3 51PARSE_MODE_SYSTEM = 4 52 53class CrashLog(symbolication.Symbolicator): 54 """Class that does parses darwin crash logs""" 55 thread_state_regex = re.compile('^Thread ([0-9]+) crashed with') 56 thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)') 57 frame_regex = re.compile('^([0-9]+) +([^ ]+) *\t(0x[0-9a-fA-F]+) +(.*)') 58 image_regex_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^<]+)<([-0-9a-fA-F]+)> (.*)'); 59 image_regex_no_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^/]+)/(.*)'); 60 empty_line_regex = re.compile('^$') 61 62 class Thread: 63 """Class that represents a thread in a darwin crash log""" 64 def __init__(self, index): 65 self.index = index 66 self.frames = list() 67 self.registers = dict() 68 self.reason = None 69 self.queue = None 70 71 def dump(self, prefix): 72 print "%sThread[%u] %s" % (prefix, self.index, self.reason) 73 if self.frames: 74 print "%s Frames:" % (prefix) 75 for frame in self.frames: 76 frame.dump(prefix + ' ') 77 if self.registers: 78 print "%s Registers:" % (prefix) 79 for reg in self.registers.keys(): 80 print "%s %-5s = %#16.16x" % (prefix, reg, self.registers[reg]) 81 82 def did_crash(self): 83 return self.reason != None 84 85 def __str__(self): 86 s = "Thread[%u]" % self.index 87 if self.reason: 88 s += ' %s' % self.reason 89 return s 90 91 92 class Frame: 93 """Class that represents a stack frame in a thread in a darwin crash log""" 94 def __init__(self, index, pc, description): 95 self.pc = pc 96 self.description = description 97 self.index = index 98 99 def __str__(self): 100 if self.description: 101 return "[%3u] 0x%16.16x %s" % (self.index, self.pc, self.description) 102 else: 103 return "[%3u] 0x%16.16x" % (self.index, self.pc) 104 105 def dump(self, prefix): 106 print "%s%s" % (prefix, str(self)) 107 108 class DarwinImage(symbolication.Image): 109 """Class that represents a binary images in a darwin crash log""" 110 dsymForUUIDBinary = os.path.expanduser('~rc/bin/dsymForUUID') 111 if not os.path.exists(dsymForUUIDBinary): 112 dsymForUUIDBinary = commands.getoutput('which dsymForUUID') 113 114 dwarfdump_uuid_regex = re.compile('UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*') 115 116 def __init__(self, text_addr_lo, text_addr_hi, identifier, version, uuid, path): 117 symbolication.Image.__init__(self, path, uuid); 118 self.add_section (symbolication.Section(text_addr_lo, text_addr_hi, "__TEXT")) 119 self.identifier = identifier 120 self.version = version 121 122 def locate_module_and_debug_symbols(self): 123 # Don't load a module twice... 124 if self.resolved: 125 return True 126 # Mark this as resolved so we don't keep trying 127 self.resolved = True 128 uuid_str = self.get_normalized_uuid_string() 129 print 'Getting symbols for %s %s...' % (uuid_str, self.path), 130 if os.path.exists(self.dsymForUUIDBinary): 131 dsym_for_uuid_command = '%s %s' % (self.dsymForUUIDBinary, uuid_str) 132 s = commands.getoutput(dsym_for_uuid_command) 133 if s: 134 plist_root = plistlib.readPlistFromString (s) 135 if plist_root: 136 plist = plist_root[uuid_str] 137 if plist: 138 if 'DBGArchitecture' in plist: 139 self.arch = plist['DBGArchitecture'] 140 if 'DBGDSYMPath' in plist: 141 self.symfile = os.path.realpath(plist['DBGDSYMPath']) 142 if 'DBGSymbolRichExecutable' in plist: 143 self.resolved_path = os.path.expanduser (plist['DBGSymbolRichExecutable']) 144 if not self.resolved_path and os.path.exists(self.path): 145 dwarfdump_cmd_output = commands.getoutput('dwarfdump --uuid "%s"' % self.path) 146 self_uuid = self.get_uuid() 147 for line in dwarfdump_cmd_output.splitlines(): 148 match = self.dwarfdump_uuid_regex.search (line) 149 if match: 150 dwarf_uuid_str = match.group(1) 151 dwarf_uuid = uuid.UUID(dwarf_uuid_str) 152 if self_uuid == dwarf_uuid: 153 self.resolved_path = self.path 154 self.arch = match.group(2) 155 break; 156 if not self.resolved_path: 157 self.unavailable = True 158 print "error\n error: unable to locate '%s' with UUID %s" % (self.path, uuid_str) 159 return False 160 if (self.resolved_path and os.path.exists(self.resolved_path)) or (self.path and os.path.exists(self.path)): 161 print 'ok' 162 # if self.resolved_path: 163 # print ' exe = "%s"' % self.resolved_path 164 # if self.symfile: 165 # print ' dsym = "%s"' % self.symfile 166 return True 167 else: 168 self.unavailable = True 169 return False 170 171 172 173 def __init__(self, path): 174 """CrashLog constructor that take a path to a darwin crash log file""" 175 symbolication.Symbolicator.__init__(self); 176 self.path = os.path.expanduser(path); 177 self.info_lines = list() 178 self.system_profile = list() 179 self.threads = list() 180 self.idents = list() # A list of the required identifiers for doing all stack backtraces 181 self.crashed_thread_idx = -1 182 self.version = -1 183 self.error = None 184 # With possible initial component of ~ or ~user replaced by that user's home directory. 185 try: 186 f = open(self.path) 187 except IOError: 188 self.error = 'error: cannot open "%s"' % self.path 189 return 190 191 self.file_lines = f.read().splitlines() 192 parse_mode = PARSE_MODE_NORMAL 193 thread = None 194 for line in self.file_lines: 195 # print line 196 line_len = len(line) 197 if line_len == 0: 198 if thread: 199 if parse_mode == PARSE_MODE_THREAD: 200 if thread.index == self.crashed_thread_idx: 201 thread.reason = '' 202 if self.thread_exception: 203 thread.reason += self.thread_exception 204 if self.thread_exception_data: 205 thread.reason += " (%s)" % self.thread_exception_data 206 self.threads.append(thread) 207 thread = None 208 else: 209 # only append an extra empty line if the previous line 210 # in the info_lines wasn't empty 211 if len(self.info_lines) > 0 and len(self.info_lines[-1]): 212 self.info_lines.append(line) 213 parse_mode = PARSE_MODE_NORMAL 214 # print 'PARSE_MODE_NORMAL' 215 elif parse_mode == PARSE_MODE_NORMAL: 216 if line.startswith ('Process:'): 217 (self.process_name, pid_with_brackets) = line[8:].strip().split() 218 self.process_id = pid_with_brackets.strip('[]') 219 elif line.startswith ('Path:'): 220 self.process_path = line[5:].strip() 221 elif line.startswith ('Identifier:'): 222 self.process_identifier = line[11:].strip() 223 elif line.startswith ('Version:'): 224 version_string = line[8:].strip() 225 matched_pair = re.search("(.+)\((.+)\)", version_string) 226 if matched_pair: 227 self.process_version = matched_pair.group(1) 228 self.process_compatability_version = matched_pair.group(2) 229 else: 230 self.process = version_string 231 self.process_compatability_version = version_string 232 elif line.startswith ('Parent Process:'): 233 (self.parent_process_name, pid_with_brackets) = line[15:].strip().split() 234 self.parent_process_id = pid_with_brackets.strip('[]') 235 elif line.startswith ('Exception Type:'): 236 self.thread_exception = line[15:].strip() 237 continue 238 elif line.startswith ('Exception Codes:'): 239 self.thread_exception_data = line[16:].strip() 240 continue 241 elif line.startswith ('Crashed Thread:'): 242 self.crashed_thread_idx = int(line[15:].strip().split()[0]) 243 continue 244 elif line.startswith ('Report Version:'): 245 self.version = int(line[15:].strip()) 246 continue 247 elif line.startswith ('System Profile:'): 248 parse_mode = PARSE_MODE_SYSTEM 249 continue 250 elif (line.startswith ('Interval Since Last Report:') or 251 line.startswith ('Crashes Since Last Report:') or 252 line.startswith ('Per-App Interval Since Last Report:') or 253 line.startswith ('Per-App Crashes Since Last Report:') or 254 line.startswith ('Sleep/Wake UUID:') or 255 line.startswith ('Anonymous UUID:')): 256 # ignore these 257 continue 258 elif line.startswith ('Thread'): 259 thread_state_match = self.thread_state_regex.search (line) 260 if thread_state_match: 261 thread_state_match = self.thread_regex.search (line) 262 thread_idx = int(thread_state_match.group(1)) 263 parse_mode = PARSE_MODE_THREGS 264 thread = self.threads[thread_idx] 265 else: 266 thread_match = self.thread_regex.search (line) 267 if thread_match: 268 # print 'PARSE_MODE_THREAD' 269 parse_mode = PARSE_MODE_THREAD 270 thread_idx = int(thread_match.group(1)) 271 thread = CrashLog.Thread(thread_idx) 272 continue 273 elif line.startswith ('Binary Images:'): 274 parse_mode = PARSE_MODE_IMAGES 275 continue 276 self.info_lines.append(line.strip()) 277 elif parse_mode == PARSE_MODE_THREAD: 278 if line.startswith ('Thread'): 279 continue 280 frame_match = self.frame_regex.search(line) 281 if frame_match: 282 ident = frame_match.group(2) 283 if not ident in self.idents: 284 self.idents.append(ident) 285 thread.frames.append (CrashLog.Frame(int(frame_match.group(1)), int(frame_match.group(3), 0), frame_match.group(4))) 286 else: 287 print 'error: frame regex failed for line: "%s"' % line 288 elif parse_mode == PARSE_MODE_IMAGES: 289 image_match = self.image_regex_uuid.search (line) 290 if image_match: 291 image = CrashLog.DarwinImage (int(image_match.group(1),0), 292 int(image_match.group(2),0), 293 image_match.group(3).strip(), 294 image_match.group(4).strip(), 295 uuid.UUID(image_match.group(5)), 296 image_match.group(6)) 297 self.images.append (image) 298 else: 299 image_match = self.image_regex_no_uuid.search (line) 300 if image_match: 301 image = CrashLog.DarwinImage (int(image_match.group(1),0), 302 int(image_match.group(2),0), 303 image_match.group(3).strip(), 304 image_match.group(4).strip(), 305 None, 306 image_match.group(5)) 307 self.images.append (image) 308 else: 309 print "error: image regex failed for: %s" % line 310 311 elif parse_mode == PARSE_MODE_THREGS: 312 stripped_line = line.strip() 313 reg_values = re.split(' +', stripped_line); 314 for reg_value in reg_values: 315 #print 'reg_value = "%s"' % reg_value 316 (reg, value) = reg_value.split(': ') 317 #print 'reg = "%s"' % reg 318 #print 'value = "%s"' % value 319 thread.registers[reg.strip()] = int(value, 0) 320 elif parse_mode == PARSE_MODE_SYSTEM: 321 self.system_profile.append(line) 322 f.close() 323 324 def dump(self): 325 print "Crash Log File: %s" % (self.path) 326 print "\nThreads:" 327 for thread in self.threads: 328 thread.dump(' ') 329 print "\nImages:" 330 for image in self.images: 331 image.dump(' ') 332 333 def find_image_with_identifier(self, identifier): 334 for image in self.images: 335 if image.identifier == identifier: 336 return image 337 return None 338 339 def create_target(self): 340 #print 'crashlog.create_target()...' 341 target = symbolication.Symbolicator.create_target(self) 342 if target: 343 return target 344 # We weren't able to open the main executable as, but we can still symbolicate 345 print 'crashlog.create_target()...2' 346 if self.idents: 347 for ident in self.idents: 348 image = self.find_image_with_identifier (ident) 349 if image: 350 target = image.create_target () 351 if target: 352 return target # success 353 print 'crashlog.create_target()...3' 354 for image in self.images: 355 target = image.create_target () 356 if target: 357 return target # success 358 print 'crashlog.create_target()...4' 359 print 'error: unable to locate any executables from the crash log' 360 return None 361 362 363def usage(): 364 print "Usage: lldb-symbolicate.py [-n name] executable-image" 365 sys.exit(0) 366 367class Interactive(cmd.Cmd): 368 '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.''' 369 image_option_parser = None 370 371 def __init__(self, crash_logs): 372 cmd.Cmd.__init__(self) 373 self.use_rawinput = False 374 self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.' 375 self.crash_logs = crash_logs 376 self.prompt = '% ' 377 378 def default(self, line): 379 '''Catch all for unknown command, which will exit the interpreter.''' 380 print "uknown command: %s" % line 381 return True 382 383 def do_q(self, line): 384 '''Quit command''' 385 return True 386 387 def do_quit(self, line): 388 '''Quit command''' 389 return True 390 391 def do_symbolicate(self, line): 392 description='''Symbolicate one or more darwin crash log files by index to provide source file and line information, 393 inlined stack frames back to the concrete functions, and disassemble the location of the crash 394 for the first frame of the crashed thread.''' 395 option_parser = CreateSymbolicateCrashLogOptions ('symbolicate', description, False) 396 command_args = shlex.split(line) 397 try: 398 (options, args) = option_parser.parse_args(command_args) 399 except: 400 return 401 402 for idx_str in args: 403 idx = int(idx_str) 404 if idx < len(self.crash_logs): 405 SymbolicateCrashLog (self.crash_logs[idx], options) 406 else: 407 print 'error: crash log index %u is out of range' % (idx) 408 409 def do_list(self, line=None): 410 '''Dump a list of all crash logs that are currently loaded. 411 412 USAGE: list''' 413 print '%u crash logs are loaded:' % len(self.crash_logs) 414 for (crash_log_idx, crash_log) in enumerate(self.crash_logs): 415 print '[%u] = %s' % (crash_log_idx, crash_log.path) 416 417 def do_image(self, line): 418 '''Dump information about an image in the crash log given an image basename. 419 420 USAGE: image <basename>''' 421 usage = "usage: %prog [options] <PATH> [PATH ...]" 422 description='''Dump information about one or more images in all crash logs. The <PATH> 423 can be a full path or a image basename.''' 424 command_args = shlex.split(line) 425 if not self.image_option_parser: 426 self.image_option_parser = optparse.OptionParser(description=description, prog='image',usage=usage) 427 self.image_option_parser.add_option('-a', '--all', action='store_true', help='show all images', default=False) 428 try: 429 (options, args) = self.image_option_parser.parse_args(command_args) 430 except: 431 return 432 433 if args: 434 for image_path in args: 435 fullpath_search = image_path[0] == '/' 436 for crash_log in self.crash_logs: 437 matches_found = 0 438 for (image_idx, image) in enumerate(crash_log.images): 439 if fullpath_search: 440 if image.get_resolved_path() == image_path: 441 matches_found += 1 442 print image 443 else: 444 image_basename = image.get_resolved_path_basename() 445 if image_basename == image_path: 446 matches_found += 1 447 print image 448 if matches_found == 0: 449 for (image_idx, image) in enumerate(crash_log.images): 450 resolved_image_path = image.get_resolved_path() 451 if resolved_image_path and string.find(image.get_resolved_path(), image_path) >= 0: 452 print image 453 else: 454 for crash_log in self.crash_logs: 455 for (image_idx, image) in enumerate(crash_log.images): 456 print '[%u] %s' % (image_idx, image) 457 return False 458 459 460def interactive_crashlogs(options, args): 461 crash_log_files = list() 462 for arg in args: 463 for resolved_path in glob.glob(arg): 464 crash_log_files.append(resolved_path) 465 466 crash_logs = list(); 467 for crash_log_file in crash_log_files: 468 #print 'crash_log_file = "%s"' % crash_log_file 469 crash_log = CrashLog(crash_log_file) 470 if crash_log.error: 471 print crash_log.error 472 continue 473 if options.debug: 474 crash_log.dump() 475 if not crash_log.images: 476 print 'error: no images in crash log "%s"' % (crash_log) 477 continue 478 else: 479 crash_logs.append(crash_log) 480 481 interpreter = Interactive(crash_logs) 482 # List all crash logs that were imported 483 interpreter.do_list() 484 interpreter.cmdloop() 485 486 487def save_crashlog(debugger, command, result, dict): 488 usage = "usage: %prog [options] <output-path>" 489 description='''Export the state of current target into a crashlog file''' 490 parser = optparse.OptionParser(description=description, prog='save_crashlog',usage=usage) 491 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False) 492 try: 493 (options, args) = parser.parse_args(shlex.split(command)) 494 except: 495 result.PutCString ("error: invalid options"); 496 return 497 if len(args) != 1: 498 result.PutCString ("error: invalid arguments, a single output file is the only valid argument") 499 return 500 out_file = open(args[0], 'w') 501 if not out_file: 502 result.PutCString ("error: failed to open file '%s' for writing...", args[0]); 503 return 504 if lldb.target: 505 identifier = lldb.target.executable.basename 506 if lldb.process: 507 pid = lldb.process.id 508 if pid != lldb.LLDB_INVALID_PROCESS_ID: 509 out_file.write('Process: %s [%u]\n' % (identifier, pid)) 510 out_file.write('Path: %s\n' % (lldb.target.executable.fullpath)) 511 out_file.write('Identifier: %s\n' % (identifier)) 512 out_file.write('\nDate/Time: %s\n' % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) 513 out_file.write('OS Version: Mac OS X %s (%s)\n' % (platform.mac_ver()[0], commands.getoutput('sysctl -n kern.osversion'))); 514 out_file.write('Report Version: 9\n') 515 for thread_idx in range(lldb.process.num_threads): 516 thread = lldb.process.thread[thread_idx] 517 out_file.write('\nThread %u:\n' % (thread_idx)) 518 for (frame_idx, frame) in enumerate(thread.frames): 519 frame_pc = frame.pc 520 frame_offset = 0 521 if frame.function: 522 block = frame.GetFrameBlock() 523 block_range = block.range[frame.addr] 524 if block_range: 525 block_start_addr = block_range[0] 526 frame_offset = frame_pc - block_start_addr.load_addr 527 else: 528 frame_offset = frame_pc - frame.function.addr.load_addr 529 elif frame.symbol: 530 frame_offset = frame_pc - frame.symbol.addr.load_addr 531 out_file.write('%-3u %-32s 0x%16.16x %s' % (frame_idx, frame.module.file.basename, frame_pc, frame.name)) 532 if frame_offset > 0: 533 out_file.write(' + %u' % (frame_offset)) 534 line_entry = frame.line_entry 535 if line_entry: 536 if options.verbose: 537 # This will output the fullpath + line + column 538 out_file.write(' %s' % (line_entry)) 539 else: 540 out_file.write(' %s:%u' % (line_entry.file.basename, line_entry.line)) 541 column = line_entry.column 542 if column: 543 out_file.write(':%u' % (column)) 544 out_file.write('\n') 545 546 out_file.write('\nBinary Images:\n') 547 for module in lldb.target.modules: 548 text_segment = module.section['__TEXT'] 549 if text_segment: 550 text_segment_load_addr = text_segment.GetLoadAddress(lldb.target) 551 if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS: 552 text_segment_end_load_addr = text_segment_load_addr + text_segment.size 553 identifier = module.file.basename 554 module_version = '???' 555 module_version_array = module.GetVersion() 556 if module_version_array: 557 module_version = '.'.join(map(str,module_version_array)) 558 out_file.write (' 0x%16.16x - 0x%16.16x %s (%s - ???) <%s> %s\n' % (text_segment_load_addr, text_segment_end_load_addr, identifier, module_version, module.GetUUIDString(), module.file.fullpath)) 559 out_file.close() 560 else: 561 result.PutCString ("error: invalid target"); 562 563 564def Symbolicate(debugger, command, result, dict): 565 try: 566 SymbolicateCrashLogs (shlex.split(command)) 567 except: 568 result.PutCString ("error: python exception %s" % sys.exc_info()[0]) 569 570def SymbolicateCrashLog(crash_log, options): 571 if crash_log.error: 572 print crash_log.error 573 return 574 if options.debug: 575 crash_log.dump() 576 if not crash_log.images: 577 print 'error: no images in crash log' 578 return 579 580 target = crash_log.create_target () 581 if not target: 582 return 583 exe_module = target.GetModuleAtIndex(0) 584 images_to_load = list() 585 loaded_images = list() 586 if options.load_all_images: 587 # --load-all option was specified, load everything up 588 for image in crash_log.images: 589 images_to_load.append(image) 590 else: 591 # Only load the images found in stack frames for the crashed threads 592 for ident in crash_log.idents: 593 images = crash_log.find_images_with_identifier (ident) 594 if images: 595 for image in images: 596 images_to_load.append(image) 597 else: 598 print 'error: can\'t find image for identifier "%s"' % ident 599 600 for image in images_to_load: 601 if image in loaded_images: 602 print "warning: skipping %s loaded at %#16.16x duplicate entry (probably commpage)" % (image.path, image.text_addr_lo) 603 else: 604 err = image.add_module (target) 605 if err: 606 print err 607 else: 608 #print 'loaded %s' % image 609 loaded_images.append(image) 610 611 for thread in crash_log.threads: 612 this_thread_crashed = thread.did_crash() 613 if options.crashed_only and this_thread_crashed == False: 614 continue 615 print "%s" % thread 616 #prev_frame_index = -1 617 for frame_idx, frame in enumerate(thread.frames): 618 disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth; 619 if frame_idx == 0: 620 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc, options.verbose) 621 else: 622 # Any frame above frame zero and we have to subtract one to get the previous line entry 623 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc - 1, options.verbose) 624 625 if symbolicated_frame_addresses: 626 symbolicated_frame_address_idx = 0 627 for symbolicated_frame_address in symbolicated_frame_addresses: 628 print '[%3u] %s' % (frame_idx, symbolicated_frame_address) 629 630 if symbolicated_frame_address_idx == 0: 631 if disassemble: 632 instructions = symbolicated_frame_address.get_instructions() 633 if instructions: 634 print 635 symbolication.disassemble_instructions (target, 636 instructions, 637 frame.pc, 638 options.disassemble_before, 639 options.disassemble_after, frame.index > 0) 640 print 641 symbolicated_frame_address_idx += 1 642 else: 643 print frame 644 print 645 646 if options.dump_image_list: 647 print "Binary Images:" 648 for image in crash_log.images: 649 print image 650 651def CreateSymbolicateCrashLogOptions(command_name, description, add_interactive_options): 652 usage = "usage: %prog [options] <FILE> [FILE ...]" 653 option_parser = optparse.OptionParser(description=description, prog='crashlog',usage=usage) 654 option_parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False) 655 option_parser.add_option('-g', '--debug', action='store_true', dest='debug', help='display verbose debug logging', default=False) 656 option_parser.add_option('-a', '--load-all', action='store_true', dest='load_all_images', help='load all executable images, not just the images found in the crashed stack frames', default=False) 657 option_parser.add_option('--images', action='store_true', dest='dump_image_list', help='show image list', default=False) 658 option_parser.add_option('--debug-delay', type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0) 659 option_parser.add_option('-c', '--crashed-only', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False) 660 option_parser.add_option('-d', '--disasm-depth', type='int', dest='disassemble_depth', help='set the depth in stack frames that should be disassembled (default is 1)', default=1) 661 option_parser.add_option('-D', '--disasm-all', action='store_true', dest='disassemble_all_threads', help='enabled disassembly of frames on all threads (not just the crashed thread)', default=False) 662 option_parser.add_option('-B', '--disasm-before', type='int', dest='disassemble_before', help='the number of instructions to disassemble before the frame PC', default=4) 663 option_parser.add_option('-A', '--disasm-after', type='int', dest='disassemble_after', help='the number of instructions to disassemble after the frame PC', default=4) 664 if add_interactive_options: 665 option_parser.add_option('-i', '--interactive', action='store_true', help='parse all crash logs and enter interactive mode', default=False) 666 return option_parser 667 668def SymbolicateCrashLogs(command_args): 669 description='''Symbolicate one or more darwin crash log files to provide source file and line information, 670inlined stack frames back to the concrete functions, and disassemble the location of the crash 671for the first frame of the crashed thread. 672If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter 673for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been 674created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows 675you to explore the program as if it were stopped at the locations described in the crash log and functions can 676be disassembled and lookups can be performed using the addresses found in the crash log.''' 677 option_parser = CreateSymbolicateCrashLogOptions ('crashlog', description, True) 678 try: 679 (options, args) = option_parser.parse_args(command_args) 680 except: 681 return 682 683 if options.debug: 684 print 'command_args = %s' % command_args 685 print 'options', options 686 print 'args', args 687 688 if options.debug_delay > 0: 689 print "Waiting %u seconds for debugger to attach..." % options.debug_delay 690 time.sleep(options.debug_delay) 691 error = lldb.SBError() 692 693 if args: 694 if options.interactive: 695 interactive_crashlogs(options, args) 696 else: 697 for crash_log_file in args: 698 crash_log = CrashLog(crash_log_file) 699 SymbolicateCrashLog (crash_log, options) 700if __name__ == '__main__': 701 # Create a new debugger instance 702 print 'main' 703 lldb.debugger = lldb.SBDebugger.Create() 704 SymbolicateCrashLogs (sys.argv[1:]) 705elif getattr(lldb, 'debugger', None): 706 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.Symbolicate crashlog') 707 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog') 708 print '"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help' 709 710