xref: /openbsd-src/gnu/llvm/lldb/examples/python/scripted_process/crashlog_scripted_process.py (revision f6aab3d83b51b91c24247ad2c2573574de475a82)
1*f6aab3d8Srobertimport os,json,struct,signal,uuid
2*f6aab3d8Srobert
3*f6aab3d8Srobertfrom typing import Any, Dict
4*f6aab3d8Srobert
5*f6aab3d8Srobertimport lldb
6*f6aab3d8Srobertfrom lldb.plugins.scripted_process import ScriptedProcess
7*f6aab3d8Srobertfrom lldb.plugins.scripted_process import ScriptedThread
8*f6aab3d8Srobert
9*f6aab3d8Srobertfrom lldb.macosx.crashlog import CrashLog,CrashLogParser
10*f6aab3d8Srobert
11*f6aab3d8Srobertclass CrashLogScriptedProcess(ScriptedProcess):
12*f6aab3d8Srobert    def parse_crashlog(self):
13*f6aab3d8Srobert        crashlog_parser = CrashLogParser.create(self.dbg, self.crashlog_path, False)
14*f6aab3d8Srobert        crash_log = crashlog_parser.parse()
15*f6aab3d8Srobert
16*f6aab3d8Srobert        self.pid = crash_log.process_id
17*f6aab3d8Srobert        self.addr_mask = crash_log.addr_mask
18*f6aab3d8Srobert        self.crashed_thread_idx = crash_log.crashed_thread_idx
19*f6aab3d8Srobert        self.loaded_images = []
20*f6aab3d8Srobert        self.exception = crash_log.exception
21*f6aab3d8Srobert        self.app_specific_thread = None
22*f6aab3d8Srobert        if hasattr(crash_log, 'asi'):
23*f6aab3d8Srobert            self.metadata['asi'] = crash_log.asi
24*f6aab3d8Srobert        if hasattr(crash_log, 'asb'):
25*f6aab3d8Srobert            self.extended_thread_info = crash_log.asb
26*f6aab3d8Srobert
27*f6aab3d8Srobert        def load_images(self, images):
28*f6aab3d8Srobert            #TODO: Add to self.loaded_images and load images in lldb
29*f6aab3d8Srobert            if images:
30*f6aab3d8Srobert                for image in images:
31*f6aab3d8Srobert                    if image not in self.loaded_images:
32*f6aab3d8Srobert                        if image.uuid == uuid.UUID(int=0):
33*f6aab3d8Srobert                            continue
34*f6aab3d8Srobert                        err = image.add_module(self.target)
35*f6aab3d8Srobert                        if err:
36*f6aab3d8Srobert                            # Append to SBCommandReturnObject
37*f6aab3d8Srobert                            print(err)
38*f6aab3d8Srobert                        else:
39*f6aab3d8Srobert                            self.loaded_images.append(image)
40*f6aab3d8Srobert
41*f6aab3d8Srobert        for thread in crash_log.threads:
42*f6aab3d8Srobert            if self.load_all_images:
43*f6aab3d8Srobert                load_images(self, crash_log.images)
44*f6aab3d8Srobert            elif thread.did_crash():
45*f6aab3d8Srobert                for ident in thread.idents:
46*f6aab3d8Srobert                    load_images(self, crash_log.find_images_with_identifier(ident))
47*f6aab3d8Srobert
48*f6aab3d8Srobert            if hasattr(thread, 'app_specific_backtrace') and thread.app_specific_backtrace:
49*f6aab3d8Srobert                # We don't want to include the Application Specific Backtrace
50*f6aab3d8Srobert                # Thread into the Scripted Process' Thread list.
51*f6aab3d8Srobert                # Instead, we will try to extract the stackframe pcs from the
52*f6aab3d8Srobert                # backtrace and inject that as the extended thread info.
53*f6aab3d8Srobert                self.app_specific_thread = thread
54*f6aab3d8Srobert                continue
55*f6aab3d8Srobert
56*f6aab3d8Srobert            self.threads[thread.index] = CrashLogScriptedThread(self, None, thread)
57*f6aab3d8Srobert
58*f6aab3d8Srobert
59*f6aab3d8Srobert        if self.app_specific_thread:
60*f6aab3d8Srobert            self.extended_thread_info = \
61*f6aab3d8Srobert                    CrashLogScriptedThread.resolve_stackframes(self.app_specific_thread,
62*f6aab3d8Srobert                                                               self.addr_mask,
63*f6aab3d8Srobert                                                               self.target)
64*f6aab3d8Srobert
65*f6aab3d8Srobert    def __init__(self, exe_ctx: lldb.SBExecutionContext, args : lldb.SBStructuredData):
66*f6aab3d8Srobert        super().__init__(exe_ctx, args)
67*f6aab3d8Srobert
68*f6aab3d8Srobert        if not self.target or not self.target.IsValid():
69*f6aab3d8Srobert            # Return error
70*f6aab3d8Srobert            return
71*f6aab3d8Srobert
72*f6aab3d8Srobert        self.crashlog_path = None
73*f6aab3d8Srobert
74*f6aab3d8Srobert        crashlog_path = args.GetValueForKey("file_path")
75*f6aab3d8Srobert        if crashlog_path and crashlog_path.IsValid():
76*f6aab3d8Srobert            if crashlog_path.GetType() == lldb.eStructuredDataTypeString:
77*f6aab3d8Srobert                self.crashlog_path = crashlog_path.GetStringValue(4096)
78*f6aab3d8Srobert
79*f6aab3d8Srobert        if not self.crashlog_path:
80*f6aab3d8Srobert            # Return error
81*f6aab3d8Srobert            return
82*f6aab3d8Srobert
83*f6aab3d8Srobert        load_all_images = args.GetValueForKey("load_all_images")
84*f6aab3d8Srobert        if load_all_images and load_all_images.IsValid():
85*f6aab3d8Srobert            if load_all_images.GetType() == lldb.eStructuredDataTypeBoolean:
86*f6aab3d8Srobert                self.load_all_images = load_all_images.GetBooleanValue()
87*f6aab3d8Srobert
88*f6aab3d8Srobert        if not self.load_all_images:
89*f6aab3d8Srobert            self.load_all_images = False
90*f6aab3d8Srobert
91*f6aab3d8Srobert        self.pid = super().get_process_id()
92*f6aab3d8Srobert        self.crashed_thread_idx = 0
93*f6aab3d8Srobert        self.exception = None
94*f6aab3d8Srobert        self.extended_thread_info = None
95*f6aab3d8Srobert        self.parse_crashlog()
96*f6aab3d8Srobert
97*f6aab3d8Srobert    def get_memory_region_containing_address(self, addr: int) -> lldb.SBMemoryRegionInfo:
98*f6aab3d8Srobert        return None
99*f6aab3d8Srobert
100*f6aab3d8Srobert    def get_thread_with_id(self, tid: int):
101*f6aab3d8Srobert        return {}
102*f6aab3d8Srobert
103*f6aab3d8Srobert    def get_registers_for_thread(self, tid: int):
104*f6aab3d8Srobert        return {}
105*f6aab3d8Srobert
106*f6aab3d8Srobert    def read_memory_at_address(self, addr: int, size: int, error: lldb.SBError) -> lldb.SBData:
107*f6aab3d8Srobert        # NOTE: CrashLogs don't contain any memory.
108*f6aab3d8Srobert        return lldb.SBData()
109*f6aab3d8Srobert
110*f6aab3d8Srobert    def get_loaded_images(self):
111*f6aab3d8Srobert        # TODO: Iterate over corefile_target modules and build a data structure
112*f6aab3d8Srobert        # from it.
113*f6aab3d8Srobert        return self.loaded_images
114*f6aab3d8Srobert
115*f6aab3d8Srobert    def get_process_id(self) -> int:
116*f6aab3d8Srobert        return self.pid
117*f6aab3d8Srobert
118*f6aab3d8Srobert    def should_stop(self) -> bool:
119*f6aab3d8Srobert        return True
120*f6aab3d8Srobert
121*f6aab3d8Srobert    def is_alive(self) -> bool:
122*f6aab3d8Srobert        return True
123*f6aab3d8Srobert
124*f6aab3d8Srobert    def get_scripted_thread_plugin(self):
125*f6aab3d8Srobert        return CrashLogScriptedThread.__module__ + "." + CrashLogScriptedThread.__name__
126*f6aab3d8Srobert
127*f6aab3d8Srobert    def get_process_metadata(self):
128*f6aab3d8Srobert        return self.metadata
129*f6aab3d8Srobert
130*f6aab3d8Srobertclass CrashLogScriptedThread(ScriptedThread):
131*f6aab3d8Srobert    def create_register_ctx(self):
132*f6aab3d8Srobert        if not self.has_crashed:
133*f6aab3d8Srobert            return dict.fromkeys([*map(lambda reg: reg['name'], self.register_info['registers'])] , 0)
134*f6aab3d8Srobert
135*f6aab3d8Srobert        if not self.backing_thread or not len(self.backing_thread.registers):
136*f6aab3d8Srobert            return dict.fromkeys([*map(lambda reg: reg['name'], self.register_info['registers'])] , 0)
137*f6aab3d8Srobert
138*f6aab3d8Srobert        for reg in self.register_info['registers']:
139*f6aab3d8Srobert            reg_name = reg['name']
140*f6aab3d8Srobert            if reg_name in self.backing_thread.registers:
141*f6aab3d8Srobert                self.register_ctx[reg_name] = self.backing_thread.registers[reg_name]
142*f6aab3d8Srobert            else:
143*f6aab3d8Srobert                self.register_ctx[reg_name] = 0
144*f6aab3d8Srobert
145*f6aab3d8Srobert        return self.register_ctx
146*f6aab3d8Srobert
147*f6aab3d8Srobert    def resolve_stackframes(thread, addr_mask, target):
148*f6aab3d8Srobert        frames = []
149*f6aab3d8Srobert        for frame in thread.frames:
150*f6aab3d8Srobert            frame_pc = frame.pc & addr_mask
151*f6aab3d8Srobert            pc = frame_pc if frame.index == 0  or frame_pc == 0 else frame_pc - 1
152*f6aab3d8Srobert            sym_addr = lldb.SBAddress()
153*f6aab3d8Srobert            sym_addr.SetLoadAddress(pc, target)
154*f6aab3d8Srobert            if not sym_addr.IsValid():
155*f6aab3d8Srobert                continue
156*f6aab3d8Srobert            frames.append({"idx": frame.index, "pc": pc})
157*f6aab3d8Srobert        return frames
158*f6aab3d8Srobert
159*f6aab3d8Srobert
160*f6aab3d8Srobert    def create_stackframes(self):
161*f6aab3d8Srobert        if not (self.scripted_process.load_all_images or self.has_crashed):
162*f6aab3d8Srobert            return None
163*f6aab3d8Srobert
164*f6aab3d8Srobert        if not self.backing_thread or not len(self.backing_thread.frames):
165*f6aab3d8Srobert            return None
166*f6aab3d8Srobert
167*f6aab3d8Srobert        self.frames = CrashLogScriptedThread.resolve_stackframes(self.backing_thread,
168*f6aab3d8Srobert                                                                 self.scripted_process.addr_mask,
169*f6aab3d8Srobert                                                                 self.target)
170*f6aab3d8Srobert
171*f6aab3d8Srobert        return self.frames
172*f6aab3d8Srobert
173*f6aab3d8Srobert    def __init__(self, process, args, crashlog_thread):
174*f6aab3d8Srobert        super().__init__(process, args)
175*f6aab3d8Srobert
176*f6aab3d8Srobert        self.backing_thread = crashlog_thread
177*f6aab3d8Srobert        self.idx = self.backing_thread.index
178*f6aab3d8Srobert        self.tid = self.backing_thread.id
179*f6aab3d8Srobert        if self.backing_thread.app_specific_backtrace:
180*f6aab3d8Srobert            self.name = "Application Specific Backtrace - " + str(self.idx)
181*f6aab3d8Srobert        else:
182*f6aab3d8Srobert            self.name = self.backing_thread.name
183*f6aab3d8Srobert        self.queue = self.backing_thread.queue
184*f6aab3d8Srobert        self.has_crashed = (self.scripted_process.crashed_thread_idx == self.idx)
185*f6aab3d8Srobert        self.create_stackframes()
186*f6aab3d8Srobert
187*f6aab3d8Srobert    def get_state(self):
188*f6aab3d8Srobert        if not self.has_crashed:
189*f6aab3d8Srobert            return lldb.eStateStopped
190*f6aab3d8Srobert        return lldb.eStateCrashed
191*f6aab3d8Srobert
192*f6aab3d8Srobert    def get_stop_reason(self) -> Dict[str, Any]:
193*f6aab3d8Srobert        if not self.has_crashed:
194*f6aab3d8Srobert            return { "type": lldb.eStopReasonNone }
195*f6aab3d8Srobert        # TODO: Investigate what stop reason should be reported when crashed
196*f6aab3d8Srobert        stop_reason = { "type": lldb.eStopReasonException, "data": {  }}
197*f6aab3d8Srobert        if self.scripted_process.exception:
198*f6aab3d8Srobert            stop_reason['data']['mach_exception'] = self.scripted_process.exception
199*f6aab3d8Srobert        return stop_reason
200*f6aab3d8Srobert
201*f6aab3d8Srobert    def get_register_context(self) -> str:
202*f6aab3d8Srobert        if not self.register_ctx:
203*f6aab3d8Srobert            self.register_ctx = self.create_register_ctx()
204*f6aab3d8Srobert
205*f6aab3d8Srobert        return struct.pack("{}Q".format(len(self.register_ctx)), *self.register_ctx.values())
206*f6aab3d8Srobert
207*f6aab3d8Srobert    def get_extended_info(self):
208*f6aab3d8Srobert        if (self.has_crashed):
209*f6aab3d8Srobert            self.extended_info = self.scripted_process.extended_thread_info
210*f6aab3d8Srobert        return self.extended_info
211*f6aab3d8Srobert
212