mib created this revision.
mib added a reviewer: JDevlieghere.
mib added a project: LLDB.
Herald added a subscriber: mgorny.
mib requested review of this revision.
Herald added a subscriber: lldb-commits.
This patch introduces a new type of ScriptedProcess: CrashLogScriptedProcess.
It takes advantage of lldb's crashlog parsers and Scripted Processes to
reconstruct a static debugging session with symbolicated stackframes, instead
of just dumping out everything in the user's terminal.
To create a CrashLogScriptedProcess, the user can just import the
crashlog module and run the new `resurrect_crashlog` command with the
path the crashlog file. This will fetch and load all the libraries that
were used by the crashed thread and re-create all the frames artificially.
Signed-off-by: Med Ismail Bennani <[email protected]>
Repository:
rG LLVM Github Monorepo
https://reviews.llvm.org/D119389
Files:
lldb/bindings/python/CMakeLists.txt
lldb/examples/python/crashlog.py
lldb/examples/python/scripted_process/crashlog_scripted_process.py
lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
Index: lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
===================================================================
--- lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
+++ lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
@@ -303,6 +303,9 @@
StructuredData::DictionarySP thread_info_sp = GetInterface().GetThreadsInfo();
+ // FIXME: Need to sort the dictionary otherwise the thread ids won't match the
+ // thread indices.
+
if (!thread_info_sp)
return ScriptedInterface::ErrorWithMessage<bool>(
LLVM_PRETTY_FUNCTION,
Index: lldb/examples/python/scripted_process/crashlog_scripted_process.py
===================================================================
--- /dev/null
+++ lldb/examples/python/scripted_process/crashlog_scripted_process.py
@@ -0,0 +1,151 @@
+import os,json,struct,signal
+
+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):
+ # NOTE: https://at.apple.com/json-crashlog-spec
+ def parse_crashlog(self):
+ try:
+ crash_log = CrashLogParser().parse(self.dbg, self.crashlog_path, False)
+ except Exception as e:
+ return
+
+ self.pid = crash_log.process_id
+ self.crashed_thread_idx = crash_log.crashed_thread_idx
+ self.loaded_images = []
+
+ for thread in crash_log.threads:
+ if thread.did_crash():
+ for ident in thread.idents:
+ images = crash_log.find_images_with_identifier(ident)
+ if images:
+ for image in images:
+ #FIXME: Add to self.loaded_images and load images in lldb
+ err = image.add_module(self.target)
+ if err:
+ print(err)
+ else:
+ self.loaded_images.append(image)
+ self.threads[thread.index] = CrashLogScriptedThread(self, None, thread)
+
+ def __init__(self, target: lldb.SBTarget, args : lldb.SBStructuredData):
+ super().__init__(target, args)
+
+ if not self.target or not self.target.IsValid():
+ return
+
+ self.crashlog_path = None
+
+ crashlog_path = args.GetValueForKey("crashlog_path")
+ if crashlog_path and crashlog_path.IsValid():
+ if crashlog_path.GetType() == lldb.eStructuredDataTypeString:
+ self.crashlog_path = crashlog_path.GetStringValue(100)
+
+ if not self.crashlog_path:
+ return
+
+ self.pid = super().get_process_id()
+ self.crashed_thread_idx = super().get_most_relevant_thread_index()
+ self.parse_crashlog()
+
+ def get_memory_region_containing_address(self, addr: int) -> lldb.SBMemoryRegionInfo:
+ return None
+
+ def get_thread_with_id(self, tid: int):
+ return {}
+
+ def get_most_relevant_thread_index(self) -> int:
+ return self.crashed_thread_idx
+
+ def get_registers_for_thread(self, tid: int):
+ return {}
+
+ def read_memory_at_address(self, addr: int, size: int) -> lldb.SBData:
+ # FIXME: Return None ?
+ 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 get_process_id(self) -> int:
+ return self.pid
+
+ 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__
+
+class CrashLogScriptedThread(ScriptedThread):
+ def create_register_ctx(self):
+ if self.scripted_process.get_most_relevant_thread_index() != self.idx:
+ 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
+
+ def create_stackframes(self):
+ if self.scripted_process.get_most_relevant_thread_index() != self.idx:
+ return None
+
+ if not self.backing_thread or not len(self.backing_thread.frames):
+ return None
+
+ for frame in self.backing_thread.frames:
+ sym_addr = lldb.SBAddress()
+ sym_addr.SetLoadAddress(frame.pc, self.target)
+ if not sym_addr.IsValid():
+ continue
+ self.frames.append({"idx": frame.index, "pc": frame.pc})
+
+ 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
+ self.create_stackframes()
+
+ def get_thread_id(self) -> int:
+ return self.idx
+
+ def get_name(self) -> str:
+ return CrashLogScriptedThread.__name__ + ".thread-" + str(self.idx)
+
+ def get_state(self):
+ if self.scripted_process.get_most_relevant_thread_index() != self.idx:
+ return lldb.eStateStopped
+ return lldb.eStateCrashed
+
+ def get_stop_reason(self) -> Dict[str, Any]:
+ if self.scripted_process.get_most_relevant_thread_index() != self.idx:
+ return { "type": lldb.eStopReasonNone, "data": { }}
+ # FIXME: Investigate what stop reason should be reported when crashed
+ return { "type": lldb.eStopReasonException, "data": { "desc": "EXC_BAD_ACCESS" }}
+
+ 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())
Index: lldb/examples/python/crashlog.py
===================================================================
--- lldb/examples/python/crashlog.py
+++ lldb/examples/python/crashlog.py
@@ -65,7 +65,6 @@
from lldb.utils import symbolication
-
def read_plist(s):
if sys.version_info.major == 3:
return plistlib.loads(s)
@@ -1096,6 +1095,56 @@
for error in crash_log.errors:
print(error)
+class ResurrectCrashProcess:
+ def __init__(self, debugger, internal_dict):
+ pass
+
+ def __call__(self, debugger, command, exe_ctx, result):
+ crashlog_path = os.path.expanduser(command)
+ if not os.path.exists(crashlog_path):
+ result.PutCString("error: crashlog file %s does not exist" % crashlog_path)
+
+ try:
+ crashlog = CrashLogParser().parse(debugger, crashlog_path, False)
+ except Exception as e:
+ result.PutCString("error: python exception: %s" % e)
+ return
+
+ target = crashlog.create_target()
+ if not target:
+ result.PutCString("error: couldn't create target")
+ return
+
+ ci = debugger.GetCommandInterpreter()
+ if not ci:
+ result.PutCString("error: couldn't get command interpreter")
+ return
+
+ res = lldb.SBCommandReturnObject()
+ ci.HandleCommand('script from lldb.macosx import crashlog_scripted_process', res)
+ if not res.Succeeded():
+ result.PutCString("error: couldn't import crashlog scripted process module")
+ return
+
+ LoadCrashLogInScriptedProcess(debugger, target, crashlog_path)
+
+ def get_short_help(self):
+ return "Load crash log into an interactive scripted process."
+
+ def get_long_help(self):
+ # FIXME: Update long help
+ option_parser = CrashLogOptionParser()
+ return option_parser.format_help()
+
+def LoadCrashLogInScriptedProcess(debugger, target, crashlog_path):
+ structured_data = lldb.SBStructuredData()
+ structured_data.SetFromJSON(json.dumps({ "crashlog_path" : crashlog_path }))
+ launch_info = lldb.SBLaunchInfo(None)
+ launch_info.SetProcessPluginName("ScriptedProcess")
+ launch_info.SetScriptedProcessClassName("crashlog_scripted_process.CrashLogScriptedProcess")
+ launch_info.SetScriptedProcessDictionary(structured_data)
+ error = lldb.SBError()
+ process = target.Launch(launch_info, error)
def CreateSymbolicateCrashLogOptions(
command_name,
@@ -1249,6 +1298,8 @@
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand(
'command script add -c lldb.macosx.crashlog.Symbolicate crashlog')
+ debugger.HandleCommand(
+ 'command script add -c lldb.macosx.crashlog.ResurrectCrashProcess resurrect_crashlog')
debugger.HandleCommand(
'command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog')
print('"crashlog" and "save_crashlog" commands have been installed, use '
Index: lldb/bindings/python/CMakeLists.txt
===================================================================
--- lldb/bindings/python/CMakeLists.txt
+++ lldb/bindings/python/CMakeLists.txt
@@ -114,6 +114,7 @@
${swig_target}
${lldb_python_target_dir} "macosx"
FILES "${LLDB_SOURCE_DIR}/examples/python/crashlog.py"
+ "${LLDB_SOURCE_DIR}/examples/python/scripted_process/crashlog_scripted_process.py"
"${LLDB_SOURCE_DIR}/examples/darwin/heap_find/heap.py")
create_python_package(
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits