JDevlieghere created this revision.
JDevlieghere added reviewers: aprantl, jasonmolenda, clayborg.
JDevlieghere requested review of this revision.

Add a parser for JSON crashlogs. The `CrashLogParser` now defers to either the 
`JSONCrashLogParser` or the `TextCrashLogParser`. It first tries to interpret 
the input as JSON, and if that fails falling back to the textual parser.


https://reviews.llvm.org/D91130

Files:
  lldb/examples/python/crashlog.py
  lldb/test/Shell/ScriptInterpreter/Python/Crashlog/Inputs/Assertion.check
  lldb/test/Shell/ScriptInterpreter/Python/Crashlog/crashlog.test
  lldb/test/Shell/ScriptInterpreter/Python/Crashlog/json_parser.test

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()
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/Inputs/Assertion.check
===================================================================
--- /dev/null
+++ lldb/test/Shell/ScriptInterpreter/Python/Crashlog/Inputs/Assertion.check
@@ -0,0 +1 @@
+# CHECK-NOT: AssertionError
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
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to