217 lines · python
1import os, json, struct, signal, uuid, tempfile2 3from typing import Any, Dict4 5import lldb6from lldb.plugins.scripted_process import ScriptedProcess7from lldb.plugins.scripted_process import ScriptedThread8 9from lldb.macosx.crashlog import CrashLog, CrashLogParser10 11 12class CrashLogScriptedProcess(ScriptedProcess):13 def parse_crashlog(self):14 if self.crashlog.process_id:15 if type(self.crashlog.process_id) is int:16 self.pid = self.crashlog.process_id17 elif type(self.crashlog.process_id) is str:18 self.pid = int(self.crashlog.process_id, 0)19 else:20 self.pid = super().get_process_id()21 self.addr_mask = self.crashlog.addr_mask22 self.crashed_thread_idx = self.crashlog.crashed_thread_idx23 self.loaded_images = []24 self.exception = self.crashlog.exception25 self.app_specific_thread = None26 if hasattr(self.crashlog, "asi"):27 self.metadata["asi"] = self.crashlog.asi28 if hasattr(self.crashlog, "asb"):29 self.extended_thread_info = self.crashlog.asb30 31 for thread in self.crashlog.threads:32 if (33 hasattr(thread, "app_specific_backtrace")34 and thread.app_specific_backtrace35 ):36 # We don't want to include the Application Specific Backtrace37 # Thread into the Scripted Process' Thread list.38 # Instead, we will try to extract the stackframe pcs from the39 # backtrace and inject that as the extended thread info.40 self.app_specific_thread = thread41 continue42 43 self.threads[thread.index] = CrashLogScriptedThread(self, None, thread)44 45 if self.app_specific_thread:46 self.extended_thread_info = CrashLogScriptedThread.resolve_stackframes(47 self.app_specific_thread, self.addr_mask, self.target48 )49 50 class CrashLogOptions:51 load_all_images = False52 crashed_only = True53 no_parallel_image_loading = False54 55 def __init__(self, exe_ctx: lldb.SBExecutionContext, args: lldb.SBStructuredData):56 super().__init__(exe_ctx, args)57 58 if not self.target or not self.target.IsValid():59 # Return error60 return61 62 self.crashlog_path = None63 64 crashlog_path = args.GetValueForKey("file_path")65 if crashlog_path and crashlog_path.IsValid():66 if crashlog_path.GetType() == lldb.eStructuredDataTypeString:67 self.crashlog_path = crashlog_path.GetStringValue(4096)68 69 if not self.crashlog_path:70 # Return error71 return72 73 self.options = self.CrashLogOptions()74 75 load_all_images = args.GetValueForKey("load_all_images")76 if load_all_images and load_all_images.IsValid():77 if load_all_images.GetType() == lldb.eStructuredDataTypeBoolean:78 self.options.load_all_images = load_all_images.GetBooleanValue()79 80 crashed_only = args.GetValueForKey("crashed_only")81 if crashed_only and crashed_only.IsValid():82 if crashed_only.GetType() == lldb.eStructuredDataTypeBoolean:83 self.options.crashed_only = crashed_only.GetBooleanValue()84 85 no_parallel_image_loading = args.GetValueForKey("no_parallel_image_loading")86 if no_parallel_image_loading and no_parallel_image_loading.IsValid():87 if no_parallel_image_loading.GetType() == lldb.eStructuredDataTypeBoolean:88 self.options.no_parallel_image_loading = (89 no_parallel_image_loading.GetBooleanValue()90 )91 92 self.crashlog = None93 crashlog = args.GetValueForKey("crashlog")94 if crashlog and crashlog.IsValid():95 if crashlog.GetType() == lldb.eStructuredDataTypeGeneric:96 self.crashlog = crashlog.GetGenericValue()97 98 if not self.crashlog:99 # Return error100 return101 102 self.pid = super().get_process_id()103 self.crashed_thread_idx = 0104 self.exception = None105 self.extended_thread_info = None106 self.parse_crashlog()107 108 def read_memory_at_address(109 self, addr: int, size: int, error: lldb.SBError110 ) -> lldb.SBData:111 # NOTE: CrashLogs don't contain any memory.112 return lldb.SBData()113 114 def get_loaded_images(self):115 if len(self.loaded_images) == 0:116 self.crashlog.load_images(self.options, self.loaded_images)117 return self.loaded_images118 119 def should_stop(self) -> bool:120 return True121 122 def is_alive(self) -> bool:123 return True124 125 def get_scripted_thread_plugin(self):126 return CrashLogScriptedThread.__module__ + "." + CrashLogScriptedThread.__name__127 128 def get_process_metadata(self):129 return self.metadata130 131 132class CrashLogScriptedThread(ScriptedThread):133 def create_register_ctx(self):134 if not self.backing_thread or not len(self.backing_thread.registers):135 return dict.fromkeys(136 [*map(lambda reg: reg["name"], self.register_info["registers"])], 0137 )138 139 for reg in self.register_info["registers"]:140 reg_name = reg["name"]141 reg_alt_name = None142 if "alt-name" in reg:143 reg_alt_name = reg["alt-name"]144 if reg_name in self.backing_thread.registers:145 self.register_ctx[reg_name] = self.backing_thread.registers[reg_name]146 elif reg_alt_name and reg_alt_name in self.backing_thread.registers:147 self.register_ctx[reg_name] = self.backing_thread.registers[148 reg_alt_name149 ]150 else:151 self.register_ctx[reg_name] = 0152 153 return self.register_ctx154 155 def resolve_stackframes(thread, addr_mask, target):156 frames = []157 for frame in thread.frames:158 frame_pc = frame.pc & addr_mask159 pc = frame_pc if frame.index == 0 or frame_pc == 0 else frame_pc - 1160 sym_addr = lldb.SBAddress()161 sym_addr.SetLoadAddress(pc, target)162 if not sym_addr.IsValid():163 continue164 frames.append({"idx": frame.index, "pc": pc})165 return frames166 167 def create_stackframes(self):168 if not (self.originating_process.options.load_all_images or self.has_crashed):169 return None170 171 if not self.backing_thread or not len(self.backing_thread.frames):172 return None173 174 self.frames = CrashLogScriptedThread.resolve_stackframes(175 self.backing_thread, self.originating_process.addr_mask, self.target176 )177 178 return self.frames179 180 def __init__(self, process, args, crashlog_thread):181 super().__init__(process, args)182 183 self.backing_thread = crashlog_thread184 self.idx = self.backing_thread.index185 self.tid = self.backing_thread.id186 self.name = self.backing_thread.name187 self.queue = self.backing_thread.queue188 self.has_crashed = self.originating_process.crashed_thread_idx == self.idx189 self.create_stackframes()190 191 def get_state(self):192 if not self.has_crashed:193 return lldb.eStateStopped194 return lldb.eStateCrashed195 196 def get_stop_reason(self) -> Dict[str, Any]:197 if not self.has_crashed:198 return {"type": lldb.eStopReasonNone}199 # TODO: Investigate what stop reason should be reported when crashed200 stop_reason = {"type": lldb.eStopReasonException, "data": {}}201 if self.originating_process.exception:202 stop_reason["data"]["mach_exception"] = self.originating_process.exception203 return stop_reason204 205 def get_register_context(self) -> str:206 if not self.register_ctx:207 self.register_ctx = self.create_register_ctx()208 209 return struct.pack(210 "{}Q".format(len(self.register_ctx)), *self.register_ctx.values()211 )212 213 def get_extended_info(self):214 if self.has_crashed:215 self.extended_info = self.originating_process.extended_thread_info216 return self.extended_info217