1*be691f3bSpatrick#!/usr/bin/env python3 2061da546Spatrick 3061da546Spatrick#---------------------------------------------------------------------- 4061da546Spatrick# Be sure to add the python path that points to the LLDB shared library. 5061da546Spatrick# 6061da546Spatrick# To use this in the embedded python interpreter using "lldb": 7061da546Spatrick# 8061da546Spatrick# cd /path/containing/crashlog.py 9061da546Spatrick# lldb 10061da546Spatrick# (lldb) script import crashlog 11061da546Spatrick# "crashlog" command installed, type "crashlog --help" for detailed help 12061da546Spatrick# (lldb) crashlog ~/Library/Logs/DiagnosticReports/a.crash 13061da546Spatrick# 14061da546Spatrick# The benefit of running the crashlog command inside lldb in the 15061da546Spatrick# embedded python interpreter is when the command completes, there 16061da546Spatrick# will be a target with all of the files loaded at the locations 17061da546Spatrick# described in the crash log. Only the files that have stack frames 18061da546Spatrick# in the backtrace will be loaded unless the "--load-all" option 19061da546Spatrick# has been specified. This allows users to explore the program in the 20061da546Spatrick# state it was in right at crash time. 21061da546Spatrick# 22061da546Spatrick# On MacOSX csh, tcsh: 23061da546Spatrick# ( setenv PYTHONPATH /path/to/LLDB.framework/Resources/Python ; ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash ) 24061da546Spatrick# 25061da546Spatrick# On MacOSX sh, bash: 26061da546Spatrick# PYTHONPATH=/path/to/LLDB.framework/Resources/Python ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash 27061da546Spatrick#---------------------------------------------------------------------- 28061da546Spatrick 29061da546Spatrickfrom __future__ import print_function 30061da546Spatrickimport cmd 31061da546Spatrickimport datetime 32061da546Spatrickimport glob 33061da546Spatrickimport optparse 34061da546Spatrickimport os 35061da546Spatrickimport platform 36061da546Spatrickimport plistlib 37061da546Spatrickimport re 38061da546Spatrickimport shlex 39061da546Spatrickimport string 40061da546Spatrickimport subprocess 41061da546Spatrickimport sys 42061da546Spatrickimport time 43061da546Spatrickimport uuid 44*be691f3bSpatrickimport json 45*be691f3bSpatrick 46*be691f3bSpatricktry: 47*be691f3bSpatrick # First try for LLDB in case PYTHONPATH is already correctly setup. 48*be691f3bSpatrick import lldb 49*be691f3bSpatrickexcept ImportError: 50*be691f3bSpatrick # Ask the command line driver for the path to the lldb module. Copy over 51*be691f3bSpatrick # the environment so that SDKROOT is propagated to xcrun. 52*be691f3bSpatrick env = os.environ.copy() 53*be691f3bSpatrick env['LLDB_DEFAULT_PYTHON_VERSION'] = str(sys.version_info.major) 54*be691f3bSpatrick command = ['xcrun', 'lldb', '-P'] if platform.system() == 'Darwin' else ['lldb', '-P'] 55*be691f3bSpatrick # Extend the PYTHONPATH if the path exists and isn't already there. 56*be691f3bSpatrick lldb_python_path = subprocess.check_output(command, env=env).decode("utf-8").strip() 57*be691f3bSpatrick if os.path.exists(lldb_python_path) and not sys.path.__contains__(lldb_python_path): 58*be691f3bSpatrick sys.path.append(lldb_python_path) 59*be691f3bSpatrick # Try importing LLDB again. 60*be691f3bSpatrick try: 61*be691f3bSpatrick import lldb 62*be691f3bSpatrick except ImportError: 63*be691f3bSpatrick print("error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly") 64*be691f3bSpatrick sys.exit(1) 65*be691f3bSpatrick 66*be691f3bSpatrickfrom lldb.utils import symbolication 67*be691f3bSpatrick 68061da546Spatrick 69061da546Spatrickdef read_plist(s): 70061da546Spatrick if sys.version_info.major == 3: 71061da546Spatrick return plistlib.loads(s) 72061da546Spatrick else: 73061da546Spatrick return plistlib.readPlistFromString(s) 74061da546Spatrick 75061da546Spatrickclass CrashLog(symbolication.Symbolicator): 76061da546Spatrick class Thread: 77061da546Spatrick """Class that represents a thread in a darwin crash log""" 78061da546Spatrick 79061da546Spatrick def __init__(self, index, app_specific_backtrace): 80061da546Spatrick self.index = index 81061da546Spatrick self.frames = list() 82061da546Spatrick self.idents = list() 83061da546Spatrick self.registers = dict() 84061da546Spatrick self.reason = None 85061da546Spatrick self.queue = None 86061da546Spatrick self.app_specific_backtrace = app_specific_backtrace 87061da546Spatrick 88061da546Spatrick def dump(self, prefix): 89061da546Spatrick if self.app_specific_backtrace: 90061da546Spatrick print("%Application Specific Backtrace[%u] %s" % (prefix, self.index, self.reason)) 91061da546Spatrick else: 92061da546Spatrick print("%sThread[%u] %s" % (prefix, self.index, self.reason)) 93061da546Spatrick if self.frames: 94061da546Spatrick print("%s Frames:" % (prefix)) 95061da546Spatrick for frame in self.frames: 96061da546Spatrick frame.dump(prefix + ' ') 97061da546Spatrick if self.registers: 98061da546Spatrick print("%s Registers:" % (prefix)) 99061da546Spatrick for reg in self.registers.keys(): 100*be691f3bSpatrick print("%s %-8s = %#16.16x" % (prefix, reg, self.registers[reg])) 101061da546Spatrick 102061da546Spatrick def dump_symbolicated(self, crash_log, options): 103061da546Spatrick this_thread_crashed = self.app_specific_backtrace 104061da546Spatrick if not this_thread_crashed: 105061da546Spatrick this_thread_crashed = self.did_crash() 106061da546Spatrick if options.crashed_only and this_thread_crashed == False: 107061da546Spatrick return 108061da546Spatrick 109061da546Spatrick print("%s" % self) 110061da546Spatrick display_frame_idx = -1 111061da546Spatrick for frame_idx, frame in enumerate(self.frames): 112061da546Spatrick disassemble = ( 113061da546Spatrick this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth 114061da546Spatrick if frame_idx == 0: 115061da546Spatrick symbolicated_frame_addresses = crash_log.symbolicate( 116061da546Spatrick frame.pc & crash_log.addr_mask, options.verbose) 117061da546Spatrick else: 118061da546Spatrick # Any frame above frame zero and we have to subtract one to 119061da546Spatrick # get the previous line entry 120061da546Spatrick symbolicated_frame_addresses = crash_log.symbolicate( 121061da546Spatrick (frame.pc & crash_log.addr_mask) - 1, options.verbose) 122061da546Spatrick 123061da546Spatrick if symbolicated_frame_addresses: 124061da546Spatrick symbolicated_frame_address_idx = 0 125061da546Spatrick for symbolicated_frame_address in symbolicated_frame_addresses: 126061da546Spatrick display_frame_idx += 1 127061da546Spatrick print('[%3u] %s' % (frame_idx, symbolicated_frame_address)) 128061da546Spatrick if (options.source_all or self.did_crash( 129061da546Spatrick )) and display_frame_idx < options.source_frames and options.source_context: 130061da546Spatrick source_context = options.source_context 131061da546Spatrick line_entry = symbolicated_frame_address.get_symbol_context().line_entry 132061da546Spatrick if line_entry.IsValid(): 133061da546Spatrick strm = lldb.SBStream() 134061da546Spatrick if line_entry: 135*be691f3bSpatrick crash_log.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers( 136061da546Spatrick line_entry.file, line_entry.line, source_context, source_context, "->", strm) 137061da546Spatrick source_text = strm.GetData() 138061da546Spatrick if source_text: 139061da546Spatrick # Indent the source a bit 140061da546Spatrick indent_str = ' ' 141061da546Spatrick join_str = '\n' + indent_str 142061da546Spatrick print('%s%s' % (indent_str, join_str.join(source_text.split('\n')))) 143061da546Spatrick if symbolicated_frame_address_idx == 0: 144061da546Spatrick if disassemble: 145061da546Spatrick instructions = symbolicated_frame_address.get_instructions() 146061da546Spatrick if instructions: 147061da546Spatrick print() 148061da546Spatrick symbolication.disassemble_instructions( 149061da546Spatrick crash_log.get_target(), 150061da546Spatrick instructions, 151061da546Spatrick frame.pc, 152061da546Spatrick options.disassemble_before, 153061da546Spatrick options.disassemble_after, 154061da546Spatrick frame.index > 0) 155061da546Spatrick print() 156061da546Spatrick symbolicated_frame_address_idx += 1 157061da546Spatrick else: 158061da546Spatrick print(frame) 159*be691f3bSpatrick if self.registers: 160*be691f3bSpatrick print() 161*be691f3bSpatrick for reg in self.registers.keys(): 162*be691f3bSpatrick print(" %-8s = %#16.16x" % (reg, self.registers[reg])) 163061da546Spatrick 164061da546Spatrick def add_ident(self, ident): 165061da546Spatrick if ident not in self.idents: 166061da546Spatrick self.idents.append(ident) 167061da546Spatrick 168061da546Spatrick def did_crash(self): 169061da546Spatrick return self.reason is not None 170061da546Spatrick 171061da546Spatrick def __str__(self): 172061da546Spatrick if self.app_specific_backtrace: 173061da546Spatrick s = "Application Specific Backtrace[%u]" % self.index 174061da546Spatrick else: 175061da546Spatrick s = "Thread[%u]" % self.index 176061da546Spatrick if self.reason: 177061da546Spatrick s += ' %s' % self.reason 178061da546Spatrick return s 179061da546Spatrick 180061da546Spatrick class Frame: 181061da546Spatrick """Class that represents a stack frame in a thread in a darwin crash log""" 182061da546Spatrick 183061da546Spatrick def __init__(self, index, pc, description): 184061da546Spatrick self.pc = pc 185061da546Spatrick self.description = description 186061da546Spatrick self.index = index 187061da546Spatrick 188061da546Spatrick def __str__(self): 189061da546Spatrick if self.description: 190061da546Spatrick return "[%3u] 0x%16.16x %s" % ( 191061da546Spatrick self.index, self.pc, self.description) 192061da546Spatrick else: 193061da546Spatrick return "[%3u] 0x%16.16x" % (self.index, self.pc) 194061da546Spatrick 195061da546Spatrick def dump(self, prefix): 196061da546Spatrick print("%s%s" % (prefix, str(self))) 197061da546Spatrick 198061da546Spatrick class DarwinImage(symbolication.Image): 199061da546Spatrick """Class that represents a binary images in a darwin crash log""" 200061da546Spatrick dsymForUUIDBinary = '/usr/local/bin/dsymForUUID' 201061da546Spatrick if not os.path.exists(dsymForUUIDBinary): 202061da546Spatrick try: 203061da546Spatrick dsymForUUIDBinary = subprocess.check_output('which dsymForUUID', 204061da546Spatrick shell=True).decode("utf-8").rstrip('\n') 205061da546Spatrick except: 206061da546Spatrick dsymForUUIDBinary = "" 207061da546Spatrick 208061da546Spatrick dwarfdump_uuid_regex = re.compile( 209061da546Spatrick 'UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*') 210061da546Spatrick 211061da546Spatrick def __init__( 212061da546Spatrick self, 213061da546Spatrick text_addr_lo, 214061da546Spatrick text_addr_hi, 215061da546Spatrick identifier, 216061da546Spatrick version, 217061da546Spatrick uuid, 218061da546Spatrick path, 219061da546Spatrick verbose): 220061da546Spatrick symbolication.Image.__init__(self, path, uuid) 221061da546Spatrick self.add_section( 222061da546Spatrick symbolication.Section( 223061da546Spatrick text_addr_lo, 224061da546Spatrick text_addr_hi, 225061da546Spatrick "__TEXT")) 226061da546Spatrick self.identifier = identifier 227061da546Spatrick self.version = version 228061da546Spatrick self.verbose = verbose 229061da546Spatrick 230061da546Spatrick def show_symbol_progress(self): 231061da546Spatrick """ 232061da546Spatrick Hide progress output and errors from system frameworks as they are plentiful. 233061da546Spatrick """ 234061da546Spatrick if self.verbose: 235061da546Spatrick return True 236061da546Spatrick return not (self.path.startswith("/System/Library/") or 237061da546Spatrick self.path.startswith("/usr/lib/")) 238061da546Spatrick 239061da546Spatrick 240061da546Spatrick def find_matching_slice(self): 241061da546Spatrick dwarfdump_cmd_output = subprocess.check_output( 242061da546Spatrick 'dwarfdump --uuid "%s"' % self.path, shell=True).decode("utf-8") 243061da546Spatrick self_uuid = self.get_uuid() 244061da546Spatrick for line in dwarfdump_cmd_output.splitlines(): 245061da546Spatrick match = self.dwarfdump_uuid_regex.search(line) 246061da546Spatrick if match: 247061da546Spatrick dwarf_uuid_str = match.group(1) 248061da546Spatrick dwarf_uuid = uuid.UUID(dwarf_uuid_str) 249061da546Spatrick if self_uuid == dwarf_uuid: 250061da546Spatrick self.resolved_path = self.path 251061da546Spatrick self.arch = match.group(2) 252061da546Spatrick return True 253061da546Spatrick if not self.resolved_path: 254061da546Spatrick self.unavailable = True 255061da546Spatrick if self.show_symbol_progress(): 256061da546Spatrick print(("error\n error: unable to locate '%s' with UUID %s" 257061da546Spatrick % (self.path, self.get_normalized_uuid_string()))) 258061da546Spatrick return False 259061da546Spatrick 260061da546Spatrick def locate_module_and_debug_symbols(self): 261061da546Spatrick # Don't load a module twice... 262061da546Spatrick if self.resolved: 263061da546Spatrick return True 264061da546Spatrick # Mark this as resolved so we don't keep trying 265061da546Spatrick self.resolved = True 266061da546Spatrick uuid_str = self.get_normalized_uuid_string() 267061da546Spatrick if self.show_symbol_progress(): 268061da546Spatrick print('Getting symbols for %s %s...' % (uuid_str, self.path), end=' ') 269061da546Spatrick if os.path.exists(self.dsymForUUIDBinary): 270061da546Spatrick dsym_for_uuid_command = '%s %s' % ( 271061da546Spatrick self.dsymForUUIDBinary, uuid_str) 272061da546Spatrick s = subprocess.check_output(dsym_for_uuid_command, shell=True) 273061da546Spatrick if s: 274061da546Spatrick try: 275061da546Spatrick plist_root = read_plist(s) 276061da546Spatrick except: 277061da546Spatrick print(("Got exception: ", sys.exc_info()[1], " handling dsymForUUID output: \n", s)) 278061da546Spatrick raise 279061da546Spatrick if plist_root: 280061da546Spatrick plist = plist_root[uuid_str] 281061da546Spatrick if plist: 282061da546Spatrick if 'DBGArchitecture' in plist: 283061da546Spatrick self.arch = plist['DBGArchitecture'] 284061da546Spatrick if 'DBGDSYMPath' in plist: 285061da546Spatrick self.symfile = os.path.realpath( 286061da546Spatrick plist['DBGDSYMPath']) 287061da546Spatrick if 'DBGSymbolRichExecutable' in plist: 288061da546Spatrick self.path = os.path.expanduser( 289061da546Spatrick plist['DBGSymbolRichExecutable']) 290061da546Spatrick self.resolved_path = self.path 291061da546Spatrick if not self.resolved_path and os.path.exists(self.path): 292061da546Spatrick if not self.find_matching_slice(): 293061da546Spatrick return False 294061da546Spatrick if not self.resolved_path and not os.path.exists(self.path): 295061da546Spatrick try: 296061da546Spatrick dsym = subprocess.check_output( 297061da546Spatrick ["/usr/bin/mdfind", 298061da546Spatrick "com_apple_xcode_dsym_uuids == %s"%uuid_str]).decode("utf-8")[:-1] 299061da546Spatrick if dsym and os.path.exists(dsym): 300061da546Spatrick print(('falling back to binary inside "%s"'%dsym)) 301061da546Spatrick self.symfile = dsym 302061da546Spatrick dwarf_dir = os.path.join(dsym, 'Contents/Resources/DWARF') 303061da546Spatrick for filename in os.listdir(dwarf_dir): 304061da546Spatrick self.path = os.path.join(dwarf_dir, filename) 305061da546Spatrick if not self.find_matching_slice(): 306061da546Spatrick return False 307061da546Spatrick break 308061da546Spatrick except: 309061da546Spatrick pass 310061da546Spatrick if (self.resolved_path and os.path.exists(self.resolved_path)) or ( 311061da546Spatrick self.path and os.path.exists(self.path)): 312061da546Spatrick print('ok') 313061da546Spatrick return True 314061da546Spatrick else: 315061da546Spatrick self.unavailable = True 316061da546Spatrick return False 317061da546Spatrick 318*be691f3bSpatrick def __init__(self, debugger, path, verbose): 319061da546Spatrick """CrashLog constructor that take a path to a darwin crash log file""" 320*be691f3bSpatrick symbolication.Symbolicator.__init__(self, debugger) 321061da546Spatrick self.path = os.path.expanduser(path) 322061da546Spatrick self.info_lines = list() 323061da546Spatrick self.system_profile = list() 324061da546Spatrick self.threads = list() 325061da546Spatrick self.backtraces = list() # For application specific backtraces 326061da546Spatrick self.idents = list() # A list of the required identifiers for doing all stack backtraces 327061da546Spatrick self.crashed_thread_idx = -1 328061da546Spatrick self.version = -1 329061da546Spatrick self.target = None 330061da546Spatrick self.verbose = verbose 331061da546Spatrick 332061da546Spatrick def dump(self): 333061da546Spatrick print("Crash Log File: %s" % (self.path)) 334061da546Spatrick if self.backtraces: 335061da546Spatrick print("\nApplication Specific Backtraces:") 336061da546Spatrick for thread in self.backtraces: 337061da546Spatrick thread.dump(' ') 338061da546Spatrick print("\nThreads:") 339061da546Spatrick for thread in self.threads: 340061da546Spatrick thread.dump(' ') 341061da546Spatrick print("\nImages:") 342061da546Spatrick for image in self.images: 343061da546Spatrick image.dump(' ') 344061da546Spatrick 345061da546Spatrick def find_image_with_identifier(self, identifier): 346061da546Spatrick for image in self.images: 347061da546Spatrick if image.identifier == identifier: 348061da546Spatrick return image 349061da546Spatrick regex_text = '^.*\.%s$' % (re.escape(identifier)) 350061da546Spatrick regex = re.compile(regex_text) 351061da546Spatrick for image in self.images: 352061da546Spatrick if regex.match(image.identifier): 353061da546Spatrick return image 354061da546Spatrick return None 355061da546Spatrick 356061da546Spatrick def create_target(self): 357061da546Spatrick if self.target is None: 358061da546Spatrick self.target = symbolication.Symbolicator.create_target(self) 359061da546Spatrick if self.target: 360061da546Spatrick return self.target 361061da546Spatrick # We weren't able to open the main executable as, but we can still 362061da546Spatrick # symbolicate 363061da546Spatrick print('crashlog.create_target()...2') 364061da546Spatrick if self.idents: 365061da546Spatrick for ident in self.idents: 366061da546Spatrick image = self.find_image_with_identifier(ident) 367061da546Spatrick if image: 368*be691f3bSpatrick self.target = image.create_target(self.debugger) 369061da546Spatrick if self.target: 370061da546Spatrick return self.target # success 371061da546Spatrick print('crashlog.create_target()...3') 372061da546Spatrick for image in self.images: 373*be691f3bSpatrick self.target = image.create_target(self.debugger) 374061da546Spatrick if self.target: 375061da546Spatrick return self.target # success 376061da546Spatrick print('crashlog.create_target()...4') 377061da546Spatrick print('error: Unable to locate any executables from the crash log.') 378061da546Spatrick print(' Try loading the executable into lldb before running crashlog') 379061da546Spatrick print(' and/or make sure the .dSYM bundles can be found by Spotlight.') 380061da546Spatrick return self.target 381061da546Spatrick 382061da546Spatrick def get_target(self): 383061da546Spatrick return self.target 384061da546Spatrick 385061da546Spatrick 386*be691f3bSpatrickclass CrashLogFormatException(Exception): 387*be691f3bSpatrick pass 388*be691f3bSpatrick 389*be691f3bSpatrick 390*be691f3bSpatrickclass CrashLogParseException(Exception): 391*be691f3bSpatrick pass 392*be691f3bSpatrick 393*be691f3bSpatrick 394*be691f3bSpatrickclass CrashLogParser: 395*be691f3bSpatrick def parse(self, debugger, path, verbose): 396*be691f3bSpatrick try: 397*be691f3bSpatrick return JSONCrashLogParser(debugger, path, verbose).parse() 398*be691f3bSpatrick except CrashLogFormatException: 399*be691f3bSpatrick return TextCrashLogParser(debugger, path, verbose).parse() 400*be691f3bSpatrick 401*be691f3bSpatrick 402*be691f3bSpatrickclass JSONCrashLogParser: 403*be691f3bSpatrick def __init__(self, debugger, path, verbose): 404*be691f3bSpatrick self.path = os.path.expanduser(path) 405*be691f3bSpatrick self.verbose = verbose 406*be691f3bSpatrick self.crashlog = CrashLog(debugger, self.path, self.verbose) 407*be691f3bSpatrick 408*be691f3bSpatrick def parse(self): 409*be691f3bSpatrick with open(self.path, 'r') as f: 410*be691f3bSpatrick buffer = f.read() 411*be691f3bSpatrick 412*be691f3bSpatrick # First line is meta-data. 413*be691f3bSpatrick buffer = buffer[buffer.index('\n') + 1:] 414*be691f3bSpatrick 415*be691f3bSpatrick try: 416*be691f3bSpatrick self.data = json.loads(buffer) 417*be691f3bSpatrick except ValueError: 418*be691f3bSpatrick raise CrashLogFormatException() 419*be691f3bSpatrick 420*be691f3bSpatrick try: 421*be691f3bSpatrick self.parse_process_info(self.data) 422*be691f3bSpatrick self.parse_images(self.data['usedImages']) 423*be691f3bSpatrick self.parse_threads(self.data['threads']) 424*be691f3bSpatrick thread = self.crashlog.threads[self.crashlog.crashed_thread_idx] 425*be691f3bSpatrick reason = self.parse_crash_reason(self.data['exception']) 426*be691f3bSpatrick if thread.reason: 427*be691f3bSpatrick thread.reason = '{} {}'.format(thread.reason, reason) 428*be691f3bSpatrick else: 429*be691f3bSpatrick thread.reason = reason 430*be691f3bSpatrick except (KeyError, ValueError, TypeError) as e: 431*be691f3bSpatrick raise CrashLogParseException( 432*be691f3bSpatrick 'Failed to parse JSON crashlog: {}: {}'.format( 433*be691f3bSpatrick type(e).__name__, e)) 434*be691f3bSpatrick 435*be691f3bSpatrick return self.crashlog 436*be691f3bSpatrick 437*be691f3bSpatrick def get_used_image(self, idx): 438*be691f3bSpatrick return self.data['usedImages'][idx] 439*be691f3bSpatrick 440*be691f3bSpatrick def parse_process_info(self, json_data): 441*be691f3bSpatrick self.crashlog.process_id = json_data['pid'] 442*be691f3bSpatrick self.crashlog.process_identifier = json_data['procName'] 443*be691f3bSpatrick self.crashlog.process_path = json_data['procPath'] 444*be691f3bSpatrick 445*be691f3bSpatrick def parse_crash_reason(self, json_exception): 446*be691f3bSpatrick exception_type = json_exception['type'] 447*be691f3bSpatrick exception_signal = json_exception['signal'] 448*be691f3bSpatrick if 'codes' in json_exception: 449*be691f3bSpatrick exception_extra = " ({})".format(json_exception['codes']) 450*be691f3bSpatrick elif 'subtype' in json_exception: 451*be691f3bSpatrick exception_extra = " ({})".format(json_exception['subtype']) 452*be691f3bSpatrick else: 453*be691f3bSpatrick exception_extra = "" 454*be691f3bSpatrick return "{} ({}){}".format(exception_type, exception_signal, 455*be691f3bSpatrick exception_extra) 456*be691f3bSpatrick 457*be691f3bSpatrick def parse_images(self, json_images): 458*be691f3bSpatrick idx = 0 459*be691f3bSpatrick for json_image in json_images: 460*be691f3bSpatrick img_uuid = uuid.UUID(json_image['uuid']) 461*be691f3bSpatrick low = int(json_image['base']) 462*be691f3bSpatrick high = int(0) 463*be691f3bSpatrick name = json_image['name'] if 'name' in json_image else '' 464*be691f3bSpatrick path = json_image['path'] if 'path' in json_image else '' 465*be691f3bSpatrick version = '' 466*be691f3bSpatrick darwin_image = self.crashlog.DarwinImage(low, high, name, version, 467*be691f3bSpatrick img_uuid, path, 468*be691f3bSpatrick self.verbose) 469*be691f3bSpatrick self.crashlog.images.append(darwin_image) 470*be691f3bSpatrick idx += 1 471*be691f3bSpatrick 472*be691f3bSpatrick def parse_frames(self, thread, json_frames): 473*be691f3bSpatrick idx = 0 474*be691f3bSpatrick for json_frame in json_frames: 475*be691f3bSpatrick image_id = int(json_frame['imageIndex']) 476*be691f3bSpatrick ident = self.get_used_image(image_id)['name'] 477*be691f3bSpatrick thread.add_ident(ident) 478*be691f3bSpatrick if ident not in self.crashlog.idents: 479*be691f3bSpatrick self.crashlog.idents.append(ident) 480*be691f3bSpatrick 481*be691f3bSpatrick frame_offset = int(json_frame['imageOffset']) 482*be691f3bSpatrick image_addr = self.get_used_image(image_id)['base'] 483*be691f3bSpatrick pc = image_addr + frame_offset 484*be691f3bSpatrick thread.frames.append(self.crashlog.Frame(idx, pc, frame_offset)) 485*be691f3bSpatrick idx += 1 486*be691f3bSpatrick 487*be691f3bSpatrick def parse_threads(self, json_threads): 488*be691f3bSpatrick idx = 0 489*be691f3bSpatrick for json_thread in json_threads: 490*be691f3bSpatrick thread = self.crashlog.Thread(idx, False) 491*be691f3bSpatrick if 'name' in json_thread: 492*be691f3bSpatrick thread.reason = json_thread['name'] 493*be691f3bSpatrick if json_thread.get('triggered', False): 494*be691f3bSpatrick self.crashlog.crashed_thread_idx = idx 495*be691f3bSpatrick thread.registers = self.parse_thread_registers( 496*be691f3bSpatrick json_thread['threadState']) 497*be691f3bSpatrick thread.queue = json_thread.get('queue') 498*be691f3bSpatrick self.parse_frames(thread, json_thread.get('frames', [])) 499*be691f3bSpatrick self.crashlog.threads.append(thread) 500*be691f3bSpatrick idx += 1 501*be691f3bSpatrick 502*be691f3bSpatrick def parse_thread_registers(self, json_thread_state): 503*be691f3bSpatrick registers = dict() 504*be691f3bSpatrick for key, state in json_thread_state.items(): 505*be691f3bSpatrick try: 506*be691f3bSpatrick value = int(state['value']) 507*be691f3bSpatrick registers[key] = value 508*be691f3bSpatrick except (TypeError, ValueError): 509*be691f3bSpatrick pass 510*be691f3bSpatrick return registers 511*be691f3bSpatrick 512*be691f3bSpatrick 513*be691f3bSpatrickclass CrashLogParseMode: 514*be691f3bSpatrick NORMAL = 0 515*be691f3bSpatrick THREAD = 1 516*be691f3bSpatrick IMAGES = 2 517*be691f3bSpatrick THREGS = 3 518*be691f3bSpatrick SYSTEM = 4 519*be691f3bSpatrick INSTRS = 5 520*be691f3bSpatrick 521*be691f3bSpatrick 522*be691f3bSpatrickclass TextCrashLogParser: 523*be691f3bSpatrick parent_process_regex = re.compile('^Parent Process:\s*(.*)\[(\d+)\]') 524*be691f3bSpatrick thread_state_regex = re.compile('^Thread ([0-9]+) crashed with') 525*be691f3bSpatrick thread_instrs_regex = re.compile('^Thread ([0-9]+) instruction stream') 526*be691f3bSpatrick thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)') 527*be691f3bSpatrick app_backtrace_regex = re.compile('^Application Specific Backtrace ([0-9]+)([^:]*):(.*)') 528*be691f3bSpatrick version = r'(\(.+\)|(arm|x86_)[0-9a-z]+)\s+' 529*be691f3bSpatrick frame_regex = re.compile(r'^([0-9]+)' r'\s' # id 530*be691f3bSpatrick r'+(.+?)' r'\s+' # img_name 531*be691f3bSpatrick r'(' +version+ r')?' # img_version 532*be691f3bSpatrick r'(0x[0-9a-fA-F]{7}[0-9a-fA-F]+)' # addr 533*be691f3bSpatrick r' +(.*)' # offs 534*be691f3bSpatrick ) 535*be691f3bSpatrick null_frame_regex = re.compile(r'^([0-9]+)\s+\?\?\?\s+(0{7}0+) +(.*)') 536*be691f3bSpatrick image_regex_uuid = re.compile(r'(0x[0-9a-fA-F]+)' # img_lo 537*be691f3bSpatrick r'\s+' '-' r'\s+' # - 538*be691f3bSpatrick r'(0x[0-9a-fA-F]+)' r'\s+' # img_hi 539*be691f3bSpatrick r'[+]?(.+?)' r'\s+' # img_name 540*be691f3bSpatrick r'(' +version+ ')?' # img_version 541*be691f3bSpatrick r'(<([-0-9a-fA-F]+)>\s+)?' # img_uuid 542*be691f3bSpatrick r'(/.*)' # img_path 543*be691f3bSpatrick ) 544*be691f3bSpatrick 545*be691f3bSpatrick 546*be691f3bSpatrick def __init__(self, debugger, path, verbose): 547*be691f3bSpatrick self.path = os.path.expanduser(path) 548*be691f3bSpatrick self.verbose = verbose 549*be691f3bSpatrick self.thread = None 550*be691f3bSpatrick self.app_specific_backtrace = False 551*be691f3bSpatrick self.crashlog = CrashLog(debugger, self.path, self.verbose) 552*be691f3bSpatrick self.parse_mode = CrashLogParseMode.NORMAL 553*be691f3bSpatrick self.parsers = { 554*be691f3bSpatrick CrashLogParseMode.NORMAL : self.parse_normal, 555*be691f3bSpatrick CrashLogParseMode.THREAD : self.parse_thread, 556*be691f3bSpatrick CrashLogParseMode.IMAGES : self.parse_images, 557*be691f3bSpatrick CrashLogParseMode.THREGS : self.parse_thread_registers, 558*be691f3bSpatrick CrashLogParseMode.SYSTEM : self.parse_system, 559*be691f3bSpatrick CrashLogParseMode.INSTRS : self.parse_instructions, 560*be691f3bSpatrick } 561*be691f3bSpatrick 562*be691f3bSpatrick def parse(self): 563*be691f3bSpatrick with open(self.path,'r') as f: 564*be691f3bSpatrick lines = f.read().splitlines() 565*be691f3bSpatrick 566*be691f3bSpatrick for line in lines: 567*be691f3bSpatrick line_len = len(line) 568*be691f3bSpatrick if line_len == 0: 569*be691f3bSpatrick if self.thread: 570*be691f3bSpatrick if self.parse_mode == CrashLogParseMode.THREAD: 571*be691f3bSpatrick if self.thread.index == self.crashlog.crashed_thread_idx: 572*be691f3bSpatrick self.thread.reason = '' 573*be691f3bSpatrick if self.crashlog.thread_exception: 574*be691f3bSpatrick self.thread.reason += self.crashlog.thread_exception 575*be691f3bSpatrick if self.crashlog.thread_exception_data: 576*be691f3bSpatrick self.thread.reason += " (%s)" % self.crashlog.thread_exception_data 577*be691f3bSpatrick if self.app_specific_backtrace: 578*be691f3bSpatrick self.crashlog.backtraces.append(self.thread) 579*be691f3bSpatrick else: 580*be691f3bSpatrick self.crashlog.threads.append(self.thread) 581*be691f3bSpatrick self.thread = None 582*be691f3bSpatrick else: 583*be691f3bSpatrick # only append an extra empty line if the previous line 584*be691f3bSpatrick # in the info_lines wasn't empty 585*be691f3bSpatrick if len(self.crashlog.info_lines) > 0 and len(self.crashlog.info_lines[-1]): 586*be691f3bSpatrick self.crashlog.info_lines.append(line) 587*be691f3bSpatrick self.parse_mode = CrashLogParseMode.NORMAL 588*be691f3bSpatrick else: 589*be691f3bSpatrick self.parsers[self.parse_mode](line) 590*be691f3bSpatrick 591*be691f3bSpatrick return self.crashlog 592*be691f3bSpatrick 593*be691f3bSpatrick 594*be691f3bSpatrick def parse_normal(self, line): 595*be691f3bSpatrick if line.startswith('Process:'): 596*be691f3bSpatrick (self.crashlog.process_name, pid_with_brackets) = line[ 597*be691f3bSpatrick 8:].strip().split(' [') 598*be691f3bSpatrick self.crashlog.process_id = pid_with_brackets.strip('[]') 599*be691f3bSpatrick elif line.startswith('Path:'): 600*be691f3bSpatrick self.crashlog.process_path = line[5:].strip() 601*be691f3bSpatrick elif line.startswith('Identifier:'): 602*be691f3bSpatrick self.crashlog.process_identifier = line[11:].strip() 603*be691f3bSpatrick elif line.startswith('Version:'): 604*be691f3bSpatrick version_string = line[8:].strip() 605*be691f3bSpatrick matched_pair = re.search("(.+)\((.+)\)", version_string) 606*be691f3bSpatrick if matched_pair: 607*be691f3bSpatrick self.crashlog.process_version = matched_pair.group(1) 608*be691f3bSpatrick self.crashlog.process_compatability_version = matched_pair.group( 609*be691f3bSpatrick 2) 610*be691f3bSpatrick else: 611*be691f3bSpatrick self.crashlog.process = version_string 612*be691f3bSpatrick self.crashlog.process_compatability_version = version_string 613*be691f3bSpatrick elif self.parent_process_regex.search(line): 614*be691f3bSpatrick parent_process_match = self.parent_process_regex.search( 615*be691f3bSpatrick line) 616*be691f3bSpatrick self.crashlog.parent_process_name = parent_process_match.group(1) 617*be691f3bSpatrick self.crashlog.parent_process_id = parent_process_match.group(2) 618*be691f3bSpatrick elif line.startswith('Exception Type:'): 619*be691f3bSpatrick self.crashlog.thread_exception = line[15:].strip() 620*be691f3bSpatrick return 621*be691f3bSpatrick elif line.startswith('Exception Codes:'): 622*be691f3bSpatrick self.crashlog.thread_exception_data = line[16:].strip() 623*be691f3bSpatrick return 624*be691f3bSpatrick elif line.startswith('Exception Subtype:'): # iOS 625*be691f3bSpatrick self.crashlog.thread_exception_data = line[18:].strip() 626*be691f3bSpatrick return 627*be691f3bSpatrick elif line.startswith('Crashed Thread:'): 628*be691f3bSpatrick self.crashlog.crashed_thread_idx = int(line[15:].strip().split()[0]) 629*be691f3bSpatrick return 630*be691f3bSpatrick elif line.startswith('Triggered by Thread:'): # iOS 631*be691f3bSpatrick self.crashlog.crashed_thread_idx = int(line[20:].strip().split()[0]) 632*be691f3bSpatrick return 633*be691f3bSpatrick elif line.startswith('Report Version:'): 634*be691f3bSpatrick self.crashlog.version = int(line[15:].strip()) 635*be691f3bSpatrick return 636*be691f3bSpatrick elif line.startswith('System Profile:'): 637*be691f3bSpatrick self.parse_mode = CrashLogParseMode.SYSTEM 638*be691f3bSpatrick return 639*be691f3bSpatrick elif (line.startswith('Interval Since Last Report:') or 640*be691f3bSpatrick line.startswith('Crashes Since Last Report:') or 641*be691f3bSpatrick line.startswith('Per-App Interval Since Last Report:') or 642*be691f3bSpatrick line.startswith('Per-App Crashes Since Last Report:') or 643*be691f3bSpatrick line.startswith('Sleep/Wake UUID:') or 644*be691f3bSpatrick line.startswith('Anonymous UUID:')): 645*be691f3bSpatrick # ignore these 646*be691f3bSpatrick return 647*be691f3bSpatrick elif line.startswith('Thread'): 648*be691f3bSpatrick thread_state_match = self.thread_state_regex.search(line) 649*be691f3bSpatrick if thread_state_match: 650*be691f3bSpatrick self.app_specific_backtrace = False 651*be691f3bSpatrick thread_state_match = self.thread_regex.search(line) 652*be691f3bSpatrick thread_idx = int(thread_state_match.group(1)) 653*be691f3bSpatrick self.parse_mode = CrashLogParseMode.THREGS 654*be691f3bSpatrick self.thread = self.crashlog.threads[thread_idx] 655*be691f3bSpatrick return 656*be691f3bSpatrick thread_insts_match = self.thread_instrs_regex.search(line) 657*be691f3bSpatrick if thread_insts_match: 658*be691f3bSpatrick self.parse_mode = CrashLogParseMode.INSTRS 659*be691f3bSpatrick return 660*be691f3bSpatrick thread_match = self.thread_regex.search(line) 661*be691f3bSpatrick if thread_match: 662*be691f3bSpatrick self.app_specific_backtrace = False 663*be691f3bSpatrick self.parse_mode = CrashLogParseMode.THREAD 664*be691f3bSpatrick thread_idx = int(thread_match.group(1)) 665*be691f3bSpatrick self.thread = self.crashlog.Thread(thread_idx, False) 666*be691f3bSpatrick return 667*be691f3bSpatrick return 668*be691f3bSpatrick elif line.startswith('Binary Images:'): 669*be691f3bSpatrick self.parse_mode = CrashLogParseMode.IMAGES 670*be691f3bSpatrick return 671*be691f3bSpatrick elif line.startswith('Application Specific Backtrace'): 672*be691f3bSpatrick app_backtrace_match = self.app_backtrace_regex.search(line) 673*be691f3bSpatrick if app_backtrace_match: 674*be691f3bSpatrick self.parse_mode = CrashLogParseMode.THREAD 675*be691f3bSpatrick self.app_specific_backtrace = True 676*be691f3bSpatrick idx = int(app_backtrace_match.group(1)) 677*be691f3bSpatrick self.thread = self.crashlog.Thread(idx, True) 678*be691f3bSpatrick elif line.startswith('Last Exception Backtrace:'): # iOS 679*be691f3bSpatrick self.parse_mode = CrashLogParseMode.THREAD 680*be691f3bSpatrick self.app_specific_backtrace = True 681*be691f3bSpatrick idx = 1 682*be691f3bSpatrick self.thread = self.crashlog.Thread(idx, True) 683*be691f3bSpatrick self.crashlog.info_lines.append(line.strip()) 684*be691f3bSpatrick 685*be691f3bSpatrick def parse_thread(self, line): 686*be691f3bSpatrick if line.startswith('Thread'): 687*be691f3bSpatrick return 688*be691f3bSpatrick if self.null_frame_regex.search(line): 689*be691f3bSpatrick print('warning: thread parser ignored null-frame: "%s"' % line) 690*be691f3bSpatrick return 691*be691f3bSpatrick frame_match = self.frame_regex.search(line) 692*be691f3bSpatrick if frame_match: 693*be691f3bSpatrick (frame_id, frame_img_name, _, frame_img_version, _, 694*be691f3bSpatrick frame_addr, frame_ofs) = frame_match.groups() 695*be691f3bSpatrick ident = frame_img_name 696*be691f3bSpatrick self.thread.add_ident(ident) 697*be691f3bSpatrick if ident not in self.crashlog.idents: 698*be691f3bSpatrick self.crashlog.idents.append(ident) 699*be691f3bSpatrick self.thread.frames.append(self.crashlog.Frame(int(frame_id), int( 700*be691f3bSpatrick frame_addr, 0), frame_ofs)) 701*be691f3bSpatrick else: 702*be691f3bSpatrick print('error: frame regex failed for line: "%s"' % line) 703*be691f3bSpatrick 704*be691f3bSpatrick def parse_images(self, line): 705*be691f3bSpatrick image_match = self.image_regex_uuid.search(line) 706*be691f3bSpatrick if image_match: 707*be691f3bSpatrick (img_lo, img_hi, img_name, _, img_version, _, 708*be691f3bSpatrick _, img_uuid, img_path) = image_match.groups() 709*be691f3bSpatrick image = self.crashlog.DarwinImage(int(img_lo, 0), int(img_hi, 0), 710*be691f3bSpatrick img_name.strip(), 711*be691f3bSpatrick img_version.strip() 712*be691f3bSpatrick if img_version else "", 713*be691f3bSpatrick uuid.UUID(img_uuid), img_path, 714*be691f3bSpatrick self.verbose) 715*be691f3bSpatrick self.crashlog.images.append(image) 716*be691f3bSpatrick else: 717*be691f3bSpatrick print("error: image regex failed for: %s" % line) 718*be691f3bSpatrick 719*be691f3bSpatrick 720*be691f3bSpatrick def parse_thread_registers(self, line): 721*be691f3bSpatrick stripped_line = line.strip() 722*be691f3bSpatrick # "r12: 0x00007fff6b5939c8 r13: 0x0000000007000006 r14: 0x0000000000002a03 r15: 0x0000000000000c00" 723*be691f3bSpatrick reg_values = re.findall( 724*be691f3bSpatrick '([a-zA-Z0-9]+: 0[Xx][0-9a-fA-F]+) *', stripped_line) 725*be691f3bSpatrick for reg_value in reg_values: 726*be691f3bSpatrick (reg, value) = reg_value.split(': ') 727*be691f3bSpatrick self.thread.registers[reg.strip()] = int(value, 0) 728*be691f3bSpatrick 729*be691f3bSpatrick def parse_system(self, line): 730*be691f3bSpatrick self.crashlog.system_profile.append(line) 731*be691f3bSpatrick 732*be691f3bSpatrick def parse_instructions(self, line): 733*be691f3bSpatrick pass 734*be691f3bSpatrick 735*be691f3bSpatrick 736061da546Spatrickdef usage(): 737061da546Spatrick print("Usage: lldb-symbolicate.py [-n name] executable-image") 738061da546Spatrick sys.exit(0) 739061da546Spatrick 740061da546Spatrick 741061da546Spatrickclass Interactive(cmd.Cmd): 742061da546Spatrick '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.''' 743061da546Spatrick image_option_parser = None 744061da546Spatrick 745061da546Spatrick def __init__(self, crash_logs): 746061da546Spatrick cmd.Cmd.__init__(self) 747061da546Spatrick self.use_rawinput = False 748061da546Spatrick self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.' 749061da546Spatrick self.crash_logs = crash_logs 750061da546Spatrick self.prompt = '% ' 751061da546Spatrick 752061da546Spatrick def default(self, line): 753061da546Spatrick '''Catch all for unknown command, which will exit the interpreter.''' 754061da546Spatrick print("uknown command: %s" % line) 755061da546Spatrick return True 756061da546Spatrick 757061da546Spatrick def do_q(self, line): 758061da546Spatrick '''Quit command''' 759061da546Spatrick return True 760061da546Spatrick 761061da546Spatrick def do_quit(self, line): 762061da546Spatrick '''Quit command''' 763061da546Spatrick return True 764061da546Spatrick 765061da546Spatrick def do_symbolicate(self, line): 766061da546Spatrick description = '''Symbolicate one or more darwin crash log files by index to provide source file and line information, 767061da546Spatrick inlined stack frames back to the concrete functions, and disassemble the location of the crash 768061da546Spatrick for the first frame of the crashed thread.''' 769061da546Spatrick option_parser = CreateSymbolicateCrashLogOptions( 770061da546Spatrick 'symbolicate', description, False) 771061da546Spatrick command_args = shlex.split(line) 772061da546Spatrick try: 773061da546Spatrick (options, args) = option_parser.parse_args(command_args) 774061da546Spatrick except: 775061da546Spatrick return 776061da546Spatrick 777061da546Spatrick if args: 778061da546Spatrick # We have arguments, they must valid be crash log file indexes 779061da546Spatrick for idx_str in args: 780061da546Spatrick idx = int(idx_str) 781061da546Spatrick if idx < len(self.crash_logs): 782061da546Spatrick SymbolicateCrashLog(self.crash_logs[idx], options) 783061da546Spatrick else: 784061da546Spatrick print('error: crash log index %u is out of range' % (idx)) 785061da546Spatrick else: 786061da546Spatrick # No arguments, symbolicate all crash logs using the options 787061da546Spatrick # provided 788061da546Spatrick for idx in range(len(self.crash_logs)): 789061da546Spatrick SymbolicateCrashLog(self.crash_logs[idx], options) 790061da546Spatrick 791061da546Spatrick def do_list(self, line=None): 792061da546Spatrick '''Dump a list of all crash logs that are currently loaded. 793061da546Spatrick 794061da546Spatrick USAGE: list''' 795061da546Spatrick print('%u crash logs are loaded:' % len(self.crash_logs)) 796061da546Spatrick for (crash_log_idx, crash_log) in enumerate(self.crash_logs): 797061da546Spatrick print('[%u] = %s' % (crash_log_idx, crash_log.path)) 798061da546Spatrick 799061da546Spatrick def do_image(self, line): 800061da546Spatrick '''Dump information about one or more binary images in the crash log given an image basename, or all images if no arguments are provided.''' 801061da546Spatrick usage = "usage: %prog [options] <PATH> [PATH ...]" 802061da546Spatrick 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.''' 803061da546Spatrick command_args = shlex.split(line) 804061da546Spatrick if not self.image_option_parser: 805061da546Spatrick self.image_option_parser = optparse.OptionParser( 806061da546Spatrick description=description, prog='image', usage=usage) 807061da546Spatrick self.image_option_parser.add_option( 808061da546Spatrick '-a', 809061da546Spatrick '--all', 810061da546Spatrick action='store_true', 811061da546Spatrick help='show all images', 812061da546Spatrick default=False) 813061da546Spatrick try: 814061da546Spatrick (options, args) = self.image_option_parser.parse_args(command_args) 815061da546Spatrick except: 816061da546Spatrick return 817061da546Spatrick 818061da546Spatrick if args: 819061da546Spatrick for image_path in args: 820061da546Spatrick fullpath_search = image_path[0] == '/' 821061da546Spatrick for (crash_log_idx, crash_log) in enumerate(self.crash_logs): 822061da546Spatrick matches_found = 0 823061da546Spatrick for (image_idx, image) in enumerate(crash_log.images): 824061da546Spatrick if fullpath_search: 825061da546Spatrick if image.get_resolved_path() == image_path: 826061da546Spatrick matches_found += 1 827061da546Spatrick print('[%u] ' % (crash_log_idx), image) 828061da546Spatrick else: 829061da546Spatrick image_basename = image.get_resolved_path_basename() 830061da546Spatrick if image_basename == image_path: 831061da546Spatrick matches_found += 1 832061da546Spatrick print('[%u] ' % (crash_log_idx), image) 833061da546Spatrick if matches_found == 0: 834061da546Spatrick for (image_idx, image) in enumerate(crash_log.images): 835061da546Spatrick resolved_image_path = image.get_resolved_path() 836061da546Spatrick if resolved_image_path and string.find( 837061da546Spatrick image.get_resolved_path(), image_path) >= 0: 838061da546Spatrick print('[%u] ' % (crash_log_idx), image) 839061da546Spatrick else: 840061da546Spatrick for crash_log in self.crash_logs: 841061da546Spatrick for (image_idx, image) in enumerate(crash_log.images): 842061da546Spatrick print('[%u] %s' % (image_idx, image)) 843061da546Spatrick return False 844061da546Spatrick 845061da546Spatrick 846*be691f3bSpatrickdef interactive_crashlogs(debugger, options, args): 847061da546Spatrick crash_log_files = list() 848061da546Spatrick for arg in args: 849061da546Spatrick for resolved_path in glob.glob(arg): 850061da546Spatrick crash_log_files.append(resolved_path) 851061da546Spatrick 852061da546Spatrick crash_logs = list() 853061da546Spatrick for crash_log_file in crash_log_files: 854*be691f3bSpatrick try: 855*be691f3bSpatrick crash_log = CrashLogParser().parse(debugger, crash_log_file, options.verbose) 856*be691f3bSpatrick except Exception as e: 857*be691f3bSpatrick print(e) 858061da546Spatrick continue 859061da546Spatrick if options.debug: 860061da546Spatrick crash_log.dump() 861061da546Spatrick if not crash_log.images: 862061da546Spatrick print('error: no images in crash log "%s"' % (crash_log)) 863061da546Spatrick continue 864061da546Spatrick else: 865061da546Spatrick crash_logs.append(crash_log) 866061da546Spatrick 867061da546Spatrick interpreter = Interactive(crash_logs) 868061da546Spatrick # List all crash logs that were imported 869061da546Spatrick interpreter.do_list() 870061da546Spatrick interpreter.cmdloop() 871061da546Spatrick 872061da546Spatrick 873061da546Spatrickdef save_crashlog(debugger, command, exe_ctx, result, dict): 874061da546Spatrick usage = "usage: %prog [options] <output-path>" 875061da546Spatrick description = '''Export the state of current target into a crashlog file''' 876061da546Spatrick parser = optparse.OptionParser( 877061da546Spatrick description=description, 878061da546Spatrick prog='save_crashlog', 879061da546Spatrick usage=usage) 880061da546Spatrick parser.add_option( 881061da546Spatrick '-v', 882061da546Spatrick '--verbose', 883061da546Spatrick action='store_true', 884061da546Spatrick dest='verbose', 885061da546Spatrick help='display verbose debug info', 886061da546Spatrick default=False) 887061da546Spatrick try: 888061da546Spatrick (options, args) = parser.parse_args(shlex.split(command)) 889061da546Spatrick except: 890061da546Spatrick result.PutCString("error: invalid options") 891061da546Spatrick return 892061da546Spatrick if len(args) != 1: 893061da546Spatrick result.PutCString( 894061da546Spatrick "error: invalid arguments, a single output file is the only valid argument") 895061da546Spatrick return 896061da546Spatrick out_file = open(args[0], 'w') 897061da546Spatrick if not out_file: 898061da546Spatrick result.PutCString( 899061da546Spatrick "error: failed to open file '%s' for writing...", 900061da546Spatrick args[0]) 901061da546Spatrick return 902061da546Spatrick target = exe_ctx.target 903061da546Spatrick if target: 904061da546Spatrick identifier = target.executable.basename 905061da546Spatrick process = exe_ctx.process 906061da546Spatrick if process: 907061da546Spatrick pid = process.id 908061da546Spatrick if pid != lldb.LLDB_INVALID_PROCESS_ID: 909061da546Spatrick out_file.write( 910061da546Spatrick 'Process: %s [%u]\n' % 911061da546Spatrick (identifier, pid)) 912061da546Spatrick out_file.write('Path: %s\n' % (target.executable.fullpath)) 913061da546Spatrick out_file.write('Identifier: %s\n' % (identifier)) 914061da546Spatrick out_file.write('\nDate/Time: %s\n' % 915061da546Spatrick (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) 916061da546Spatrick out_file.write( 917061da546Spatrick 'OS Version: Mac OS X %s (%s)\n' % 918061da546Spatrick (platform.mac_ver()[0], subprocess.check_output('sysctl -n kern.osversion', shell=True).decode("utf-8"))) 919061da546Spatrick out_file.write('Report Version: 9\n') 920061da546Spatrick for thread_idx in range(process.num_threads): 921061da546Spatrick thread = process.thread[thread_idx] 922061da546Spatrick out_file.write('\nThread %u:\n' % (thread_idx)) 923061da546Spatrick for (frame_idx, frame) in enumerate(thread.frames): 924061da546Spatrick frame_pc = frame.pc 925061da546Spatrick frame_offset = 0 926061da546Spatrick if frame.function: 927061da546Spatrick block = frame.GetFrameBlock() 928061da546Spatrick block_range = block.range[frame.addr] 929061da546Spatrick if block_range: 930061da546Spatrick block_start_addr = block_range[0] 931dda28197Spatrick frame_offset = frame_pc - block_start_addr.GetLoadAddress(target) 932061da546Spatrick else: 933dda28197Spatrick frame_offset = frame_pc - frame.function.addr.GetLoadAddress(target) 934061da546Spatrick elif frame.symbol: 935dda28197Spatrick frame_offset = frame_pc - frame.symbol.addr.GetLoadAddress(target) 936061da546Spatrick out_file.write( 937061da546Spatrick '%-3u %-32s 0x%16.16x %s' % 938061da546Spatrick (frame_idx, frame.module.file.basename, frame_pc, frame.name)) 939061da546Spatrick if frame_offset > 0: 940061da546Spatrick out_file.write(' + %u' % (frame_offset)) 941061da546Spatrick line_entry = frame.line_entry 942061da546Spatrick if line_entry: 943061da546Spatrick if options.verbose: 944061da546Spatrick # This will output the fullpath + line + column 945061da546Spatrick out_file.write(' %s' % (line_entry)) 946061da546Spatrick else: 947061da546Spatrick out_file.write( 948061da546Spatrick ' %s:%u' % 949061da546Spatrick (line_entry.file.basename, line_entry.line)) 950061da546Spatrick column = line_entry.column 951061da546Spatrick if column: 952061da546Spatrick out_file.write(':%u' % (column)) 953061da546Spatrick out_file.write('\n') 954061da546Spatrick 955061da546Spatrick out_file.write('\nBinary Images:\n') 956061da546Spatrick for module in target.modules: 957061da546Spatrick text_segment = module.section['__TEXT'] 958061da546Spatrick if text_segment: 959061da546Spatrick text_segment_load_addr = text_segment.GetLoadAddress(target) 960061da546Spatrick if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS: 961061da546Spatrick text_segment_end_load_addr = text_segment_load_addr + text_segment.size 962061da546Spatrick identifier = module.file.basename 963061da546Spatrick module_version = '???' 964061da546Spatrick module_version_array = module.GetVersion() 965061da546Spatrick if module_version_array: 966061da546Spatrick module_version = '.'.join( 967061da546Spatrick map(str, module_version_array)) 968061da546Spatrick out_file.write( 969061da546Spatrick ' 0x%16.16x - 0x%16.16x %s (%s - ???) <%s> %s\n' % 970061da546Spatrick (text_segment_load_addr, 971061da546Spatrick text_segment_end_load_addr, 972061da546Spatrick identifier, 973061da546Spatrick module_version, 974061da546Spatrick module.GetUUIDString(), 975061da546Spatrick module.file.fullpath)) 976061da546Spatrick out_file.close() 977061da546Spatrick else: 978061da546Spatrick result.PutCString("error: invalid target") 979061da546Spatrick 980061da546Spatrick 981061da546Spatrickdef Symbolicate(debugger, command, result, dict): 982061da546Spatrick try: 983*be691f3bSpatrick SymbolicateCrashLogs(debugger, shlex.split(command)) 984*be691f3bSpatrick except Exception as e: 985*be691f3bSpatrick result.PutCString("error: python exception: %s" % e) 986061da546Spatrick 987061da546Spatrick 988061da546Spatrickdef SymbolicateCrashLog(crash_log, options): 989061da546Spatrick if options.debug: 990061da546Spatrick crash_log.dump() 991061da546Spatrick if not crash_log.images: 992061da546Spatrick print('error: no images in crash log') 993061da546Spatrick return 994061da546Spatrick 995061da546Spatrick if options.dump_image_list: 996061da546Spatrick print("Binary Images:") 997061da546Spatrick for image in crash_log.images: 998061da546Spatrick if options.verbose: 999061da546Spatrick print(image.debug_dump()) 1000061da546Spatrick else: 1001061da546Spatrick print(image) 1002061da546Spatrick 1003061da546Spatrick target = crash_log.create_target() 1004061da546Spatrick if not target: 1005061da546Spatrick return 1006061da546Spatrick exe_module = target.GetModuleAtIndex(0) 1007061da546Spatrick images_to_load = list() 1008061da546Spatrick loaded_images = list() 1009061da546Spatrick if options.load_all_images: 1010061da546Spatrick # --load-all option was specified, load everything up 1011061da546Spatrick for image in crash_log.images: 1012061da546Spatrick images_to_load.append(image) 1013061da546Spatrick else: 1014061da546Spatrick # Only load the images found in stack frames for the crashed threads 1015061da546Spatrick if options.crashed_only: 1016061da546Spatrick for thread in crash_log.threads: 1017061da546Spatrick if thread.did_crash(): 1018061da546Spatrick for ident in thread.idents: 1019061da546Spatrick images = crash_log.find_images_with_identifier(ident) 1020061da546Spatrick if images: 1021061da546Spatrick for image in images: 1022061da546Spatrick images_to_load.append(image) 1023061da546Spatrick else: 1024061da546Spatrick print('error: can\'t find image for identifier "%s"' % ident) 1025061da546Spatrick else: 1026061da546Spatrick for ident in crash_log.idents: 1027061da546Spatrick images = crash_log.find_images_with_identifier(ident) 1028061da546Spatrick if images: 1029061da546Spatrick for image in images: 1030061da546Spatrick images_to_load.append(image) 1031061da546Spatrick else: 1032061da546Spatrick print('error: can\'t find image for identifier "%s"' % ident) 1033061da546Spatrick 1034061da546Spatrick for image in images_to_load: 1035061da546Spatrick if image not in loaded_images: 1036061da546Spatrick err = image.add_module(target) 1037061da546Spatrick if err: 1038061da546Spatrick print(err) 1039061da546Spatrick else: 1040061da546Spatrick loaded_images.append(image) 1041061da546Spatrick 1042061da546Spatrick if crash_log.backtraces: 1043061da546Spatrick for thread in crash_log.backtraces: 1044061da546Spatrick thread.dump_symbolicated(crash_log, options) 1045061da546Spatrick print() 1046061da546Spatrick 1047061da546Spatrick for thread in crash_log.threads: 1048061da546Spatrick thread.dump_symbolicated(crash_log, options) 1049061da546Spatrick print() 1050061da546Spatrick 1051061da546Spatrick 1052061da546Spatrickdef CreateSymbolicateCrashLogOptions( 1053061da546Spatrick command_name, 1054061da546Spatrick description, 1055061da546Spatrick add_interactive_options): 1056061da546Spatrick usage = "usage: %prog [options] <FILE> [FILE ...]" 1057061da546Spatrick option_parser = optparse.OptionParser( 1058061da546Spatrick description=description, prog='crashlog', usage=usage) 1059061da546Spatrick option_parser.add_option( 1060061da546Spatrick '--verbose', 1061061da546Spatrick '-v', 1062061da546Spatrick action='store_true', 1063061da546Spatrick dest='verbose', 1064061da546Spatrick help='display verbose debug info', 1065061da546Spatrick default=False) 1066061da546Spatrick option_parser.add_option( 1067061da546Spatrick '--debug', 1068061da546Spatrick '-g', 1069061da546Spatrick action='store_true', 1070061da546Spatrick dest='debug', 1071061da546Spatrick help='display verbose debug logging', 1072061da546Spatrick default=False) 1073061da546Spatrick option_parser.add_option( 1074061da546Spatrick '--load-all', 1075061da546Spatrick '-a', 1076061da546Spatrick action='store_true', 1077061da546Spatrick dest='load_all_images', 1078061da546Spatrick help='load all executable images, not just the images found in the crashed stack frames', 1079061da546Spatrick default=False) 1080061da546Spatrick option_parser.add_option( 1081061da546Spatrick '--images', 1082061da546Spatrick action='store_true', 1083061da546Spatrick dest='dump_image_list', 1084061da546Spatrick help='show image list', 1085061da546Spatrick default=False) 1086061da546Spatrick option_parser.add_option( 1087061da546Spatrick '--debug-delay', 1088061da546Spatrick type='int', 1089061da546Spatrick dest='debug_delay', 1090061da546Spatrick metavar='NSEC', 1091061da546Spatrick help='pause for NSEC seconds for debugger', 1092061da546Spatrick default=0) 1093061da546Spatrick option_parser.add_option( 1094061da546Spatrick '--crashed-only', 1095061da546Spatrick '-c', 1096061da546Spatrick action='store_true', 1097061da546Spatrick dest='crashed_only', 1098061da546Spatrick help='only symbolicate the crashed thread', 1099061da546Spatrick default=False) 1100061da546Spatrick option_parser.add_option( 1101061da546Spatrick '--disasm-depth', 1102061da546Spatrick '-d', 1103061da546Spatrick type='int', 1104061da546Spatrick dest='disassemble_depth', 1105061da546Spatrick help='set the depth in stack frames that should be disassembled (default is 1)', 1106061da546Spatrick default=1) 1107061da546Spatrick option_parser.add_option( 1108061da546Spatrick '--disasm-all', 1109061da546Spatrick '-D', 1110061da546Spatrick action='store_true', 1111061da546Spatrick dest='disassemble_all_threads', 1112061da546Spatrick help='enabled disassembly of frames on all threads (not just the crashed thread)', 1113061da546Spatrick default=False) 1114061da546Spatrick option_parser.add_option( 1115061da546Spatrick '--disasm-before', 1116061da546Spatrick '-B', 1117061da546Spatrick type='int', 1118061da546Spatrick dest='disassemble_before', 1119061da546Spatrick help='the number of instructions to disassemble before the frame PC', 1120061da546Spatrick default=4) 1121061da546Spatrick option_parser.add_option( 1122061da546Spatrick '--disasm-after', 1123061da546Spatrick '-A', 1124061da546Spatrick type='int', 1125061da546Spatrick dest='disassemble_after', 1126061da546Spatrick help='the number of instructions to disassemble after the frame PC', 1127061da546Spatrick default=4) 1128061da546Spatrick option_parser.add_option( 1129061da546Spatrick '--source-context', 1130061da546Spatrick '-C', 1131061da546Spatrick type='int', 1132061da546Spatrick metavar='NLINES', 1133061da546Spatrick dest='source_context', 1134061da546Spatrick help='show NLINES source lines of source context (default = 4)', 1135061da546Spatrick default=4) 1136061da546Spatrick option_parser.add_option( 1137061da546Spatrick '--source-frames', 1138061da546Spatrick type='int', 1139061da546Spatrick metavar='NFRAMES', 1140061da546Spatrick dest='source_frames', 1141061da546Spatrick help='show source for NFRAMES (default = 4)', 1142061da546Spatrick default=4) 1143061da546Spatrick option_parser.add_option( 1144061da546Spatrick '--source-all', 1145061da546Spatrick action='store_true', 1146061da546Spatrick dest='source_all', 1147061da546Spatrick help='show source for all threads, not just the crashed thread', 1148061da546Spatrick default=False) 1149061da546Spatrick if add_interactive_options: 1150061da546Spatrick option_parser.add_option( 1151061da546Spatrick '-i', 1152061da546Spatrick '--interactive', 1153061da546Spatrick action='store_true', 1154061da546Spatrick help='parse all crash logs and enter interactive mode', 1155061da546Spatrick default=False) 1156061da546Spatrick return option_parser 1157061da546Spatrick 1158061da546Spatrick 1159*be691f3bSpatrickdef SymbolicateCrashLogs(debugger, command_args): 1160061da546Spatrick description = '''Symbolicate one or more darwin crash log files to provide source file and line information, 1161061da546Spatrickinlined stack frames back to the concrete functions, and disassemble the location of the crash 1162061da546Spatrickfor the first frame of the crashed thread. 1163061da546SpatrickIf this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter 1164061da546Spatrickfor use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been 1165061da546Spatrickcreated that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows 1166061da546Spatrickyou to explore the program as if it were stopped at the locations described in the crash log and functions can 1167061da546Spatrickbe disassembled and lookups can be performed using the addresses found in the crash log.''' 1168061da546Spatrick option_parser = CreateSymbolicateCrashLogOptions( 1169061da546Spatrick 'crashlog', description, True) 1170061da546Spatrick try: 1171061da546Spatrick (options, args) = option_parser.parse_args(command_args) 1172061da546Spatrick except: 1173061da546Spatrick return 1174061da546Spatrick 1175061da546Spatrick if options.debug: 1176061da546Spatrick print('command_args = %s' % command_args) 1177061da546Spatrick print('options', options) 1178061da546Spatrick print('args', args) 1179061da546Spatrick 1180061da546Spatrick if options.debug_delay > 0: 1181061da546Spatrick print("Waiting %u seconds for debugger to attach..." % options.debug_delay) 1182061da546Spatrick time.sleep(options.debug_delay) 1183061da546Spatrick error = lldb.SBError() 1184061da546Spatrick 1185061da546Spatrick if args: 1186061da546Spatrick if options.interactive: 1187*be691f3bSpatrick interactive_crashlogs(debugger, options, args) 1188061da546Spatrick else: 1189061da546Spatrick for crash_log_file in args: 1190*be691f3bSpatrick crash_log = CrashLogParser().parse(debugger, crash_log_file, options.verbose) 1191061da546Spatrick SymbolicateCrashLog(crash_log, options) 1192061da546Spatrickif __name__ == '__main__': 1193061da546Spatrick # Create a new debugger instance 1194*be691f3bSpatrick debugger = lldb.SBDebugger.Create() 1195*be691f3bSpatrick SymbolicateCrashLogs(debugger, sys.argv[1:]) 1196*be691f3bSpatrick lldb.SBDebugger.Destroy(debugger) 1197061da546Spatrickelif getattr(lldb, 'debugger', None): 1198061da546Spatrick lldb.debugger.HandleCommand( 1199061da546Spatrick 'command script add -f lldb.macosx.crashlog.Symbolicate crashlog') 1200061da546Spatrick lldb.debugger.HandleCommand( 1201061da546Spatrick 'command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog') 1202