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