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 target = debugger.GetSelectedTarget() 634 if target: 635 identifier = target.executable.basename 636 if lldb.process: 637 pid = lldb.process.id 638 if pid != lldb.LLDB_INVALID_PROCESS_ID: 639 out_file.write('Process: %s [%u]\n' % (identifier, pid)) 640 out_file.write('Path: %s\n' % (target.executable.fullpath)) 641 out_file.write('Identifier: %s\n' % (identifier)) 642 out_file.write('\nDate/Time: %s\n' % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) 643 out_file.write('OS Version: Mac OS X %s (%s)\n' % (platform.mac_ver()[0], commands.getoutput('sysctl -n kern.osversion'))); 644 out_file.write('Report Version: 9\n') 645 for thread_idx in range(lldb.process.num_threads): 646 thread = lldb.process.thread[thread_idx] 647 out_file.write('\nThread %u:\n' % (thread_idx)) 648 for (frame_idx, frame) in enumerate(thread.frames): 649 frame_pc = frame.pc 650 frame_offset = 0 651 if frame.function: 652 block = frame.GetFrameBlock() 653 block_range = block.range[frame.addr] 654 if block_range: 655 block_start_addr = block_range[0] 656 frame_offset = frame_pc - block_start_addr.load_addr 657 else: 658 frame_offset = frame_pc - frame.function.addr.load_addr 659 elif frame.symbol: 660 frame_offset = frame_pc - frame.symbol.addr.load_addr 661 out_file.write('%-3u %-32s 0x%16.16x %s' % (frame_idx, frame.module.file.basename, frame_pc, frame.name)) 662 if frame_offset > 0: 663 out_file.write(' + %u' % (frame_offset)) 664 line_entry = frame.line_entry 665 if line_entry: 666 if options.verbose: 667 # This will output the fullpath + line + column 668 out_file.write(' %s' % (line_entry)) 669 else: 670 out_file.write(' %s:%u' % (line_entry.file.basename, line_entry.line)) 671 column = line_entry.column 672 if column: 673 out_file.write(':%u' % (column)) 674 out_file.write('\n') 675 676 out_file.write('\nBinary Images:\n') 677 for module in target.modules: 678 text_segment = module.section['__TEXT'] 679 if text_segment: 680 text_segment_load_addr = text_segment.GetLoadAddress(target) 681 if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS: 682 text_segment_end_load_addr = text_segment_load_addr + text_segment.size 683 identifier = module.file.basename 684 module_version = '???' 685 module_version_array = module.GetVersion() 686 if module_version_array: 687 module_version = '.'.join(map(str,module_version_array)) 688 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)) 689 out_file.close() 690 else: 691 result.PutCString ("error: invalid target"); 692 693 694def Symbolicate(debugger, command, result, dict): 695 try: 696 SymbolicateCrashLogs (shlex.split(command)) 697 except: 698 result.PutCString ("error: python exception %s" % sys.exc_info()[0]) 699 700def SymbolicateCrashLog(crash_log, options): 701 if crash_log.error: 702 print crash_log.error 703 return 704 if options.debug: 705 crash_log.dump() 706 if not crash_log.images: 707 print 'error: no images in crash log' 708 return 709 710 if options.dump_image_list: 711 print "Binary Images:" 712 for image in crash_log.images: 713 if options.verbose: 714 print image.debug_dump() 715 else: 716 print image 717 718 target = crash_log.create_target () 719 if not target: 720 return 721 exe_module = target.GetModuleAtIndex(0) 722 images_to_load = list() 723 loaded_images = list() 724 if options.load_all_images: 725 # --load-all option was specified, load everything up 726 for image in crash_log.images: 727 images_to_load.append(image) 728 else: 729 # Only load the images found in stack frames for the crashed threads 730 if options.crashed_only: 731 for thread in crash_log.threads: 732 if thread.did_crash(): 733 for ident in thread.idents: 734 images = crash_log.find_images_with_identifier (ident) 735 if images: 736 for image in images: 737 images_to_load.append(image) 738 else: 739 print 'error: can\'t find image for identifier "%s"' % ident 740 else: 741 for ident in crash_log.idents: 742 images = crash_log.find_images_with_identifier (ident) 743 if images: 744 for image in images: 745 images_to_load.append(image) 746 else: 747 print 'error: can\'t find image for identifier "%s"' % ident 748 749 for image in images_to_load: 750 if not image in loaded_images: 751 err = image.add_module (target) 752 if err: 753 print err 754 else: 755 #print 'loaded %s' % image 756 loaded_images.append(image) 757 758 if crash_log.backtraces: 759 for thread in crash_log.backtraces: 760 thread.dump_symbolicated (crash_log, options) 761 print 762 763 for thread in crash_log.threads: 764 thread.dump_symbolicated (crash_log, options) 765 print 766 767 768def CreateSymbolicateCrashLogOptions(command_name, description, add_interactive_options): 769 usage = "usage: %prog [options] <FILE> [FILE ...]" 770 option_parser = optparse.OptionParser(description=description, prog='crashlog',usage=usage) 771 option_parser.add_option('--verbose' , '-v', action='store_true', dest='verbose', help='display verbose debug info', default=False) 772 option_parser.add_option('--debug' , '-g', action='store_true', dest='debug', help='display verbose debug logging', default=False) 773 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) 774 option_parser.add_option('--images' , action='store_true', dest='dump_image_list', help='show image list', default=False) 775 option_parser.add_option('--debug-delay' , type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0) 776 option_parser.add_option('--crashed-only' , '-c', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False) 777 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) 778 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) 779 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) 780 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) 781 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) 782 option_parser.add_option('--source-frames' , type='int', metavar='NFRAMES', dest='source_frames', help='show source for NFRAMES (default = 4)', default=4) 783 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) 784 if add_interactive_options: 785 option_parser.add_option('-i', '--interactive', action='store_true', help='parse all crash logs and enter interactive mode', default=False) 786 return option_parser 787 788def SymbolicateCrashLogs(command_args): 789 description='''Symbolicate one or more darwin crash log files to provide source file and line information, 790inlined stack frames back to the concrete functions, and disassemble the location of the crash 791for the first frame of the crashed thread. 792If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter 793for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been 794created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows 795you to explore the program as if it were stopped at the locations described in the crash log and functions can 796be disassembled and lookups can be performed using the addresses found in the crash log.''' 797 option_parser = CreateSymbolicateCrashLogOptions ('crashlog', description, True) 798 try: 799 (options, args) = option_parser.parse_args(command_args) 800 except: 801 return 802 803 if options.debug: 804 print 'command_args = %s' % command_args 805 print 'options', options 806 print 'args', args 807 808 if options.debug_delay > 0: 809 print "Waiting %u seconds for debugger to attach..." % options.debug_delay 810 time.sleep(options.debug_delay) 811 error = lldb.SBError() 812 813 if args: 814 if options.interactive: 815 interactive_crashlogs(options, args) 816 else: 817 for crash_log_file in args: 818 crash_log = CrashLog(crash_log_file) 819 SymbolicateCrashLog (crash_log, options) 820if __name__ == '__main__': 821 # Create a new debugger instance 822 lldb.debugger = lldb.SBDebugger.Create() 823 SymbolicateCrashLogs (sys.argv[1:]) 824 lldb.SBDebugger.Destroy (lldb.debugger) 825elif getattr(lldb, 'debugger', None): 826 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.Symbolicate crashlog') 827 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog') 828 print '"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help' 829 830