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