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 commands 30import cmd 31import datetime 32import glob 33import optparse 34import os 35import platform 36import plistlib 37import pprint # pp = pprint.PrettyPrinter(indent=4); pp.pprint(command_args) 38import re 39import shlex 40import string 41import sys 42import time 43import uuid 44 45try: 46 # Just try for LLDB in case PYTHONPATH is already correctly setup 47 import lldb 48except ImportError: 49 lldb_python_dirs = list() 50 # lldb is not in the PYTHONPATH, try some defaults for the current platform 51 platform_system = platform.system() 52 if platform_system == 'Darwin': 53 # On Darwin, try the currently selected Xcode directory 54 xcode_dir = commands.getoutput("xcode-select --print-path") 55 if xcode_dir: 56 lldb_python_dirs.append( 57 os.path.realpath( 58 xcode_dir + 59 '/../SharedFrameworks/LLDB.framework/Resources/Python')) 60 lldb_python_dirs.append( 61 xcode_dir + '/Library/PrivateFrameworks/LLDB.framework/Resources/Python') 62 lldb_python_dirs.append( 63 '/System/Library/PrivateFrameworks/LLDB.framework/Resources/Python') 64 success = False 65 for lldb_python_dir in lldb_python_dirs: 66 if os.path.exists(lldb_python_dir): 67 if not (sys.path.__contains__(lldb_python_dir)): 68 sys.path.append(lldb_python_dir) 69 try: 70 import lldb 71 except ImportError: 72 pass 73 else: 74 print 'imported lldb from: "%s"' % (lldb_python_dir) 75 success = True 76 break 77 if not success: 78 print "error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly" 79 sys.exit(1) 80 81from lldb.utils import symbolication 82 83PARSE_MODE_NORMAL = 0 84PARSE_MODE_THREAD = 1 85PARSE_MODE_IMAGES = 2 86PARSE_MODE_THREGS = 3 87PARSE_MODE_SYSTEM = 4 88 89 90class CrashLog(symbolication.Symbolicator): 91 """Class that does parses darwin crash logs""" 92 parent_process_regex = re.compile('^Parent Process:\s*(.*)\[(\d+)\]') 93 thread_state_regex = re.compile('^Thread ([0-9]+) crashed with') 94 thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)') 95 app_backtrace_regex = re.compile( 96 '^Application Specific Backtrace ([0-9]+)([^:]*):(.*)') 97 frame_regex = re.compile('^([0-9]+)\s+([^ ]+)\s+(0x[0-9a-fA-F]+) +(.*)') 98 image_regex_uuid = re.compile( 99 '(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^<]+)<([-0-9a-fA-F]+)> (.*)') 100 image_regex_no_uuid = re.compile( 101 '(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^/]+)/(.*)') 102 empty_line_regex = re.compile('^$') 103 104 class Thread: 105 """Class that represents a thread in a darwin crash log""" 106 107 def __init__(self, index, app_specific_backtrace): 108 self.index = index 109 self.frames = list() 110 self.idents = list() 111 self.registers = dict() 112 self.reason = None 113 self.queue = None 114 self.app_specific_backtrace = app_specific_backtrace 115 116 def dump(self, prefix): 117 if self.app_specific_backtrace: 118 print "%Application Specific Backtrace[%u] %s" % (prefix, self.index, self.reason) 119 else: 120 print "%sThread[%u] %s" % (prefix, self.index, self.reason) 121 if self.frames: 122 print "%s Frames:" % (prefix) 123 for frame in self.frames: 124 frame.dump(prefix + ' ') 125 if self.registers: 126 print "%s Registers:" % (prefix) 127 for reg in self.registers.keys(): 128 print "%s %-5s = %#16.16x" % (prefix, reg, self.registers[reg]) 129 130 def dump_symbolicated(self, crash_log, options): 131 this_thread_crashed = self.app_specific_backtrace 132 if not this_thread_crashed: 133 this_thread_crashed = self.did_crash() 134 if options.crashed_only and this_thread_crashed == False: 135 return 136 137 print "%s" % self 138 #prev_frame_index = -1 139 display_frame_idx = -1 140 for frame_idx, frame in enumerate(self.frames): 141 disassemble = ( 142 this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth 143 if frame_idx == 0: 144 symbolicated_frame_addresses = crash_log.symbolicate( 145 frame.pc & crash_log.addr_mask, options.verbose) 146 else: 147 # Any frame above frame zero and we have to subtract one to 148 # get the previous line entry 149 symbolicated_frame_addresses = crash_log.symbolicate( 150 (frame.pc & crash_log.addr_mask) - 1, options.verbose) 151 152 if symbolicated_frame_addresses: 153 symbolicated_frame_address_idx = 0 154 for symbolicated_frame_address in symbolicated_frame_addresses: 155 display_frame_idx += 1 156 print '[%3u] %s' % (frame_idx, symbolicated_frame_address) 157 if (options.source_all or self.did_crash( 158 )) and display_frame_idx < options.source_frames and options.source_context: 159 source_context = options.source_context 160 line_entry = symbolicated_frame_address.get_symbol_context().line_entry 161 if line_entry.IsValid(): 162 strm = lldb.SBStream() 163 if line_entry: 164 lldb.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers( 165 line_entry.file, line_entry.line, source_context, source_context, "->", strm) 166 source_text = strm.GetData() 167 if source_text: 168 # Indent the source a bit 169 indent_str = ' ' 170 join_str = '\n' + indent_str 171 print '%s%s' % (indent_str, join_str.join(source_text.split('\n'))) 172 if symbolicated_frame_address_idx == 0: 173 if disassemble: 174 instructions = symbolicated_frame_address.get_instructions() 175 if instructions: 176 print 177 symbolication.disassemble_instructions( 178 crash_log.get_target(), 179 instructions, 180 frame.pc, 181 options.disassemble_before, 182 options.disassemble_after, 183 frame.index > 0) 184 print 185 symbolicated_frame_address_idx += 1 186 else: 187 print frame 188 189 def add_ident(self, ident): 190 if ident not in self.idents: 191 self.idents.append(ident) 192 193 def did_crash(self): 194 return self.reason is not None 195 196 def __str__(self): 197 if self.app_specific_backtrace: 198 s = "Application Specific Backtrace[%u]" % self.index 199 else: 200 s = "Thread[%u]" % self.index 201 if self.reason: 202 s += ' %s' % self.reason 203 return s 204 205 class Frame: 206 """Class that represents a stack frame in a thread in a darwin crash log""" 207 208 def __init__(self, index, pc, description): 209 self.pc = pc 210 self.description = description 211 self.index = index 212 213 def __str__(self): 214 if self.description: 215 return "[%3u] 0x%16.16x %s" % ( 216 self.index, self.pc, self.description) 217 else: 218 return "[%3u] 0x%16.16x" % (self.index, self.pc) 219 220 def dump(self, prefix): 221 print "%s%s" % (prefix, str(self)) 222 223 class DarwinImage(symbolication.Image): 224 """Class that represents a binary images in a darwin crash log""" 225 dsymForUUIDBinary = os.path.expanduser('~rc/bin/dsymForUUID') 226 if not os.path.exists(dsymForUUIDBinary): 227 dsymForUUIDBinary = commands.getoutput('which dsymForUUID') 228 229 dwarfdump_uuid_regex = re.compile( 230 'UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*') 231 232 def __init__( 233 self, 234 text_addr_lo, 235 text_addr_hi, 236 identifier, 237 version, 238 uuid, 239 path): 240 symbolication.Image.__init__(self, path, uuid) 241 self.add_section( 242 symbolication.Section( 243 text_addr_lo, 244 text_addr_hi, 245 "__TEXT")) 246 self.identifier = identifier 247 self.version = version 248 249 def locate_module_and_debug_symbols(self): 250 # Don't load a module twice... 251 if self.resolved: 252 return True 253 # Mark this as resolved so we don't keep trying 254 self.resolved = True 255 uuid_str = self.get_normalized_uuid_string() 256 print 'Getting symbols for %s %s...' % (uuid_str, self.path), 257 if os.path.exists(self.dsymForUUIDBinary): 258 dsym_for_uuid_command = '%s %s' % ( 259 self.dsymForUUIDBinary, uuid_str) 260 s = commands.getoutput(dsym_for_uuid_command) 261 if s: 262 plist_root = plistlib.readPlistFromString(s) 263 if plist_root: 264 plist = plist_root[uuid_str] 265 if plist: 266 if 'DBGArchitecture' in plist: 267 self.arch = plist['DBGArchitecture'] 268 if 'DBGDSYMPath' in plist: 269 self.symfile = os.path.realpath( 270 plist['DBGDSYMPath']) 271 if 'DBGSymbolRichExecutable' in plist: 272 self.path = os.path.expanduser( 273 plist['DBGSymbolRichExecutable']) 274 self.resolved_path = self.path 275 if not self.resolved_path and os.path.exists(self.path): 276 dwarfdump_cmd_output = commands.getoutput( 277 'dwarfdump --uuid "%s"' % self.path) 278 self_uuid = self.get_uuid() 279 for line in dwarfdump_cmd_output.splitlines(): 280 match = self.dwarfdump_uuid_regex.search(line) 281 if match: 282 dwarf_uuid_str = match.group(1) 283 dwarf_uuid = uuid.UUID(dwarf_uuid_str) 284 if self_uuid == dwarf_uuid: 285 self.resolved_path = self.path 286 self.arch = match.group(2) 287 break 288 if not self.resolved_path: 289 self.unavailable = True 290 print "error\n error: unable to locate '%s' with UUID %s" % (self.path, uuid_str) 291 return False 292 if (self.resolved_path and os.path.exists(self.resolved_path)) or ( 293 self.path and os.path.exists(self.path)): 294 print 'ok' 295 # if self.resolved_path: 296 # print ' exe = "%s"' % self.resolved_path 297 # if self.symfile: 298 # print ' dsym = "%s"' % self.symfile 299 return True 300 else: 301 self.unavailable = True 302 return False 303 304 def __init__(self, path): 305 """CrashLog constructor that take a path to a darwin crash log file""" 306 symbolication.Symbolicator.__init__(self) 307 self.path = os.path.expanduser(path) 308 self.info_lines = list() 309 self.system_profile = list() 310 self.threads = list() 311 self.backtraces = list() # For application specific backtraces 312 self.idents = list() # A list of the required identifiers for doing all stack backtraces 313 self.crashed_thread_idx = -1 314 self.version = -1 315 self.error = None 316 self.target = None 317 # With possible initial component of ~ or ~user replaced by that user's 318 # home directory. 319 try: 320 f = open(self.path) 321 except IOError: 322 self.error = 'error: cannot open "%s"' % self.path 323 return 324 325 self.file_lines = f.read().splitlines() 326 parse_mode = PARSE_MODE_NORMAL 327 thread = None 328 app_specific_backtrace = False 329 for line in self.file_lines: 330 # print line 331 line_len = len(line) 332 if line_len == 0: 333 if thread: 334 if parse_mode == PARSE_MODE_THREAD: 335 if thread.index == self.crashed_thread_idx: 336 thread.reason = '' 337 if self.thread_exception: 338 thread.reason += self.thread_exception 339 if self.thread_exception_data: 340 thread.reason += " (%s)" % self.thread_exception_data 341 if app_specific_backtrace: 342 self.backtraces.append(thread) 343 else: 344 self.threads.append(thread) 345 thread = None 346 else: 347 # only append an extra empty line if the previous line 348 # in the info_lines wasn't empty 349 if len(self.info_lines) > 0 and len(self.info_lines[-1]): 350 self.info_lines.append(line) 351 parse_mode = PARSE_MODE_NORMAL 352 # print 'PARSE_MODE_NORMAL' 353 elif parse_mode == PARSE_MODE_NORMAL: 354 if line.startswith('Process:'): 355 (self.process_name, pid_with_brackets) = line[ 356 8:].strip().split(' [') 357 self.process_id = pid_with_brackets.strip('[]') 358 elif line.startswith('Path:'): 359 self.process_path = line[5:].strip() 360 elif line.startswith('Identifier:'): 361 self.process_identifier = line[11:].strip() 362 elif line.startswith('Version:'): 363 version_string = line[8:].strip() 364 matched_pair = re.search("(.+)\((.+)\)", version_string) 365 if matched_pair: 366 self.process_version = matched_pair.group(1) 367 self.process_compatability_version = matched_pair.group( 368 2) 369 else: 370 self.process = version_string 371 self.process_compatability_version = version_string 372 elif self.parent_process_regex.search(line): 373 parent_process_match = self.parent_process_regex.search( 374 line) 375 self.parent_process_name = parent_process_match.group(1) 376 self.parent_process_id = parent_process_match.group(2) 377 elif line.startswith('Exception Type:'): 378 self.thread_exception = line[15:].strip() 379 continue 380 elif line.startswith('Exception Codes:'): 381 self.thread_exception_data = line[16:].strip() 382 continue 383 elif line.startswith('Exception Subtype:'): # iOS 384 self.thread_exception_data = line[18:].strip() 385 continue 386 elif line.startswith('Crashed Thread:'): 387 self.crashed_thread_idx = int(line[15:].strip().split()[0]) 388 continue 389 elif line.startswith('Triggered by Thread:'): # iOS 390 self.crashed_thread_idx = int(line[20:].strip().split()[0]) 391 continue 392 elif line.startswith('Report Version:'): 393 self.version = int(line[15:].strip()) 394 continue 395 elif line.startswith('System Profile:'): 396 parse_mode = PARSE_MODE_SYSTEM 397 continue 398 elif (line.startswith('Interval Since Last Report:') or 399 line.startswith('Crashes Since Last Report:') or 400 line.startswith('Per-App Interval Since Last Report:') or 401 line.startswith('Per-App Crashes Since Last Report:') or 402 line.startswith('Sleep/Wake UUID:') or 403 line.startswith('Anonymous UUID:')): 404 # ignore these 405 continue 406 elif line.startswith('Thread'): 407 thread_state_match = self.thread_state_regex.search(line) 408 if thread_state_match: 409 app_specific_backtrace = False 410 thread_state_match = self.thread_regex.search(line) 411 thread_idx = int(thread_state_match.group(1)) 412 parse_mode = PARSE_MODE_THREGS 413 thread = self.threads[thread_idx] 414 else: 415 thread_match = self.thread_regex.search(line) 416 if thread_match: 417 app_specific_backtrace = False 418 parse_mode = PARSE_MODE_THREAD 419 thread_idx = int(thread_match.group(1)) 420 thread = CrashLog.Thread(thread_idx, False) 421 continue 422 elif line.startswith('Binary Images:'): 423 parse_mode = PARSE_MODE_IMAGES 424 continue 425 elif line.startswith('Application Specific Backtrace'): 426 app_backtrace_match = self.app_backtrace_regex.search(line) 427 if app_backtrace_match: 428 parse_mode = PARSE_MODE_THREAD 429 app_specific_backtrace = True 430 idx = int(app_backtrace_match.group(1)) 431 thread = CrashLog.Thread(idx, True) 432 elif line.startswith('Last Exception Backtrace:'): # iOS 433 parse_mode = PARSE_MODE_THREAD 434 app_specific_backtrace = True 435 idx = 1 436 thread = CrashLog.Thread(idx, True) 437 self.info_lines.append(line.strip()) 438 elif parse_mode == PARSE_MODE_THREAD: 439 if line.startswith('Thread'): 440 continue 441 frame_match = self.frame_regex.search(line) 442 if frame_match: 443 ident = frame_match.group(2) 444 thread.add_ident(ident) 445 if ident not in self.idents: 446 self.idents.append(ident) 447 thread.frames.append(CrashLog.Frame(int(frame_match.group(1)), int( 448 frame_match.group(3), 0), frame_match.group(4))) 449 else: 450 print 'error: frame regex failed for line: "%s"' % line 451 elif parse_mode == PARSE_MODE_IMAGES: 452 image_match = self.image_regex_uuid.search(line) 453 if image_match: 454 image = CrashLog.DarwinImage(int(image_match.group(1), 0), 455 int(image_match.group(2), 0), 456 image_match.group(3).strip(), 457 image_match.group(4).strip(), 458 uuid.UUID(image_match.group(5)), 459 image_match.group(6)) 460 self.images.append(image) 461 else: 462 image_match = self.image_regex_no_uuid.search(line) 463 if image_match: 464 image = CrashLog.DarwinImage(int(image_match.group(1), 0), 465 int(image_match.group(2), 0), 466 image_match.group(3).strip(), 467 image_match.group(4).strip(), 468 None, 469 image_match.group(5)) 470 self.images.append(image) 471 else: 472 print "error: image regex failed for: %s" % line 473 474 elif parse_mode == PARSE_MODE_THREGS: 475 stripped_line = line.strip() 476 # "r12: 0x00007fff6b5939c8 r13: 0x0000000007000006 r14: 0x0000000000002a03 r15: 0x0000000000000c00" 477 reg_values = re.findall( 478 '([a-zA-Z0-9]+: 0[Xx][0-9a-fA-F]+) *', stripped_line) 479 for reg_value in reg_values: 480 # print 'reg_value = "%s"' % reg_value 481 (reg, value) = reg_value.split(': ') 482 # print 'reg = "%s"' % reg 483 # print 'value = "%s"' % value 484 thread.registers[reg.strip()] = int(value, 0) 485 elif parse_mode == PARSE_MODE_SYSTEM: 486 self.system_profile.append(line) 487 f.close() 488 489 def dump(self): 490 print "Crash Log File: %s" % (self.path) 491 if self.backtraces: 492 print "\nApplication Specific Backtraces:" 493 for thread in self.backtraces: 494 thread.dump(' ') 495 print "\nThreads:" 496 for thread in self.threads: 497 thread.dump(' ') 498 print "\nImages:" 499 for image in self.images: 500 image.dump(' ') 501 502 def find_image_with_identifier(self, identifier): 503 for image in self.images: 504 if image.identifier == identifier: 505 return image 506 regex_text = '^.*\.%s$' % (re.escape(identifier)) 507 regex = re.compile(regex_text) 508 for image in self.images: 509 if regex.match(image.identifier): 510 return image 511 return None 512 513 def create_target(self): 514 # print 'crashlog.create_target()...' 515 if self.target is None: 516 self.target = symbolication.Symbolicator.create_target(self) 517 if self.target: 518 return self.target 519 # We weren't able to open the main executable as, but we can still 520 # symbolicate 521 print 'crashlog.create_target()...2' 522 if self.idents: 523 for ident in self.idents: 524 image = self.find_image_with_identifier(ident) 525 if image: 526 self.target = image.create_target() 527 if self.target: 528 return self.target # success 529 print 'crashlog.create_target()...3' 530 for image in self.images: 531 self.target = image.create_target() 532 if self.target: 533 return self.target # success 534 print 'crashlog.create_target()...4' 535 print 'error: unable to locate any executables from the crash log' 536 return self.target 537 538 def get_target(self): 539 return self.target 540 541 542def usage(): 543 print "Usage: lldb-symbolicate.py [-n name] executable-image" 544 sys.exit(0) 545 546 547class Interactive(cmd.Cmd): 548 '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.''' 549 image_option_parser = None 550 551 def __init__(self, crash_logs): 552 cmd.Cmd.__init__(self) 553 self.use_rawinput = False 554 self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.' 555 self.crash_logs = crash_logs 556 self.prompt = '% ' 557 558 def default(self, line): 559 '''Catch all for unknown command, which will exit the interpreter.''' 560 print "uknown command: %s" % line 561 return True 562 563 def do_q(self, line): 564 '''Quit command''' 565 return True 566 567 def do_quit(self, line): 568 '''Quit command''' 569 return True 570 571 def do_symbolicate(self, line): 572 description = '''Symbolicate one or more darwin crash log files by index to provide source file and line information, 573 inlined stack frames back to the concrete functions, and disassemble the location of the crash 574 for the first frame of the crashed thread.''' 575 option_parser = CreateSymbolicateCrashLogOptions( 576 'symbolicate', description, False) 577 command_args = shlex.split(line) 578 try: 579 (options, args) = option_parser.parse_args(command_args) 580 except: 581 return 582 583 if args: 584 # We have arguments, they must valid be crash log file indexes 585 for idx_str in args: 586 idx = int(idx_str) 587 if idx < len(self.crash_logs): 588 SymbolicateCrashLog(self.crash_logs[idx], options) 589 else: 590 print 'error: crash log index %u is out of range' % (idx) 591 else: 592 # No arguments, symbolicate all crash logs using the options 593 # provided 594 for idx in range(len(self.crash_logs)): 595 SymbolicateCrashLog(self.crash_logs[idx], options) 596 597 def do_list(self, line=None): 598 '''Dump a list of all crash logs that are currently loaded. 599 600 USAGE: list''' 601 print '%u crash logs are loaded:' % len(self.crash_logs) 602 for (crash_log_idx, crash_log) in enumerate(self.crash_logs): 603 print '[%u] = %s' % (crash_log_idx, crash_log.path) 604 605 def do_image(self, line): 606 '''Dump information about one or more binary images in the crash log given an image basename, or all images if no arguments are provided.''' 607 usage = "usage: %prog [options] <PATH> [PATH ...]" 608 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.''' 609 command_args = shlex.split(line) 610 if not self.image_option_parser: 611 self.image_option_parser = optparse.OptionParser( 612 description=description, prog='image', usage=usage) 613 self.image_option_parser.add_option( 614 '-a', 615 '--all', 616 action='store_true', 617 help='show all images', 618 default=False) 619 try: 620 (options, args) = self.image_option_parser.parse_args(command_args) 621 except: 622 return 623 624 if args: 625 for image_path in args: 626 fullpath_search = image_path[0] == '/' 627 for (crash_log_idx, crash_log) in enumerate(self.crash_logs): 628 matches_found = 0 629 for (image_idx, image) in enumerate(crash_log.images): 630 if fullpath_search: 631 if image.get_resolved_path() == image_path: 632 matches_found += 1 633 print '[%u] ' % (crash_log_idx), image 634 else: 635 image_basename = image.get_resolved_path_basename() 636 if image_basename == image_path: 637 matches_found += 1 638 print '[%u] ' % (crash_log_idx), image 639 if matches_found == 0: 640 for (image_idx, image) in enumerate(crash_log.images): 641 resolved_image_path = image.get_resolved_path() 642 if resolved_image_path and string.find( 643 image.get_resolved_path(), image_path) >= 0: 644 print '[%u] ' % (crash_log_idx), image 645 else: 646 for crash_log in self.crash_logs: 647 for (image_idx, image) in enumerate(crash_log.images): 648 print '[%u] %s' % (image_idx, image) 649 return False 650 651 652def interactive_crashlogs(options, args): 653 crash_log_files = list() 654 for arg in args: 655 for resolved_path in glob.glob(arg): 656 crash_log_files.append(resolved_path) 657 658 crash_logs = list() 659 for crash_log_file in crash_log_files: 660 # print 'crash_log_file = "%s"' % crash_log_file 661 crash_log = CrashLog(crash_log_file) 662 if crash_log.error: 663 print crash_log.error 664 continue 665 if options.debug: 666 crash_log.dump() 667 if not crash_log.images: 668 print 'error: no images in crash log "%s"' % (crash_log) 669 continue 670 else: 671 crash_logs.append(crash_log) 672 673 interpreter = Interactive(crash_logs) 674 # List all crash logs that were imported 675 interpreter.do_list() 676 interpreter.cmdloop() 677 678 679def save_crashlog(debugger, command, result, dict): 680 usage = "usage: %prog [options] <output-path>" 681 description = '''Export the state of current target into a crashlog file''' 682 parser = optparse.OptionParser( 683 description=description, 684 prog='save_crashlog', 685 usage=usage) 686 parser.add_option( 687 '-v', 688 '--verbose', 689 action='store_true', 690 dest='verbose', 691 help='display verbose debug info', 692 default=False) 693 try: 694 (options, args) = parser.parse_args(shlex.split(command)) 695 except: 696 result.PutCString("error: invalid options") 697 return 698 if len(args) != 1: 699 result.PutCString( 700 "error: invalid arguments, a single output file is the only valid argument") 701 return 702 out_file = open(args[0], 'w') 703 if not out_file: 704 result.PutCString( 705 "error: failed to open file '%s' for writing...", 706 args[0]) 707 return 708 target = debugger.GetSelectedTarget() 709 if target: 710 identifier = target.executable.basename 711 if lldb.process: 712 pid = lldb.process.id 713 if pid != lldb.LLDB_INVALID_PROCESS_ID: 714 out_file.write( 715 'Process: %s [%u]\n' % 716 (identifier, pid)) 717 out_file.write('Path: %s\n' % (target.executable.fullpath)) 718 out_file.write('Identifier: %s\n' % (identifier)) 719 out_file.write('\nDate/Time: %s\n' % 720 (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) 721 out_file.write( 722 'OS Version: Mac OS X %s (%s)\n' % 723 (platform.mac_ver()[0], commands.getoutput('sysctl -n kern.osversion'))) 724 out_file.write('Report Version: 9\n') 725 for thread_idx in range(lldb.process.num_threads): 726 thread = lldb.process.thread[thread_idx] 727 out_file.write('\nThread %u:\n' % (thread_idx)) 728 for (frame_idx, frame) in enumerate(thread.frames): 729 frame_pc = frame.pc 730 frame_offset = 0 731 if frame.function: 732 block = frame.GetFrameBlock() 733 block_range = block.range[frame.addr] 734 if block_range: 735 block_start_addr = block_range[0] 736 frame_offset = frame_pc - block_start_addr.load_addr 737 else: 738 frame_offset = frame_pc - frame.function.addr.load_addr 739 elif frame.symbol: 740 frame_offset = frame_pc - frame.symbol.addr.load_addr 741 out_file.write( 742 '%-3u %-32s 0x%16.16x %s' % 743 (frame_idx, frame.module.file.basename, frame_pc, frame.name)) 744 if frame_offset > 0: 745 out_file.write(' + %u' % (frame_offset)) 746 line_entry = frame.line_entry 747 if line_entry: 748 if options.verbose: 749 # This will output the fullpath + line + column 750 out_file.write(' %s' % (line_entry)) 751 else: 752 out_file.write( 753 ' %s:%u' % 754 (line_entry.file.basename, line_entry.line)) 755 column = line_entry.column 756 if column: 757 out_file.write(':%u' % (column)) 758 out_file.write('\n') 759 760 out_file.write('\nBinary Images:\n') 761 for module in target.modules: 762 text_segment = module.section['__TEXT'] 763 if text_segment: 764 text_segment_load_addr = text_segment.GetLoadAddress(target) 765 if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS: 766 text_segment_end_load_addr = text_segment_load_addr + text_segment.size 767 identifier = module.file.basename 768 module_version = '???' 769 module_version_array = module.GetVersion() 770 if module_version_array: 771 module_version = '.'.join( 772 map(str, module_version_array)) 773 out_file.write( 774 ' 0x%16.16x - 0x%16.16x %s (%s - ???) <%s> %s\n' % 775 (text_segment_load_addr, 776 text_segment_end_load_addr, 777 identifier, 778 module_version, 779 module.GetUUIDString(), 780 module.file.fullpath)) 781 out_file.close() 782 else: 783 result.PutCString("error: invalid target") 784 785 786def Symbolicate(debugger, command, result, dict): 787 try: 788 SymbolicateCrashLogs(shlex.split(command)) 789 except: 790 result.PutCString("error: python exception %s" % sys.exc_info()[0]) 791 792 793def SymbolicateCrashLog(crash_log, options): 794 if crash_log.error: 795 print crash_log.error 796 return 797 if options.debug: 798 crash_log.dump() 799 if not crash_log.images: 800 print 'error: no images in crash log' 801 return 802 803 if options.dump_image_list: 804 print "Binary Images:" 805 for image in crash_log.images: 806 if options.verbose: 807 print image.debug_dump() 808 else: 809 print image 810 811 target = crash_log.create_target() 812 if not target: 813 return 814 exe_module = target.GetModuleAtIndex(0) 815 images_to_load = list() 816 loaded_images = list() 817 if options.load_all_images: 818 # --load-all option was specified, load everything up 819 for image in crash_log.images: 820 images_to_load.append(image) 821 else: 822 # Only load the images found in stack frames for the crashed threads 823 if options.crashed_only: 824 for thread in crash_log.threads: 825 if thread.did_crash(): 826 for ident in thread.idents: 827 images = crash_log.find_images_with_identifier(ident) 828 if images: 829 for image in images: 830 images_to_load.append(image) 831 else: 832 print 'error: can\'t find image for identifier "%s"' % ident 833 else: 834 for ident in crash_log.idents: 835 images = crash_log.find_images_with_identifier(ident) 836 if images: 837 for image in images: 838 images_to_load.append(image) 839 else: 840 print 'error: can\'t find image for identifier "%s"' % ident 841 842 for image in images_to_load: 843 if image not in loaded_images: 844 err = image.add_module(target) 845 if err: 846 print err 847 else: 848 # print 'loaded %s' % image 849 loaded_images.append(image) 850 851 if crash_log.backtraces: 852 for thread in crash_log.backtraces: 853 thread.dump_symbolicated(crash_log, options) 854 print 855 856 for thread in crash_log.threads: 857 thread.dump_symbolicated(crash_log, options) 858 print 859 860 861def CreateSymbolicateCrashLogOptions( 862 command_name, 863 description, 864 add_interactive_options): 865 usage = "usage: %prog [options] <FILE> [FILE ...]" 866 option_parser = optparse.OptionParser( 867 description=description, prog='crashlog', usage=usage) 868 option_parser.add_option( 869 '--verbose', 870 '-v', 871 action='store_true', 872 dest='verbose', 873 help='display verbose debug info', 874 default=False) 875 option_parser.add_option( 876 '--debug', 877 '-g', 878 action='store_true', 879 dest='debug', 880 help='display verbose debug logging', 881 default=False) 882 option_parser.add_option( 883 '--load-all', 884 '-a', 885 action='store_true', 886 dest='load_all_images', 887 help='load all executable images, not just the images found in the crashed stack frames', 888 default=False) 889 option_parser.add_option( 890 '--images', 891 action='store_true', 892 dest='dump_image_list', 893 help='show image list', 894 default=False) 895 option_parser.add_option( 896 '--debug-delay', 897 type='int', 898 dest='debug_delay', 899 metavar='NSEC', 900 help='pause for NSEC seconds for debugger', 901 default=0) 902 option_parser.add_option( 903 '--crashed-only', 904 '-c', 905 action='store_true', 906 dest='crashed_only', 907 help='only symbolicate the crashed thread', 908 default=False) 909 option_parser.add_option( 910 '--disasm-depth', 911 '-d', 912 type='int', 913 dest='disassemble_depth', 914 help='set the depth in stack frames that should be disassembled (default is 1)', 915 default=1) 916 option_parser.add_option( 917 '--disasm-all', 918 '-D', 919 action='store_true', 920 dest='disassemble_all_threads', 921 help='enabled disassembly of frames on all threads (not just the crashed thread)', 922 default=False) 923 option_parser.add_option( 924 '--disasm-before', 925 '-B', 926 type='int', 927 dest='disassemble_before', 928 help='the number of instructions to disassemble before the frame PC', 929 default=4) 930 option_parser.add_option( 931 '--disasm-after', 932 '-A', 933 type='int', 934 dest='disassemble_after', 935 help='the number of instructions to disassemble after the frame PC', 936 default=4) 937 option_parser.add_option( 938 '--source-context', 939 '-C', 940 type='int', 941 metavar='NLINES', 942 dest='source_context', 943 help='show NLINES source lines of source context (default = 4)', 944 default=4) 945 option_parser.add_option( 946 '--source-frames', 947 type='int', 948 metavar='NFRAMES', 949 dest='source_frames', 950 help='show source for NFRAMES (default = 4)', 951 default=4) 952 option_parser.add_option( 953 '--source-all', 954 action='store_true', 955 dest='source_all', 956 help='show source for all threads, not just the crashed thread', 957 default=False) 958 if add_interactive_options: 959 option_parser.add_option( 960 '-i', 961 '--interactive', 962 action='store_true', 963 help='parse all crash logs and enter interactive mode', 964 default=False) 965 return option_parser 966 967 968def SymbolicateCrashLogs(command_args): 969 description = '''Symbolicate one or more darwin crash log files to provide source file and line information, 970inlined stack frames back to the concrete functions, and disassemble the location of the crash 971for the first frame of the crashed thread. 972If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter 973for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been 974created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows 975you to explore the program as if it were stopped at the locations described in the crash log and functions can 976be disassembled and lookups can be performed using the addresses found in the crash log.''' 977 option_parser = CreateSymbolicateCrashLogOptions( 978 'crashlog', description, True) 979 try: 980 (options, args) = option_parser.parse_args(command_args) 981 except: 982 return 983 984 if options.debug: 985 print 'command_args = %s' % command_args 986 print 'options', options 987 print 'args', args 988 989 if options.debug_delay > 0: 990 print "Waiting %u seconds for debugger to attach..." % options.debug_delay 991 time.sleep(options.debug_delay) 992 error = lldb.SBError() 993 994 if args: 995 if options.interactive: 996 interactive_crashlogs(options, args) 997 else: 998 for crash_log_file in args: 999 crash_log = CrashLog(crash_log_file) 1000 SymbolicateCrashLog(crash_log, options) 1001if __name__ == '__main__': 1002 # Create a new debugger instance 1003 lldb.debugger = lldb.SBDebugger.Create() 1004 SymbolicateCrashLogs(sys.argv[1:]) 1005 lldb.SBDebugger.Destroy(lldb.debugger) 1006elif getattr(lldb, 'debugger', None): 1007 lldb.debugger.HandleCommand( 1008 'command script add -f lldb.macosx.crashlog.Symbolicate crashlog') 1009 lldb.debugger.HandleCommand( 1010 'command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog') 1011 print '"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help' 1012