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