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