Index: lldb/examples/python/crashlog.py =================================================================== --- lldb/examples/python/crashlog.py +++ lldb/examples/python/crashlog.py @@ -41,6 +41,7 @@ import sys import time import uuid +import json try: # First try for LLDB in case PYTHONPATH is already correctly setup. @@ -378,6 +379,129 @@ return self.target +class CrashLogFormatException(Exception): + pass + + +class CrashLogParser: + def parse(self, debugger, path, verbose): + try: + return JSONCrashLogParser(debugger, path, verbose).parse() + except CrashLogFormatException: + return TextCrashLogParser(debugger, path, verbose).parse() + + +class JSONCrashLogParser: + def __init__(self, debugger, path, verbose): + self.path = os.path.expanduser(path) + self.verbose = verbose + self.crashlog = CrashLog(debugger, self.path, self.verbose) + + def parse(self): + with open(self.path, 'r') as f: + buffer = f.read() + + # First line is meta-data. + buffer = buffer[buffer.index('\n') + 1:] + + try: + self.data = json.loads(buffer) + except ValueError: + raise CrashLogFormatException() + + self.parse_process_info(self.data) + self.parse_images(self.data['usedImages']) + self.parse_threads(self.data['threads']) + + thread = self.crashlog.threads[self.crashlog.crashed_thread_idx] + thread.reason = self.parse_crash_reason(self.data['exception']) + thread.registers = self.parse_thread_registers(self.data['threadState']) + + return self.crashlog + + def get_image_extra_info(self, idx): + return self.data['legacyInfo']['imageExtraInfo'][idx] + + def get_used_image(self, idx): + return self.data['usedImages'][idx] + + def parse_process_info(self, json_data): + self.crashlog.process_id = json_data['pid'] + self.crashlog.process_identifier = json_data['procName'] + self.crashlog.process_path = json_data['procPath'] + + def parse_crash_reason(self, json_exception): + exception_type = json_exception['type'] + exception_signal = json_exception['signal'] + if 'codes' in json_exception: + exception_extra = " ({})".format(json_exception['codes']) + elif 'subtype' in json_exception: + exception_extra = " ({})".format(json_exception['subtype']) + else: + exception_extra = "" + return "{} ({}){}".format(exception_type, exception_signal, + exception_extra) + + def parse_images(self, json_images): + idx = 0 + for json_images in json_images: + img_uuid = uuid.UUID(json_images[0]) + low = int(json_images[1]) + high = 0 + extra_info = self.get_image_extra_info(idx) + name = extra_info['name'] + path = extra_info['path'] + version = "" + darwin_image = self.crashlog.DarwinImage(low, high, name, version, + img_uuid, path, + self.verbose) + self.crashlog.images.append(darwin_image) + idx += 1 + + def parse_frames(self, thread, json_frames): + idx = 0 + for json_frame in json_frames: + image_id = int(json_frame[0]) + + ident = self.get_image_extra_info(image_id)['name'] + thread.add_ident(ident) + if ident not in self.crashlog.idents: + self.crashlog.idents.append(ident) + + frame_offset = int(json_frame[1]) + image = self.get_used_image(image_id) + image_addr = int(image[1]) + pc = image_addr + frame_offset + thread.frames.append(self.crashlog.Frame(idx, pc, frame_offset)) + idx += 1 + + def parse_threads(self, json_threads): + idx = 0 + for json_thread in json_threads: + thread = self.crashlog.Thread(idx, False) + if json_thread.get('triggered', False): + self.crashlog.crashed_thread_idx = idx + thread.queue = json_thread.get('queue') + self.parse_frames(thread, json_thread.get('frames', [])) + self.crashlog.threads.append(thread) + idx += 1 + + def parse_thread_registers(self, json_thread_state): + idx = 0 + registers = dict() + for reg in json_thread_state.get('x', []): + key = str('x{}'.format(idx)) + value = int(reg) + registers[key] = value + idx += 1 + + for register in ['lr', 'cpsr', 'fp', 'sp', 'esr', 'pc']: + if register in json_thread_state: + registers[register] = int(json_thread_state[register]) + + return registers + + class CrashLogParseMode: NORMAL = 0 THREAD = 1 @@ -387,7 +511,7 @@ INSTRS = 5 -class CrashLogParser: +class TextCrashLogParser: 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') @@ -720,7 +844,7 @@ crash_logs = list() for crash_log_file in crash_log_files: try: - crash_log = CrashLogParser(debugger, crash_log_file, options.verbose).parse() + crash_log = CrashLogParser().parse(debugger, crash_log_file, options.verbose) except Exception as e: print(e) continue @@ -1055,8 +1179,7 @@ interactive_crashlogs(debugger, options, args) else: for crash_log_file in args: - crash_log_parser = CrashLogParser(debugger, crash_log_file, options.verbose) - crash_log = crash_log_parser.parse() + crash_log = CrashLogParser().parse(debugger, crash_log_file, options.verbose) SymbolicateCrashLog(crash_log, options) if __name__ == '__main__': # Create a new debugger instance Index: lldb/test/Shell/ScriptInterpreter/Python/Crashlog/Inputs/Assertion.check =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Python/Crashlog/Inputs/Assertion.check @@ -0,0 +1 @@ +# CHECK-NOT: AssertionError Index: lldb/test/Shell/ScriptInterpreter/Python/Crashlog/crashlog.test =================================================================== --- lldb/test/Shell/ScriptInterpreter/Python/Crashlog/crashlog.test +++ lldb/test/Shell/ScriptInterpreter/Python/Crashlog/crashlog.test @@ -4,7 +4,7 @@ # CHECK-LABEL: {{S}}KIP BEYOND CHECKS script import crashlog -crash_log_parser = crashlog.CrashLogParser +crash_log_parser = crashlog.TextCrashLogParser 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", Index: lldb/test/Shell/ScriptInterpreter/Python/Crashlog/json_parser.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Python/Crashlog/json_parser.test @@ -0,0 +1,45 @@ +# -*- python -*- +# RUN: cd %S/../../../../../examples/python && cat %s | %lldb 2>&1 > %t.out +# RUN: cat %t.out | FileCheck %S/Inputs/Assertion.check +script +import crashlog +import json + +parser = crashlog.JSONCrashLogParser("", "", False) + +process_info_json = json.loads('{"pid" : 287, "procName" : "mediaserverd", "procPath" : "\/usr\/sbin\/mediaserverd"}') +parser.parse_process_info(process_info_json) + +assert parser.crashlog.process_id == 287 +assert parser.crashlog.process_identifier == "mediaserverd" +assert parser.crashlog.process_path == "/usr/sbin/mediaserverd" + +crash_reason_json = json.loads('{"type" : "EXC_BAD_ACCESS", "signal" : "SIGSEGV", "subtype" : "KERN_INVALID_ADDRESS"}') +assert parser.parse_crash_reason(crash_reason_json) == "EXC_BAD_ACCESS (SIGSEGV) (KERN_INVALID_ADDRESS)" + +crash_reason_json = json.loads('{"type" : "EXC_BAD_ACCESS", "signal" : "SIGSEGV"}') +assert parser.parse_crash_reason(crash_reason_json) == "EXC_BAD_ACCESS (SIGSEGV)" + +crash_reason_json = json.loads('{"type" : "EXC_BAD_ACCESS", "signal" : "SIGSEGV", "codes" : "0x0000000000000000, 0x0000000000000000"}') +assert parser.parse_crash_reason(crash_reason_json) == "EXC_BAD_ACCESS (SIGSEGV) (0x0000000000000000, 0x0000000000000000)" + +thread_state_json = json.loads('{"x":[268451845,117442566],"lr":7309751904,"cpsr":1073741824,"fp":6093236784,"sp":6093236704,"esr":1442840704,"pc":7309755088}') +registers = parser.parse_thread_registers(thread_state_json) +assert registers['x0'] == 268451845 +assert registers['x1'] == 117442566 +assert registers['lr'] == 7309751904 +assert registers['cpsr'] ==1073741824 +assert registers['fp'] == 6093236784 +assert registers['sp'] == 6093236704 +assert registers['esr'] == 1442840704 +assert registers['pc'] == 7309755088 + +parser.data = json.loads('{"usedImages":[["f4d85377-f215-3da3-921e-3fe870e622e9",7309737984,"P"]],"legacyInfo":{"imageExtraInfo":[{"size":204800,"arch":"arm64e","path":"/usr/lib/system/libsystem_kernel.dylib","name":"libsystem_kernel.dylib"}]}}') +thread_json = json.loads('[{"triggered":true,"id":3835,"queue":"com.apple.bwgraph.devicevendor","frames":[[0,101472],[0,408892]]}]') +parser.parse_threads(thread_json) +assert len(parser.crashlog.threads) == 1 +assert parser.crashlog.threads[0].queue == "com.apple.bwgraph.devicevendor" +assert len(parser.crashlog.threads[0].frames) == 2 +assert parser.crashlog.threads[0].frames[0].pc == 7309839456 +assert parser.crashlog.threads[0].frames[0].description == 101472 +exit()