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 reg_values = re.split(' +', stripped_line); 320 for reg_value in reg_values: 321 #print 'reg_value = "%s"' % reg_value 322 (reg, value) = reg_value.split(': ') 323 #print 'reg = "%s"' % reg 324 #print 'value = "%s"' % value 325 thread.registers[reg.strip()] = int(value, 0) 326 elif parse_mode == PARSE_MODE_SYSTEM: 327 self.system_profile.append(line) 328 f.close() 329 330 def dump(self): 331 print "Crash Log File: %s" % (self.path) 332 print "\nThreads:" 333 for thread in self.threads: 334 thread.dump(' ') 335 print "\nImages:" 336 for image in self.images: 337 image.dump(' ') 338 339 def find_image_with_identifier(self, identifier): 340 for image in self.images: 341 if image.identifier == identifier: 342 return image 343 return None 344 345 def create_target(self): 346 #print 'crashlog.create_target()...' 347 target = symbolication.Symbolicator.create_target(self) 348 if target: 349 return target 350 # We weren't able to open the main executable as, but we can still symbolicate 351 print 'crashlog.create_target()...2' 352 if self.idents: 353 for ident in self.idents: 354 image = self.find_image_with_identifier (ident) 355 if image: 356 target = image.create_target () 357 if target: 358 return target # success 359 print 'crashlog.create_target()...3' 360 for image in self.images: 361 target = image.create_target () 362 if target: 363 return target # success 364 print 'crashlog.create_target()...4' 365 print 'error: unable to locate any executables from the crash log' 366 return None 367 368 369def usage(): 370 print "Usage: lldb-symbolicate.py [-n name] executable-image" 371 sys.exit(0) 372 373class Interactive(cmd.Cmd): 374 '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.''' 375 image_option_parser = None 376 377 def __init__(self, crash_logs): 378 cmd.Cmd.__init__(self) 379 self.use_rawinput = False 380 self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.' 381 self.crash_logs = crash_logs 382 self.prompt = '% ' 383 384 def default(self, line): 385 '''Catch all for unknown command, which will exit the interpreter.''' 386 print "uknown command: %s" % line 387 return True 388 389 def do_q(self, line): 390 '''Quit command''' 391 return True 392 393 def do_quit(self, line): 394 '''Quit command''' 395 return True 396 397 def do_symbolicate(self, line): 398 description='''Symbolicate one or more darwin crash log files by index to provide source file and line information, 399 inlined stack frames back to the concrete functions, and disassemble the location of the crash 400 for the first frame of the crashed thread.''' 401 option_parser = CreateSymbolicateCrashLogOptions ('symbolicate', description, False) 402 command_args = shlex.split(line) 403 try: 404 (options, args) = option_parser.parse_args(command_args) 405 except: 406 return 407 408 if args: 409 # We have arguments, they must valid be crash log file indexes 410 for idx_str in args: 411 idx = int(idx_str) 412 if idx < len(self.crash_logs): 413 SymbolicateCrashLog (self.crash_logs[idx], options) 414 else: 415 print 'error: crash log index %u is out of range' % (idx) 416 else: 417 # No arguments, symbolicate all crash logs using the options provided 418 for idx in range(len(self.crash_logs)): 419 SymbolicateCrashLog (self.crash_logs[idx], options) 420 421 def do_list(self, line=None): 422 '''Dump a list of all crash logs that are currently loaded. 423 424 USAGE: list''' 425 print '%u crash logs are loaded:' % len(self.crash_logs) 426 for (crash_log_idx, crash_log) in enumerate(self.crash_logs): 427 print '[%u] = %s' % (crash_log_idx, crash_log.path) 428 429 def do_image(self, line): 430 '''Dump information about one or more binary images in the crash log given an image basename, or all images if no arguments are provided.''' 431 usage = "usage: %prog [options] <PATH> [PATH ...]" 432 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.''' 433 command_args = shlex.split(line) 434 if not self.image_option_parser: 435 self.image_option_parser = optparse.OptionParser(description=description, prog='image',usage=usage) 436 self.image_option_parser.add_option('-a', '--all', action='store_true', help='show all images', default=False) 437 try: 438 (options, args) = self.image_option_parser.parse_args(command_args) 439 except: 440 return 441 442 if args: 443 for image_path in args: 444 fullpath_search = image_path[0] == '/' 445 for (crash_log_idx, crash_log) in enumerate(self.crash_logs): 446 matches_found = 0 447 for (image_idx, image) in enumerate(crash_log.images): 448 if fullpath_search: 449 if image.get_resolved_path() == image_path: 450 matches_found += 1 451 print '[%u] ' % (crash_log_idx), image 452 else: 453 image_basename = image.get_resolved_path_basename() 454 if image_basename == image_path: 455 matches_found += 1 456 print '[%u] ' % (crash_log_idx), image 457 if matches_found == 0: 458 for (image_idx, image) in enumerate(crash_log.images): 459 resolved_image_path = image.get_resolved_path() 460 if resolved_image_path and string.find(image.get_resolved_path(), image_path) >= 0: 461 print '[%u] ' % (crash_log_idx), image 462 else: 463 for crash_log in self.crash_logs: 464 for (image_idx, image) in enumerate(crash_log.images): 465 print '[%u] %s' % (image_idx, image) 466 return False 467 468 469def interactive_crashlogs(options, args): 470 crash_log_files = list() 471 for arg in args: 472 for resolved_path in glob.glob(arg): 473 crash_log_files.append(resolved_path) 474 475 crash_logs = list(); 476 for crash_log_file in crash_log_files: 477 #print 'crash_log_file = "%s"' % crash_log_file 478 crash_log = CrashLog(crash_log_file) 479 if crash_log.error: 480 print crash_log.error 481 continue 482 if options.debug: 483 crash_log.dump() 484 if not crash_log.images: 485 print 'error: no images in crash log "%s"' % (crash_log) 486 continue 487 else: 488 crash_logs.append(crash_log) 489 490 interpreter = Interactive(crash_logs) 491 # List all crash logs that were imported 492 interpreter.do_list() 493 interpreter.cmdloop() 494 495 496def save_crashlog(debugger, command, result, dict): 497 usage = "usage: %prog [options] <output-path>" 498 description='''Export the state of current target into a crashlog file''' 499 parser = optparse.OptionParser(description=description, prog='save_crashlog',usage=usage) 500 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False) 501 try: 502 (options, args) = parser.parse_args(shlex.split(command)) 503 except: 504 result.PutCString ("error: invalid options"); 505 return 506 if len(args) != 1: 507 result.PutCString ("error: invalid arguments, a single output file is the only valid argument") 508 return 509 out_file = open(args[0], 'w') 510 if not out_file: 511 result.PutCString ("error: failed to open file '%s' for writing...", args[0]); 512 return 513 if lldb.target: 514 identifier = lldb.target.executable.basename 515 if lldb.process: 516 pid = lldb.process.id 517 if pid != lldb.LLDB_INVALID_PROCESS_ID: 518 out_file.write('Process: %s [%u]\n' % (identifier, pid)) 519 out_file.write('Path: %s\n' % (lldb.target.executable.fullpath)) 520 out_file.write('Identifier: %s\n' % (identifier)) 521 out_file.write('\nDate/Time: %s\n' % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) 522 out_file.write('OS Version: Mac OS X %s (%s)\n' % (platform.mac_ver()[0], commands.getoutput('sysctl -n kern.osversion'))); 523 out_file.write('Report Version: 9\n') 524 for thread_idx in range(lldb.process.num_threads): 525 thread = lldb.process.thread[thread_idx] 526 out_file.write('\nThread %u:\n' % (thread_idx)) 527 for (frame_idx, frame) in enumerate(thread.frames): 528 frame_pc = frame.pc 529 frame_offset = 0 530 if frame.function: 531 block = frame.GetFrameBlock() 532 block_range = block.range[frame.addr] 533 if block_range: 534 block_start_addr = block_range[0] 535 frame_offset = frame_pc - block_start_addr.load_addr 536 else: 537 frame_offset = frame_pc - frame.function.addr.load_addr 538 elif frame.symbol: 539 frame_offset = frame_pc - frame.symbol.addr.load_addr 540 out_file.write('%-3u %-32s 0x%16.16x %s' % (frame_idx, frame.module.file.basename, frame_pc, frame.name)) 541 if frame_offset > 0: 542 out_file.write(' + %u' % (frame_offset)) 543 line_entry = frame.line_entry 544 if line_entry: 545 if options.verbose: 546 # This will output the fullpath + line + column 547 out_file.write(' %s' % (line_entry)) 548 else: 549 out_file.write(' %s:%u' % (line_entry.file.basename, line_entry.line)) 550 column = line_entry.column 551 if column: 552 out_file.write(':%u' % (column)) 553 out_file.write('\n') 554 555 out_file.write('\nBinary Images:\n') 556 for module in lldb.target.modules: 557 text_segment = module.section['__TEXT'] 558 if text_segment: 559 text_segment_load_addr = text_segment.GetLoadAddress(lldb.target) 560 if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS: 561 text_segment_end_load_addr = text_segment_load_addr + text_segment.size 562 identifier = module.file.basename 563 module_version = '???' 564 module_version_array = module.GetVersion() 565 if module_version_array: 566 module_version = '.'.join(map(str,module_version_array)) 567 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)) 568 out_file.close() 569 else: 570 result.PutCString ("error: invalid target"); 571 572 573def Symbolicate(debugger, command, result, dict): 574 try: 575 SymbolicateCrashLogs (shlex.split(command)) 576 except: 577 result.PutCString ("error: python exception %s" % sys.exc_info()[0]) 578 579def SymbolicateCrashLog(crash_log, options): 580 if crash_log.error: 581 print crash_log.error 582 return 583 if options.debug: 584 crash_log.dump() 585 if not crash_log.images: 586 print 'error: no images in crash log' 587 return 588 589 target = crash_log.create_target () 590 if not target: 591 return 592 exe_module = target.GetModuleAtIndex(0) 593 images_to_load = list() 594 loaded_images = list() 595 if options.load_all_images: 596 # --load-all option was specified, load everything up 597 for image in crash_log.images: 598 images_to_load.append(image) 599 else: 600 # Only load the images found in stack frames for the crashed threads 601 if options.crashed_only: 602 for thread in crash_log.threads: 603 if thread.did_crash(): 604 for ident in thread.idents: 605 images = crash_log.find_images_with_identifier (ident) 606 if images: 607 for image in images: 608 images_to_load.append(image) 609 else: 610 print 'error: can\'t find image for identifier "%s"' % ident 611 else: 612 for ident in crash_log.idents: 613 images = crash_log.find_images_with_identifier (ident) 614 if images: 615 for image in images: 616 images_to_load.append(image) 617 else: 618 print 'error: can\'t find image for identifier "%s"' % ident 619 620 for image in images_to_load: 621 if image in loaded_images: 622 print "warning: skipping %s loaded at %#16.16x duplicate entry (probably commpage)" % (image.path, image.text_addr_lo) 623 else: 624 err = image.add_module (target) 625 if err: 626 print err 627 else: 628 #print 'loaded %s' % image 629 loaded_images.append(image) 630 631 for thread in crash_log.threads: 632 this_thread_crashed = thread.did_crash() 633 if options.crashed_only and this_thread_crashed == False: 634 continue 635 print "%s" % thread 636 #prev_frame_index = -1 637 display_frame_idx = -1 638 for frame_idx, frame in enumerate(thread.frames): 639 disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth; 640 if frame_idx == 0: 641 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc, options.verbose) 642 else: 643 # Any frame above frame zero and we have to subtract one to get the previous line entry 644 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc - 1, options.verbose) 645 646 if symbolicated_frame_addresses: 647 symbolicated_frame_address_idx = 0 648 for symbolicated_frame_address in symbolicated_frame_addresses: 649 display_frame_idx += 1 650 print '[%3u] %s' % (frame_idx, symbolicated_frame_address) 651 if (options.source_all or thread.did_crash()) and display_frame_idx < options.source_frames and options.source_context: 652 source_context = options.source_context 653 line_entry = symbolicated_frame_address.get_symbol_context().line_entry 654 if line_entry.IsValid(): 655 strm = lldb.SBStream() 656 if line_entry: 657 lldb.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers(line_entry.file, line_entry.line, source_context, source_context, "->", strm) 658 source_text = strm.GetData() 659 if source_text: 660 # Indent the source a bit 661 indent_str = ' ' 662 join_str = '\n' + indent_str 663 print '%s%s' % (indent_str, join_str.join(source_text.split('\n'))) 664 if symbolicated_frame_address_idx == 0: 665 if disassemble: 666 instructions = symbolicated_frame_address.get_instructions() 667 if instructions: 668 print 669 symbolication.disassemble_instructions (target, 670 instructions, 671 frame.pc, 672 options.disassemble_before, 673 options.disassemble_after, frame.index > 0) 674 print 675 symbolicated_frame_address_idx += 1 676 else: 677 print frame 678 print 679 680 if options.dump_image_list: 681 print "Binary Images:" 682 for image in crash_log.images: 683 print image 684 685def CreateSymbolicateCrashLogOptions(command_name, description, add_interactive_options): 686 usage = "usage: %prog [options] <FILE> [FILE ...]" 687 option_parser = optparse.OptionParser(description=description, prog='crashlog',usage=usage) 688 option_parser.add_option('--verbose' , '-v', action='store_true', dest='verbose', help='display verbose debug info', default=False) 689 option_parser.add_option('--debug' , '-g', action='store_true', dest='debug', help='display verbose debug logging', default=False) 690 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) 691 option_parser.add_option('--images' , action='store_true', dest='dump_image_list', help='show image list', default=False) 692 option_parser.add_option('--debug-delay' , type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0) 693 option_parser.add_option('--crashed-only' , '-c', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False) 694 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) 695 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) 696 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) 697 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) 698 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) 699 option_parser.add_option('--source-frames' , type='int', metavar='NFRAMES', dest='source_frames', help='show source for NFRAMES (default = 4)', default=4) 700 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) 701 if add_interactive_options: 702 option_parser.add_option('-i', '--interactive', action='store_true', help='parse all crash logs and enter interactive mode', default=False) 703 return option_parser 704 705def SymbolicateCrashLogs(command_args): 706 description='''Symbolicate one or more darwin crash log files to provide source file and line information, 707inlined stack frames back to the concrete functions, and disassemble the location of the crash 708for the first frame of the crashed thread. 709If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter 710for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been 711created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows 712you to explore the program as if it were stopped at the locations described in the crash log and functions can 713be disassembled and lookups can be performed using the addresses found in the crash log.''' 714 option_parser = CreateSymbolicateCrashLogOptions ('crashlog', description, True) 715 try: 716 (options, args) = option_parser.parse_args(command_args) 717 except: 718 return 719 720 if options.debug: 721 print 'command_args = %s' % command_args 722 print 'options', options 723 print 'args', args 724 725 if options.debug_delay > 0: 726 print "Waiting %u seconds for debugger to attach..." % options.debug_delay 727 time.sleep(options.debug_delay) 728 error = lldb.SBError() 729 730 if args: 731 if options.interactive: 732 interactive_crashlogs(options, args) 733 else: 734 for crash_log_file in args: 735 crash_log = CrashLog(crash_log_file) 736 SymbolicateCrashLog (crash_log, options) 737if __name__ == '__main__': 738 # Create a new debugger instance 739 print 'main' 740 lldb.debugger = lldb.SBDebugger.Create() 741 SymbolicateCrashLogs (sys.argv[1:]) 742elif getattr(lldb, 'debugger', None): 743 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.Symbolicate crashlog') 744 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog') 745 print '"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help' 746 747