diff --git a/lldb/examples/python/crashlog.py b/lldb/examples/python/crashlog.py --- a/lldb/examples/python/crashlog.py +++ b/lldb/examples/python/crashlog.py @@ -71,41 +71,7 @@ else: return plistlib.readPlistFromString(s) -class CrashLogParseMode: - NORMAL = 0 - THREAD = 1 - IMAGES = 2 - THREGS = 3 - SYSTEM = 4 - INSTRS = 5 - - class CrashLog(symbolication.Symbolicator): - """Class that does parses darwin crash logs""" - parent_process_regex = re.compile('^Parent Process:\s*(.*)\[(\d+)\]') - thread_state_regex = re.compile('^Thread ([0-9]+) crashed with') - thread_instrs_regex = re.compile('^Thread ([0-9]+) instruction stream') - thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)') - app_backtrace_regex = re.compile( - '^Application Specific Backtrace ([0-9]+)([^:]*):(.*)') - version = r'(\(.+\)|(arm|x86_)[0-9a-z]+)\s+' - frame_regex = re.compile(r'^([0-9]+)' r'\s' # id - r'+(.+?)' r'\s+' # img_name - r'(' +version+ r')?' # img_version - r'(0x[0-9a-fA-F]{7}[0-9a-fA-F]+)' # addr - r' +(.*)' # offs - ) - null_frame_regex = re.compile(r'^([0-9]+)\s+\?\?\?\s+(0{7}0+) +(.*)') - image_regex_uuid = re.compile(r'(0x[0-9a-fA-F]+)' # img_lo - r'\s+' '-' r'\s+' # - - r'(0x[0-9a-fA-F]+)' r'\s+' # img_hi - r'[+]?(.+?)' r'\s+' # img_name - r'(' +version+ ')?' # img_version - r'(<([-0-9a-fA-F]+)>\s+)?' # img_uuid - r'(/.*)' # img_path - ) - empty_line_regex = re.compile('^$') - class Thread: """Class that represents a thread in a darwin crash log""" @@ -355,88 +321,174 @@ self.idents = list() # A list of the required identifiers for doing all stack backtraces self.crashed_thread_idx = -1 self.version = -1 - self.error = None self.target = None self.verbose = verbose - # With possible initial component of ~ or ~user replaced by that user's - # home directory. - try: - f = open(self.path) - except IOError: - self.error = 'error: cannot open "%s"' % self.path - return - self.file_lines = f.read().splitlines() - parse_mode = CrashLogParseMode.NORMAL - thread = None - app_specific_backtrace = False - for line in self.file_lines: - # print line + def dump(self): + print("Crash Log File: %s" % (self.path)) + if self.backtraces: + print("\nApplication Specific Backtraces:") + for thread in self.backtraces: + thread.dump(' ') + print("\nThreads:") + for thread in self.threads: + thread.dump(' ') + print("\nImages:") + for image in self.images: + image.dump(' ') + + def find_image_with_identifier(self, identifier): + for image in self.images: + if image.identifier == identifier: + return image + regex_text = '^.*\.%s$' % (re.escape(identifier)) + regex = re.compile(regex_text) + for image in self.images: + if regex.match(image.identifier): + return image + return None + + def create_target(self): + if self.target is None: + self.target = symbolication.Symbolicator.create_target(self) + if self.target: + return self.target + # We weren't able to open the main executable as, but we can still + # symbolicate + print('crashlog.create_target()...2') + if self.idents: + for ident in self.idents: + image = self.find_image_with_identifier(ident) + if image: + self.target = image.create_target() + if self.target: + return self.target # success + print('crashlog.create_target()...3') + for image in self.images: + self.target = image.create_target() + if self.target: + return self.target # success + print('crashlog.create_target()...4') + print('error: Unable to locate any executables from the crash log.') + print(' Try loading the executable into lldb before running crashlog') + print(' and/or make sure the .dSYM bundles can be found by Spotlight.') + return self.target + + def get_target(self): + return self.target + + +class CrashLogParseMode: + NORMAL = 0 + THREAD = 1 + IMAGES = 2 + THREGS = 3 + SYSTEM = 4 + INSTRS = 5 + + +class CrashLogParser: + parent_process_regex = re.compile('^Parent Process:\s*(.*)\[(\d+)\]') + thread_state_regex = re.compile('^Thread ([0-9]+) crashed with') + thread_instrs_regex = re.compile('^Thread ([0-9]+) instruction stream') + thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)') + app_backtrace_regex = re.compile('^Application Specific Backtrace ([0-9]+)([^:]*):(.*)') + version = r'(\(.+\)|(arm|x86_)[0-9a-z]+)\s+' + frame_regex = re.compile(r'^([0-9]+)' r'\s' # id + r'+(.+?)' r'\s+' # img_name + r'(' +version+ r')?' # img_version + r'(0x[0-9a-fA-F]{7}[0-9a-fA-F]+)' # addr + r' +(.*)' # offs + ) + null_frame_regex = re.compile(r'^([0-9]+)\s+\?\?\?\s+(0{7}0+) +(.*)') + image_regex_uuid = re.compile(r'(0x[0-9a-fA-F]+)' # img_lo + r'\s+' '-' r'\s+' # - + r'(0x[0-9a-fA-F]+)' r'\s+' # img_hi + r'[+]?(.+?)' r'\s+' # img_name + r'(' +version+ ')?' # img_version + r'(<([-0-9a-fA-F]+)>\s+)?' # img_uuid + r'(/.*)' # img_path + ) + + + def __init__(self, path, verbose): + self.path = os.path.expanduser(path) + self.verbose = verbose + self.parse_mode = CrashLogParseMode.NORMAL + self.thread = None + self.app_specific_backtrace = False + self.crashlog = CrashLog(self.path, self.verbose) + + def parse(self): + with open(self.path,'r') as f: + lines = f.read().splitlines() + + for line in lines: line_len = len(line) if line_len == 0: - if thread: - if parse_mode == CrashLogParseMode.THREAD: - if thread.index == self.crashed_thread_idx: - thread.reason = '' - if self.thread_exception: - thread.reason += self.thread_exception - if self.thread_exception_data: - thread.reason += " (%s)" % self.thread_exception_data - if app_specific_backtrace: - self.backtraces.append(thread) + if self.thread: + if self.parse_mode == CrashLogParseMode.THREAD: + if self.thread.index == self.crashlog.crashed_thread_idx: + self.thread.reason = '' + if self.crashlog.thread_exception: + self.thread.reason += self.crashlog.thread_exception + if self.crashlog.thread_exception_data: + self.thread.reason += " (%s)" % self.crashlog.thread_exception_data + if self.app_specific_backtrace: + self.crashlog.backtraces.append(self.thread) else: - self.threads.append(thread) - thread = None + self.crashlog.threads.append(self.thread) + self.thread = None else: # only append an extra empty line if the previous line # in the info_lines wasn't empty - if len(self.info_lines) > 0 and len(self.info_lines[-1]): - self.info_lines.append(line) - parse_mode = CrashLogParseMode.NORMAL - elif parse_mode == CrashLogParseMode.NORMAL: + if len(self.crashlog.info_lines) > 0 and len(self.crashlog.info_lines[-1]): + self.crashlog.info_lines.append(line) + self.parse_mode = CrashLogParseMode.NORMAL + elif self.parse_mode == CrashLogParseMode.NORMAL: if line.startswith('Process:'): - (self.process_name, pid_with_brackets) = line[ + (self.crashlog.process_name, pid_with_brackets) = line[ 8:].strip().split(' [') - self.process_id = pid_with_brackets.strip('[]') + self.crashlog.process_id = pid_with_brackets.strip('[]') elif line.startswith('Path:'): - self.process_path = line[5:].strip() + self.crashlog.process_path = line[5:].strip() elif line.startswith('Identifier:'): - self.process_identifier = line[11:].strip() + self.crashlog.process_identifier = line[11:].strip() elif line.startswith('Version:'): version_string = line[8:].strip() matched_pair = re.search("(.+)\((.+)\)", version_string) if matched_pair: - self.process_version = matched_pair.group(1) - self.process_compatability_version = matched_pair.group( + self.crashlog.process_version = matched_pair.group(1) + self.crashlog.process_compatability_version = matched_pair.group( 2) else: - self.process = version_string - self.process_compatability_version = version_string + self.crashlog.process = version_string + self.crashlog.process_compatability_version = version_string elif self.parent_process_regex.search(line): parent_process_match = self.parent_process_regex.search( line) - self.parent_process_name = parent_process_match.group(1) - self.parent_process_id = parent_process_match.group(2) + self.crashlog.parent_process_name = parent_process_match.group(1) + self.crashlog.parent_process_id = parent_process_match.group(2) elif line.startswith('Exception Type:'): - self.thread_exception = line[15:].strip() + self.crashlog.thread_exception = line[15:].strip() continue elif line.startswith('Exception Codes:'): - self.thread_exception_data = line[16:].strip() + self.crashlog.thread_exception_data = line[16:].strip() continue elif line.startswith('Exception Subtype:'): # iOS - self.thread_exception_data = line[18:].strip() + self.crashlog.thread_exception_data = line[18:].strip() continue elif line.startswith('Crashed Thread:'): - self.crashed_thread_idx = int(line[15:].strip().split()[0]) + self.crashlog.crashed_thread_idx = int(line[15:].strip().split()[0]) continue elif line.startswith('Triggered by Thread:'): # iOS - self.crashed_thread_idx = int(line[20:].strip().split()[0]) + self.crashlog.crashed_thread_idx = int(line[20:].strip().split()[0]) continue elif line.startswith('Report Version:'): - self.version = int(line[15:].strip()) + self.crashlog.version = int(line[15:].strip()) continue elif line.startswith('System Profile:'): - parse_mode = CrashLogParseMode.SYSTEM + self.parse_mode = CrashLogParseMode.SYSTEM continue elif (line.startswith('Interval Since Last Report:') or line.startswith('Crashes Since Last Report:') or @@ -449,41 +501,41 @@ elif line.startswith('Thread'): thread_state_match = self.thread_state_regex.search(line) if thread_state_match: - app_specific_backtrace = False + self.app_specific_backtrace = False thread_state_match = self.thread_regex.search(line) thread_idx = int(thread_state_match.group(1)) - parse_mode = CrashLogParseMode.THREGS - thread = self.threads[thread_idx] + self.parse_mode = CrashLogParseMode.THREGS + self.thread = self.crashlog.threads[thread_idx] continue thread_insts_match = self.thread_instrs_regex.search(line) if thread_insts_match: - parse_mode = CrashLogParseMode.INSTRS + self.parse_mode = CrashLogParseMode.INSTRS continue thread_match = self.thread_regex.search(line) if thread_match: - app_specific_backtrace = False - parse_mode = CrashLogParseMode.THREAD + self.app_specific_backtrace = False + self.parse_mode = CrashLogParseMode.THREAD thread_idx = int(thread_match.group(1)) - thread = CrashLog.Thread(thread_idx, False) + self.thread = self.crashlog.Thread(thread_idx, False) continue continue elif line.startswith('Binary Images:'): - parse_mode = CrashLogParseMode.IMAGES + self.parse_mode = CrashLogParseMode.IMAGES continue elif line.startswith('Application Specific Backtrace'): app_backtrace_match = self.app_backtrace_regex.search(line) if app_backtrace_match: - parse_mode = CrashLogParseMode.THREAD - app_specific_backtrace = True + self.parse_mode = CrashLogParseMode.THREAD + self.app_specific_backtrace = True idx = int(app_backtrace_match.group(1)) - thread = CrashLog.Thread(idx, True) + self.thread = self.crashlog.Thread(idx, True) elif line.startswith('Last Exception Backtrace:'): # iOS - parse_mode = CrashLogParseMode.THREAD - app_specific_backtrace = True + self.parse_mode = CrashLogParseMode.THREAD + self.app_specific_backtrace = True idx = 1 - thread = CrashLog.Thread(idx, True) - self.info_lines.append(line.strip()) - elif parse_mode == CrashLogParseMode.THREAD: + self.thread = self.crashlog.Thread(idx, True) + self.crashlog.info_lines.append(line.strip()) + elif self.parse_mode == CrashLogParseMode.THREAD: if line.startswith('Thread'): continue if self.null_frame_regex.search(line): @@ -494,94 +546,43 @@ (frame_id, frame_img_name, _, frame_img_version, _, frame_addr, frame_ofs) = frame_match.groups() ident = frame_img_name - thread.add_ident(ident) - if ident not in self.idents: - self.idents.append(ident) - thread.frames.append(CrashLog.Frame(int(frame_id), int( + self.thread.add_ident(ident) + if ident not in self.crashlog.idents: + self.crashlog.idents.append(ident) + self.thread.frames.append(self.crashlog.Frame(int(frame_id), int( frame_addr, 0), frame_ofs)) else: print('error: frame regex failed for line: "%s"' % line) - elif parse_mode == CrashLogParseMode.IMAGES: + elif self.parse_mode == CrashLogParseMode.IMAGES: image_match = self.image_regex_uuid.search(line) if image_match: (img_lo, img_hi, img_name, _, img_version, _, _, img_uuid, img_path) = image_match.groups() - image = CrashLog.DarwinImage(int(img_lo, 0), int(img_hi, 0), + image = self.crashlog.DarwinImage(int(img_lo, 0), int(img_hi, 0), img_name.strip(), img_version.strip() if img_version else "", uuid.UUID(img_uuid), img_path, self.verbose) - self.images.append(image) + self.crashlog.images.append(image) else: print("error: image regex failed for: %s" % line) - elif parse_mode == CrashLogParseMode.THREGS: + elif self.parse_mode == CrashLogParseMode.THREGS: stripped_line = line.strip() # "r12: 0x00007fff6b5939c8 r13: 0x0000000007000006 r14: 0x0000000000002a03 r15: 0x0000000000000c00" reg_values = re.findall( '([a-zA-Z0-9]+: 0[Xx][0-9a-fA-F]+) *', stripped_line) for reg_value in reg_values: (reg, value) = reg_value.split(': ') - thread.registers[reg.strip()] = int(value, 0) - elif parse_mode == CrashLogParseMode.SYSTEM: - self.system_profile.append(line) - elif parse_mode == CrashLogParseMode.INSTRS: + self.thread.registers[reg.strip()] = int(value, 0) + elif self.parse_mode == CrashLogParseMode.SYSTEM: + self.crashlog.system_profile.append(line) + elif self.parse_mode == CrashLogParseMode.INSTRS: pass - f.close() - def dump(self): - print("Crash Log File: %s" % (self.path)) - if self.backtraces: - print("\nApplication Specific Backtraces:") - for thread in self.backtraces: - thread.dump(' ') - print("\nThreads:") - for thread in self.threads: - thread.dump(' ') - print("\nImages:") - for image in self.images: - image.dump(' ') + return self.crashlog - def find_image_with_identifier(self, identifier): - for image in self.images: - if image.identifier == identifier: - return image - regex_text = '^.*\.%s$' % (re.escape(identifier)) - regex = re.compile(regex_text) - for image in self.images: - if regex.match(image.identifier): - return image - return None - - def create_target(self): - if self.target is None: - self.target = symbolication.Symbolicator.create_target(self) - if self.target: - return self.target - # We weren't able to open the main executable as, but we can still - # symbolicate - print('crashlog.create_target()...2') - if self.idents: - for ident in self.idents: - image = self.find_image_with_identifier(ident) - if image: - self.target = image.create_target() - if self.target: - return self.target # success - print('crashlog.create_target()...3') - for image in self.images: - self.target = image.create_target() - if self.target: - return self.target # success - print('crashlog.create_target()...4') - print('error: Unable to locate any executables from the crash log.') - print(' Try loading the executable into lldb before running crashlog') - print(' and/or make sure the .dSYM bundles can be found by Spotlight.') - return self.target - - def get_target(self): - return self.target def usage(): @@ -702,9 +703,10 @@ crash_logs = list() for crash_log_file in crash_log_files: - crash_log = CrashLog(crash_log_file, options.verbose) - if crash_log.error: - print(crash_log.error) + try: + crash_log = CrashLogParser(crash_log_file, options.verbose).parse() + except Exception as e: + print(e) continue if options.debug: crash_log.dump() @@ -836,9 +838,6 @@ def SymbolicateCrashLog(crash_log, options): - if crash_log.error: - print(crash_log.error) - return if options.debug: crash_log.dump() if not crash_log.images: @@ -1040,7 +1039,8 @@ interactive_crashlogs(options, args) else: for crash_log_file in args: - crash_log = CrashLog(crash_log_file, options.verbose) + crash_log_parser = CrashLogParser(crash_log_file, options.verbose) + crash_log = crash_log_parser.parse() SymbolicateCrashLog(crash_log, options) if __name__ == '__main__': # Create a new debugger instance @@ -1052,4 +1052,3 @@ 'command script add -f lldb.macosx.crashlog.Symbolicate crashlog') lldb.debugger.HandleCommand( 'command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog') - print('"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help') diff --git a/lldb/test/Shell/ScriptInterpreter/Python/crashlog.test b/lldb/test/Shell/ScriptInterpreter/Python/Crashlog/crashlog.test rename from lldb/test/Shell/ScriptInterpreter/Python/crashlog.test rename to lldb/test/Shell/ScriptInterpreter/Python/Crashlog/crashlog.test --- a/lldb/test/Shell/ScriptInterpreter/Python/crashlog.test +++ b/lldb/test/Shell/ScriptInterpreter/Python/Crashlog/crashlog.test @@ -1,12 +1,11 @@ # -*- python -*- -# REQUIRES: system-darwin -# UNSUPPORTED: lldb-repro -# DEBUG: cd %S/../../../../examples/python && cat %s | %lldb && false -# RUN: cd %S/../../../../examples/python && cat %s | %lldb | FileCheck %s +# DEBUG: cd %S/../../../../../examples/python && cat %s | %lldb && false +# RUN: cd %S/../../../../../examples/python && cat %s | %lldb | FileCheck %s # CHECK-LABEL: {{S}}KIP BEYOND CHECKS script import crashlog -cl = crashlog.CrashLog +crash_log_parser = crashlog.CrashLogParser +crash_log = crashlog.CrashLog images = [ "0x10b60b000 - 0x10f707fff com.apple.LLDB.framework (1.1000.11.38.2 - 1000.11.38.2) <96E36F5C-1A83-39A1-8713-5FDD9701C3F1> /Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/LLDB", # CHECK: 0x10b60b000 @@ -120,7 +119,7 @@ for image in images: print('"%s"'%image) print("--------------") - match = cl.image_regex_uuid.search(image) + match = crash_log_parser.image_regex_uuid.search(image) for group in match.groups(): print(group) @@ -128,7 +127,7 @@ for frame in frames: print('"%s"'%frame) print("--------------") - match = cl.frame_regex.search(frame) + match = crash_log_parser.frame_regex.search(frame) for group in match.groups(): print(group) diff --git a/lldb/test/Shell/ScriptInterpreter/Python/Crashlog/interactive.test b/lldb/test/Shell/ScriptInterpreter/Python/Crashlog/interactive.test new file mode 100644 --- /dev/null +++ b/lldb/test/Shell/ScriptInterpreter/Python/Crashlog/interactive.test @@ -0,0 +1,8 @@ +# RUN: echo "quit" | %S/../../../../../examples/python/crashlog.py -i %s 2>&1 | FileCheck %s +# CHECK: 1 crash logs are loaded: +# CHECK: [0] = {{.*}}interactive.test +# CHECK: Interactive crashlogs prompt, type "help" to see a list of supported commands. + +Binary Images: + 0x10ab87000 - 0x10abdafff +lldb (10.0.0) <87BD1384-BAE9-3625-A838-9D241CBAEF87> /Volumes/VOLUME/*/lldb + 0x10ac45000 - 0x10ae94fff com.apple.python3 (3.8.2 - 3.8.2) <20BC3FC4-CAAD-3002-ACDF-423A3188F24C> /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/Python3 diff --git a/lldb/test/Shell/ScriptInterpreter/Python/Crashlog/lit.local.cfg b/lldb/test/Shell/ScriptInterpreter/Python/Crashlog/lit.local.cfg new file mode 100644 --- /dev/null +++ b/lldb/test/Shell/ScriptInterpreter/Python/Crashlog/lit.local.cfg @@ -0,0 +1,5 @@ +if 'system-darwin' not in config.available_features: + config.unsupported = True + +if 'lldb-repro' in config.available_features: + config.unsupported = True