xref: /llvm-project/lldb/examples/python/crashlog_scripted_process.py (revision 86dddbe3b54eae22db6e208e6bc1c3cda9b7e149)
16bff2d51SMed Ismail Bennaniimport os, json, struct, signal, uuid, tempfile
26bff2d51SMed Ismail Bennani
36bff2d51SMed Ismail Bennanifrom typing import Any, Dict
46bff2d51SMed Ismail Bennani
56bff2d51SMed Ismail Bennaniimport lldb
66bff2d51SMed Ismail Bennanifrom lldb.plugins.scripted_process import ScriptedProcess
76bff2d51SMed Ismail Bennanifrom lldb.plugins.scripted_process import ScriptedThread
86bff2d51SMed Ismail Bennani
96bff2d51SMed Ismail Bennanifrom lldb.macosx.crashlog import CrashLog, CrashLogParser
106bff2d51SMed Ismail Bennani
116bff2d51SMed Ismail Bennani
126bff2d51SMed Ismail Bennaniclass CrashLogScriptedProcess(ScriptedProcess):
136bff2d51SMed Ismail Bennani    def set_crashlog(self, crashlog):
146bff2d51SMed Ismail Bennani        self.crashlog = crashlog
156bff2d51SMed Ismail Bennani        if self.crashlog.process_id:
166bff2d51SMed Ismail Bennani            if type(self.crashlog.process_id) is int:
176bff2d51SMed Ismail Bennani                self.pid = self.crashlog.process_id
186bff2d51SMed Ismail Bennani            elif type(self.crashlog.process_id) is str:
196bff2d51SMed Ismail Bennani                self.pid = int(self.crashlog.process_id, 0)
206bff2d51SMed Ismail Bennani            else:
216bff2d51SMed Ismail Bennani                self.pid = super().get_process_id()
226bff2d51SMed Ismail Bennani        self.addr_mask = self.crashlog.addr_mask
236bff2d51SMed Ismail Bennani        self.crashed_thread_idx = self.crashlog.crashed_thread_idx
246bff2d51SMed Ismail Bennani        self.loaded_images = []
256bff2d51SMed Ismail Bennani        self.exception = self.crashlog.exception
266bff2d51SMed Ismail Bennani        self.app_specific_thread = None
276bff2d51SMed Ismail Bennani        if hasattr(self.crashlog, "asi"):
286bff2d51SMed Ismail Bennani            self.metadata["asi"] = self.crashlog.asi
296bff2d51SMed Ismail Bennani        if hasattr(self.crashlog, "asb"):
306bff2d51SMed Ismail Bennani            self.extended_thread_info = self.crashlog.asb
316bff2d51SMed Ismail Bennani
32785b143aSMed Ismail Bennani        crashlog.load_images(self.options, self.loaded_images)
336bff2d51SMed Ismail Bennani
346bff2d51SMed Ismail Bennani        for thread in self.crashlog.threads:
356bff2d51SMed Ismail Bennani            if (
366bff2d51SMed Ismail Bennani                hasattr(thread, "app_specific_backtrace")
376bff2d51SMed Ismail Bennani                and thread.app_specific_backtrace
386bff2d51SMed Ismail Bennani            ):
396bff2d51SMed Ismail Bennani                # We don't want to include the Application Specific Backtrace
406bff2d51SMed Ismail Bennani                # Thread into the Scripted Process' Thread list.
416bff2d51SMed Ismail Bennani                # Instead, we will try to extract the stackframe pcs from the
426bff2d51SMed Ismail Bennani                # backtrace and inject that as the extended thread info.
436bff2d51SMed Ismail Bennani                self.app_specific_thread = thread
446bff2d51SMed Ismail Bennani                continue
456bff2d51SMed Ismail Bennani
466bff2d51SMed Ismail Bennani            self.threads[thread.index] = CrashLogScriptedThread(self, None, thread)
476bff2d51SMed Ismail Bennani
486bff2d51SMed Ismail Bennani        if self.app_specific_thread:
496bff2d51SMed Ismail Bennani            self.extended_thread_info = CrashLogScriptedThread.resolve_stackframes(
506bff2d51SMed Ismail Bennani                self.app_specific_thread, self.addr_mask, self.target
516bff2d51SMed Ismail Bennani            )
526bff2d51SMed Ismail Bennani
53785b143aSMed Ismail Bennani    class CrashLogOptions:
54785b143aSMed Ismail Bennani        load_all_images = False
55785b143aSMed Ismail Bennani        crashed_only = True
56*68a9cb79SMed Ismail Bennani        no_parallel_image_loading = False
57785b143aSMed Ismail Bennani
586bff2d51SMed Ismail Bennani    def __init__(self, exe_ctx: lldb.SBExecutionContext, args: lldb.SBStructuredData):
596bff2d51SMed Ismail Bennani        super().__init__(exe_ctx, args)
606bff2d51SMed Ismail Bennani
616bff2d51SMed Ismail Bennani        if not self.target or not self.target.IsValid():
626bff2d51SMed Ismail Bennani            # Return error
636bff2d51SMed Ismail Bennani            return
646bff2d51SMed Ismail Bennani
656bff2d51SMed Ismail Bennani        self.crashlog_path = None
666bff2d51SMed Ismail Bennani
676bff2d51SMed Ismail Bennani        crashlog_path = args.GetValueForKey("file_path")
686bff2d51SMed Ismail Bennani        if crashlog_path and crashlog_path.IsValid():
696bff2d51SMed Ismail Bennani            if crashlog_path.GetType() == lldb.eStructuredDataTypeString:
706bff2d51SMed Ismail Bennani                self.crashlog_path = crashlog_path.GetStringValue(4096)
716bff2d51SMed Ismail Bennani
726bff2d51SMed Ismail Bennani        if not self.crashlog_path:
736bff2d51SMed Ismail Bennani            # Return error
746bff2d51SMed Ismail Bennani            return
756bff2d51SMed Ismail Bennani
76785b143aSMed Ismail Bennani        self.options = self.CrashLogOptions()
77785b143aSMed Ismail Bennani
786bff2d51SMed Ismail Bennani        load_all_images = args.GetValueForKey("load_all_images")
796bff2d51SMed Ismail Bennani        if load_all_images and load_all_images.IsValid():
806bff2d51SMed Ismail Bennani            if load_all_images.GetType() == lldb.eStructuredDataTypeBoolean:
81785b143aSMed Ismail Bennani                self.options.load_all_images = load_all_images.GetBooleanValue()
826bff2d51SMed Ismail Bennani
83785b143aSMed Ismail Bennani        crashed_only = args.GetValueForKey("crashed_only")
84785b143aSMed Ismail Bennani        if crashed_only and crashed_only.IsValid():
85785b143aSMed Ismail Bennani            if crashed_only.GetType() == lldb.eStructuredDataTypeBoolean:
86785b143aSMed Ismail Bennani                self.options.crashed_only = crashed_only.GetBooleanValue()
876bff2d51SMed Ismail Bennani
88*68a9cb79SMed Ismail Bennani        no_parallel_image_loading = args.GetValueForKey("no_parallel_image_loading")
89*68a9cb79SMed Ismail Bennani        if no_parallel_image_loading and no_parallel_image_loading.IsValid():
90*68a9cb79SMed Ismail Bennani            if no_parallel_image_loading.GetType() == lldb.eStructuredDataTypeBoolean:
91*68a9cb79SMed Ismail Bennani                self.options.no_parallel_image_loading = (
92*68a9cb79SMed Ismail Bennani                    no_parallel_image_loading.GetBooleanValue()
93*68a9cb79SMed Ismail Bennani                )
94*68a9cb79SMed Ismail Bennani
956bff2d51SMed Ismail Bennani        self.pid = super().get_process_id()
966bff2d51SMed Ismail Bennani        self.crashed_thread_idx = 0
976bff2d51SMed Ismail Bennani        self.exception = None
986bff2d51SMed Ismail Bennani        self.extended_thread_info = None
996bff2d51SMed Ismail Bennani
1006bff2d51SMed Ismail Bennani    def read_memory_at_address(
1016bff2d51SMed Ismail Bennani        self, addr: int, size: int, error: lldb.SBError
1026bff2d51SMed Ismail Bennani    ) -> lldb.SBData:
1036bff2d51SMed Ismail Bennani        # NOTE: CrashLogs don't contain any memory.
1046bff2d51SMed Ismail Bennani        return lldb.SBData()
1056bff2d51SMed Ismail Bennani
1066bff2d51SMed Ismail Bennani    def get_loaded_images(self):
1076bff2d51SMed Ismail Bennani        # TODO: Iterate over corefile_target modules and build a data structure
1086bff2d51SMed Ismail Bennani        # from it.
1096bff2d51SMed Ismail Bennani        return self.loaded_images
1106bff2d51SMed Ismail Bennani
1116bff2d51SMed Ismail Bennani    def should_stop(self) -> bool:
1126bff2d51SMed Ismail Bennani        return True
1136bff2d51SMed Ismail Bennani
1146bff2d51SMed Ismail Bennani    def is_alive(self) -> bool:
1156bff2d51SMed Ismail Bennani        return True
1166bff2d51SMed Ismail Bennani
1176bff2d51SMed Ismail Bennani    def get_scripted_thread_plugin(self):
1186bff2d51SMed Ismail Bennani        return CrashLogScriptedThread.__module__ + "." + CrashLogScriptedThread.__name__
1196bff2d51SMed Ismail Bennani
1206bff2d51SMed Ismail Bennani    def get_process_metadata(self):
1216bff2d51SMed Ismail Bennani        return self.metadata
1226bff2d51SMed Ismail Bennani
1236bff2d51SMed Ismail Bennani
1246bff2d51SMed Ismail Bennaniclass CrashLogScriptedThread(ScriptedThread):
1256bff2d51SMed Ismail Bennani    def create_register_ctx(self):
1266bff2d51SMed Ismail Bennani        if not self.has_crashed:
1276bff2d51SMed Ismail Bennani            return dict.fromkeys(
1286bff2d51SMed Ismail Bennani                [*map(lambda reg: reg["name"], self.register_info["registers"])], 0
1296bff2d51SMed Ismail Bennani            )
1306bff2d51SMed Ismail Bennani
1316bff2d51SMed Ismail Bennani        if not self.backing_thread or not len(self.backing_thread.registers):
1326bff2d51SMed Ismail Bennani            return dict.fromkeys(
1336bff2d51SMed Ismail Bennani                [*map(lambda reg: reg["name"], self.register_info["registers"])], 0
1346bff2d51SMed Ismail Bennani            )
1356bff2d51SMed Ismail Bennani
1366bff2d51SMed Ismail Bennani        for reg in self.register_info["registers"]:
1376bff2d51SMed Ismail Bennani            reg_name = reg["name"]
1386bff2d51SMed Ismail Bennani            if reg_name in self.backing_thread.registers:
1396bff2d51SMed Ismail Bennani                self.register_ctx[reg_name] = self.backing_thread.registers[reg_name]
1406bff2d51SMed Ismail Bennani            else:
1416bff2d51SMed Ismail Bennani                self.register_ctx[reg_name] = 0
1426bff2d51SMed Ismail Bennani
1436bff2d51SMed Ismail Bennani        return self.register_ctx
1446bff2d51SMed Ismail Bennani
1456bff2d51SMed Ismail Bennani    def resolve_stackframes(thread, addr_mask, target):
1466bff2d51SMed Ismail Bennani        frames = []
1476bff2d51SMed Ismail Bennani        for frame in thread.frames:
1486bff2d51SMed Ismail Bennani            frame_pc = frame.pc & addr_mask
1496bff2d51SMed Ismail Bennani            pc = frame_pc if frame.index == 0 or frame_pc == 0 else frame_pc - 1
1506bff2d51SMed Ismail Bennani            sym_addr = lldb.SBAddress()
1516bff2d51SMed Ismail Bennani            sym_addr.SetLoadAddress(pc, target)
1526bff2d51SMed Ismail Bennani            if not sym_addr.IsValid():
1536bff2d51SMed Ismail Bennani                continue
1546bff2d51SMed Ismail Bennani            frames.append({"idx": frame.index, "pc": pc})
1556bff2d51SMed Ismail Bennani        return frames
1566bff2d51SMed Ismail Bennani
1576bff2d51SMed Ismail Bennani    def create_stackframes(self):
158785b143aSMed Ismail Bennani        if not (self.originating_process.options.load_all_images or self.has_crashed):
1596bff2d51SMed Ismail Bennani            return None
1606bff2d51SMed Ismail Bennani
1616bff2d51SMed Ismail Bennani        if not self.backing_thread or not len(self.backing_thread.frames):
1626bff2d51SMed Ismail Bennani            return None
1636bff2d51SMed Ismail Bennani
1646bff2d51SMed Ismail Bennani        self.frames = CrashLogScriptedThread.resolve_stackframes(
1654ec9cda6SMed Ismail Bennani            self.backing_thread, self.originating_process.addr_mask, self.target
1666bff2d51SMed Ismail Bennani        )
1676bff2d51SMed Ismail Bennani
1686bff2d51SMed Ismail Bennani        return self.frames
1696bff2d51SMed Ismail Bennani
1706bff2d51SMed Ismail Bennani    def __init__(self, process, args, crashlog_thread):
1716bff2d51SMed Ismail Bennani        super().__init__(process, args)
1726bff2d51SMed Ismail Bennani
1736bff2d51SMed Ismail Bennani        self.backing_thread = crashlog_thread
1746bff2d51SMed Ismail Bennani        self.idx = self.backing_thread.index
1756bff2d51SMed Ismail Bennani        self.tid = self.backing_thread.id
1766bff2d51SMed Ismail Bennani        self.name = self.backing_thread.name
1776bff2d51SMed Ismail Bennani        self.queue = self.backing_thread.queue
1784ec9cda6SMed Ismail Bennani        self.has_crashed = self.originating_process.crashed_thread_idx == self.idx
1796bff2d51SMed Ismail Bennani        self.create_stackframes()
1806bff2d51SMed Ismail Bennani
1816bff2d51SMed Ismail Bennani    def get_state(self):
1826bff2d51SMed Ismail Bennani        if not self.has_crashed:
1836bff2d51SMed Ismail Bennani            return lldb.eStateStopped
1846bff2d51SMed Ismail Bennani        return lldb.eStateCrashed
1856bff2d51SMed Ismail Bennani
1866bff2d51SMed Ismail Bennani    def get_stop_reason(self) -> Dict[str, Any]:
1876bff2d51SMed Ismail Bennani        if not self.has_crashed:
1886bff2d51SMed Ismail Bennani            return {"type": lldb.eStopReasonNone}
1896bff2d51SMed Ismail Bennani        # TODO: Investigate what stop reason should be reported when crashed
1906bff2d51SMed Ismail Bennani        stop_reason = {"type": lldb.eStopReasonException, "data": {}}
1914ec9cda6SMed Ismail Bennani        if self.originating_process.exception:
1924ec9cda6SMed Ismail Bennani            stop_reason["data"]["mach_exception"] = self.originating_process.exception
1936bff2d51SMed Ismail Bennani        return stop_reason
1946bff2d51SMed Ismail Bennani
1956bff2d51SMed Ismail Bennani    def get_register_context(self) -> str:
1966bff2d51SMed Ismail Bennani        if not self.register_ctx:
1976bff2d51SMed Ismail Bennani            self.register_ctx = self.create_register_ctx()
1986bff2d51SMed Ismail Bennani
1996bff2d51SMed Ismail Bennani        return struct.pack(
2006bff2d51SMed Ismail Bennani            "{}Q".format(len(self.register_ctx)), *self.register_ctx.values()
2016bff2d51SMed Ismail Bennani        )
2026bff2d51SMed Ismail Bennani
2036bff2d51SMed Ismail Bennani    def get_extended_info(self):
2046bff2d51SMed Ismail Bennani        if self.has_crashed:
2054ec9cda6SMed Ismail Bennani            self.extended_info = self.originating_process.extended_thread_info
2066bff2d51SMed Ismail Bennani        return self.extended_info
207