1061da546Spatrick#!/usr/bin/python 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 pprint # pp = pprint.PrettyPrinter(indent=4); pp.pprint(command_args) 38061da546Spatrickimport re 39061da546Spatrickimport shlex 40061da546Spatrickimport string 41061da546Spatrickimport subprocess 42061da546Spatrickimport sys 43061da546Spatrickimport time 44061da546Spatrickimport uuid 45061da546Spatrick 46061da546Spatrickdef read_plist(s): 47061da546Spatrick if sys.version_info.major == 3: 48061da546Spatrick return plistlib.loads(s) 49061da546Spatrick else: 50061da546Spatrick return plistlib.readPlistFromString(s) 51061da546Spatrick 52061da546Spatricktry: 53061da546Spatrick # Just try for LLDB in case PYTHONPATH is already correctly setup 54061da546Spatrick import lldb 55061da546Spatrickexcept ImportError: 56061da546Spatrick lldb_python_dirs = list() 57061da546Spatrick # lldb is not in the PYTHONPATH, try some defaults for the current platform 58061da546Spatrick platform_system = platform.system() 59061da546Spatrick if platform_system == 'Darwin': 60061da546Spatrick # On Darwin, try the currently selected Xcode directory 61061da546Spatrick xcode_dir = subprocess.check_output("xcode-select --print-path", shell=True).decode("utf-8") 62061da546Spatrick if xcode_dir: 63061da546Spatrick lldb_python_dirs.append( 64061da546Spatrick os.path.realpath( 65061da546Spatrick xcode_dir + 66061da546Spatrick '/../SharedFrameworks/LLDB.framework/Resources/Python')) 67061da546Spatrick lldb_python_dirs.append( 68061da546Spatrick xcode_dir + '/Library/PrivateFrameworks/LLDB.framework/Resources/Python') 69061da546Spatrick lldb_python_dirs.append( 70061da546Spatrick '/System/Library/PrivateFrameworks/LLDB.framework/Resources/Python') 71061da546Spatrick success = False 72061da546Spatrick for lldb_python_dir in lldb_python_dirs: 73061da546Spatrick if os.path.exists(lldb_python_dir): 74061da546Spatrick if not (sys.path.__contains__(lldb_python_dir)): 75061da546Spatrick sys.path.append(lldb_python_dir) 76061da546Spatrick try: 77061da546Spatrick import lldb 78061da546Spatrick except ImportError: 79061da546Spatrick pass 80061da546Spatrick else: 81061da546Spatrick print('imported lldb from: "%s"' % (lldb_python_dir)) 82061da546Spatrick success = True 83061da546Spatrick break 84061da546Spatrick if not success: 85061da546Spatrick print("error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly") 86061da546Spatrick sys.exit(1) 87061da546Spatrick 88061da546Spatrickfrom lldb.utils import symbolication 89061da546Spatrick 90061da546SpatrickPARSE_MODE_NORMAL = 0 91061da546SpatrickPARSE_MODE_THREAD = 1 92061da546SpatrickPARSE_MODE_IMAGES = 2 93061da546SpatrickPARSE_MODE_THREGS = 3 94061da546SpatrickPARSE_MODE_SYSTEM = 4 95061da546Spatrick 96061da546Spatrick 97061da546Spatrickclass CrashLog(symbolication.Symbolicator): 98061da546Spatrick """Class that does parses darwin crash logs""" 99061da546Spatrick parent_process_regex = re.compile('^Parent Process:\s*(.*)\[(\d+)\]') 100061da546Spatrick thread_state_regex = re.compile('^Thread ([0-9]+) crashed with') 101061da546Spatrick thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)') 102061da546Spatrick app_backtrace_regex = re.compile( 103061da546Spatrick '^Application Specific Backtrace ([0-9]+)([^:]*):(.*)') 104061da546Spatrick version = r'(\(.+\)|(arm|x86_)[0-9a-z]+)\s+' 105061da546Spatrick frame_regex = re.compile(r'^([0-9]+)' r'\s' # id 106061da546Spatrick r'+(.+?)' r'\s+' # img_name 107061da546Spatrick r'(' +version+ r')?' # img_version 108061da546Spatrick r'(0x[0-9a-fA-F]{7}[0-9a-fA-F]+)' # addr 109061da546Spatrick r' +(.*)' # offs 110061da546Spatrick ) 111061da546Spatrick null_frame_regex = re.compile(r'^([0-9]+)\s+\?\?\?\s+(0{7}0+) +(.*)') 112061da546Spatrick image_regex_uuid = re.compile(r'(0x[0-9a-fA-F]+)' # img_lo 113061da546Spatrick r'\s+' '-' r'\s+' # - 114061da546Spatrick r'(0x[0-9a-fA-F]+)' r'\s+' # img_hi 115061da546Spatrick r'[+]?(.+?)' r'\s+' # img_name 116061da546Spatrick r'(' +version+ ')?' # img_version 117061da546Spatrick r'(<([-0-9a-fA-F]+)>\s+)?' # img_uuid 118061da546Spatrick r'(/.*)' # img_path 119061da546Spatrick ) 120061da546Spatrick empty_line_regex = re.compile('^$') 121061da546Spatrick 122061da546Spatrick class Thread: 123061da546Spatrick """Class that represents a thread in a darwin crash log""" 124061da546Spatrick 125061da546Spatrick def __init__(self, index, app_specific_backtrace): 126061da546Spatrick self.index = index 127061da546Spatrick self.frames = list() 128061da546Spatrick self.idents = list() 129061da546Spatrick self.registers = dict() 130061da546Spatrick self.reason = None 131061da546Spatrick self.queue = None 132061da546Spatrick self.app_specific_backtrace = app_specific_backtrace 133061da546Spatrick 134061da546Spatrick def dump(self, prefix): 135061da546Spatrick if self.app_specific_backtrace: 136061da546Spatrick print("%Application Specific Backtrace[%u] %s" % (prefix, self.index, self.reason)) 137061da546Spatrick else: 138061da546Spatrick print("%sThread[%u] %s" % (prefix, self.index, self.reason)) 139061da546Spatrick if self.frames: 140061da546Spatrick print("%s Frames:" % (prefix)) 141061da546Spatrick for frame in self.frames: 142061da546Spatrick frame.dump(prefix + ' ') 143061da546Spatrick if self.registers: 144061da546Spatrick print("%s Registers:" % (prefix)) 145061da546Spatrick for reg in self.registers.keys(): 146061da546Spatrick print("%s %-5s = %#16.16x" % (prefix, reg, self.registers[reg])) 147061da546Spatrick 148061da546Spatrick def dump_symbolicated(self, crash_log, options): 149061da546Spatrick this_thread_crashed = self.app_specific_backtrace 150061da546Spatrick if not this_thread_crashed: 151061da546Spatrick this_thread_crashed = self.did_crash() 152061da546Spatrick if options.crashed_only and this_thread_crashed == False: 153061da546Spatrick return 154061da546Spatrick 155061da546Spatrick print("%s" % self) 156061da546Spatrick #prev_frame_index = -1 157061da546Spatrick display_frame_idx = -1 158061da546Spatrick for frame_idx, frame in enumerate(self.frames): 159061da546Spatrick disassemble = ( 160061da546Spatrick this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth 161061da546Spatrick if frame_idx == 0: 162061da546Spatrick symbolicated_frame_addresses = crash_log.symbolicate( 163061da546Spatrick frame.pc & crash_log.addr_mask, options.verbose) 164061da546Spatrick else: 165061da546Spatrick # Any frame above frame zero and we have to subtract one to 166061da546Spatrick # get the previous line entry 167061da546Spatrick symbolicated_frame_addresses = crash_log.symbolicate( 168061da546Spatrick (frame.pc & crash_log.addr_mask) - 1, options.verbose) 169061da546Spatrick 170061da546Spatrick if symbolicated_frame_addresses: 171061da546Spatrick symbolicated_frame_address_idx = 0 172061da546Spatrick for symbolicated_frame_address in symbolicated_frame_addresses: 173061da546Spatrick display_frame_idx += 1 174061da546Spatrick print('[%3u] %s' % (frame_idx, symbolicated_frame_address)) 175061da546Spatrick if (options.source_all or self.did_crash( 176061da546Spatrick )) and display_frame_idx < options.source_frames and options.source_context: 177061da546Spatrick source_context = options.source_context 178061da546Spatrick line_entry = symbolicated_frame_address.get_symbol_context().line_entry 179061da546Spatrick if line_entry.IsValid(): 180061da546Spatrick strm = lldb.SBStream() 181061da546Spatrick if line_entry: 182061da546Spatrick lldb.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers( 183061da546Spatrick line_entry.file, line_entry.line, source_context, source_context, "->", strm) 184061da546Spatrick source_text = strm.GetData() 185061da546Spatrick if source_text: 186061da546Spatrick # Indent the source a bit 187061da546Spatrick indent_str = ' ' 188061da546Spatrick join_str = '\n' + indent_str 189061da546Spatrick print('%s%s' % (indent_str, join_str.join(source_text.split('\n')))) 190061da546Spatrick if symbolicated_frame_address_idx == 0: 191061da546Spatrick if disassemble: 192061da546Spatrick instructions = symbolicated_frame_address.get_instructions() 193061da546Spatrick if instructions: 194061da546Spatrick print() 195061da546Spatrick symbolication.disassemble_instructions( 196061da546Spatrick crash_log.get_target(), 197061da546Spatrick instructions, 198061da546Spatrick frame.pc, 199061da546Spatrick options.disassemble_before, 200061da546Spatrick options.disassemble_after, 201061da546Spatrick frame.index > 0) 202061da546Spatrick print() 203061da546Spatrick symbolicated_frame_address_idx += 1 204061da546Spatrick else: 205061da546Spatrick print(frame) 206061da546Spatrick 207061da546Spatrick def add_ident(self, ident): 208061da546Spatrick if ident not in self.idents: 209061da546Spatrick self.idents.append(ident) 210061da546Spatrick 211061da546Spatrick def did_crash(self): 212061da546Spatrick return self.reason is not None 213061da546Spatrick 214061da546Spatrick def __str__(self): 215061da546Spatrick if self.app_specific_backtrace: 216061da546Spatrick s = "Application Specific Backtrace[%u]" % self.index 217061da546Spatrick else: 218061da546Spatrick s = "Thread[%u]" % self.index 219061da546Spatrick if self.reason: 220061da546Spatrick s += ' %s' % self.reason 221061da546Spatrick return s 222061da546Spatrick 223061da546Spatrick class Frame: 224061da546Spatrick """Class that represents a stack frame in a thread in a darwin crash log""" 225061da546Spatrick 226061da546Spatrick def __init__(self, index, pc, description): 227061da546Spatrick self.pc = pc 228061da546Spatrick self.description = description 229061da546Spatrick self.index = index 230061da546Spatrick 231061da546Spatrick def __str__(self): 232061da546Spatrick if self.description: 233061da546Spatrick return "[%3u] 0x%16.16x %s" % ( 234061da546Spatrick self.index, self.pc, self.description) 235061da546Spatrick else: 236061da546Spatrick return "[%3u] 0x%16.16x" % (self.index, self.pc) 237061da546Spatrick 238061da546Spatrick def dump(self, prefix): 239061da546Spatrick print("%s%s" % (prefix, str(self))) 240061da546Spatrick 241061da546Spatrick class DarwinImage(symbolication.Image): 242061da546Spatrick """Class that represents a binary images in a darwin crash log""" 243061da546Spatrick dsymForUUIDBinary = '/usr/local/bin/dsymForUUID' 244061da546Spatrick if not os.path.exists(dsymForUUIDBinary): 245061da546Spatrick try: 246061da546Spatrick dsymForUUIDBinary = subprocess.check_output('which dsymForUUID', 247061da546Spatrick shell=True).decode("utf-8").rstrip('\n') 248061da546Spatrick except: 249061da546Spatrick dsymForUUIDBinary = "" 250061da546Spatrick 251061da546Spatrick dwarfdump_uuid_regex = re.compile( 252061da546Spatrick 'UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*') 253061da546Spatrick 254061da546Spatrick def __init__( 255061da546Spatrick self, 256061da546Spatrick text_addr_lo, 257061da546Spatrick text_addr_hi, 258061da546Spatrick identifier, 259061da546Spatrick version, 260061da546Spatrick uuid, 261061da546Spatrick path, 262061da546Spatrick verbose): 263061da546Spatrick symbolication.Image.__init__(self, path, uuid) 264061da546Spatrick self.add_section( 265061da546Spatrick symbolication.Section( 266061da546Spatrick text_addr_lo, 267061da546Spatrick text_addr_hi, 268061da546Spatrick "__TEXT")) 269061da546Spatrick self.identifier = identifier 270061da546Spatrick self.version = version 271061da546Spatrick self.verbose = verbose 272061da546Spatrick 273061da546Spatrick def show_symbol_progress(self): 274061da546Spatrick """ 275061da546Spatrick Hide progress output and errors from system frameworks as they are plentiful. 276061da546Spatrick """ 277061da546Spatrick if self.verbose: 278061da546Spatrick return True 279061da546Spatrick return not (self.path.startswith("/System/Library/") or 280061da546Spatrick self.path.startswith("/usr/lib/")) 281061da546Spatrick 282061da546Spatrick 283061da546Spatrick def find_matching_slice(self): 284061da546Spatrick dwarfdump_cmd_output = subprocess.check_output( 285061da546Spatrick 'dwarfdump --uuid "%s"' % self.path, shell=True).decode("utf-8") 286061da546Spatrick self_uuid = self.get_uuid() 287061da546Spatrick for line in dwarfdump_cmd_output.splitlines(): 288061da546Spatrick match = self.dwarfdump_uuid_regex.search(line) 289061da546Spatrick if match: 290061da546Spatrick dwarf_uuid_str = match.group(1) 291061da546Spatrick dwarf_uuid = uuid.UUID(dwarf_uuid_str) 292061da546Spatrick if self_uuid == dwarf_uuid: 293061da546Spatrick self.resolved_path = self.path 294061da546Spatrick self.arch = match.group(2) 295061da546Spatrick return True 296061da546Spatrick if not self.resolved_path: 297061da546Spatrick self.unavailable = True 298061da546Spatrick if self.show_symbol_progress(): 299061da546Spatrick print(("error\n error: unable to locate '%s' with UUID %s" 300061da546Spatrick % (self.path, self.get_normalized_uuid_string()))) 301061da546Spatrick return False 302061da546Spatrick 303061da546Spatrick def locate_module_and_debug_symbols(self): 304061da546Spatrick # Don't load a module twice... 305061da546Spatrick if self.resolved: 306061da546Spatrick return True 307061da546Spatrick # Mark this as resolved so we don't keep trying 308061da546Spatrick self.resolved = True 309061da546Spatrick uuid_str = self.get_normalized_uuid_string() 310061da546Spatrick if self.show_symbol_progress(): 311061da546Spatrick print('Getting symbols for %s %s...' % (uuid_str, self.path), end=' ') 312061da546Spatrick if os.path.exists(self.dsymForUUIDBinary): 313061da546Spatrick dsym_for_uuid_command = '%s %s' % ( 314061da546Spatrick self.dsymForUUIDBinary, uuid_str) 315061da546Spatrick s = subprocess.check_output(dsym_for_uuid_command, shell=True) 316061da546Spatrick if s: 317061da546Spatrick try: 318061da546Spatrick plist_root = read_plist(s) 319061da546Spatrick except: 320061da546Spatrick print(("Got exception: ", sys.exc_info()[1], " handling dsymForUUID output: \n", s)) 321061da546Spatrick raise 322061da546Spatrick if plist_root: 323061da546Spatrick plist = plist_root[uuid_str] 324061da546Spatrick if plist: 325061da546Spatrick if 'DBGArchitecture' in plist: 326061da546Spatrick self.arch = plist['DBGArchitecture'] 327061da546Spatrick if 'DBGDSYMPath' in plist: 328061da546Spatrick self.symfile = os.path.realpath( 329061da546Spatrick plist['DBGDSYMPath']) 330061da546Spatrick if 'DBGSymbolRichExecutable' in plist: 331061da546Spatrick self.path = os.path.expanduser( 332061da546Spatrick plist['DBGSymbolRichExecutable']) 333061da546Spatrick self.resolved_path = self.path 334061da546Spatrick if not self.resolved_path and os.path.exists(self.path): 335061da546Spatrick if not self.find_matching_slice(): 336061da546Spatrick return False 337061da546Spatrick if not self.resolved_path and not os.path.exists(self.path): 338061da546Spatrick try: 339061da546Spatrick dsym = subprocess.check_output( 340061da546Spatrick ["/usr/bin/mdfind", 341061da546Spatrick "com_apple_xcode_dsym_uuids == %s"%uuid_str]).decode("utf-8")[:-1] 342061da546Spatrick if dsym and os.path.exists(dsym): 343061da546Spatrick print(('falling back to binary inside "%s"'%dsym)) 344061da546Spatrick self.symfile = dsym 345061da546Spatrick dwarf_dir = os.path.join(dsym, 'Contents/Resources/DWARF') 346061da546Spatrick for filename in os.listdir(dwarf_dir): 347061da546Spatrick self.path = os.path.join(dwarf_dir, filename) 348061da546Spatrick if not self.find_matching_slice(): 349061da546Spatrick return False 350061da546Spatrick break 351061da546Spatrick except: 352061da546Spatrick pass 353061da546Spatrick if (self.resolved_path and os.path.exists(self.resolved_path)) or ( 354061da546Spatrick self.path and os.path.exists(self.path)): 355061da546Spatrick print('ok') 356061da546Spatrick return True 357061da546Spatrick else: 358061da546Spatrick self.unavailable = True 359061da546Spatrick return False 360061da546Spatrick 361061da546Spatrick def __init__(self, path, verbose): 362061da546Spatrick """CrashLog constructor that take a path to a darwin crash log file""" 363061da546Spatrick symbolication.Symbolicator.__init__(self) 364061da546Spatrick self.path = os.path.expanduser(path) 365061da546Spatrick self.info_lines = list() 366061da546Spatrick self.system_profile = list() 367061da546Spatrick self.threads = list() 368061da546Spatrick self.backtraces = list() # For application specific backtraces 369061da546Spatrick self.idents = list() # A list of the required identifiers for doing all stack backtraces 370061da546Spatrick self.crashed_thread_idx = -1 371061da546Spatrick self.version = -1 372061da546Spatrick self.error = None 373061da546Spatrick self.target = None 374061da546Spatrick self.verbose = verbose 375061da546Spatrick # With possible initial component of ~ or ~user replaced by that user's 376061da546Spatrick # home directory. 377061da546Spatrick try: 378061da546Spatrick f = open(self.path) 379061da546Spatrick except IOError: 380061da546Spatrick self.error = 'error: cannot open "%s"' % self.path 381061da546Spatrick return 382061da546Spatrick 383061da546Spatrick self.file_lines = f.read().splitlines() 384061da546Spatrick parse_mode = PARSE_MODE_NORMAL 385061da546Spatrick thread = None 386061da546Spatrick app_specific_backtrace = False 387061da546Spatrick for line in self.file_lines: 388061da546Spatrick # print line 389061da546Spatrick line_len = len(line) 390061da546Spatrick if line_len == 0: 391061da546Spatrick if thread: 392061da546Spatrick if parse_mode == PARSE_MODE_THREAD: 393061da546Spatrick if thread.index == self.crashed_thread_idx: 394061da546Spatrick thread.reason = '' 395061da546Spatrick if self.thread_exception: 396061da546Spatrick thread.reason += self.thread_exception 397061da546Spatrick if self.thread_exception_data: 398061da546Spatrick thread.reason += " (%s)" % self.thread_exception_data 399061da546Spatrick if app_specific_backtrace: 400061da546Spatrick self.backtraces.append(thread) 401061da546Spatrick else: 402061da546Spatrick self.threads.append(thread) 403061da546Spatrick thread = None 404061da546Spatrick else: 405061da546Spatrick # only append an extra empty line if the previous line 406061da546Spatrick # in the info_lines wasn't empty 407061da546Spatrick if len(self.info_lines) > 0 and len(self.info_lines[-1]): 408061da546Spatrick self.info_lines.append(line) 409061da546Spatrick parse_mode = PARSE_MODE_NORMAL 410061da546Spatrick # print 'PARSE_MODE_NORMAL' 411061da546Spatrick elif parse_mode == PARSE_MODE_NORMAL: 412061da546Spatrick if line.startswith('Process:'): 413061da546Spatrick (self.process_name, pid_with_brackets) = line[ 414061da546Spatrick 8:].strip().split(' [') 415061da546Spatrick self.process_id = pid_with_brackets.strip('[]') 416061da546Spatrick elif line.startswith('Path:'): 417061da546Spatrick self.process_path = line[5:].strip() 418061da546Spatrick elif line.startswith('Identifier:'): 419061da546Spatrick self.process_identifier = line[11:].strip() 420061da546Spatrick elif line.startswith('Version:'): 421061da546Spatrick version_string = line[8:].strip() 422061da546Spatrick matched_pair = re.search("(.+)\((.+)\)", version_string) 423061da546Spatrick if matched_pair: 424061da546Spatrick self.process_version = matched_pair.group(1) 425061da546Spatrick self.process_compatability_version = matched_pair.group( 426061da546Spatrick 2) 427061da546Spatrick else: 428061da546Spatrick self.process = version_string 429061da546Spatrick self.process_compatability_version = version_string 430061da546Spatrick elif self.parent_process_regex.search(line): 431061da546Spatrick parent_process_match = self.parent_process_regex.search( 432061da546Spatrick line) 433061da546Spatrick self.parent_process_name = parent_process_match.group(1) 434061da546Spatrick self.parent_process_id = parent_process_match.group(2) 435061da546Spatrick elif line.startswith('Exception Type:'): 436061da546Spatrick self.thread_exception = line[15:].strip() 437061da546Spatrick continue 438061da546Spatrick elif line.startswith('Exception Codes:'): 439061da546Spatrick self.thread_exception_data = line[16:].strip() 440061da546Spatrick continue 441061da546Spatrick elif line.startswith('Exception Subtype:'): # iOS 442061da546Spatrick self.thread_exception_data = line[18:].strip() 443061da546Spatrick continue 444061da546Spatrick elif line.startswith('Crashed Thread:'): 445061da546Spatrick self.crashed_thread_idx = int(line[15:].strip().split()[0]) 446061da546Spatrick continue 447061da546Spatrick elif line.startswith('Triggered by Thread:'): # iOS 448061da546Spatrick self.crashed_thread_idx = int(line[20:].strip().split()[0]) 449061da546Spatrick continue 450061da546Spatrick elif line.startswith('Report Version:'): 451061da546Spatrick self.version = int(line[15:].strip()) 452061da546Spatrick continue 453061da546Spatrick elif line.startswith('System Profile:'): 454061da546Spatrick parse_mode = PARSE_MODE_SYSTEM 455061da546Spatrick continue 456061da546Spatrick elif (line.startswith('Interval Since Last Report:') or 457061da546Spatrick line.startswith('Crashes Since Last Report:') or 458061da546Spatrick line.startswith('Per-App Interval Since Last Report:') or 459061da546Spatrick line.startswith('Per-App Crashes Since Last Report:') or 460061da546Spatrick line.startswith('Sleep/Wake UUID:') or 461061da546Spatrick line.startswith('Anonymous UUID:')): 462061da546Spatrick # ignore these 463061da546Spatrick continue 464061da546Spatrick elif line.startswith('Thread'): 465061da546Spatrick thread_state_match = self.thread_state_regex.search(line) 466061da546Spatrick if thread_state_match: 467061da546Spatrick app_specific_backtrace = False 468061da546Spatrick thread_state_match = self.thread_regex.search(line) 469061da546Spatrick thread_idx = int(thread_state_match.group(1)) 470061da546Spatrick parse_mode = PARSE_MODE_THREGS 471061da546Spatrick thread = self.threads[thread_idx] 472061da546Spatrick else: 473061da546Spatrick thread_match = self.thread_regex.search(line) 474061da546Spatrick if thread_match: 475061da546Spatrick app_specific_backtrace = False 476061da546Spatrick parse_mode = PARSE_MODE_THREAD 477061da546Spatrick thread_idx = int(thread_match.group(1)) 478061da546Spatrick thread = CrashLog.Thread(thread_idx, False) 479061da546Spatrick continue 480061da546Spatrick elif line.startswith('Binary Images:'): 481061da546Spatrick parse_mode = PARSE_MODE_IMAGES 482061da546Spatrick continue 483061da546Spatrick elif line.startswith('Application Specific Backtrace'): 484061da546Spatrick app_backtrace_match = self.app_backtrace_regex.search(line) 485061da546Spatrick if app_backtrace_match: 486061da546Spatrick parse_mode = PARSE_MODE_THREAD 487061da546Spatrick app_specific_backtrace = True 488061da546Spatrick idx = int(app_backtrace_match.group(1)) 489061da546Spatrick thread = CrashLog.Thread(idx, True) 490061da546Spatrick elif line.startswith('Last Exception Backtrace:'): # iOS 491061da546Spatrick parse_mode = PARSE_MODE_THREAD 492061da546Spatrick app_specific_backtrace = True 493061da546Spatrick idx = 1 494061da546Spatrick thread = CrashLog.Thread(idx, True) 495061da546Spatrick self.info_lines.append(line.strip()) 496061da546Spatrick elif parse_mode == PARSE_MODE_THREAD: 497061da546Spatrick if line.startswith('Thread'): 498061da546Spatrick continue 499061da546Spatrick if self.null_frame_regex.search(line): 500061da546Spatrick print('warning: thread parser ignored null-frame: "%s"' % line) 501061da546Spatrick continue 502061da546Spatrick frame_match = self.frame_regex.search(line) 503061da546Spatrick if frame_match: 504061da546Spatrick (frame_id, frame_img_name, _, frame_img_version, _, 505061da546Spatrick frame_addr, frame_ofs) = frame_match.groups() 506061da546Spatrick ident = frame_img_name 507061da546Spatrick thread.add_ident(ident) 508061da546Spatrick if ident not in self.idents: 509061da546Spatrick self.idents.append(ident) 510061da546Spatrick thread.frames.append(CrashLog.Frame(int(frame_id), int( 511061da546Spatrick frame_addr, 0), frame_ofs)) 512061da546Spatrick else: 513061da546Spatrick print('error: frame regex failed for line: "%s"' % line) 514061da546Spatrick elif parse_mode == PARSE_MODE_IMAGES: 515061da546Spatrick image_match = self.image_regex_uuid.search(line) 516061da546Spatrick if image_match: 517061da546Spatrick (img_lo, img_hi, img_name, _, img_version, _, 518061da546Spatrick _, img_uuid, img_path) = image_match.groups() 519061da546Spatrick image = CrashLog.DarwinImage(int(img_lo, 0), int(img_hi, 0), 520061da546Spatrick img_name.strip(), 521061da546Spatrick img_version.strip() 522061da546Spatrick if img_version else "", 523061da546Spatrick uuid.UUID(img_uuid), img_path, 524061da546Spatrick self.verbose) 525061da546Spatrick self.images.append(image) 526061da546Spatrick else: 527061da546Spatrick print("error: image regex failed for: %s" % line) 528061da546Spatrick 529061da546Spatrick elif parse_mode == PARSE_MODE_THREGS: 530061da546Spatrick stripped_line = line.strip() 531061da546Spatrick # "r12: 0x00007fff6b5939c8 r13: 0x0000000007000006 r14: 0x0000000000002a03 r15: 0x0000000000000c00" 532061da546Spatrick reg_values = re.findall( 533061da546Spatrick '([a-zA-Z0-9]+: 0[Xx][0-9a-fA-F]+) *', stripped_line) 534061da546Spatrick for reg_value in reg_values: 535061da546Spatrick # print 'reg_value = "%s"' % reg_value 536061da546Spatrick (reg, value) = reg_value.split(': ') 537061da546Spatrick # print 'reg = "%s"' % reg 538061da546Spatrick # print 'value = "%s"' % value 539061da546Spatrick thread.registers[reg.strip()] = int(value, 0) 540061da546Spatrick elif parse_mode == PARSE_MODE_SYSTEM: 541061da546Spatrick self.system_profile.append(line) 542061da546Spatrick f.close() 543061da546Spatrick 544061da546Spatrick def dump(self): 545061da546Spatrick print("Crash Log File: %s" % (self.path)) 546061da546Spatrick if self.backtraces: 547061da546Spatrick print("\nApplication Specific Backtraces:") 548061da546Spatrick for thread in self.backtraces: 549061da546Spatrick thread.dump(' ') 550061da546Spatrick print("\nThreads:") 551061da546Spatrick for thread in self.threads: 552061da546Spatrick thread.dump(' ') 553061da546Spatrick print("\nImages:") 554061da546Spatrick for image in self.images: 555061da546Spatrick image.dump(' ') 556061da546Spatrick 557061da546Spatrick def find_image_with_identifier(self, identifier): 558061da546Spatrick for image in self.images: 559061da546Spatrick if image.identifier == identifier: 560061da546Spatrick return image 561061da546Spatrick regex_text = '^.*\.%s$' % (re.escape(identifier)) 562061da546Spatrick regex = re.compile(regex_text) 563061da546Spatrick for image in self.images: 564061da546Spatrick if regex.match(image.identifier): 565061da546Spatrick return image 566061da546Spatrick return None 567061da546Spatrick 568061da546Spatrick def create_target(self): 569061da546Spatrick # print 'crashlog.create_target()...' 570061da546Spatrick if self.target is None: 571061da546Spatrick self.target = symbolication.Symbolicator.create_target(self) 572061da546Spatrick if self.target: 573061da546Spatrick return self.target 574061da546Spatrick # We weren't able to open the main executable as, but we can still 575061da546Spatrick # symbolicate 576061da546Spatrick print('crashlog.create_target()...2') 577061da546Spatrick if self.idents: 578061da546Spatrick for ident in self.idents: 579061da546Spatrick image = self.find_image_with_identifier(ident) 580061da546Spatrick if image: 581061da546Spatrick self.target = image.create_target() 582061da546Spatrick if self.target: 583061da546Spatrick return self.target # success 584061da546Spatrick print('crashlog.create_target()...3') 585061da546Spatrick for image in self.images: 586061da546Spatrick self.target = image.create_target() 587061da546Spatrick if self.target: 588061da546Spatrick return self.target # success 589061da546Spatrick print('crashlog.create_target()...4') 590061da546Spatrick print('error: Unable to locate any executables from the crash log.') 591061da546Spatrick print(' Try loading the executable into lldb before running crashlog') 592061da546Spatrick print(' and/or make sure the .dSYM bundles can be found by Spotlight.') 593061da546Spatrick return self.target 594061da546Spatrick 595061da546Spatrick def get_target(self): 596061da546Spatrick return self.target 597061da546Spatrick 598061da546Spatrick 599061da546Spatrickdef usage(): 600061da546Spatrick print("Usage: lldb-symbolicate.py [-n name] executable-image") 601061da546Spatrick sys.exit(0) 602061da546Spatrick 603061da546Spatrick 604061da546Spatrickclass Interactive(cmd.Cmd): 605061da546Spatrick '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.''' 606061da546Spatrick image_option_parser = None 607061da546Spatrick 608061da546Spatrick def __init__(self, crash_logs): 609061da546Spatrick cmd.Cmd.__init__(self) 610061da546Spatrick self.use_rawinput = False 611061da546Spatrick self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.' 612061da546Spatrick self.crash_logs = crash_logs 613061da546Spatrick self.prompt = '% ' 614061da546Spatrick 615061da546Spatrick def default(self, line): 616061da546Spatrick '''Catch all for unknown command, which will exit the interpreter.''' 617061da546Spatrick print("uknown command: %s" % line) 618061da546Spatrick return True 619061da546Spatrick 620061da546Spatrick def do_q(self, line): 621061da546Spatrick '''Quit command''' 622061da546Spatrick return True 623061da546Spatrick 624061da546Spatrick def do_quit(self, line): 625061da546Spatrick '''Quit command''' 626061da546Spatrick return True 627061da546Spatrick 628061da546Spatrick def do_symbolicate(self, line): 629061da546Spatrick description = '''Symbolicate one or more darwin crash log files by index to provide source file and line information, 630061da546Spatrick inlined stack frames back to the concrete functions, and disassemble the location of the crash 631061da546Spatrick for the first frame of the crashed thread.''' 632061da546Spatrick option_parser = CreateSymbolicateCrashLogOptions( 633061da546Spatrick 'symbolicate', description, False) 634061da546Spatrick command_args = shlex.split(line) 635061da546Spatrick try: 636061da546Spatrick (options, args) = option_parser.parse_args(command_args) 637061da546Spatrick except: 638061da546Spatrick return 639061da546Spatrick 640061da546Spatrick if args: 641061da546Spatrick # We have arguments, they must valid be crash log file indexes 642061da546Spatrick for idx_str in args: 643061da546Spatrick idx = int(idx_str) 644061da546Spatrick if idx < len(self.crash_logs): 645061da546Spatrick SymbolicateCrashLog(self.crash_logs[idx], options) 646061da546Spatrick else: 647061da546Spatrick print('error: crash log index %u is out of range' % (idx)) 648061da546Spatrick else: 649061da546Spatrick # No arguments, symbolicate all crash logs using the options 650061da546Spatrick # provided 651061da546Spatrick for idx in range(len(self.crash_logs)): 652061da546Spatrick SymbolicateCrashLog(self.crash_logs[idx], options) 653061da546Spatrick 654061da546Spatrick def do_list(self, line=None): 655061da546Spatrick '''Dump a list of all crash logs that are currently loaded. 656061da546Spatrick 657061da546Spatrick USAGE: list''' 658061da546Spatrick print('%u crash logs are loaded:' % len(self.crash_logs)) 659061da546Spatrick for (crash_log_idx, crash_log) in enumerate(self.crash_logs): 660061da546Spatrick print('[%u] = %s' % (crash_log_idx, crash_log.path)) 661061da546Spatrick 662061da546Spatrick def do_image(self, line): 663061da546Spatrick '''Dump information about one or more binary images in the crash log given an image basename, or all images if no arguments are provided.''' 664061da546Spatrick usage = "usage: %prog [options] <PATH> [PATH ...]" 665061da546Spatrick 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.''' 666061da546Spatrick command_args = shlex.split(line) 667061da546Spatrick if not self.image_option_parser: 668061da546Spatrick self.image_option_parser = optparse.OptionParser( 669061da546Spatrick description=description, prog='image', usage=usage) 670061da546Spatrick self.image_option_parser.add_option( 671061da546Spatrick '-a', 672061da546Spatrick '--all', 673061da546Spatrick action='store_true', 674061da546Spatrick help='show all images', 675061da546Spatrick default=False) 676061da546Spatrick try: 677061da546Spatrick (options, args) = self.image_option_parser.parse_args(command_args) 678061da546Spatrick except: 679061da546Spatrick return 680061da546Spatrick 681061da546Spatrick if args: 682061da546Spatrick for image_path in args: 683061da546Spatrick fullpath_search = image_path[0] == '/' 684061da546Spatrick for (crash_log_idx, crash_log) in enumerate(self.crash_logs): 685061da546Spatrick matches_found = 0 686061da546Spatrick for (image_idx, image) in enumerate(crash_log.images): 687061da546Spatrick if fullpath_search: 688061da546Spatrick if image.get_resolved_path() == image_path: 689061da546Spatrick matches_found += 1 690061da546Spatrick print('[%u] ' % (crash_log_idx), image) 691061da546Spatrick else: 692061da546Spatrick image_basename = image.get_resolved_path_basename() 693061da546Spatrick if image_basename == image_path: 694061da546Spatrick matches_found += 1 695061da546Spatrick print('[%u] ' % (crash_log_idx), image) 696061da546Spatrick if matches_found == 0: 697061da546Spatrick for (image_idx, image) in enumerate(crash_log.images): 698061da546Spatrick resolved_image_path = image.get_resolved_path() 699061da546Spatrick if resolved_image_path and string.find( 700061da546Spatrick image.get_resolved_path(), image_path) >= 0: 701061da546Spatrick print('[%u] ' % (crash_log_idx), image) 702061da546Spatrick else: 703061da546Spatrick for crash_log in self.crash_logs: 704061da546Spatrick for (image_idx, image) in enumerate(crash_log.images): 705061da546Spatrick print('[%u] %s' % (image_idx, image)) 706061da546Spatrick return False 707061da546Spatrick 708061da546Spatrick 709061da546Spatrickdef interactive_crashlogs(options, args): 710061da546Spatrick crash_log_files = list() 711061da546Spatrick for arg in args: 712061da546Spatrick for resolved_path in glob.glob(arg): 713061da546Spatrick crash_log_files.append(resolved_path) 714061da546Spatrick 715061da546Spatrick crash_logs = list() 716061da546Spatrick for crash_log_file in crash_log_files: 717061da546Spatrick # print 'crash_log_file = "%s"' % crash_log_file 718061da546Spatrick crash_log = CrashLog(crash_log_file, options.verbose) 719061da546Spatrick if crash_log.error: 720061da546Spatrick print(crash_log.error) 721061da546Spatrick continue 722061da546Spatrick if options.debug: 723061da546Spatrick crash_log.dump() 724061da546Spatrick if not crash_log.images: 725061da546Spatrick print('error: no images in crash log "%s"' % (crash_log)) 726061da546Spatrick continue 727061da546Spatrick else: 728061da546Spatrick crash_logs.append(crash_log) 729061da546Spatrick 730061da546Spatrick interpreter = Interactive(crash_logs) 731061da546Spatrick # List all crash logs that were imported 732061da546Spatrick interpreter.do_list() 733061da546Spatrick interpreter.cmdloop() 734061da546Spatrick 735061da546Spatrick 736061da546Spatrickdef save_crashlog(debugger, command, exe_ctx, result, dict): 737061da546Spatrick usage = "usage: %prog [options] <output-path>" 738061da546Spatrick description = '''Export the state of current target into a crashlog file''' 739061da546Spatrick parser = optparse.OptionParser( 740061da546Spatrick description=description, 741061da546Spatrick prog='save_crashlog', 742061da546Spatrick usage=usage) 743061da546Spatrick parser.add_option( 744061da546Spatrick '-v', 745061da546Spatrick '--verbose', 746061da546Spatrick action='store_true', 747061da546Spatrick dest='verbose', 748061da546Spatrick help='display verbose debug info', 749061da546Spatrick default=False) 750061da546Spatrick try: 751061da546Spatrick (options, args) = parser.parse_args(shlex.split(command)) 752061da546Spatrick except: 753061da546Spatrick result.PutCString("error: invalid options") 754061da546Spatrick return 755061da546Spatrick if len(args) != 1: 756061da546Spatrick result.PutCString( 757061da546Spatrick "error: invalid arguments, a single output file is the only valid argument") 758061da546Spatrick return 759061da546Spatrick out_file = open(args[0], 'w') 760061da546Spatrick if not out_file: 761061da546Spatrick result.PutCString( 762061da546Spatrick "error: failed to open file '%s' for writing...", 763061da546Spatrick args[0]) 764061da546Spatrick return 765061da546Spatrick target = exe_ctx.target 766061da546Spatrick if target: 767061da546Spatrick identifier = target.executable.basename 768061da546Spatrick process = exe_ctx.process 769061da546Spatrick if process: 770061da546Spatrick pid = process.id 771061da546Spatrick if pid != lldb.LLDB_INVALID_PROCESS_ID: 772061da546Spatrick out_file.write( 773061da546Spatrick 'Process: %s [%u]\n' % 774061da546Spatrick (identifier, pid)) 775061da546Spatrick out_file.write('Path: %s\n' % (target.executable.fullpath)) 776061da546Spatrick out_file.write('Identifier: %s\n' % (identifier)) 777061da546Spatrick out_file.write('\nDate/Time: %s\n' % 778061da546Spatrick (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) 779061da546Spatrick out_file.write( 780061da546Spatrick 'OS Version: Mac OS X %s (%s)\n' % 781061da546Spatrick (platform.mac_ver()[0], subprocess.check_output('sysctl -n kern.osversion', shell=True).decode("utf-8"))) 782061da546Spatrick out_file.write('Report Version: 9\n') 783061da546Spatrick for thread_idx in range(process.num_threads): 784061da546Spatrick thread = process.thread[thread_idx] 785061da546Spatrick out_file.write('\nThread %u:\n' % (thread_idx)) 786061da546Spatrick for (frame_idx, frame) in enumerate(thread.frames): 787061da546Spatrick frame_pc = frame.pc 788061da546Spatrick frame_offset = 0 789061da546Spatrick if frame.function: 790061da546Spatrick block = frame.GetFrameBlock() 791061da546Spatrick block_range = block.range[frame.addr] 792061da546Spatrick if block_range: 793061da546Spatrick block_start_addr = block_range[0] 794*dda28197Spatrick frame_offset = frame_pc - block_start_addr.GetLoadAddress(target) 795061da546Spatrick else: 796*dda28197Spatrick frame_offset = frame_pc - frame.function.addr.GetLoadAddress(target) 797061da546Spatrick elif frame.symbol: 798*dda28197Spatrick frame_offset = frame_pc - frame.symbol.addr.GetLoadAddress(target) 799061da546Spatrick out_file.write( 800061da546Spatrick '%-3u %-32s 0x%16.16x %s' % 801061da546Spatrick (frame_idx, frame.module.file.basename, frame_pc, frame.name)) 802061da546Spatrick if frame_offset > 0: 803061da546Spatrick out_file.write(' + %u' % (frame_offset)) 804061da546Spatrick line_entry = frame.line_entry 805061da546Spatrick if line_entry: 806061da546Spatrick if options.verbose: 807061da546Spatrick # This will output the fullpath + line + column 808061da546Spatrick out_file.write(' %s' % (line_entry)) 809061da546Spatrick else: 810061da546Spatrick out_file.write( 811061da546Spatrick ' %s:%u' % 812061da546Spatrick (line_entry.file.basename, line_entry.line)) 813061da546Spatrick column = line_entry.column 814061da546Spatrick if column: 815061da546Spatrick out_file.write(':%u' % (column)) 816061da546Spatrick out_file.write('\n') 817061da546Spatrick 818061da546Spatrick out_file.write('\nBinary Images:\n') 819061da546Spatrick for module in target.modules: 820061da546Spatrick text_segment = module.section['__TEXT'] 821061da546Spatrick if text_segment: 822061da546Spatrick text_segment_load_addr = text_segment.GetLoadAddress(target) 823061da546Spatrick if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS: 824061da546Spatrick text_segment_end_load_addr = text_segment_load_addr + text_segment.size 825061da546Spatrick identifier = module.file.basename 826061da546Spatrick module_version = '???' 827061da546Spatrick module_version_array = module.GetVersion() 828061da546Spatrick if module_version_array: 829061da546Spatrick module_version = '.'.join( 830061da546Spatrick map(str, module_version_array)) 831061da546Spatrick out_file.write( 832061da546Spatrick ' 0x%16.16x - 0x%16.16x %s (%s - ???) <%s> %s\n' % 833061da546Spatrick (text_segment_load_addr, 834061da546Spatrick text_segment_end_load_addr, 835061da546Spatrick identifier, 836061da546Spatrick module_version, 837061da546Spatrick module.GetUUIDString(), 838061da546Spatrick module.file.fullpath)) 839061da546Spatrick out_file.close() 840061da546Spatrick else: 841061da546Spatrick result.PutCString("error: invalid target") 842061da546Spatrick 843061da546Spatrick 844061da546Spatrickdef Symbolicate(debugger, command, result, dict): 845061da546Spatrick try: 846061da546Spatrick SymbolicateCrashLogs(shlex.split(command)) 847061da546Spatrick except: 848061da546Spatrick result.PutCString("error: python exception %s" % sys.exc_info()[0]) 849061da546Spatrick 850061da546Spatrick 851061da546Spatrickdef SymbolicateCrashLog(crash_log, options): 852061da546Spatrick if crash_log.error: 853061da546Spatrick print(crash_log.error) 854061da546Spatrick return 855061da546Spatrick if options.debug: 856061da546Spatrick crash_log.dump() 857061da546Spatrick if not crash_log.images: 858061da546Spatrick print('error: no images in crash log') 859061da546Spatrick return 860061da546Spatrick 861061da546Spatrick if options.dump_image_list: 862061da546Spatrick print("Binary Images:") 863061da546Spatrick for image in crash_log.images: 864061da546Spatrick if options.verbose: 865061da546Spatrick print(image.debug_dump()) 866061da546Spatrick else: 867061da546Spatrick print(image) 868061da546Spatrick 869061da546Spatrick target = crash_log.create_target() 870061da546Spatrick if not target: 871061da546Spatrick return 872061da546Spatrick exe_module = target.GetModuleAtIndex(0) 873061da546Spatrick images_to_load = list() 874061da546Spatrick loaded_images = list() 875061da546Spatrick if options.load_all_images: 876061da546Spatrick # --load-all option was specified, load everything up 877061da546Spatrick for image in crash_log.images: 878061da546Spatrick images_to_load.append(image) 879061da546Spatrick else: 880061da546Spatrick # Only load the images found in stack frames for the crashed threads 881061da546Spatrick if options.crashed_only: 882061da546Spatrick for thread in crash_log.threads: 883061da546Spatrick if thread.did_crash(): 884061da546Spatrick for ident in thread.idents: 885061da546Spatrick images = crash_log.find_images_with_identifier(ident) 886061da546Spatrick if images: 887061da546Spatrick for image in images: 888061da546Spatrick images_to_load.append(image) 889061da546Spatrick else: 890061da546Spatrick print('error: can\'t find image for identifier "%s"' % ident) 891061da546Spatrick else: 892061da546Spatrick for ident in crash_log.idents: 893061da546Spatrick images = crash_log.find_images_with_identifier(ident) 894061da546Spatrick if images: 895061da546Spatrick for image in images: 896061da546Spatrick images_to_load.append(image) 897061da546Spatrick else: 898061da546Spatrick print('error: can\'t find image for identifier "%s"' % ident) 899061da546Spatrick 900061da546Spatrick for image in images_to_load: 901061da546Spatrick if image not in loaded_images: 902061da546Spatrick err = image.add_module(target) 903061da546Spatrick if err: 904061da546Spatrick print(err) 905061da546Spatrick else: 906061da546Spatrick # print 'loaded %s' % image 907061da546Spatrick loaded_images.append(image) 908061da546Spatrick 909061da546Spatrick if crash_log.backtraces: 910061da546Spatrick for thread in crash_log.backtraces: 911061da546Spatrick thread.dump_symbolicated(crash_log, options) 912061da546Spatrick print() 913061da546Spatrick 914061da546Spatrick for thread in crash_log.threads: 915061da546Spatrick thread.dump_symbolicated(crash_log, options) 916061da546Spatrick print() 917061da546Spatrick 918061da546Spatrick 919061da546Spatrickdef CreateSymbolicateCrashLogOptions( 920061da546Spatrick command_name, 921061da546Spatrick description, 922061da546Spatrick add_interactive_options): 923061da546Spatrick usage = "usage: %prog [options] <FILE> [FILE ...]" 924061da546Spatrick option_parser = optparse.OptionParser( 925061da546Spatrick description=description, prog='crashlog', usage=usage) 926061da546Spatrick option_parser.add_option( 927061da546Spatrick '--verbose', 928061da546Spatrick '-v', 929061da546Spatrick action='store_true', 930061da546Spatrick dest='verbose', 931061da546Spatrick help='display verbose debug info', 932061da546Spatrick default=False) 933061da546Spatrick option_parser.add_option( 934061da546Spatrick '--debug', 935061da546Spatrick '-g', 936061da546Spatrick action='store_true', 937061da546Spatrick dest='debug', 938061da546Spatrick help='display verbose debug logging', 939061da546Spatrick default=False) 940061da546Spatrick option_parser.add_option( 941061da546Spatrick '--load-all', 942061da546Spatrick '-a', 943061da546Spatrick action='store_true', 944061da546Spatrick dest='load_all_images', 945061da546Spatrick help='load all executable images, not just the images found in the crashed stack frames', 946061da546Spatrick default=False) 947061da546Spatrick option_parser.add_option( 948061da546Spatrick '--images', 949061da546Spatrick action='store_true', 950061da546Spatrick dest='dump_image_list', 951061da546Spatrick help='show image list', 952061da546Spatrick default=False) 953061da546Spatrick option_parser.add_option( 954061da546Spatrick '--debug-delay', 955061da546Spatrick type='int', 956061da546Spatrick dest='debug_delay', 957061da546Spatrick metavar='NSEC', 958061da546Spatrick help='pause for NSEC seconds for debugger', 959061da546Spatrick default=0) 960061da546Spatrick option_parser.add_option( 961061da546Spatrick '--crashed-only', 962061da546Spatrick '-c', 963061da546Spatrick action='store_true', 964061da546Spatrick dest='crashed_only', 965061da546Spatrick help='only symbolicate the crashed thread', 966061da546Spatrick default=False) 967061da546Spatrick option_parser.add_option( 968061da546Spatrick '--disasm-depth', 969061da546Spatrick '-d', 970061da546Spatrick type='int', 971061da546Spatrick dest='disassemble_depth', 972061da546Spatrick help='set the depth in stack frames that should be disassembled (default is 1)', 973061da546Spatrick default=1) 974061da546Spatrick option_parser.add_option( 975061da546Spatrick '--disasm-all', 976061da546Spatrick '-D', 977061da546Spatrick action='store_true', 978061da546Spatrick dest='disassemble_all_threads', 979061da546Spatrick help='enabled disassembly of frames on all threads (not just the crashed thread)', 980061da546Spatrick default=False) 981061da546Spatrick option_parser.add_option( 982061da546Spatrick '--disasm-before', 983061da546Spatrick '-B', 984061da546Spatrick type='int', 985061da546Spatrick dest='disassemble_before', 986061da546Spatrick help='the number of instructions to disassemble before the frame PC', 987061da546Spatrick default=4) 988061da546Spatrick option_parser.add_option( 989061da546Spatrick '--disasm-after', 990061da546Spatrick '-A', 991061da546Spatrick type='int', 992061da546Spatrick dest='disassemble_after', 993061da546Spatrick help='the number of instructions to disassemble after the frame PC', 994061da546Spatrick default=4) 995061da546Spatrick option_parser.add_option( 996061da546Spatrick '--source-context', 997061da546Spatrick '-C', 998061da546Spatrick type='int', 999061da546Spatrick metavar='NLINES', 1000061da546Spatrick dest='source_context', 1001061da546Spatrick help='show NLINES source lines of source context (default = 4)', 1002061da546Spatrick default=4) 1003061da546Spatrick option_parser.add_option( 1004061da546Spatrick '--source-frames', 1005061da546Spatrick type='int', 1006061da546Spatrick metavar='NFRAMES', 1007061da546Spatrick dest='source_frames', 1008061da546Spatrick help='show source for NFRAMES (default = 4)', 1009061da546Spatrick default=4) 1010061da546Spatrick option_parser.add_option( 1011061da546Spatrick '--source-all', 1012061da546Spatrick action='store_true', 1013061da546Spatrick dest='source_all', 1014061da546Spatrick help='show source for all threads, not just the crashed thread', 1015061da546Spatrick default=False) 1016061da546Spatrick if add_interactive_options: 1017061da546Spatrick option_parser.add_option( 1018061da546Spatrick '-i', 1019061da546Spatrick '--interactive', 1020061da546Spatrick action='store_true', 1021061da546Spatrick help='parse all crash logs and enter interactive mode', 1022061da546Spatrick default=False) 1023061da546Spatrick return option_parser 1024061da546Spatrick 1025061da546Spatrick 1026061da546Spatrickdef SymbolicateCrashLogs(command_args): 1027061da546Spatrick description = '''Symbolicate one or more darwin crash log files to provide source file and line information, 1028061da546Spatrickinlined stack frames back to the concrete functions, and disassemble the location of the crash 1029061da546Spatrickfor the first frame of the crashed thread. 1030061da546SpatrickIf this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter 1031061da546Spatrickfor use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been 1032061da546Spatrickcreated that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows 1033061da546Spatrickyou to explore the program as if it were stopped at the locations described in the crash log and functions can 1034061da546Spatrickbe disassembled and lookups can be performed using the addresses found in the crash log.''' 1035061da546Spatrick option_parser = CreateSymbolicateCrashLogOptions( 1036061da546Spatrick 'crashlog', description, True) 1037061da546Spatrick try: 1038061da546Spatrick (options, args) = option_parser.parse_args(command_args) 1039061da546Spatrick except: 1040061da546Spatrick return 1041061da546Spatrick 1042061da546Spatrick if options.debug: 1043061da546Spatrick print('command_args = %s' % command_args) 1044061da546Spatrick print('options', options) 1045061da546Spatrick print('args', args) 1046061da546Spatrick 1047061da546Spatrick if options.debug_delay > 0: 1048061da546Spatrick print("Waiting %u seconds for debugger to attach..." % options.debug_delay) 1049061da546Spatrick time.sleep(options.debug_delay) 1050061da546Spatrick error = lldb.SBError() 1051061da546Spatrick 1052061da546Spatrick if args: 1053061da546Spatrick if options.interactive: 1054061da546Spatrick interactive_crashlogs(options, args) 1055061da546Spatrick else: 1056061da546Spatrick for crash_log_file in args: 1057061da546Spatrick crash_log = CrashLog(crash_log_file, options.verbose) 1058061da546Spatrick SymbolicateCrashLog(crash_log, options) 1059061da546Spatrickif __name__ == '__main__': 1060061da546Spatrick # Create a new debugger instance 1061061da546Spatrick lldb.debugger = lldb.SBDebugger.Create() 1062061da546Spatrick SymbolicateCrashLogs(sys.argv[1:]) 1063061da546Spatrick lldb.SBDebugger.Destroy(lldb.debugger) 1064061da546Spatrickelif getattr(lldb, 'debugger', None): 1065061da546Spatrick lldb.debugger.HandleCommand( 1066061da546Spatrick 'command script add -f lldb.macosx.crashlog.Symbolicate crashlog') 1067061da546Spatrick lldb.debugger.HandleCommand( 1068061da546Spatrick 'command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog') 1069061da546Spatrick print('"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help') 1070