[Lldb-commits] [lldb] Allow scripted frames to implement custom step operations (PR #223834)
via lldb-commits
lldb-commits at lists.llvm.org
Wed Sep 16 13:59:04 PDT 2026
================
@@ -0,0 +1,242 @@
+"""
+This frame provider just adds a step plan. We use it to ensure that the
+scripted frames can change the step behavior of the frame.
+"""
+
+import lldb
+import struct
+
+from lldb.plugins.scripted_process import ScriptedFrame
+from lldb.plugins.scripted_frame_provider import ScriptedFrameProvider
+from lldb.plugins.scripted_thread_plan import ScriptedThreadPlan
+
+
+class BaseStepFrame(ScriptedFrame):
+ """A frame that wraps a real frame but changes the meaning of stepping."""
+
+ def __init__(self, thread, orig_frame, idx):
+ args = lldb.SBStructuredData()
+ super().__init__(thread, args)
+
+ self.orig_frame = orig_frame
+ self.idx = idx
+
+ def get_id(self):
+ return self.idx
+
+ def is_artificial(self):
+ # lldb won't step out to artificial frames, but these
+ # mirror real frames so they aren't artificial.
+ return False
+
+ def get_cfa(self):
+ return self.orig_frame.GetCFA()
+
+ def get_pc(self):
+ pc = self.orig_frame.GetPC()
+ return pc
+
+ def get_symbol_context(self):
+ return self.orig_frame.GetSymbolContext(lldb.eSymbolContextEverything)
+
+ def get_function_name(self):
+ return self.orig_frame.GetFunctionName() or "<wrapped>"
+
+ def get_register_context(self):
+ """Forward the wrapped frame's GPRs, packed in register_info order."""
+ regs = {}
+ for reg_set in self.orig_frame.registers:
+ if "general purpose" in reg_set.name.lower():
+ for reg in reg_set:
+ regs[reg.name] = (
+ int(reg.value, 16) if reg.value else 0,
+ reg.GetByteSize(),
+ )
+ break
+ if not regs:
+ return None
+
+ info = self.get_register_info()["registers"]
+
+ def read(entry):
+ # A register set reports a register under the name LLDB displays,
+ # which can be an alias of the architectural name the register info
+ # uses. The register info carries that alias in "alt-name".
+ if entry["name"] in regs:
+ return regs[entry["name"]]
+
+ try:
+ return regs[entry["alt-name"]]
+ except KeyError:
+ return 0, entry["bitsize"] // 8
+
+ struct_format = ""
+ struct_data = []
+ sizes = {1: "B", 2: "H", 4: "I", 8: "Q"}
+
+ for reg in info:
+ value, size = read(reg)
+ struct_format += sizes[size]
+ struct_data.append(value)
+
+ return struct.pack(struct_format, *struct_data)
+
+
+class StepTypeFrame(BaseStepFrame):
+ def get_plan_for_step_type(self, step_type):
+ dict = {
+ "class_name": "frame_provider.StepTwice",
+ "extra_args": {"step_type": str(step_type)},
+ }
+ return dict
+
+
+class BadStepFrame(BaseStepFrame):
+ def get_plan_for_step_type(self, step_type):
+ dict = {
+ "class_name": "frame_provider.Oops",
+ "extra_args": {"step_type": str(step_type)},
+ }
+ return dict
+
+
+class NoStepFrame(BaseStepFrame):
+ def get_plan_for_step_type(self, step_type):
+ dict = {
+ "class_name": "",
+ "extra_args": {"step_type": str(step_type)},
+ }
+ return dict
+
+
+class BaseFrameProvider(ScriptedFrameProvider):
+ """
+ Provider that passes through every frame from its parent StackFrameList
+ but adds a prefix to each function name.
+
+ This verifies that the provider can freely access its input_frames
+ (the parent list) without hitting circular dependencies or deadlocks.
+ """
+
+ PREFIX = "my_custom_"
+
+ def __init__(self, input_frames, args):
+ super().__init__(input_frames, args)
+
+ @staticmethod
+ def get_description():
+ return "Provider that prefixes all function names with 'my_custom_'"
+
+
+class CorrectStepProvider(BaseFrameProvider):
+ def get_frame_at_index(self, idx):
+ if idx < len(self.input_frames):
+ frame = self.input_frames[idx]
+ try:
+ returned_frame = StepTypeFrame(self.thread, frame, idx)
+ except Exception as err:
+ print(f"Got err: {str(err)}")
+ return returned_frame
+ return None
+
+
+class NoStepProvider(BaseFrameProvider):
+ def get_frame_at_index(self, idx):
+ if idx < len(self.input_frames):
+ frame = self.input_frames[idx]
+ try:
+ returned_frame = NoStepFrame(self.thread, frame, idx)
+ except Exception as err:
+ print(f"Got err: {str(err)}")
+ return returned_frame
+ return None
+
+
+class BadStepProvider(BaseFrameProvider):
+ def get_frame_at_index(self, idx):
+ if idx < len(self.input_frames):
+ frame = self.input_frames[idx]
+ try:
+ returned_frame = BadStepFrame(self.thread, frame, idx)
+ except Exception as err:
+ print(f"Got err: {str(err)}")
+ return returned_frame
+ return None
+
+
+class StepTwice(ScriptedThreadPlan):
+ """This thread plan does whatever it is asked to do twice."""
+
+ def __init__(
+ self, thread_plan: lldb.SBThreadPlan, extra_args: lldb.SBStructuredData
+ ):
+ super().__init__(thread_plan)
+ self.counter = 1
+ self.thread = self.thread_plan.GetThread()
+ step_type = extra_args.GetValueForKey("step_type")
+ if not step_type.IsValid():
+ thread_plan.SetPlanComplete(False)
+ return
+
+ step_str = step_type.GetStringValue()
+ self.step_val = int(step_str)
----------------
jimingham wrote:
That didn't work at some earlier point in the patch's development, but I must have been misdiagnosing the cause because it works now...
https://github.com/llvm/llvm-project/pull/223834
More information about the lldb-commits
mailing list