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