1be691f3bSpatrick#!/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 29*f6aab3d8Srobertimport abc 30*f6aab3d8Srobertimport concurrent.futures 31*f6aab3d8Srobertimport contextlib 32061da546Spatrickimport datetime 33*f6aab3d8Srobertimport json 34061da546Spatrickimport optparse 35061da546Spatrickimport os 36061da546Spatrickimport platform 37061da546Spatrickimport plistlib 38061da546Spatrickimport re 39061da546Spatrickimport shlex 40061da546Spatrickimport string 41061da546Spatrickimport subprocess 42061da546Spatrickimport sys 43*f6aab3d8Srobertimport threading 44061da546Spatrickimport time 45061da546Spatrickimport uuid 46*f6aab3d8Srobert 47*f6aab3d8Srobert 48*f6aab3d8Srobertprint_lock = threading.RLock() 49be691f3bSpatrick 50be691f3bSpatricktry: 51be691f3bSpatrick # First try for LLDB in case PYTHONPATH is already correctly setup. 52be691f3bSpatrick import lldb 53be691f3bSpatrickexcept ImportError: 54be691f3bSpatrick # Ask the command line driver for the path to the lldb module. Copy over 55be691f3bSpatrick # the environment so that SDKROOT is propagated to xcrun. 56be691f3bSpatrick command = ['xcrun', 'lldb', '-P'] if platform.system() == 'Darwin' else ['lldb', '-P'] 57be691f3bSpatrick # Extend the PYTHONPATH if the path exists and isn't already there. 58*f6aab3d8Srobert lldb_python_path = subprocess.check_output(command).decode("utf-8").strip() 59be691f3bSpatrick if os.path.exists(lldb_python_path) and not sys.path.__contains__(lldb_python_path): 60be691f3bSpatrick sys.path.append(lldb_python_path) 61be691f3bSpatrick # Try importing LLDB again. 62be691f3bSpatrick try: 63be691f3bSpatrick import lldb 64be691f3bSpatrick except ImportError: 65be691f3bSpatrick print("error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly") 66be691f3bSpatrick sys.exit(1) 67be691f3bSpatrick 68be691f3bSpatrickfrom lldb.utils import symbolication 69be691f3bSpatrick 70061da546Spatrickdef read_plist(s): 71061da546Spatrick if sys.version_info.major == 3: 72061da546Spatrick return plistlib.loads(s) 73061da546Spatrick else: 74061da546Spatrick return plistlib.readPlistFromString(s) 75061da546Spatrick 76061da546Spatrickclass CrashLog(symbolication.Symbolicator): 77061da546Spatrick class Thread: 78061da546Spatrick """Class that represents a thread in a darwin crash log""" 79061da546Spatrick 80061da546Spatrick def __init__(self, index, app_specific_backtrace): 81061da546Spatrick self.index = index 82*f6aab3d8Srobert self.id = index 83061da546Spatrick self.frames = list() 84061da546Spatrick self.idents = list() 85061da546Spatrick self.registers = dict() 86061da546Spatrick self.reason = None 87*f6aab3d8Srobert self.name = None 88061da546Spatrick self.queue = None 89*f6aab3d8Srobert self.crashed = False 90061da546Spatrick self.app_specific_backtrace = app_specific_backtrace 91061da546Spatrick 92061da546Spatrick def dump(self, prefix): 93061da546Spatrick if self.app_specific_backtrace: 94061da546Spatrick print("%Application Specific Backtrace[%u] %s" % (prefix, self.index, self.reason)) 95061da546Spatrick else: 96061da546Spatrick print("%sThread[%u] %s" % (prefix, self.index, self.reason)) 97061da546Spatrick if self.frames: 98061da546Spatrick print("%s Frames:" % (prefix)) 99061da546Spatrick for frame in self.frames: 100061da546Spatrick frame.dump(prefix + ' ') 101061da546Spatrick if self.registers: 102061da546Spatrick print("%s Registers:" % (prefix)) 103061da546Spatrick for reg in self.registers.keys(): 104be691f3bSpatrick print("%s %-8s = %#16.16x" % (prefix, reg, self.registers[reg])) 105061da546Spatrick 106061da546Spatrick def dump_symbolicated(self, crash_log, options): 107061da546Spatrick this_thread_crashed = self.app_specific_backtrace 108061da546Spatrick if not this_thread_crashed: 109061da546Spatrick this_thread_crashed = self.did_crash() 110061da546Spatrick if options.crashed_only and this_thread_crashed == False: 111061da546Spatrick return 112061da546Spatrick 113061da546Spatrick print("%s" % self) 114061da546Spatrick display_frame_idx = -1 115061da546Spatrick for frame_idx, frame in enumerate(self.frames): 116061da546Spatrick disassemble = ( 117061da546Spatrick this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth 118*f6aab3d8Srobert 119*f6aab3d8Srobert # Except for the zeroth frame, we should subtract 1 from every 120*f6aab3d8Srobert # frame pc to get the previous line entry. 121*f6aab3d8Srobert pc = frame.pc & crash_log.addr_mask 122*f6aab3d8Srobert pc = pc if frame_idx == 0 or pc == 0 else pc - 1 123*f6aab3d8Srobert symbolicated_frame_addresses = crash_log.symbolicate(pc, options.verbose) 124061da546Spatrick 125061da546Spatrick if symbolicated_frame_addresses: 126061da546Spatrick symbolicated_frame_address_idx = 0 127061da546Spatrick for symbolicated_frame_address in symbolicated_frame_addresses: 128061da546Spatrick display_frame_idx += 1 129061da546Spatrick print('[%3u] %s' % (frame_idx, symbolicated_frame_address)) 130061da546Spatrick if (options.source_all or self.did_crash( 131061da546Spatrick )) and display_frame_idx < options.source_frames and options.source_context: 132061da546Spatrick source_context = options.source_context 133061da546Spatrick line_entry = symbolicated_frame_address.get_symbol_context().line_entry 134061da546Spatrick if line_entry.IsValid(): 135061da546Spatrick strm = lldb.SBStream() 136061da546Spatrick if line_entry: 137be691f3bSpatrick crash_log.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers( 138061da546Spatrick line_entry.file, line_entry.line, source_context, source_context, "->", strm) 139061da546Spatrick source_text = strm.GetData() 140061da546Spatrick if source_text: 141061da546Spatrick # Indent the source a bit 142061da546Spatrick indent_str = ' ' 143061da546Spatrick join_str = '\n' + indent_str 144061da546Spatrick print('%s%s' % (indent_str, join_str.join(source_text.split('\n')))) 145061da546Spatrick if symbolicated_frame_address_idx == 0: 146061da546Spatrick if disassemble: 147061da546Spatrick instructions = symbolicated_frame_address.get_instructions() 148061da546Spatrick if instructions: 149061da546Spatrick print() 150061da546Spatrick symbolication.disassemble_instructions( 151061da546Spatrick crash_log.get_target(), 152061da546Spatrick instructions, 153061da546Spatrick frame.pc, 154061da546Spatrick options.disassemble_before, 155061da546Spatrick options.disassemble_after, 156061da546Spatrick frame.index > 0) 157061da546Spatrick print() 158061da546Spatrick symbolicated_frame_address_idx += 1 159061da546Spatrick else: 160061da546Spatrick print(frame) 161be691f3bSpatrick if self.registers: 162be691f3bSpatrick print() 163be691f3bSpatrick for reg in self.registers.keys(): 164be691f3bSpatrick print(" %-8s = %#16.16x" % (reg, self.registers[reg])) 165*f6aab3d8Srobert elif self.crashed: 166*f6aab3d8Srobert print() 167*f6aab3d8Srobert print("No thread state (register information) available") 168061da546Spatrick 169061da546Spatrick def add_ident(self, ident): 170061da546Spatrick if ident not in self.idents: 171061da546Spatrick self.idents.append(ident) 172061da546Spatrick 173061da546Spatrick def did_crash(self): 174061da546Spatrick return self.reason is not None 175061da546Spatrick 176061da546Spatrick def __str__(self): 177061da546Spatrick if self.app_specific_backtrace: 178061da546Spatrick s = "Application Specific Backtrace[%u]" % self.index 179061da546Spatrick else: 180061da546Spatrick s = "Thread[%u]" % self.index 181061da546Spatrick if self.reason: 182061da546Spatrick s += ' %s' % self.reason 183061da546Spatrick return s 184061da546Spatrick 185061da546Spatrick class Frame: 186061da546Spatrick """Class that represents a stack frame in a thread in a darwin crash log""" 187061da546Spatrick 188061da546Spatrick def __init__(self, index, pc, description): 189061da546Spatrick self.pc = pc 190061da546Spatrick self.description = description 191061da546Spatrick self.index = index 192061da546Spatrick 193061da546Spatrick def __str__(self): 194061da546Spatrick if self.description: 195061da546Spatrick return "[%3u] 0x%16.16x %s" % ( 196061da546Spatrick self.index, self.pc, self.description) 197061da546Spatrick else: 198061da546Spatrick return "[%3u] 0x%16.16x" % (self.index, self.pc) 199061da546Spatrick 200061da546Spatrick def dump(self, prefix): 201061da546Spatrick print("%s%s" % (prefix, str(self))) 202061da546Spatrick 203061da546Spatrick class DarwinImage(symbolication.Image): 204061da546Spatrick """Class that represents a binary images in a darwin crash log""" 205061da546Spatrick dsymForUUIDBinary = '/usr/local/bin/dsymForUUID' 206061da546Spatrick if not os.path.exists(dsymForUUIDBinary): 207061da546Spatrick try: 208061da546Spatrick dsymForUUIDBinary = subprocess.check_output('which dsymForUUID', 209061da546Spatrick shell=True).decode("utf-8").rstrip('\n') 210061da546Spatrick except: 211061da546Spatrick dsymForUUIDBinary = "" 212061da546Spatrick 213061da546Spatrick dwarfdump_uuid_regex = re.compile( 214061da546Spatrick 'UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*') 215061da546Spatrick 216061da546Spatrick def __init__( 217061da546Spatrick self, 218061da546Spatrick text_addr_lo, 219061da546Spatrick text_addr_hi, 220061da546Spatrick identifier, 221061da546Spatrick version, 222061da546Spatrick uuid, 223061da546Spatrick path, 224061da546Spatrick verbose): 225061da546Spatrick symbolication.Image.__init__(self, path, uuid) 226061da546Spatrick self.add_section( 227061da546Spatrick symbolication.Section( 228061da546Spatrick text_addr_lo, 229061da546Spatrick text_addr_hi, 230061da546Spatrick "__TEXT")) 231061da546Spatrick self.identifier = identifier 232061da546Spatrick self.version = version 233061da546Spatrick self.verbose = verbose 234061da546Spatrick 235061da546Spatrick def show_symbol_progress(self): 236061da546Spatrick """ 237061da546Spatrick Hide progress output and errors from system frameworks as they are plentiful. 238061da546Spatrick """ 239061da546Spatrick if self.verbose: 240061da546Spatrick return True 241061da546Spatrick return not (self.path.startswith("/System/Library/") or 242061da546Spatrick self.path.startswith("/usr/lib/")) 243061da546Spatrick 244061da546Spatrick 245061da546Spatrick def find_matching_slice(self): 246061da546Spatrick dwarfdump_cmd_output = subprocess.check_output( 247061da546Spatrick 'dwarfdump --uuid "%s"' % self.path, shell=True).decode("utf-8") 248061da546Spatrick self_uuid = self.get_uuid() 249061da546Spatrick for line in dwarfdump_cmd_output.splitlines(): 250061da546Spatrick match = self.dwarfdump_uuid_regex.search(line) 251061da546Spatrick if match: 252061da546Spatrick dwarf_uuid_str = match.group(1) 253061da546Spatrick dwarf_uuid = uuid.UUID(dwarf_uuid_str) 254061da546Spatrick if self_uuid == dwarf_uuid: 255061da546Spatrick self.resolved_path = self.path 256061da546Spatrick self.arch = match.group(2) 257061da546Spatrick return True 258061da546Spatrick if not self.resolved_path: 259061da546Spatrick self.unavailable = True 260061da546Spatrick if self.show_symbol_progress(): 261061da546Spatrick print(("error\n error: unable to locate '%s' with UUID %s" 262061da546Spatrick % (self.path, self.get_normalized_uuid_string()))) 263061da546Spatrick return False 264061da546Spatrick 265061da546Spatrick def locate_module_and_debug_symbols(self): 266061da546Spatrick # Don't load a module twice... 267061da546Spatrick if self.resolved: 268061da546Spatrick return True 269061da546Spatrick # Mark this as resolved so we don't keep trying 270061da546Spatrick self.resolved = True 271061da546Spatrick uuid_str = self.get_normalized_uuid_string() 272061da546Spatrick if self.show_symbol_progress(): 273*f6aab3d8Srobert with print_lock: 274*f6aab3d8Srobert print('Getting symbols for %s %s...' % (uuid_str, self.path)) 275061da546Spatrick if os.path.exists(self.dsymForUUIDBinary): 276061da546Spatrick dsym_for_uuid_command = '%s %s' % ( 277061da546Spatrick self.dsymForUUIDBinary, uuid_str) 278061da546Spatrick s = subprocess.check_output(dsym_for_uuid_command, shell=True) 279061da546Spatrick if s: 280061da546Spatrick try: 281061da546Spatrick plist_root = read_plist(s) 282061da546Spatrick except: 283*f6aab3d8Srobert with print_lock: 284061da546Spatrick print(("Got exception: ", sys.exc_info()[1], " handling dsymForUUID output: \n", s)) 285061da546Spatrick raise 286061da546Spatrick if plist_root: 287061da546Spatrick plist = plist_root[uuid_str] 288061da546Spatrick if plist: 289061da546Spatrick if 'DBGArchitecture' in plist: 290061da546Spatrick self.arch = plist['DBGArchitecture'] 291061da546Spatrick if 'DBGDSYMPath' in plist: 292061da546Spatrick self.symfile = os.path.realpath( 293061da546Spatrick plist['DBGDSYMPath']) 294061da546Spatrick if 'DBGSymbolRichExecutable' in plist: 295061da546Spatrick self.path = os.path.expanduser( 296061da546Spatrick plist['DBGSymbolRichExecutable']) 297061da546Spatrick self.resolved_path = self.path 298061da546Spatrick if not self.resolved_path and os.path.exists(self.path): 299061da546Spatrick if not self.find_matching_slice(): 300061da546Spatrick return False 301061da546Spatrick if not self.resolved_path and not os.path.exists(self.path): 302061da546Spatrick try: 303*f6aab3d8Srobert mdfind_results = subprocess.check_output( 304061da546Spatrick ["/usr/bin/mdfind", 305*f6aab3d8Srobert "com_apple_xcode_dsym_uuids == %s" % uuid_str]).decode("utf-8").splitlines() 306*f6aab3d8Srobert found_matching_slice = False 307*f6aab3d8Srobert for dsym in mdfind_results: 308061da546Spatrick dwarf_dir = os.path.join(dsym, 'Contents/Resources/DWARF') 309*f6aab3d8Srobert if not os.path.exists(dwarf_dir): 310*f6aab3d8Srobert # Not a dSYM bundle, probably an Xcode archive. 311*f6aab3d8Srobert continue 312*f6aab3d8Srobert with print_lock: 313*f6aab3d8Srobert print('falling back to binary inside "%s"' % dsym) 314*f6aab3d8Srobert self.symfile = dsym 315061da546Spatrick for filename in os.listdir(dwarf_dir): 316061da546Spatrick self.path = os.path.join(dwarf_dir, filename) 317*f6aab3d8Srobert if self.find_matching_slice(): 318*f6aab3d8Srobert found_matching_slice = True 319*f6aab3d8Srobert break 320*f6aab3d8Srobert if found_matching_slice: 321061da546Spatrick break 322061da546Spatrick except: 323061da546Spatrick pass 324061da546Spatrick if (self.resolved_path and os.path.exists(self.resolved_path)) or ( 325061da546Spatrick self.path and os.path.exists(self.path)): 326*f6aab3d8Srobert with print_lock: 327*f6aab3d8Srobert print('Resolved symbols for %s %s...' % (uuid_str, self.path)) 328061da546Spatrick return True 329061da546Spatrick else: 330061da546Spatrick self.unavailable = True 331061da546Spatrick return False 332061da546Spatrick 333be691f3bSpatrick def __init__(self, debugger, path, verbose): 334061da546Spatrick """CrashLog constructor that take a path to a darwin crash log file""" 335be691f3bSpatrick symbolication.Symbolicator.__init__(self, debugger) 336061da546Spatrick self.path = os.path.expanduser(path) 337061da546Spatrick self.info_lines = list() 338061da546Spatrick self.system_profile = list() 339061da546Spatrick self.threads = list() 340061da546Spatrick self.backtraces = list() # For application specific backtraces 341061da546Spatrick self.idents = list() # A list of the required identifiers for doing all stack backtraces 342*f6aab3d8Srobert self.errors = list() 343*f6aab3d8Srobert self.exception = dict() 344061da546Spatrick self.crashed_thread_idx = -1 345061da546Spatrick self.version = -1 346061da546Spatrick self.target = None 347061da546Spatrick self.verbose = verbose 348061da546Spatrick 349061da546Spatrick def dump(self): 350061da546Spatrick print("Crash Log File: %s" % (self.path)) 351061da546Spatrick if self.backtraces: 352061da546Spatrick print("\nApplication Specific Backtraces:") 353061da546Spatrick for thread in self.backtraces: 354061da546Spatrick thread.dump(' ') 355061da546Spatrick print("\nThreads:") 356061da546Spatrick for thread in self.threads: 357061da546Spatrick thread.dump(' ') 358061da546Spatrick print("\nImages:") 359061da546Spatrick for image in self.images: 360061da546Spatrick image.dump(' ') 361061da546Spatrick 362*f6aab3d8Srobert def set_main_image(self, identifier): 363*f6aab3d8Srobert for i, image in enumerate(self.images): 364*f6aab3d8Srobert if image.identifier == identifier: 365*f6aab3d8Srobert self.images.insert(0, self.images.pop(i)) 366*f6aab3d8Srobert break 367*f6aab3d8Srobert 368061da546Spatrick def find_image_with_identifier(self, identifier): 369061da546Spatrick for image in self.images: 370061da546Spatrick if image.identifier == identifier: 371061da546Spatrick return image 372061da546Spatrick regex_text = '^.*\.%s$' % (re.escape(identifier)) 373061da546Spatrick regex = re.compile(regex_text) 374061da546Spatrick for image in self.images: 375061da546Spatrick if regex.match(image.identifier): 376061da546Spatrick return image 377061da546Spatrick return None 378061da546Spatrick 379061da546Spatrick def create_target(self): 380061da546Spatrick if self.target is None: 381061da546Spatrick self.target = symbolication.Symbolicator.create_target(self) 382061da546Spatrick if self.target: 383061da546Spatrick return self.target 384061da546Spatrick # We weren't able to open the main executable as, but we can still 385061da546Spatrick # symbolicate 386061da546Spatrick print('crashlog.create_target()...2') 387061da546Spatrick if self.idents: 388061da546Spatrick for ident in self.idents: 389061da546Spatrick image = self.find_image_with_identifier(ident) 390061da546Spatrick if image: 391be691f3bSpatrick self.target = image.create_target(self.debugger) 392061da546Spatrick if self.target: 393061da546Spatrick return self.target # success 394061da546Spatrick print('crashlog.create_target()...3') 395061da546Spatrick for image in self.images: 396be691f3bSpatrick self.target = image.create_target(self.debugger) 397061da546Spatrick if self.target: 398061da546Spatrick return self.target # success 399061da546Spatrick print('crashlog.create_target()...4') 400061da546Spatrick print('error: Unable to locate any executables from the crash log.') 401061da546Spatrick print(' Try loading the executable into lldb before running crashlog') 402061da546Spatrick print(' and/or make sure the .dSYM bundles can be found by Spotlight.') 403061da546Spatrick return self.target 404061da546Spatrick 405061da546Spatrick def get_target(self): 406061da546Spatrick return self.target 407061da546Spatrick 408061da546Spatrick 409be691f3bSpatrickclass CrashLogFormatException(Exception): 410be691f3bSpatrick pass 411be691f3bSpatrick 412be691f3bSpatrick 413be691f3bSpatrickclass CrashLogParseException(Exception): 414be691f3bSpatrick pass 415be691f3bSpatrick 416*f6aab3d8Srobertclass InteractiveCrashLogException(Exception): 417*f6aab3d8Srobert pass 418be691f3bSpatrick 419be691f3bSpatrickclass CrashLogParser: 420*f6aab3d8Srobert @staticmethod 421*f6aab3d8Srobert def create(debugger, path, verbose): 422*f6aab3d8Srobert data = JSONCrashLogParser.is_valid_json(path) 423*f6aab3d8Srobert if data: 424*f6aab3d8Srobert parser = JSONCrashLogParser(debugger, path, verbose) 425*f6aab3d8Srobert parser.data = data 426*f6aab3d8Srobert return parser 427*f6aab3d8Srobert else: 428*f6aab3d8Srobert return TextCrashLogParser(debugger, path, verbose) 429be691f3bSpatrick 430be691f3bSpatrick def __init__(self, debugger, path, verbose): 431be691f3bSpatrick self.path = os.path.expanduser(path) 432be691f3bSpatrick self.verbose = verbose 433be691f3bSpatrick self.crashlog = CrashLog(debugger, self.path, self.verbose) 434be691f3bSpatrick 435*f6aab3d8Srobert @abc.abstractmethod 436be691f3bSpatrick def parse(self): 437*f6aab3d8Srobert pass 438be691f3bSpatrick 439be691f3bSpatrick 440*f6aab3d8Srobertclass JSONCrashLogParser(CrashLogParser): 441*f6aab3d8Srobert @staticmethod 442*f6aab3d8Srobert def is_valid_json(path): 443*f6aab3d8Srobert def parse_json(buffer): 444be691f3bSpatrick try: 445*f6aab3d8Srobert return json.loads(buffer) 446*f6aab3d8Srobert except: 447*f6aab3d8Srobert # The first line can contain meta data. Try stripping it and 448*f6aab3d8Srobert # try again. 449*f6aab3d8Srobert head, _, tail = buffer.partition('\n') 450*f6aab3d8Srobert return json.loads(tail) 451be691f3bSpatrick 452*f6aab3d8Srobert with open(path, 'r', encoding='utf-8') as f: 453*f6aab3d8Srobert buffer = f.read() 454*f6aab3d8Srobert try: 455*f6aab3d8Srobert return parse_json(buffer) 456*f6aab3d8Srobert except: 457*f6aab3d8Srobert return None 458*f6aab3d8Srobert 459*f6aab3d8Srobert def parse(self): 460be691f3bSpatrick try: 461be691f3bSpatrick self.parse_process_info(self.data) 462be691f3bSpatrick self.parse_images(self.data['usedImages']) 463*f6aab3d8Srobert self.parse_main_image(self.data) 464be691f3bSpatrick self.parse_threads(self.data['threads']) 465*f6aab3d8Srobert if 'asi' in self.data: 466*f6aab3d8Srobert self.crashlog.asi = self.data['asi'] 467*f6aab3d8Srobert if 'asiBacktraces' in self.data: 468*f6aab3d8Srobert self.parse_app_specific_backtraces(self.data['asiBacktraces']) 469*f6aab3d8Srobert if 'lastExceptionBacktrace' in self.data: 470*f6aab3d8Srobert self.crashlog.asb = self.data['lastExceptionBacktrace'] 471*f6aab3d8Srobert self.parse_errors(self.data) 472be691f3bSpatrick thread = self.crashlog.threads[self.crashlog.crashed_thread_idx] 473be691f3bSpatrick reason = self.parse_crash_reason(self.data['exception']) 474be691f3bSpatrick if thread.reason: 475be691f3bSpatrick thread.reason = '{} {}'.format(thread.reason, reason) 476be691f3bSpatrick else: 477be691f3bSpatrick thread.reason = reason 478be691f3bSpatrick except (KeyError, ValueError, TypeError) as e: 479be691f3bSpatrick raise CrashLogParseException( 480be691f3bSpatrick 'Failed to parse JSON crashlog: {}: {}'.format( 481be691f3bSpatrick type(e).__name__, e)) 482be691f3bSpatrick 483be691f3bSpatrick return self.crashlog 484be691f3bSpatrick 485be691f3bSpatrick def get_used_image(self, idx): 486be691f3bSpatrick return self.data['usedImages'][idx] 487be691f3bSpatrick 488be691f3bSpatrick def parse_process_info(self, json_data): 489be691f3bSpatrick self.crashlog.process_id = json_data['pid'] 490be691f3bSpatrick self.crashlog.process_identifier = json_data['procName'] 491be691f3bSpatrick 492be691f3bSpatrick def parse_crash_reason(self, json_exception): 493*f6aab3d8Srobert self.crashlog.exception = json_exception 494be691f3bSpatrick exception_type = json_exception['type'] 495*f6aab3d8Srobert exception_signal = " " 496*f6aab3d8Srobert if 'signal' in json_exception: 497*f6aab3d8Srobert exception_signal += "({})".format(json_exception['signal']) 498*f6aab3d8Srobert 499be691f3bSpatrick if 'codes' in json_exception: 500be691f3bSpatrick exception_extra = " ({})".format(json_exception['codes']) 501be691f3bSpatrick elif 'subtype' in json_exception: 502be691f3bSpatrick exception_extra = " ({})".format(json_exception['subtype']) 503be691f3bSpatrick else: 504be691f3bSpatrick exception_extra = "" 505*f6aab3d8Srobert return "{}{}{}".format(exception_type, exception_signal, 506be691f3bSpatrick exception_extra) 507be691f3bSpatrick 508be691f3bSpatrick def parse_images(self, json_images): 509be691f3bSpatrick idx = 0 510be691f3bSpatrick for json_image in json_images: 511be691f3bSpatrick img_uuid = uuid.UUID(json_image['uuid']) 512be691f3bSpatrick low = int(json_image['base']) 513be691f3bSpatrick high = int(0) 514be691f3bSpatrick name = json_image['name'] if 'name' in json_image else '' 515be691f3bSpatrick path = json_image['path'] if 'path' in json_image else '' 516be691f3bSpatrick version = '' 517be691f3bSpatrick darwin_image = self.crashlog.DarwinImage(low, high, name, version, 518be691f3bSpatrick img_uuid, path, 519be691f3bSpatrick self.verbose) 520be691f3bSpatrick self.crashlog.images.append(darwin_image) 521be691f3bSpatrick idx += 1 522be691f3bSpatrick 523*f6aab3d8Srobert def parse_main_image(self, json_data): 524*f6aab3d8Srobert if 'procName' in json_data: 525*f6aab3d8Srobert proc_name = json_data['procName'] 526*f6aab3d8Srobert self.crashlog.set_main_image(proc_name) 527*f6aab3d8Srobert 528be691f3bSpatrick def parse_frames(self, thread, json_frames): 529be691f3bSpatrick idx = 0 530be691f3bSpatrick for json_frame in json_frames: 531be691f3bSpatrick image_id = int(json_frame['imageIndex']) 532*f6aab3d8Srobert json_image = self.get_used_image(image_id) 533*f6aab3d8Srobert ident = json_image['name'] if 'name' in json_image else '' 534be691f3bSpatrick thread.add_ident(ident) 535be691f3bSpatrick if ident not in self.crashlog.idents: 536be691f3bSpatrick self.crashlog.idents.append(ident) 537be691f3bSpatrick 538be691f3bSpatrick frame_offset = int(json_frame['imageOffset']) 539be691f3bSpatrick image_addr = self.get_used_image(image_id)['base'] 540be691f3bSpatrick pc = image_addr + frame_offset 541be691f3bSpatrick thread.frames.append(self.crashlog.Frame(idx, pc, frame_offset)) 542*f6aab3d8Srobert 543*f6aab3d8Srobert # on arm64 systems, if it jump through a null function pointer, 544*f6aab3d8Srobert # we end up at address 0 and the crash reporter unwinder 545*f6aab3d8Srobert # misses the frame that actually faulted. 546*f6aab3d8Srobert # But $lr can tell us where the last BL/BLR instruction used 547*f6aab3d8Srobert # was at, so insert that address as the caller stack frame. 548*f6aab3d8Srobert if idx == 0 and pc == 0 and "lr" in thread.registers: 549*f6aab3d8Srobert pc = thread.registers["lr"] 550*f6aab3d8Srobert for image in self.data['usedImages']: 551*f6aab3d8Srobert text_lo = image['base'] 552*f6aab3d8Srobert text_hi = text_lo + image['size'] 553*f6aab3d8Srobert if text_lo <= pc < text_hi: 554*f6aab3d8Srobert idx += 1 555*f6aab3d8Srobert frame_offset = pc - text_lo 556*f6aab3d8Srobert thread.frames.append(self.crashlog.Frame(idx, pc, frame_offset)) 557*f6aab3d8Srobert break 558*f6aab3d8Srobert 559be691f3bSpatrick idx += 1 560be691f3bSpatrick 561be691f3bSpatrick def parse_threads(self, json_threads): 562be691f3bSpatrick idx = 0 563be691f3bSpatrick for json_thread in json_threads: 564be691f3bSpatrick thread = self.crashlog.Thread(idx, False) 565be691f3bSpatrick if 'name' in json_thread: 566*f6aab3d8Srobert thread.name = json_thread['name'] 567be691f3bSpatrick thread.reason = json_thread['name'] 568*f6aab3d8Srobert if 'id' in json_thread: 569*f6aab3d8Srobert thread.id = int(json_thread['id']) 570be691f3bSpatrick if json_thread.get('triggered', False): 571be691f3bSpatrick self.crashlog.crashed_thread_idx = idx 572*f6aab3d8Srobert thread.crashed = True 573*f6aab3d8Srobert if 'threadState' in json_thread: 574be691f3bSpatrick thread.registers = self.parse_thread_registers( 575be691f3bSpatrick json_thread['threadState']) 576*f6aab3d8Srobert if 'queue' in json_thread: 577be691f3bSpatrick thread.queue = json_thread.get('queue') 578be691f3bSpatrick self.parse_frames(thread, json_thread.get('frames', [])) 579be691f3bSpatrick self.crashlog.threads.append(thread) 580be691f3bSpatrick idx += 1 581be691f3bSpatrick 582*f6aab3d8Srobert def parse_asi_backtrace(self, thread, bt): 583*f6aab3d8Srobert for line in bt.split('\n'): 584*f6aab3d8Srobert frame_match = TextCrashLogParser.frame_regex.search(line) 585*f6aab3d8Srobert if not frame_match: 586*f6aab3d8Srobert print("error: can't parse application specific backtrace.") 587*f6aab3d8Srobert return False 588*f6aab3d8Srobert 589*f6aab3d8Srobert (frame_id, frame_img_name, frame_addr, 590*f6aab3d8Srobert frame_ofs) = frame_match.groups() 591*f6aab3d8Srobert 592*f6aab3d8Srobert thread.add_ident(frame_img_name) 593*f6aab3d8Srobert if frame_img_name not in self.crashlog.idents: 594*f6aab3d8Srobert self.crashlog.idents.append(frame_img_name) 595*f6aab3d8Srobert thread.frames.append(self.crashlog.Frame(int(frame_id), int( 596*f6aab3d8Srobert frame_addr, 0), frame_ofs)) 597*f6aab3d8Srobert 598*f6aab3d8Srobert return True 599*f6aab3d8Srobert 600*f6aab3d8Srobert def parse_app_specific_backtraces(self, json_app_specific_bts): 601*f6aab3d8Srobert for idx, backtrace in enumerate(json_app_specific_bts): 602*f6aab3d8Srobert thread = self.crashlog.Thread(idx, True) 603*f6aab3d8Srobert thread.queue = "Application Specific Backtrace" 604*f6aab3d8Srobert if self.parse_asi_backtrace(thread, backtrace): 605*f6aab3d8Srobert self.crashlog.threads.append(thread) 606*f6aab3d8Srobert 607*f6aab3d8Srobert def parse_thread_registers(self, json_thread_state, prefix=None): 608be691f3bSpatrick registers = dict() 609be691f3bSpatrick for key, state in json_thread_state.items(): 610*f6aab3d8Srobert if key == "rosetta": 611*f6aab3d8Srobert registers.update(self.parse_thread_registers(state)) 612*f6aab3d8Srobert continue 613*f6aab3d8Srobert if key == "x": 614*f6aab3d8Srobert gpr_dict = { str(idx) : reg for idx,reg in enumerate(state) } 615*f6aab3d8Srobert registers.update(self.parse_thread_registers(gpr_dict, key)) 616*f6aab3d8Srobert continue 617be691f3bSpatrick try: 618be691f3bSpatrick value = int(state['value']) 619*f6aab3d8Srobert registers["{}{}".format(prefix or '',key)] = value 620*f6aab3d8Srobert except (KeyError, ValueError, TypeError): 621be691f3bSpatrick pass 622be691f3bSpatrick return registers 623be691f3bSpatrick 624*f6aab3d8Srobert def parse_errors(self, json_data): 625*f6aab3d8Srobert if 'reportNotes' in json_data: 626*f6aab3d8Srobert self.crashlog.errors = json_data['reportNotes'] 627*f6aab3d8Srobert 628be691f3bSpatrick 629be691f3bSpatrickclass CrashLogParseMode: 630be691f3bSpatrick NORMAL = 0 631be691f3bSpatrick THREAD = 1 632be691f3bSpatrick IMAGES = 2 633be691f3bSpatrick THREGS = 3 634be691f3bSpatrick SYSTEM = 4 635be691f3bSpatrick INSTRS = 5 636be691f3bSpatrick 637*f6aab3d8Srobertclass TextCrashLogParser(CrashLogParser): 638*f6aab3d8Srobert parent_process_regex = re.compile(r'^Parent Process:\s*(.*)\[(\d+)\]') 639*f6aab3d8Srobert thread_state_regex = re.compile(r'^Thread \d+ crashed with') 640*f6aab3d8Srobert thread_instrs_regex = re.compile(r'^Thread \d+ instruction stream') 641*f6aab3d8Srobert thread_regex = re.compile(r'^Thread (\d+).*:') 642*f6aab3d8Srobert app_backtrace_regex = re.compile(r'^Application Specific Backtrace (\d+).*:') 643*f6aab3d8Srobert version = r'\(.+\)|(?:arm|x86_)[0-9a-z]+' 644*f6aab3d8Srobert frame_regex = re.compile(r'^(\d+)\s+' # id 645*f6aab3d8Srobert r'(.+?)\s+' # img_name 646*f6aab3d8Srobert r'(?:' +version+ r'\s+)?' # img_version 647*f6aab3d8Srobert r'(0x[0-9a-fA-F]{4,})' # addr (4 chars or more) 648*f6aab3d8Srobert r'(?: +(.*))?' # offs 649be691f3bSpatrick ) 650*f6aab3d8Srobert null_frame_regex = re.compile(r'^\d+\s+\?\?\?\s+0{4,} +') 651be691f3bSpatrick image_regex_uuid = re.compile(r'(0x[0-9a-fA-F]+)' # img_lo 652*f6aab3d8Srobert r'\s+-\s+' # - 653*f6aab3d8Srobert r'(0x[0-9a-fA-F]+)\s+' # img_hi 654*f6aab3d8Srobert r'[+]?(.+?)\s+' # img_name 655*f6aab3d8Srobert r'(?:(' +version+ r')\s+)?' # img_version 656*f6aab3d8Srobert r'(?:<([-0-9a-fA-F]+)>\s+)?' # img_uuid 657*f6aab3d8Srobert r'(\?+|/.*)' # img_path 658be691f3bSpatrick ) 659*f6aab3d8Srobert exception_type_regex = re.compile(r'^Exception Type:\s+(EXC_[A-Z_]+)(?:\s+\((.*)\))?') 660*f6aab3d8Srobert exception_codes_regex = re.compile(r'^Exception Codes:\s+(0x[0-9a-fA-F]+),\s*(0x[0-9a-fA-F]+)') 661*f6aab3d8Srobert exception_extra_regex = re.compile(r'^Exception\s+.*:\s+(.*)') 662be691f3bSpatrick 663be691f3bSpatrick def __init__(self, debugger, path, verbose): 664*f6aab3d8Srobert super().__init__(debugger, path, verbose) 665be691f3bSpatrick self.thread = None 666be691f3bSpatrick self.app_specific_backtrace = False 667be691f3bSpatrick self.parse_mode = CrashLogParseMode.NORMAL 668be691f3bSpatrick self.parsers = { 669be691f3bSpatrick CrashLogParseMode.NORMAL : self.parse_normal, 670be691f3bSpatrick CrashLogParseMode.THREAD : self.parse_thread, 671be691f3bSpatrick CrashLogParseMode.IMAGES : self.parse_images, 672be691f3bSpatrick CrashLogParseMode.THREGS : self.parse_thread_registers, 673be691f3bSpatrick CrashLogParseMode.SYSTEM : self.parse_system, 674be691f3bSpatrick CrashLogParseMode.INSTRS : self.parse_instructions, 675be691f3bSpatrick } 676be691f3bSpatrick 677be691f3bSpatrick def parse(self): 678*f6aab3d8Srobert with open(self.path,'r', encoding='utf-8') as f: 679be691f3bSpatrick lines = f.read().splitlines() 680be691f3bSpatrick 681be691f3bSpatrick for line in lines: 682be691f3bSpatrick line_len = len(line) 683be691f3bSpatrick if line_len == 0: 684be691f3bSpatrick if self.thread: 685be691f3bSpatrick if self.parse_mode == CrashLogParseMode.THREAD: 686be691f3bSpatrick if self.thread.index == self.crashlog.crashed_thread_idx: 687be691f3bSpatrick self.thread.reason = '' 688*f6aab3d8Srobert if hasattr(self.crashlog, 'thread_exception'): 689be691f3bSpatrick self.thread.reason += self.crashlog.thread_exception 690*f6aab3d8Srobert if hasattr(self.crashlog, 'thread_exception_data'): 691be691f3bSpatrick self.thread.reason += " (%s)" % self.crashlog.thread_exception_data 692be691f3bSpatrick if self.app_specific_backtrace: 693be691f3bSpatrick self.crashlog.backtraces.append(self.thread) 694be691f3bSpatrick else: 695be691f3bSpatrick self.crashlog.threads.append(self.thread) 696be691f3bSpatrick self.thread = None 697be691f3bSpatrick else: 698be691f3bSpatrick # only append an extra empty line if the previous line 699be691f3bSpatrick # in the info_lines wasn't empty 700be691f3bSpatrick if len(self.crashlog.info_lines) > 0 and len(self.crashlog.info_lines[-1]): 701be691f3bSpatrick self.crashlog.info_lines.append(line) 702be691f3bSpatrick self.parse_mode = CrashLogParseMode.NORMAL 703be691f3bSpatrick else: 704be691f3bSpatrick self.parsers[self.parse_mode](line) 705be691f3bSpatrick 706be691f3bSpatrick return self.crashlog 707be691f3bSpatrick 708*f6aab3d8Srobert def parse_exception(self, line): 709*f6aab3d8Srobert if not line.startswith('Exception'): 710*f6aab3d8Srobert return 711*f6aab3d8Srobert if line.startswith('Exception Type:'): 712*f6aab3d8Srobert self.crashlog.thread_exception = line[15:].strip() 713*f6aab3d8Srobert exception_type_match = self.exception_type_regex.search(line) 714*f6aab3d8Srobert if exception_type_match: 715*f6aab3d8Srobert exc_type, exc_signal = exception_type_match.groups() 716*f6aab3d8Srobert self.crashlog.exception['type'] = exc_type 717*f6aab3d8Srobert if exc_signal: 718*f6aab3d8Srobert self.crashlog.exception['signal'] = exc_signal 719*f6aab3d8Srobert elif line.startswith('Exception Subtype:'): 720*f6aab3d8Srobert self.crashlog.thread_exception_subtype = line[18:].strip() 721*f6aab3d8Srobert if 'type' in self.crashlog.exception: 722*f6aab3d8Srobert self.crashlog.exception['subtype'] = self.crashlog.thread_exception_subtype 723*f6aab3d8Srobert elif line.startswith('Exception Codes:'): 724*f6aab3d8Srobert self.crashlog.thread_exception_data = line[16:].strip() 725*f6aab3d8Srobert if 'type' not in self.crashlog.exception: 726*f6aab3d8Srobert return 727*f6aab3d8Srobert exception_codes_match = self.exception_codes_regex.search(line) 728*f6aab3d8Srobert if exception_codes_match: 729*f6aab3d8Srobert self.crashlog.exception['codes'] = self.crashlog.thread_exception_data 730*f6aab3d8Srobert code, subcode = exception_codes_match.groups() 731*f6aab3d8Srobert self.crashlog.exception['rawCodes'] = [int(code, base=16), 732*f6aab3d8Srobert int(subcode, base=16)] 733*f6aab3d8Srobert else: 734*f6aab3d8Srobert if 'type' not in self.crashlog.exception: 735*f6aab3d8Srobert return 736*f6aab3d8Srobert exception_extra_match = self.exception_extra_regex.search(line) 737*f6aab3d8Srobert if exception_extra_match: 738*f6aab3d8Srobert self.crashlog.exception['message'] = exception_extra_match.group(1) 739be691f3bSpatrick 740be691f3bSpatrick def parse_normal(self, line): 741be691f3bSpatrick if line.startswith('Process:'): 742be691f3bSpatrick (self.crashlog.process_name, pid_with_brackets) = line[ 743be691f3bSpatrick 8:].strip().split(' [') 744be691f3bSpatrick self.crashlog.process_id = pid_with_brackets.strip('[]') 745be691f3bSpatrick elif line.startswith('Identifier:'): 746be691f3bSpatrick self.crashlog.process_identifier = line[11:].strip() 747be691f3bSpatrick elif line.startswith('Version:'): 748be691f3bSpatrick version_string = line[8:].strip() 749be691f3bSpatrick matched_pair = re.search("(.+)\((.+)\)", version_string) 750be691f3bSpatrick if matched_pair: 751be691f3bSpatrick self.crashlog.process_version = matched_pair.group(1) 752be691f3bSpatrick self.crashlog.process_compatability_version = matched_pair.group( 753be691f3bSpatrick 2) 754be691f3bSpatrick else: 755be691f3bSpatrick self.crashlog.process = version_string 756be691f3bSpatrick self.crashlog.process_compatability_version = version_string 757be691f3bSpatrick elif self.parent_process_regex.search(line): 758be691f3bSpatrick parent_process_match = self.parent_process_regex.search( 759be691f3bSpatrick line) 760be691f3bSpatrick self.crashlog.parent_process_name = parent_process_match.group(1) 761be691f3bSpatrick self.crashlog.parent_process_id = parent_process_match.group(2) 762*f6aab3d8Srobert elif line.startswith('Exception'): 763*f6aab3d8Srobert self.parse_exception(line) 764be691f3bSpatrick return 765be691f3bSpatrick elif line.startswith('Crashed Thread:'): 766be691f3bSpatrick self.crashlog.crashed_thread_idx = int(line[15:].strip().split()[0]) 767be691f3bSpatrick return 768be691f3bSpatrick elif line.startswith('Triggered by Thread:'): # iOS 769be691f3bSpatrick self.crashlog.crashed_thread_idx = int(line[20:].strip().split()[0]) 770be691f3bSpatrick return 771be691f3bSpatrick elif line.startswith('Report Version:'): 772be691f3bSpatrick self.crashlog.version = int(line[15:].strip()) 773be691f3bSpatrick return 774be691f3bSpatrick elif line.startswith('System Profile:'): 775be691f3bSpatrick self.parse_mode = CrashLogParseMode.SYSTEM 776be691f3bSpatrick return 777be691f3bSpatrick elif (line.startswith('Interval Since Last Report:') or 778be691f3bSpatrick line.startswith('Crashes Since Last Report:') or 779be691f3bSpatrick line.startswith('Per-App Interval Since Last Report:') or 780be691f3bSpatrick line.startswith('Per-App Crashes Since Last Report:') or 781be691f3bSpatrick line.startswith('Sleep/Wake UUID:') or 782be691f3bSpatrick line.startswith('Anonymous UUID:')): 783be691f3bSpatrick # ignore these 784be691f3bSpatrick return 785be691f3bSpatrick elif line.startswith('Thread'): 786be691f3bSpatrick thread_state_match = self.thread_state_regex.search(line) 787be691f3bSpatrick if thread_state_match: 788be691f3bSpatrick self.app_specific_backtrace = False 789be691f3bSpatrick thread_state_match = self.thread_regex.search(line) 790be691f3bSpatrick thread_idx = int(thread_state_match.group(1)) 791be691f3bSpatrick self.parse_mode = CrashLogParseMode.THREGS 792be691f3bSpatrick self.thread = self.crashlog.threads[thread_idx] 793be691f3bSpatrick return 794be691f3bSpatrick thread_insts_match = self.thread_instrs_regex.search(line) 795be691f3bSpatrick if thread_insts_match: 796be691f3bSpatrick self.parse_mode = CrashLogParseMode.INSTRS 797be691f3bSpatrick return 798be691f3bSpatrick thread_match = self.thread_regex.search(line) 799be691f3bSpatrick if thread_match: 800be691f3bSpatrick self.app_specific_backtrace = False 801be691f3bSpatrick self.parse_mode = CrashLogParseMode.THREAD 802be691f3bSpatrick thread_idx = int(thread_match.group(1)) 803be691f3bSpatrick self.thread = self.crashlog.Thread(thread_idx, False) 804be691f3bSpatrick return 805be691f3bSpatrick return 806be691f3bSpatrick elif line.startswith('Binary Images:'): 807be691f3bSpatrick self.parse_mode = CrashLogParseMode.IMAGES 808be691f3bSpatrick return 809be691f3bSpatrick elif line.startswith('Application Specific Backtrace'): 810be691f3bSpatrick app_backtrace_match = self.app_backtrace_regex.search(line) 811be691f3bSpatrick if app_backtrace_match: 812be691f3bSpatrick self.parse_mode = CrashLogParseMode.THREAD 813be691f3bSpatrick self.app_specific_backtrace = True 814be691f3bSpatrick idx = int(app_backtrace_match.group(1)) 815be691f3bSpatrick self.thread = self.crashlog.Thread(idx, True) 816be691f3bSpatrick elif line.startswith('Last Exception Backtrace:'): # iOS 817be691f3bSpatrick self.parse_mode = CrashLogParseMode.THREAD 818be691f3bSpatrick self.app_specific_backtrace = True 819be691f3bSpatrick idx = 1 820be691f3bSpatrick self.thread = self.crashlog.Thread(idx, True) 821be691f3bSpatrick self.crashlog.info_lines.append(line.strip()) 822be691f3bSpatrick 823be691f3bSpatrick def parse_thread(self, line): 824be691f3bSpatrick if line.startswith('Thread'): 825be691f3bSpatrick return 826be691f3bSpatrick if self.null_frame_regex.search(line): 827be691f3bSpatrick print('warning: thread parser ignored null-frame: "%s"' % line) 828be691f3bSpatrick return 829be691f3bSpatrick frame_match = self.frame_regex.search(line) 830be691f3bSpatrick if frame_match: 831*f6aab3d8Srobert (frame_id, frame_img_name, frame_addr, 832*f6aab3d8Srobert frame_ofs) = frame_match.groups() 833be691f3bSpatrick ident = frame_img_name 834be691f3bSpatrick self.thread.add_ident(ident) 835be691f3bSpatrick if ident not in self.crashlog.idents: 836be691f3bSpatrick self.crashlog.idents.append(ident) 837be691f3bSpatrick self.thread.frames.append(self.crashlog.Frame(int(frame_id), int( 838be691f3bSpatrick frame_addr, 0), frame_ofs)) 839be691f3bSpatrick else: 840be691f3bSpatrick print('error: frame regex failed for line: "%s"' % line) 841be691f3bSpatrick 842be691f3bSpatrick def parse_images(self, line): 843be691f3bSpatrick image_match = self.image_regex_uuid.search(line) 844be691f3bSpatrick if image_match: 845*f6aab3d8Srobert (img_lo, img_hi, img_name, img_version, 846*f6aab3d8Srobert img_uuid, img_path) = image_match.groups() 847be691f3bSpatrick image = self.crashlog.DarwinImage(int(img_lo, 0), int(img_hi, 0), 848be691f3bSpatrick img_name.strip(), 849be691f3bSpatrick img_version.strip() 850be691f3bSpatrick if img_version else "", 851be691f3bSpatrick uuid.UUID(img_uuid), img_path, 852be691f3bSpatrick self.verbose) 853be691f3bSpatrick self.crashlog.images.append(image) 854be691f3bSpatrick else: 855be691f3bSpatrick print("error: image regex failed for: %s" % line) 856be691f3bSpatrick 857be691f3bSpatrick 858be691f3bSpatrick def parse_thread_registers(self, line): 859be691f3bSpatrick # "r12: 0x00007fff6b5939c8 r13: 0x0000000007000006 r14: 0x0000000000002a03 r15: 0x0000000000000c00" 860*f6aab3d8Srobert reg_values = re.findall('([a-z0-9]+): (0x[0-9a-f]+)', line, re.I) 861*f6aab3d8Srobert for reg, value in reg_values: 862*f6aab3d8Srobert self.thread.registers[reg] = int(value, 16) 863be691f3bSpatrick 864be691f3bSpatrick def parse_system(self, line): 865be691f3bSpatrick self.crashlog.system_profile.append(line) 866be691f3bSpatrick 867be691f3bSpatrick def parse_instructions(self, line): 868be691f3bSpatrick pass 869be691f3bSpatrick 870be691f3bSpatrick 871061da546Spatrickdef usage(): 872061da546Spatrick print("Usage: lldb-symbolicate.py [-n name] executable-image") 873061da546Spatrick sys.exit(0) 874061da546Spatrick 875061da546Spatrick 876061da546Spatrickdef save_crashlog(debugger, command, exe_ctx, result, dict): 877061da546Spatrick usage = "usage: %prog [options] <output-path>" 878061da546Spatrick description = '''Export the state of current target into a crashlog file''' 879061da546Spatrick parser = optparse.OptionParser( 880061da546Spatrick description=description, 881061da546Spatrick prog='save_crashlog', 882061da546Spatrick usage=usage) 883061da546Spatrick parser.add_option( 884061da546Spatrick '-v', 885061da546Spatrick '--verbose', 886061da546Spatrick action='store_true', 887061da546Spatrick dest='verbose', 888061da546Spatrick help='display verbose debug info', 889061da546Spatrick default=False) 890061da546Spatrick try: 891061da546Spatrick (options, args) = parser.parse_args(shlex.split(command)) 892061da546Spatrick except: 893061da546Spatrick result.PutCString("error: invalid options") 894061da546Spatrick return 895061da546Spatrick if len(args) != 1: 896061da546Spatrick result.PutCString( 897061da546Spatrick "error: invalid arguments, a single output file is the only valid argument") 898061da546Spatrick return 899*f6aab3d8Srobert out_file = open(args[0], 'w', encoding='utf-8') 900061da546Spatrick if not out_file: 901061da546Spatrick result.PutCString( 902061da546Spatrick "error: failed to open file '%s' for writing...", 903061da546Spatrick args[0]) 904061da546Spatrick return 905061da546Spatrick target = exe_ctx.target 906061da546Spatrick if target: 907061da546Spatrick identifier = target.executable.basename 908061da546Spatrick process = exe_ctx.process 909061da546Spatrick if process: 910061da546Spatrick pid = process.id 911061da546Spatrick if pid != lldb.LLDB_INVALID_PROCESS_ID: 912061da546Spatrick out_file.write( 913061da546Spatrick 'Process: %s [%u]\n' % 914061da546Spatrick (identifier, pid)) 915061da546Spatrick out_file.write('Path: %s\n' % (target.executable.fullpath)) 916061da546Spatrick out_file.write('Identifier: %s\n' % (identifier)) 917061da546Spatrick out_file.write('\nDate/Time: %s\n' % 918061da546Spatrick (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) 919061da546Spatrick out_file.write( 920061da546Spatrick 'OS Version: Mac OS X %s (%s)\n' % 921061da546Spatrick (platform.mac_ver()[0], subprocess.check_output('sysctl -n kern.osversion', shell=True).decode("utf-8"))) 922061da546Spatrick out_file.write('Report Version: 9\n') 923061da546Spatrick for thread_idx in range(process.num_threads): 924061da546Spatrick thread = process.thread[thread_idx] 925061da546Spatrick out_file.write('\nThread %u:\n' % (thread_idx)) 926061da546Spatrick for (frame_idx, frame) in enumerate(thread.frames): 927061da546Spatrick frame_pc = frame.pc 928061da546Spatrick frame_offset = 0 929061da546Spatrick if frame.function: 930061da546Spatrick block = frame.GetFrameBlock() 931061da546Spatrick block_range = block.range[frame.addr] 932061da546Spatrick if block_range: 933061da546Spatrick block_start_addr = block_range[0] 934dda28197Spatrick frame_offset = frame_pc - block_start_addr.GetLoadAddress(target) 935061da546Spatrick else: 936dda28197Spatrick frame_offset = frame_pc - frame.function.addr.GetLoadAddress(target) 937061da546Spatrick elif frame.symbol: 938dda28197Spatrick frame_offset = frame_pc - frame.symbol.addr.GetLoadAddress(target) 939061da546Spatrick out_file.write( 940061da546Spatrick '%-3u %-32s 0x%16.16x %s' % 941061da546Spatrick (frame_idx, frame.module.file.basename, frame_pc, frame.name)) 942061da546Spatrick if frame_offset > 0: 943061da546Spatrick out_file.write(' + %u' % (frame_offset)) 944061da546Spatrick line_entry = frame.line_entry 945061da546Spatrick if line_entry: 946061da546Spatrick if options.verbose: 947061da546Spatrick # This will output the fullpath + line + column 948061da546Spatrick out_file.write(' %s' % (line_entry)) 949061da546Spatrick else: 950061da546Spatrick out_file.write( 951061da546Spatrick ' %s:%u' % 952061da546Spatrick (line_entry.file.basename, line_entry.line)) 953061da546Spatrick column = line_entry.column 954061da546Spatrick if column: 955061da546Spatrick out_file.write(':%u' % (column)) 956061da546Spatrick out_file.write('\n') 957061da546Spatrick 958061da546Spatrick out_file.write('\nBinary Images:\n') 959061da546Spatrick for module in target.modules: 960061da546Spatrick text_segment = module.section['__TEXT'] 961061da546Spatrick if text_segment: 962061da546Spatrick text_segment_load_addr = text_segment.GetLoadAddress(target) 963061da546Spatrick if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS: 964061da546Spatrick text_segment_end_load_addr = text_segment_load_addr + text_segment.size 965061da546Spatrick identifier = module.file.basename 966061da546Spatrick module_version = '???' 967061da546Spatrick module_version_array = module.GetVersion() 968061da546Spatrick if module_version_array: 969061da546Spatrick module_version = '.'.join( 970061da546Spatrick map(str, module_version_array)) 971061da546Spatrick out_file.write( 972061da546Spatrick ' 0x%16.16x - 0x%16.16x %s (%s - ???) <%s> %s\n' % 973061da546Spatrick (text_segment_load_addr, 974061da546Spatrick text_segment_end_load_addr, 975061da546Spatrick identifier, 976061da546Spatrick module_version, 977061da546Spatrick module.GetUUIDString(), 978061da546Spatrick module.file.fullpath)) 979061da546Spatrick out_file.close() 980061da546Spatrick else: 981061da546Spatrick result.PutCString("error: invalid target") 982061da546Spatrick 983061da546Spatrick 984*f6aab3d8Srobertclass Symbolicate: 985*f6aab3d8Srobert def __init__(self, debugger, internal_dict): 986*f6aab3d8Srobert pass 987*f6aab3d8Srobert 988*f6aab3d8Srobert def __call__(self, debugger, command, exe_ctx, result): 989*f6aab3d8Srobert SymbolicateCrashLogs(debugger, shlex.split(command), result) 990*f6aab3d8Srobert 991*f6aab3d8Srobert def get_short_help(self): 992*f6aab3d8Srobert return "Symbolicate one or more darwin crash log files." 993*f6aab3d8Srobert 994*f6aab3d8Srobert def get_long_help(self): 995*f6aab3d8Srobert option_parser = CrashLogOptionParser() 996*f6aab3d8Srobert return option_parser.format_help() 997061da546Spatrick 998061da546Spatrick 999061da546Spatrickdef SymbolicateCrashLog(crash_log, options): 1000061da546Spatrick if options.debug: 1001061da546Spatrick crash_log.dump() 1002061da546Spatrick if not crash_log.images: 1003061da546Spatrick print('error: no images in crash log') 1004061da546Spatrick return 1005061da546Spatrick 1006061da546Spatrick if options.dump_image_list: 1007061da546Spatrick print("Binary Images:") 1008061da546Spatrick for image in crash_log.images: 1009061da546Spatrick if options.verbose: 1010061da546Spatrick print(image.debug_dump()) 1011061da546Spatrick else: 1012061da546Spatrick print(image) 1013061da546Spatrick 1014061da546Spatrick target = crash_log.create_target() 1015061da546Spatrick if not target: 1016061da546Spatrick return 1017061da546Spatrick exe_module = target.GetModuleAtIndex(0) 1018061da546Spatrick images_to_load = list() 1019061da546Spatrick loaded_images = list() 1020061da546Spatrick if options.load_all_images: 1021061da546Spatrick # --load-all option was specified, load everything up 1022061da546Spatrick for image in crash_log.images: 1023061da546Spatrick images_to_load.append(image) 1024061da546Spatrick else: 1025061da546Spatrick # Only load the images found in stack frames for the crashed threads 1026061da546Spatrick if options.crashed_only: 1027061da546Spatrick for thread in crash_log.threads: 1028061da546Spatrick if thread.did_crash(): 1029061da546Spatrick for ident in thread.idents: 1030061da546Spatrick images = crash_log.find_images_with_identifier(ident) 1031061da546Spatrick if images: 1032061da546Spatrick for image in images: 1033061da546Spatrick images_to_load.append(image) 1034061da546Spatrick else: 1035061da546Spatrick print('error: can\'t find image for identifier "%s"' % ident) 1036061da546Spatrick else: 1037061da546Spatrick for ident in crash_log.idents: 1038061da546Spatrick images = crash_log.find_images_with_identifier(ident) 1039061da546Spatrick if images: 1040061da546Spatrick for image in images: 1041061da546Spatrick images_to_load.append(image) 1042061da546Spatrick else: 1043061da546Spatrick print('error: can\'t find image for identifier "%s"' % ident) 1044061da546Spatrick 1045*f6aab3d8Srobert futures = [] 1046*f6aab3d8Srobert with concurrent.futures.ThreadPoolExecutor() as executor: 1047*f6aab3d8Srobert def add_module(image, target): 1048*f6aab3d8Srobert return image, image.add_module(target) 1049*f6aab3d8Srobert 1050061da546Spatrick for image in images_to_load: 1051*f6aab3d8Srobert futures.append(executor.submit(add_module, image=image, target=target)) 1052*f6aab3d8Srobert 1053*f6aab3d8Srobert for future in concurrent.futures.as_completed(futures): 1054*f6aab3d8Srobert image, err = future.result() 1055061da546Spatrick if err: 1056061da546Spatrick print(err) 1057061da546Spatrick else: 1058061da546Spatrick loaded_images.append(image) 1059061da546Spatrick 1060061da546Spatrick if crash_log.backtraces: 1061061da546Spatrick for thread in crash_log.backtraces: 1062061da546Spatrick thread.dump_symbolicated(crash_log, options) 1063061da546Spatrick print() 1064061da546Spatrick 1065061da546Spatrick for thread in crash_log.threads: 1066061da546Spatrick thread.dump_symbolicated(crash_log, options) 1067061da546Spatrick print() 1068061da546Spatrick 1069*f6aab3d8Srobert if crash_log.errors: 1070*f6aab3d8Srobert print("Errors:") 1071*f6aab3d8Srobert for error in crash_log.errors: 1072*f6aab3d8Srobert print(error) 1073*f6aab3d8Srobert 1074*f6aab3d8Srobertdef load_crashlog_in_scripted_process(debugger, crash_log_file, options, result): 1075*f6aab3d8Srobert crashlog_path = os.path.expanduser(crash_log_file) 1076*f6aab3d8Srobert if not os.path.exists(crashlog_path): 1077*f6aab3d8Srobert raise InteractiveCrashLogException("crashlog file %s does not exist" % crashlog_path) 1078*f6aab3d8Srobert 1079*f6aab3d8Srobert crashlog = CrashLogParser.create(debugger, crashlog_path, False).parse() 1080*f6aab3d8Srobert 1081*f6aab3d8Srobert target = lldb.SBTarget() 1082*f6aab3d8Srobert # 1. Try to use the user-provided target 1083*f6aab3d8Srobert if options.target_path: 1084*f6aab3d8Srobert target = debugger.CreateTarget(options.target_path) 1085*f6aab3d8Srobert if not target: 1086*f6aab3d8Srobert raise InteractiveCrashLogException("couldn't create target provided by the user (%s)" % options.target_path) 1087*f6aab3d8Srobert 1088*f6aab3d8Srobert # 2. If the user didn't provide a target, try to create a target using the symbolicator 1089*f6aab3d8Srobert if not target or not target.IsValid(): 1090*f6aab3d8Srobert target = crashlog.create_target() 1091*f6aab3d8Srobert # 3. If that didn't work, and a target is already loaded, use it 1092*f6aab3d8Srobert if (target is None or not target.IsValid()) and debugger.GetNumTargets() > 0: 1093*f6aab3d8Srobert target = debugger.GetTargetAtIndex(0) 1094*f6aab3d8Srobert # 4. Fail 1095*f6aab3d8Srobert if target is None or not target.IsValid(): 1096*f6aab3d8Srobert raise InteractiveCrashLogException("couldn't create target") 1097*f6aab3d8Srobert 1098*f6aab3d8Srobert ci = debugger.GetCommandInterpreter() 1099*f6aab3d8Srobert if not ci: 1100*f6aab3d8Srobert raise InteractiveCrashLogException("couldn't get command interpreter") 1101*f6aab3d8Srobert 1102*f6aab3d8Srobert ci.HandleCommand('script from lldb.macosx import crashlog_scripted_process', result) 1103*f6aab3d8Srobert if not result.Succeeded(): 1104*f6aab3d8Srobert raise InteractiveCrashLogException("couldn't import crashlog scripted process module") 1105*f6aab3d8Srobert 1106*f6aab3d8Srobert structured_data = lldb.SBStructuredData() 1107*f6aab3d8Srobert structured_data.SetFromJSON(json.dumps({ "file_path" : crashlog_path, 1108*f6aab3d8Srobert "load_all_images": options.load_all_images })) 1109*f6aab3d8Srobert launch_info = lldb.SBLaunchInfo(None) 1110*f6aab3d8Srobert launch_info.SetProcessPluginName("ScriptedProcess") 1111*f6aab3d8Srobert launch_info.SetScriptedProcessClassName("crashlog_scripted_process.CrashLogScriptedProcess") 1112*f6aab3d8Srobert launch_info.SetScriptedProcessDictionary(structured_data) 1113*f6aab3d8Srobert error = lldb.SBError() 1114*f6aab3d8Srobert process = target.Launch(launch_info, error) 1115*f6aab3d8Srobert 1116*f6aab3d8Srobert if not process or error.Fail(): 1117*f6aab3d8Srobert raise InteractiveCrashLogException("couldn't launch Scripted Process", error) 1118*f6aab3d8Srobert 1119*f6aab3d8Srobert if not options.skip_status: 1120*f6aab3d8Srobert @contextlib.contextmanager 1121*f6aab3d8Srobert def synchronous(debugger): 1122*f6aab3d8Srobert async_state = debugger.GetAsync() 1123*f6aab3d8Srobert debugger.SetAsync(False) 1124*f6aab3d8Srobert try: 1125*f6aab3d8Srobert yield 1126*f6aab3d8Srobert finally: 1127*f6aab3d8Srobert debugger.SetAsync(async_state) 1128*f6aab3d8Srobert 1129*f6aab3d8Srobert with synchronous(debugger): 1130*f6aab3d8Srobert run_options = lldb.SBCommandInterpreterRunOptions() 1131*f6aab3d8Srobert run_options.SetStopOnError(True) 1132*f6aab3d8Srobert run_options.SetStopOnCrash(True) 1133*f6aab3d8Srobert run_options.SetEchoCommands(True) 1134*f6aab3d8Srobert 1135*f6aab3d8Srobert commands_stream = lldb.SBStream() 1136*f6aab3d8Srobert commands_stream.Print("process status --verbose\n") 1137*f6aab3d8Srobert commands_stream.Print("thread backtrace --extended true\n") 1138*f6aab3d8Srobert error = debugger.SetInputString(commands_stream.GetData()) 1139*f6aab3d8Srobert if error.Success(): 1140*f6aab3d8Srobert debugger.RunCommandInterpreter(True, False, run_options, 0, False, True) 1141061da546Spatrick 1142061da546Spatrickdef CreateSymbolicateCrashLogOptions( 1143061da546Spatrick command_name, 1144061da546Spatrick description, 1145061da546Spatrick add_interactive_options): 1146061da546Spatrick usage = "usage: %prog [options] <FILE> [FILE ...]" 1147061da546Spatrick option_parser = optparse.OptionParser( 1148061da546Spatrick description=description, prog='crashlog', usage=usage) 1149061da546Spatrick option_parser.add_option( 1150*f6aab3d8Srobert '--version', 1151*f6aab3d8Srobert '-V', 1152*f6aab3d8Srobert dest='version', 1153*f6aab3d8Srobert action='store_true', 1154*f6aab3d8Srobert help='Show crashlog version', 1155*f6aab3d8Srobert default=False) 1156*f6aab3d8Srobert option_parser.add_option( 1157061da546Spatrick '--verbose', 1158061da546Spatrick '-v', 1159061da546Spatrick action='store_true', 1160061da546Spatrick dest='verbose', 1161061da546Spatrick help='display verbose debug info', 1162061da546Spatrick default=False) 1163061da546Spatrick option_parser.add_option( 1164061da546Spatrick '--debug', 1165061da546Spatrick '-g', 1166061da546Spatrick action='store_true', 1167061da546Spatrick dest='debug', 1168061da546Spatrick help='display verbose debug logging', 1169061da546Spatrick default=False) 1170061da546Spatrick option_parser.add_option( 1171061da546Spatrick '--load-all', 1172061da546Spatrick '-a', 1173061da546Spatrick action='store_true', 1174061da546Spatrick dest='load_all_images', 1175*f6aab3d8Srobert help='load all executable images, not just the images found in the ' 1176*f6aab3d8Srobert 'crashed stack frames, loads stackframes for all the threads in ' 1177*f6aab3d8Srobert 'interactive mode.', 1178061da546Spatrick default=False) 1179061da546Spatrick option_parser.add_option( 1180061da546Spatrick '--images', 1181061da546Spatrick action='store_true', 1182061da546Spatrick dest='dump_image_list', 1183061da546Spatrick help='show image list', 1184061da546Spatrick default=False) 1185061da546Spatrick option_parser.add_option( 1186061da546Spatrick '--debug-delay', 1187061da546Spatrick type='int', 1188061da546Spatrick dest='debug_delay', 1189061da546Spatrick metavar='NSEC', 1190061da546Spatrick help='pause for NSEC seconds for debugger', 1191061da546Spatrick default=0) 1192061da546Spatrick option_parser.add_option( 1193061da546Spatrick '--crashed-only', 1194061da546Spatrick '-c', 1195061da546Spatrick action='store_true', 1196061da546Spatrick dest='crashed_only', 1197061da546Spatrick help='only symbolicate the crashed thread', 1198061da546Spatrick default=False) 1199061da546Spatrick option_parser.add_option( 1200061da546Spatrick '--disasm-depth', 1201061da546Spatrick '-d', 1202061da546Spatrick type='int', 1203061da546Spatrick dest='disassemble_depth', 1204061da546Spatrick help='set the depth in stack frames that should be disassembled (default is 1)', 1205061da546Spatrick default=1) 1206061da546Spatrick option_parser.add_option( 1207061da546Spatrick '--disasm-all', 1208061da546Spatrick '-D', 1209061da546Spatrick action='store_true', 1210061da546Spatrick dest='disassemble_all_threads', 1211061da546Spatrick help='enabled disassembly of frames on all threads (not just the crashed thread)', 1212061da546Spatrick default=False) 1213061da546Spatrick option_parser.add_option( 1214061da546Spatrick '--disasm-before', 1215061da546Spatrick '-B', 1216061da546Spatrick type='int', 1217061da546Spatrick dest='disassemble_before', 1218061da546Spatrick help='the number of instructions to disassemble before the frame PC', 1219061da546Spatrick default=4) 1220061da546Spatrick option_parser.add_option( 1221061da546Spatrick '--disasm-after', 1222061da546Spatrick '-A', 1223061da546Spatrick type='int', 1224061da546Spatrick dest='disassemble_after', 1225061da546Spatrick help='the number of instructions to disassemble after the frame PC', 1226061da546Spatrick default=4) 1227061da546Spatrick option_parser.add_option( 1228061da546Spatrick '--source-context', 1229061da546Spatrick '-C', 1230061da546Spatrick type='int', 1231061da546Spatrick metavar='NLINES', 1232061da546Spatrick dest='source_context', 1233061da546Spatrick help='show NLINES source lines of source context (default = 4)', 1234061da546Spatrick default=4) 1235061da546Spatrick option_parser.add_option( 1236061da546Spatrick '--source-frames', 1237061da546Spatrick type='int', 1238061da546Spatrick metavar='NFRAMES', 1239061da546Spatrick dest='source_frames', 1240061da546Spatrick help='show source for NFRAMES (default = 4)', 1241061da546Spatrick default=4) 1242061da546Spatrick option_parser.add_option( 1243061da546Spatrick '--source-all', 1244061da546Spatrick action='store_true', 1245061da546Spatrick dest='source_all', 1246061da546Spatrick help='show source for all threads, not just the crashed thread', 1247061da546Spatrick default=False) 1248061da546Spatrick if add_interactive_options: 1249061da546Spatrick option_parser.add_option( 1250061da546Spatrick '-i', 1251061da546Spatrick '--interactive', 1252061da546Spatrick action='store_true', 1253*f6aab3d8Srobert help='parse a crash log and load it in a ScriptedProcess', 1254*f6aab3d8Srobert default=False) 1255*f6aab3d8Srobert option_parser.add_option( 1256*f6aab3d8Srobert '-b', 1257*f6aab3d8Srobert '--batch', 1258*f6aab3d8Srobert action='store_true', 1259*f6aab3d8Srobert help='dump symbolicated stackframes without creating a debug session', 1260*f6aab3d8Srobert default=True) 1261*f6aab3d8Srobert option_parser.add_option( 1262*f6aab3d8Srobert '--target', 1263*f6aab3d8Srobert '-t', 1264*f6aab3d8Srobert dest='target_path', 1265*f6aab3d8Srobert help='the target binary path that should be used for interactive crashlog (optional)', 1266*f6aab3d8Srobert default=None) 1267*f6aab3d8Srobert option_parser.add_option( 1268*f6aab3d8Srobert '--skip-status', 1269*f6aab3d8Srobert '-s', 1270*f6aab3d8Srobert dest='skip_status', 1271*f6aab3d8Srobert action='store_true', 1272*f6aab3d8Srobert help='prevent the interactive crashlog to dump the process status and thread backtrace at launch', 1273061da546Spatrick default=False) 1274061da546Spatrick return option_parser 1275061da546Spatrick 1276061da546Spatrick 1277*f6aab3d8Srobertdef CrashLogOptionParser(): 1278061da546Spatrick description = '''Symbolicate one or more darwin crash log files to provide source file and line information, 1279061da546Spatrickinlined stack frames back to the concrete functions, and disassemble the location of the crash 1280061da546Spatrickfor the first frame of the crashed thread. 1281061da546SpatrickIf this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter 1282061da546Spatrickfor use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been 1283061da546Spatrickcreated that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows 1284061da546Spatrickyou to explore the program as if it were stopped at the locations described in the crash log and functions can 1285061da546Spatrickbe disassembled and lookups can be performed using the addresses found in the crash log.''' 1286*f6aab3d8Srobert return CreateSymbolicateCrashLogOptions('crashlog', description, True) 1287*f6aab3d8Srobert 1288*f6aab3d8Srobertdef SymbolicateCrashLogs(debugger, command_args, result): 1289*f6aab3d8Srobert option_parser = CrashLogOptionParser() 1290*f6aab3d8Srobert 1291*f6aab3d8Srobert if not len(command_args): 1292*f6aab3d8Srobert option_parser.print_help() 1293*f6aab3d8Srobert return 1294*f6aab3d8Srobert 1295061da546Spatrick try: 1296061da546Spatrick (options, args) = option_parser.parse_args(command_args) 1297061da546Spatrick except: 1298061da546Spatrick return 1299061da546Spatrick 1300*f6aab3d8Srobert if options.version: 1301*f6aab3d8Srobert print(debugger.GetVersionString()) 1302*f6aab3d8Srobert return 1303*f6aab3d8Srobert 1304061da546Spatrick if options.debug: 1305061da546Spatrick print('command_args = %s' % command_args) 1306061da546Spatrick print('options', options) 1307061da546Spatrick print('args', args) 1308061da546Spatrick 1309061da546Spatrick if options.debug_delay > 0: 1310061da546Spatrick print("Waiting %u seconds for debugger to attach..." % options.debug_delay) 1311061da546Spatrick time.sleep(options.debug_delay) 1312061da546Spatrick error = lldb.SBError() 1313061da546Spatrick 1314*f6aab3d8Srobert def should_run_in_interactive_mode(options, ci): 1315061da546Spatrick if options.interactive: 1316*f6aab3d8Srobert return True 1317*f6aab3d8Srobert elif options.batch: 1318*f6aab3d8Srobert return False 1319*f6aab3d8Srobert # elif ci and ci.IsInteractive(): 1320*f6aab3d8Srobert # return True 1321061da546Spatrick else: 1322*f6aab3d8Srobert return False 1323*f6aab3d8Srobert 1324*f6aab3d8Srobert ci = debugger.GetCommandInterpreter() 1325*f6aab3d8Srobert 1326*f6aab3d8Srobert if args: 1327061da546Spatrick for crash_log_file in args: 1328*f6aab3d8Srobert if should_run_in_interactive_mode(options, ci): 1329*f6aab3d8Srobert try: 1330*f6aab3d8Srobert load_crashlog_in_scripted_process(debugger, crash_log_file, 1331*f6aab3d8Srobert options, result) 1332*f6aab3d8Srobert except InteractiveCrashLogException as e: 1333*f6aab3d8Srobert result.SetError(str(e)) 1334*f6aab3d8Srobert else: 1335*f6aab3d8Srobert crash_log = CrashLogParser.create(debugger, crash_log_file, options.verbose).parse() 1336061da546Spatrick SymbolicateCrashLog(crash_log, options) 1337*f6aab3d8Srobert 1338061da546Spatrickif __name__ == '__main__': 1339061da546Spatrick # Create a new debugger instance 1340be691f3bSpatrick debugger = lldb.SBDebugger.Create() 1341*f6aab3d8Srobert result = lldb.SBCommandReturnObject() 1342*f6aab3d8Srobert SymbolicateCrashLogs(debugger, sys.argv[1:], result) 1343be691f3bSpatrick lldb.SBDebugger.Destroy(debugger) 1344*f6aab3d8Srobert 1345*f6aab3d8Srobertdef __lldb_init_module(debugger, internal_dict): 1346*f6aab3d8Srobert debugger.HandleCommand( 1347*f6aab3d8Srobert 'command script add -o -c lldb.macosx.crashlog.Symbolicate crashlog') 1348*f6aab3d8Srobert debugger.HandleCommand( 1349*f6aab3d8Srobert 'command script add -o -f lldb.macosx.crashlog.save_crashlog save_crashlog') 1350*f6aab3d8Srobert print('"crashlog" and "save_crashlog" commands have been installed, use ' 1351*f6aab3d8Srobert 'the "--help" options on these commands for detailed help.') 1352