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