2022-08-09 17:31:31 -07:00
|
|
|
import os,json,struct,signal,uuid
|
2022-02-16 11:43:44 -08:00
|
|
|
|
|
|
|
|
from typing import Any, Dict
|
|
|
|
|
|
|
|
|
|
import lldb
|
|
|
|
|
from lldb.plugins.scripted_process import ScriptedProcess
|
|
|
|
|
from lldb.plugins.scripted_process import ScriptedThread
|
|
|
|
|
|
|
|
|
|
from lldb.macosx.crashlog import CrashLog,CrashLogParser
|
|
|
|
|
|
|
|
|
|
class CrashLogScriptedProcess(ScriptedProcess):
|
2023-04-11 17:03:49 -07:00
|
|
|
def set_crashlog(self, crashlog):
|
|
|
|
|
self.crashlog = crashlog
|
|
|
|
|
self.pid = self.crashlog.process_id
|
|
|
|
|
self.addr_mask = self.crashlog.addr_mask
|
|
|
|
|
self.crashed_thread_idx = self.crashlog.crashed_thread_idx
|
2022-02-16 11:43:44 -08:00
|
|
|
self.loaded_images = []
|
2023-04-11 17:03:49 -07:00
|
|
|
self.exception = self.crashlog.exception
|
2022-11-03 14:27:28 -07:00
|
|
|
self.app_specific_thread = None
|
2023-04-11 17:03:49 -07:00
|
|
|
if hasattr(self.crashlog, 'asi'):
|
|
|
|
|
self.metadata['asi'] = self.crashlog.asi
|
|
|
|
|
if hasattr(self.crashlog, 'asb'):
|
|
|
|
|
self.extended_thread_info = self.crashlog.asb
|
2022-02-16 11:43:44 -08:00
|
|
|
|
2023-04-13 16:27:53 -07:00
|
|
|
if self.load_all_images:
|
|
|
|
|
for image in self.crashlog.images:
|
|
|
|
|
image.resolve = True
|
|
|
|
|
else:
|
|
|
|
|
for thread in self.crashlog.threads:
|
|
|
|
|
if thread.did_crash():
|
|
|
|
|
for ident in thread.idents:
|
|
|
|
|
for image in self.crashlog.find_images_with_identifier(ident):
|
|
|
|
|
image.resolve = True
|
|
|
|
|
|
|
|
|
|
for image in self.crashlog.images:
|
|
|
|
|
if image not in self.loaded_images:
|
|
|
|
|
if image.uuid == uuid.UUID(int=0):
|
|
|
|
|
continue
|
|
|
|
|
err = image.add_module(self.target)
|
|
|
|
|
if err:
|
|
|
|
|
# Append to SBCommandReturnObject
|
|
|
|
|
print(err)
|
|
|
|
|
else:
|
|
|
|
|
self.loaded_images.append(image)
|
2022-03-16 15:44:31 -07:00
|
|
|
|
2023-04-11 17:03:49 -07:00
|
|
|
for thread in self.crashlog.threads:
|
2022-11-03 14:27:28 -07:00
|
|
|
if hasattr(thread, 'app_specific_backtrace') and thread.app_specific_backtrace:
|
|
|
|
|
# We don't want to include the Application Specific Backtrace
|
|
|
|
|
# Thread into the Scripted Process' Thread list.
|
|
|
|
|
# Instead, we will try to extract the stackframe pcs from the
|
|
|
|
|
# backtrace and inject that as the extended thread info.
|
|
|
|
|
self.app_specific_thread = thread
|
|
|
|
|
continue
|
|
|
|
|
|
2022-02-16 11:43:44 -08:00
|
|
|
self.threads[thread.index] = CrashLogScriptedThread(self, None, thread)
|
|
|
|
|
|
2022-11-03 14:27:28 -07:00
|
|
|
|
|
|
|
|
if self.app_specific_thread:
|
|
|
|
|
self.extended_thread_info = \
|
|
|
|
|
CrashLogScriptedThread.resolve_stackframes(self.app_specific_thread,
|
|
|
|
|
self.addr_mask,
|
|
|
|
|
self.target)
|
|
|
|
|
|
2023-01-11 23:04:24 -08:00
|
|
|
def __init__(self, exe_ctx: lldb.SBExecutionContext, args : lldb.SBStructuredData):
|
|
|
|
|
super().__init__(exe_ctx, args)
|
2022-02-16 11:43:44 -08:00
|
|
|
|
|
|
|
|
if not self.target or not self.target.IsValid():
|
2022-08-03 14:11:43 -07:00
|
|
|
# Return error
|
2022-02-16 11:43:44 -08:00
|
|
|
return
|
|
|
|
|
|
|
|
|
|
self.crashlog_path = None
|
|
|
|
|
|
2022-11-04 21:58:51 -07:00
|
|
|
crashlog_path = args.GetValueForKey("file_path")
|
2022-02-16 11:43:44 -08:00
|
|
|
if crashlog_path and crashlog_path.IsValid():
|
|
|
|
|
if crashlog_path.GetType() == lldb.eStructuredDataTypeString:
|
|
|
|
|
self.crashlog_path = crashlog_path.GetStringValue(4096)
|
|
|
|
|
|
|
|
|
|
if not self.crashlog_path:
|
2022-08-03 14:11:43 -07:00
|
|
|
# Return error
|
2022-02-16 11:43:44 -08:00
|
|
|
return
|
|
|
|
|
|
2022-03-16 15:44:31 -07:00
|
|
|
load_all_images = args.GetValueForKey("load_all_images")
|
|
|
|
|
if load_all_images and load_all_images.IsValid():
|
|
|
|
|
if load_all_images.GetType() == lldb.eStructuredDataTypeBoolean:
|
|
|
|
|
self.load_all_images = load_all_images.GetBooleanValue()
|
|
|
|
|
|
|
|
|
|
if not self.load_all_images:
|
|
|
|
|
self.load_all_images = False
|
|
|
|
|
|
2022-02-16 11:43:44 -08:00
|
|
|
self.pid = super().get_process_id()
|
|
|
|
|
self.crashed_thread_idx = 0
|
2022-08-11 22:26:36 -07:00
|
|
|
self.exception = None
|
2022-11-03 14:27:28 -07:00
|
|
|
self.extended_thread_info = None
|
2022-02-16 11:43:44 -08:00
|
|
|
|
2022-11-18 13:53:57 -08:00
|
|
|
def read_memory_at_address(self, addr: int, size: int, error: lldb.SBError) -> lldb.SBData:
|
2022-02-16 11:43:44 -08:00
|
|
|
# NOTE: CrashLogs don't contain any memory.
|
|
|
|
|
return lldb.SBData()
|
|
|
|
|
|
|
|
|
|
def get_loaded_images(self):
|
|
|
|
|
# TODO: Iterate over corefile_target modules and build a data structure
|
|
|
|
|
# from it.
|
|
|
|
|
return self.loaded_images
|
|
|
|
|
|
|
|
|
|
def should_stop(self) -> bool:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def is_alive(self) -> bool:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def get_scripted_thread_plugin(self):
|
|
|
|
|
return CrashLogScriptedThread.__module__ + "." + CrashLogScriptedThread.__name__
|
|
|
|
|
|
2022-11-03 14:27:28 -07:00
|
|
|
def get_process_metadata(self):
|
|
|
|
|
return self.metadata
|
|
|
|
|
|
2022-02-16 11:43:44 -08:00
|
|
|
class CrashLogScriptedThread(ScriptedThread):
|
|
|
|
|
def create_register_ctx(self):
|
|
|
|
|
if not self.has_crashed:
|
|
|
|
|
return dict.fromkeys([*map(lambda reg: reg['name'], self.register_info['registers'])] , 0)
|
|
|
|
|
|
|
|
|
|
if not self.backing_thread or not len(self.backing_thread.registers):
|
|
|
|
|
return dict.fromkeys([*map(lambda reg: reg['name'], self.register_info['registers'])] , 0)
|
|
|
|
|
|
|
|
|
|
for reg in self.register_info['registers']:
|
|
|
|
|
reg_name = reg['name']
|
|
|
|
|
if reg_name in self.backing_thread.registers:
|
|
|
|
|
self.register_ctx[reg_name] = self.backing_thread.registers[reg_name]
|
|
|
|
|
else:
|
|
|
|
|
self.register_ctx[reg_name] = 0
|
|
|
|
|
|
|
|
|
|
return self.register_ctx
|
|
|
|
|
|
2022-11-03 14:27:28 -07:00
|
|
|
def resolve_stackframes(thread, addr_mask, target):
|
|
|
|
|
frames = []
|
|
|
|
|
for frame in thread.frames:
|
|
|
|
|
frame_pc = frame.pc & addr_mask
|
|
|
|
|
pc = frame_pc if frame.index == 0 or frame_pc == 0 else frame_pc - 1
|
|
|
|
|
sym_addr = lldb.SBAddress()
|
|
|
|
|
sym_addr.SetLoadAddress(pc, target)
|
|
|
|
|
if not sym_addr.IsValid():
|
|
|
|
|
continue
|
|
|
|
|
frames.append({"idx": frame.index, "pc": pc})
|
|
|
|
|
return frames
|
|
|
|
|
|
|
|
|
|
|
2022-02-16 11:43:44 -08:00
|
|
|
def create_stackframes(self):
|
2022-03-16 15:44:31 -07:00
|
|
|
if not (self.scripted_process.load_all_images or self.has_crashed):
|
2022-02-16 11:43:44 -08:00
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
if not self.backing_thread or not len(self.backing_thread.frames):
|
|
|
|
|
return None
|
|
|
|
|
|
2022-11-03 14:27:28 -07:00
|
|
|
self.frames = CrashLogScriptedThread.resolve_stackframes(self.backing_thread,
|
|
|
|
|
self.scripted_process.addr_mask,
|
|
|
|
|
self.target)
|
2022-02-16 11:43:44 -08:00
|
|
|
|
|
|
|
|
return self.frames
|
|
|
|
|
|
|
|
|
|
def __init__(self, process, args, crashlog_thread):
|
|
|
|
|
super().__init__(process, args)
|
|
|
|
|
|
|
|
|
|
self.backing_thread = crashlog_thread
|
|
|
|
|
self.idx = self.backing_thread.index
|
2022-03-24 17:19:33 -07:00
|
|
|
self.tid = self.backing_thread.id
|
2022-11-03 14:27:28 -07:00
|
|
|
if self.backing_thread.app_specific_backtrace:
|
|
|
|
|
self.name = "Application Specific Backtrace - " + str(self.idx)
|
|
|
|
|
else:
|
|
|
|
|
self.name = self.backing_thread.name
|
2022-03-24 17:19:33 -07:00
|
|
|
self.queue = self.backing_thread.queue
|
2022-02-16 11:43:44 -08:00
|
|
|
self.has_crashed = (self.scripted_process.crashed_thread_idx == self.idx)
|
|
|
|
|
self.create_stackframes()
|
|
|
|
|
|
|
|
|
|
def get_state(self):
|
|
|
|
|
if not self.has_crashed:
|
|
|
|
|
return lldb.eStateStopped
|
|
|
|
|
return lldb.eStateCrashed
|
|
|
|
|
|
|
|
|
|
def get_stop_reason(self) -> Dict[str, Any]:
|
|
|
|
|
if not self.has_crashed:
|
2022-08-11 22:26:36 -07:00
|
|
|
return { "type": lldb.eStopReasonNone }
|
2022-02-16 11:43:44 -08:00
|
|
|
# TODO: Investigate what stop reason should be reported when crashed
|
2022-08-11 22:26:36 -07:00
|
|
|
stop_reason = { "type": lldb.eStopReasonException, "data": { }}
|
|
|
|
|
if self.scripted_process.exception:
|
|
|
|
|
stop_reason['data']['mach_exception'] = self.scripted_process.exception
|
|
|
|
|
return stop_reason
|
2022-02-16 11:43:44 -08:00
|
|
|
|
|
|
|
|
def get_register_context(self) -> str:
|
|
|
|
|
if not self.register_ctx:
|
|
|
|
|
self.register_ctx = self.create_register_ctx()
|
|
|
|
|
|
|
|
|
|
return struct.pack("{}Q".format(len(self.register_ctx)), *self.register_ctx.values())
|
2022-11-03 14:27:28 -07:00
|
|
|
|
|
|
|
|
def get_extended_info(self):
|
|
|
|
|
if (self.has_crashed):
|
|
|
|
|
self.extended_info = self.scripted_process.extended_thread_info
|
|
|
|
|
return self.extended_info
|
|
|
|
|
|