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