[Lldb-commits] [lldb] a0dfeec - Allow scripted frames to implement custom step operations (#223834)
via lldb-commits
lldb-commits at lists.llvm.org
Wed Sep 16 17:57:43 PDT 2026
Author: jimingham
Date: 2026-09-16T17:57:37-07:00
New Revision: a0dfeecc18b8a769cfdb589ad4a0151744716ab5
URL: https://github.com/llvm/llvm-project/commit/a0dfeecc18b8a769cfdb589ad4a0151744716ab5
DIFF: https://github.com/llvm/llvm-project/commit/a0dfeecc18b8a769cfdb589ad4a0151744716ab5.diff
LOG: Allow scripted frames to implement custom step operations (#223834)
Add an API to the ScriptedFrame interface that gives the ScriptedFrame a
chance to return a scripted ThreadPlan class name for a class that
implements the requested step type.
LLDB will consult the ScriptedFrame (if any) first. If the ScriptedFrame
returns an empty class name, the standard stepping algorithms will be
used, otherwise an instance of the provided class will be constructed -
passing in an optional - ScriptedFrame provided `extra_args` dictionary
- and added to the ThreadPlanStack to handle this step.
Added:
lldb/test/API/functionalities/scripted_frame_provider/step_with_type/Makefile
lldb/test/API/functionalities/scripted_frame_provider/step_with_type/TestFrameProviderStepping.py
lldb/test/API/functionalities/scripted_frame_provider/step_with_type/frame_provider.py
lldb/test/API/functionalities/scripted_frame_provider/step_with_type/main.c
Modified:
lldb/bindings/python/python-swigsafecast.swig
lldb/docs/use/tutorials/automating-stepping-logic.md
lldb/examples/python/templates/scripted_process.py
lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h
lldb/include/lldb/Target/StackFrame.h
lldb/include/lldb/lldb-enumerations.h
lldb/include/lldb/lldb-private-enumerations.h
lldb/source/API/SBThread.cpp
lldb/source/Commands/CommandObjectThread.cpp
lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
lldb/source/Plugins/Process/scripted/ScriptedFrame.h
lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp
lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.h
lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
lldb/source/Target/StackFrameList.cpp
Removed:
################################################################################
diff --git a/lldb/bindings/python/python-swigsafecast.swig b/lldb/bindings/python/python-swigsafecast.swig
index c5003c019aae2..e7e8b15ba69a4 100644
--- a/lldb/bindings/python/python-swigsafecast.swig
+++ b/lldb/bindings/python/python-swigsafecast.swig
@@ -158,5 +158,9 @@ PythonObject SWIGBridge::ToSWIGWrapper(lldb::DescriptionLevel level) {
return PythonInteger((int64_t) level);
}
+PythonObject SWIGBridge::ToSWIGWrapper(lldb::StepType step_type) {
+ return PythonInteger((int64_t) step_type);
+}
+
} // namespace python
} // namespace lldb_private
diff --git a/lldb/docs/use/tutorials/automating-stepping-logic.md b/lldb/docs/use/tutorials/automating-stepping-logic.md
index 564d3ec1f14d4..15e16f3532efb 100644
--- a/lldb/docs/use/tutorials/automating-stepping-logic.md
+++ b/lldb/docs/use/tutorials/automating-stepping-logic.md
@@ -39,4 +39,10 @@ used to compose more complex stepping operations. When you use subsidiary plans
your explains_stop and should_stop methods won't get called until the
subsidiary plan is done, or the process stops for an event the subsidiary plan
doesn't explain. For instance, step over plans don't explain a breakpoint hit
-while performing the step-over.
\ No newline at end of file
+while performing the step-over.
+
+Another way to trigger scripted stepping plans is to implement a ScriptedFrameProvider
+which returns ScriptedFrames which in turn implement the `get_plan_spec_for_step_type` interface.
+Then whenever the user issues any of the step operations when stopped in your ScriptedFrame,
+it will consult this API, and if it returns a step plan class, that will be used instead of
+the built-in stepping algorithms for this step type.
\ No newline at end of file
diff --git a/lldb/examples/python/templates/scripted_process.py b/lldb/examples/python/templates/scripted_process.py
index 215659401e258..4358aa552c2ae 100644
--- a/lldb/examples/python/templates/scripted_process.py
+++ b/lldb/examples/python/templates/scripted_process.py
@@ -517,6 +517,15 @@ def get_pc(self) -> Optional[int]:
"""
return None
+ def get_cfa(self) -> int:
+ """Get the Call Frame Address for this frame.
+ By default pass the ID of this frame so the CFA's and the
+ ID's order the same way on this stop. This won't support
+ step-in and step-out, for those the frames have to have a
+ stable CFA.
+ """
+ return self.get_id()
+
def get_symbol_context(self) -> Optional[lldb.SBSymbolContext]:
"""Get the scripted frame symbol context.
@@ -630,6 +639,22 @@ def get_register_context(self) -> str:
"""
pass
+ # def get_plan_spec_for_step_type(self, step_type : lldb.StepType):
+ # """Optional method. If this ScriptedFrame can produce a ThreadPlan
+ # that implements the given step_type, then it should return a Python
+ # dictionary with the `class_name` key giving the name of a class that
+ # implements the step plan, and an optional extra_args dictionary that
+ # will be passed to the constructor of your step-plan class. If the
+ # class name is an empty string, that means use the standard stepping
+ # algorithms for this step.
+ # The body below tells lldb to fall back to the standard stepping
+ # algorithm. However, the method is commented out in the base class,
+ # since if you really don't intend to provide stepping support,
+ # it's simpler to just not implement this API."""
+ #
+ # dict = {"class_name" : "", extra_args : {"step_type" : str(step_type)}
+ # return dict
+
class PassthroughScriptedProcess(ScriptedProcess):
"""A reference `ScriptedProcess` subclass that forwards every request to
a "driving" process running in another target of the same debugger.
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h
index b33688d062da3..0666f2daaa73c 100644
--- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h
@@ -28,6 +28,8 @@ class ScriptedFrameInterface : virtual public ScriptedInterface {
virtual lldb::addr_t GetPC() { return LLDB_INVALID_ADDRESS; }
+ virtual lldb::addr_t GetCFA() { return LLDB_INVALID_ADDRESS; }
+
virtual std::optional<SymbolContext> GetSymbolContext() {
return std::nullopt;
}
@@ -74,6 +76,12 @@ class ScriptedFrameInterface : virtual public ScriptedInterface {
Status &error) {
return nullptr;
}
+
+ virtual llvm::Expected<ScriptedMetadata>
+ GetThreadPlanMetadataForStepType(lldb::StepType step_type) {
+ return llvm::createStringError(
+ "the current interpreter doesn't support scripted stepping");
+ }
};
} // namespace lldb_private
diff --git a/lldb/include/lldb/Target/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h
index 7ad1aaacb91a3..e6ac3d1ad5275 100644
--- a/lldb/include/lldb/Target/StackFrame.h
+++ b/lldb/include/lldb/Target/StackFrame.h
@@ -563,6 +563,14 @@ class StackFrame : public ExecutionContextScope,
virtual lldb::RecognizedStackFrameSP GetRecognizedFrame();
+ // Return a ThreadPlanSP or an error. Returning an empty ThreadPlanSP means
+ // the frame doesn't have a custom step plan, only return an error if the
+ // frame intended to provide a plan but there was an error in doing so.
+ virtual llvm::Expected<lldb::ThreadPlanSP>
+ GetThreadPlanForStepType(lldb::StepType step_type) {
+ return lldb::ThreadPlanSP();
+ }
+
/// Get the identifier of the StackFrameList that contains this frame.
///
/// Returns the StackFrameList identifier that contains this frame, allowing
diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h
index eff007806499e..b79e4ff9a56fb 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -152,6 +152,17 @@ enum RunMode { eOnlyThisThread, eAllThreads, eOnlyDuringStepping };
/// Execution directions
enum RunDirection { eRunForward, eRunReverse };
+// Thread Step Types
+enum StepType {
+ eStepTypeNone = 0,
+ eStepTypeTrace, ///< Single step one instruction.
+ eStepTypeTraceOver, ///< Single step one instruction, stepping over.
+ eStepTypeInto, ///< Single step into a specified context.
+ eStepTypeOver, ///< Single step over a specified context.
+ eStepTypeOut, ///< Single step out a specified context.
+ eStepTypeScripted ///< A step type implemented by the script interpreter.
+};
+
/// Byte ordering definitions.
enum ByteOrder {
eByteOrderInvalid = 0,
diff --git a/lldb/include/lldb/lldb-private-enumerations.h b/lldb/include/lldb/lldb-private-enumerations.h
index f6278dbc74325..328846e7bd274 100644
--- a/lldb/include/lldb/lldb-private-enumerations.h
+++ b/lldb/include/lldb/lldb-private-enumerations.h
@@ -17,17 +17,6 @@
namespace lldb_private {
-// Thread Step Types
-enum StepType {
- eStepTypeNone,
- eStepTypeTrace, ///< Single step one instruction.
- eStepTypeTraceOver, ///< Single step one instruction, stepping over.
- eStepTypeInto, ///< Single step into a specified context.
- eStepTypeOver, ///< Single step over a specified context.
- eStepTypeOut, ///< Single step out a specified context.
- eStepTypeScripted ///< A step type implemented by the script interpreter.
-};
-
// Address Types
enum AddressType {
eAddressTypeInvalid = 0,
diff --git a/lldb/source/API/SBThread.cpp b/lldb/source/API/SBThread.cpp
index c3c2e80af6913..a44a1efb51e99 100644
--- a/lldb/source/API/SBThread.cpp
+++ b/lldb/source/API/SBThread.cpp
@@ -496,11 +496,32 @@ void SBThread::StepOver(lldb::RunMode stop_other_threads, SBError &error) {
Thread *thread = exe_ctx->GetThreadPtr();
bool abort_other_plans = false;
StackFrameSP frame_sp(thread->GetStackFrameAtIndex(0));
+ if (!frame_sp) {
+ error.SetErrorString("No frame to step over");
+ return;
+ }
- Status new_plan_status;
ThreadPlanSP new_plan_sp;
- if (frame_sp) {
- if (frame_sp->HasDebugInformation()) {
+ lldb::StepType step_type =
+ frame_sp->HasDebugInformation() || frame_sp->IsSynthetic()
+ ? eStepTypeOver
+ : eStepTypeTraceOver;
+
+ llvm::Expected<lldb::ThreadPlanSP> frame_plan_result =
+ frame_sp->GetThreadPlanForStepType(step_type);
+ if (auto llvm_err = frame_plan_result.takeError()) {
+ error.SetErrorStringWithFormat("scripted frame provider got an error "
+ "while constructing step plan: \"%s\"",
+ llvm::toString(std::move(llvm_err)).c_str());
+ return;
+ }
+ new_plan_sp = *frame_plan_result;
+
+ if (new_plan_sp) {
+ thread->QueueThreadPlan(new_plan_sp, false);
+ } else {
+ Status new_plan_status;
+ if (step_type == eStepTypeOver) {
const LazyBool avoid_no_debug = eLazyBoolCalculate;
SymbolContext sc(frame_sp->GetSymbolContext(eSymbolContextEverything));
new_plan_sp = thread->QueueThreadPlanForStepOverRange(
@@ -550,31 +571,56 @@ void SBThread::StepInto(const char *target_name, uint32_t end_line,
StackFrameSP frame_sp(thread->GetStackFrameAtIndex(0));
ThreadPlanSP new_plan_sp;
Status new_plan_status;
+ lldb::StepType step_type;
- if (frame_sp && frame_sp->HasDebugInformation()) {
- SymbolContext sc(frame_sp->GetSymbolContext(eSymbolContextEverything));
- AddressRange range;
- if (end_line == LLDB_INVALID_LINE_NUMBER)
- range = sc.line_entry.range;
- else {
- llvm::Error err = sc.GetAddressRangeFromHereToEndLine(end_line, range);
- if (err) {
- error = Status::FromErrorString(llvm::toString(std::move(err)).c_str());
- return;
- }
+ if (frame_sp && (frame_sp->HasDebugInformation() || frame_sp->IsSynthetic()))
+ step_type = eStepTypeInto;
+ else
+ step_type = eStepTypeTrace;
+
+ // First see if the Frame has some special way to do this step:
+ if (frame_sp) {
+ llvm::Expected<lldb::ThreadPlanSP> frame_plan_result =
+ frame_sp->GetThreadPlanForStepType(step_type);
+ if (auto llvm_err = frame_plan_result.takeError()) {
+ error.SetErrorStringWithFormat(
+ "scripted frame provider got an error "
+ "while constructing step plan: \"%s\"",
+ llvm::toString(std::move(llvm_err)).c_str());
+ return;
}
+ new_plan_sp = *frame_plan_result;
+ }
- const LazyBool step_out_avoids_code_without_debug_info =
- eLazyBoolCalculate;
- const LazyBool step_in_avoids_code_without_debug_info =
- eLazyBoolCalculate;
- new_plan_sp = thread->QueueThreadPlanForStepInRange(
- abort_other_plans, range, sc, target_name, stop_other_threads,
- new_plan_status, step_in_avoids_code_without_debug_info,
- step_out_avoids_code_without_debug_info);
+ if (new_plan_sp) {
+ thread->QueueThreadPlan(new_plan_sp, false);
} else {
- new_plan_sp = thread->QueueThreadPlanForStepSingleInstruction(
- false, abort_other_plans, stop_other_threads, new_plan_status);
+ if (step_type == eStepTypeInto) {
+ SymbolContext sc(frame_sp->GetSymbolContext(eSymbolContextEverything));
+ AddressRange range;
+ if (end_line == LLDB_INVALID_LINE_NUMBER)
+ range = sc.line_entry.range;
+ else {
+ llvm::Error err = sc.GetAddressRangeFromHereToEndLine(end_line, range);
+ if (err) {
+ error =
+ Status::FromErrorString(llvm::toString(std::move(err)).c_str());
+ return;
+ }
+ }
+
+ const LazyBool step_out_avoids_code_without_debug_info =
+ eLazyBoolCalculate;
+ const LazyBool step_in_avoids_code_without_debug_info =
+ eLazyBoolCalculate;
+ new_plan_sp = thread->QueueThreadPlanForStepInRange(
+ abort_other_plans, range, sc, target_name, stop_other_threads,
+ new_plan_status, step_in_avoids_code_without_debug_info,
+ step_out_avoids_code_without_debug_info);
+ } else {
+ new_plan_sp = thread->QueueThreadPlanForStepSingleInstruction(
+ false, abort_other_plans, stop_other_threads, new_plan_status);
+ }
}
if (new_plan_status.Success())
@@ -608,13 +654,34 @@ void SBThread::StepOut(SBError &error) {
bool abort_other_plans = false;
bool stop_other_threads = false;
+ ThreadPlanSP new_plan_sp;
+
Thread *thread = exe_ctx->GetThreadPtr();
+ StackFrameSP frame_sp(thread->GetStackFrameAtIndex(0));
+ if (frame_sp) {
+ llvm::Expected<lldb::ThreadPlanSP> frame_plan_result =
+ frame_sp->GetThreadPlanForStepType(eStepTypeOut);
+ if (auto llvm_err = frame_plan_result.takeError()) {
+ error.SetErrorStringWithFormat(
+ "scripted frame provider got an error "
+ "while constructing step plan: \"%s\"",
+ llvm::toString(std::move(llvm_err)).c_str());
+ return;
+ }
+ new_plan_sp = *frame_plan_result;
+ }
- const LazyBool avoid_no_debug = eLazyBoolCalculate;
Status new_plan_status;
- ThreadPlanSP new_plan_sp(thread->QueueThreadPlanForStepOut(
- abort_other_plans, nullptr, false, stop_other_threads, eVoteYes,
- eVoteNoOpinion, 0, new_plan_status, avoid_no_debug));
+ if (new_plan_sp) {
+ // FIXME: Carry over stop_other_threads, and avoid_no_debug to
+ // the new plan?
+ thread->QueueThreadPlan(new_plan_sp, false);
+ } else {
+ const LazyBool avoid_no_debug = eLazyBoolCalculate;
+ new_plan_sp = thread->QueueThreadPlanForStepOut(
+ abort_other_plans, nullptr, false, stop_other_threads, eVoteYes,
+ eVoteNoOpinion, 0, new_plan_status, avoid_no_debug);
+ }
if (new_plan_status.Success())
error = ResumeNewPlan(std::move(*exe_ctx), new_plan_sp.get());
@@ -693,9 +760,28 @@ void SBThread::StepInstruction(bool step_over, SBError &error) {
}
Thread *thread = exe_ctx->GetThreadPtr();
+ StackFrameSP frame_sp(thread->GetStackFrameAtIndex(0));
+ lldb::StepType step_type = step_over ? eStepTypeTraceOver : eStepTypeTrace;
+
+ ThreadPlanSP new_plan_sp;
+
+ if (frame_sp) {
+ llvm::Expected<lldb::ThreadPlanSP> frame_plan_result =
+ frame_sp->GetThreadPlanForStepType(step_type);
+ if (auto llvm_err = frame_plan_result.takeError()) {
+ error.SetErrorStringWithFormat(
+ "scripted frame provider got an error "
+ "while constructing step plan: \"%s\"",
+ llvm::toString(std::move(llvm_err)).c_str());
+ return;
+ }
+ new_plan_sp = *frame_plan_result;
+ }
+
Status new_plan_status;
- ThreadPlanSP new_plan_sp(thread->QueueThreadPlanForStepSingleInstruction(
- step_over, false, true, new_plan_status));
+ if (!new_plan_sp)
+ new_plan_sp = thread->QueueThreadPlanForStepSingleInstruction(
+ step_over, false, true, new_plan_status);
if (new_plan_status.Success())
error = ResumeNewPlan(std::move(*exe_ctx), new_plan_sp.get());
diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp
index e8873b5048f06..74a138690d477 100644
--- a/lldb/source/Commands/CommandObjectThread.cpp
+++ b/lldb/source/Commands/CommandObjectThread.cpp
@@ -663,96 +663,117 @@ class CommandObjectThreadStepWithTypeAndScope : public CommandObjectParsed {
ThreadPlanSP new_plan_sp;
Status new_plan_status;
- if (m_step_type == eStepTypeInto) {
- StackFrame *frame = thread->GetStackFrameAtIndex(0).get();
- assert(frame != nullptr);
+ StackFrame *frame = thread->GetStackFrameAtIndex(0).get();
+ assert(frame != nullptr);
+
+ // First see if the frame has a custom step plan for us:
+ if (frame) {
+ llvm::Expected<lldb::ThreadPlanSP> frame_plan_result =
+ frame->GetThreadPlanForStepType(m_step_type);
+ if (auto llvm_err = frame_plan_result.takeError()) {
+ result.AppendErrorWithFormat(
+ "scripted frame provider got an error "
+ "while constructing step plan: \"%s\"",
+ llvm::toString(std::move(llvm_err)).c_str());
+ return;
+ }
+ new_plan_sp = *frame_plan_result;
+ }
- if (frame->HasDebugInformation()) {
- AddressRange range;
- SymbolContext sc = frame->GetSymbolContext(eSymbolContextEverything);
- if (m_options.m_end_line != LLDB_INVALID_LINE_NUMBER) {
- llvm::Error err =
- sc.GetAddressRangeFromHereToEndLine(m_options.m_end_line, range);
- if (err) {
- result.AppendErrorWithFormatv("invalid end-line option: {0}.",
- llvm::toString(std::move(err)));
- return;
- }
- } else if (m_options.m_end_line_is_block_end) {
- Status error;
- Block *block = frame->GetSymbolContext(eSymbolContextBlock).block;
- if (!block) {
- result.AppendErrorWithFormat("Could not find the current block");
- return;
- }
+ if (new_plan_sp) {
+ thread->QueueThreadPlan(new_plan_sp, abort_other_plans);
+ new_plan_sp->SetStopOthers(bool_stop_other_threads);
+ } else {
+ if (m_step_type == eStepTypeInto) {
+ if (frame->HasDebugInformation()) {
+ AddressRange range;
+ SymbolContext sc = frame->GetSymbolContext(eSymbolContextEverything);
+ if (m_options.m_end_line != LLDB_INVALID_LINE_NUMBER) {
+ llvm::Error err = sc.GetAddressRangeFromHereToEndLine(
+ m_options.m_end_line, range);
+ if (err) {
+ result.AppendErrorWithFormatv("invalid end-line option: {0}.",
+ llvm::toString(std::move(err)));
+ return;
+ }
+ } else if (m_options.m_end_line_is_block_end) {
+ Status error;
+ Block *block = frame->GetSymbolContext(eSymbolContextBlock).block;
+ if (!block) {
+ result.AppendErrorWithFormat("Could not find the current block");
+ return;
+ }
- AddressRange block_range;
- Address pc_address = frame->GetFrameCodeAddress();
- block->GetRangeContainingAddress(pc_address, block_range);
- if (!block_range.GetBaseAddress().IsValid()) {
- result.AppendErrorWithFormat(
- "Could not find the current block address");
- return;
+ AddressRange block_range;
+ Address pc_address = frame->GetFrameCodeAddress();
+ block->GetRangeContainingAddress(pc_address, block_range);
+ if (!block_range.GetBaseAddress().IsValid()) {
+ result.AppendErrorWithFormat(
+ "Could not find the current block address");
+ return;
+ }
+ lldb::addr_t pc_offset_in_block =
+ pc_address.GetFileAddress() -
+ block_range.GetBaseAddress().GetFileAddress();
+ lldb::addr_t range_length =
+ block_range.GetByteSize() - pc_offset_in_block;
+ range = AddressRange(pc_address, range_length);
+ } else {
+ range = sc.line_entry.range;
}
- lldb::addr_t pc_offset_in_block =
- pc_address.GetFileAddress() -
- block_range.GetBaseAddress().GetFileAddress();
- lldb::addr_t range_length =
- block_range.GetByteSize() - pc_offset_in_block;
- range = AddressRange(pc_address, range_length);
- } else {
- range = sc.line_entry.range;
- }
- new_plan_sp = thread->QueueThreadPlanForStepInRange(
- abort_other_plans, range,
- frame->GetSymbolContext(eSymbolContextEverything),
- m_options.m_step_in_target, stop_other_threads, new_plan_status,
- m_options.m_step_in_avoid_no_debug,
- m_options.m_step_out_avoid_no_debug);
-
- if (new_plan_sp && !m_options.m_avoid_regexp.empty()) {
- ThreadPlanStepInRange *step_in_range_plan =
- static_cast<ThreadPlanStepInRange *>(new_plan_sp.get());
- step_in_range_plan->SetAvoidRegexp(m_options.m_avoid_regexp.c_str());
- }
- } else
+ new_plan_sp = thread->QueueThreadPlanForStepInRange(
+ abort_other_plans, range,
+ frame->GetSymbolContext(eSymbolContextEverything),
+ m_options.m_step_in_target, stop_other_threads, new_plan_status,
+ m_options.m_step_in_avoid_no_debug,
+ m_options.m_step_out_avoid_no_debug);
+
+ if (new_plan_sp && !m_options.m_avoid_regexp.empty()) {
+ ThreadPlanStepInRange *step_in_range_plan =
+ static_cast<ThreadPlanStepInRange *>(new_plan_sp.get());
+ step_in_range_plan->SetAvoidRegexp(
+ m_options.m_avoid_regexp.c_str());
+ }
+ } else
+ new_plan_sp = thread->QueueThreadPlanForStepSingleInstruction(
+ false, abort_other_plans, bool_stop_other_threads,
+ new_plan_status);
+ } else if (m_step_type == eStepTypeOver) {
+
+ if (frame->HasDebugInformation())
+ new_plan_sp = thread->QueueThreadPlanForStepOverRange(
+ abort_other_plans,
+ frame->GetSymbolContext(eSymbolContextEverything).line_entry,
+ frame->GetSymbolContext(eSymbolContextEverything),
+ stop_other_threads, new_plan_status,
+ m_options.m_step_out_avoid_no_debug);
+ else
+ new_plan_sp = thread->QueueThreadPlanForStepSingleInstruction(
+ true, abort_other_plans, bool_stop_other_threads,
+ new_plan_status);
+ } else if (m_step_type == eStepTypeTrace) {
new_plan_sp = thread->QueueThreadPlanForStepSingleInstruction(
false, abort_other_plans, bool_stop_other_threads, new_plan_status);
- } else if (m_step_type == eStepTypeOver) {
- StackFrame *frame = thread->GetStackFrameAtIndex(0).get();
-
- if (frame->HasDebugInformation())
- new_plan_sp = thread->QueueThreadPlanForStepOverRange(
- abort_other_plans,
- frame->GetSymbolContext(eSymbolContextEverything).line_entry,
- frame->GetSymbolContext(eSymbolContextEverything),
- stop_other_threads, new_plan_status,
- m_options.m_step_out_avoid_no_debug);
- else
+ } else if (m_step_type == eStepTypeTraceOver) {
new_plan_sp = thread->QueueThreadPlanForStepSingleInstruction(
true, abort_other_plans, bool_stop_other_threads, new_plan_status);
- } else if (m_step_type == eStepTypeTrace) {
- new_plan_sp = thread->QueueThreadPlanForStepSingleInstruction(
- false, abort_other_plans, bool_stop_other_threads, new_plan_status);
- } else if (m_step_type == eStepTypeTraceOver) {
- new_plan_sp = thread->QueueThreadPlanForStepSingleInstruction(
- true, abort_other_plans, bool_stop_other_threads, new_plan_status);
- } else if (m_step_type == eStepTypeOut) {
- new_plan_sp = thread->QueueThreadPlanForStepOut(
- abort_other_plans, nullptr, false, bool_stop_other_threads, eVoteYes,
- eVoteNoOpinion,
- thread->GetSelectedFrameIndex(DoNoSelectMostRelevantFrame),
- new_plan_status, m_options.m_step_out_avoid_no_debug);
- } else if (m_step_type == eStepTypeScripted) {
- ScriptedMetadata scripted_metadata(m_class_options.GetName(),
- m_class_options.GetStructuredData());
- new_plan_sp = thread->QueueThreadPlanForStepScripted(
- abort_other_plans, scripted_metadata, bool_stop_other_threads,
- new_plan_status);
- } else {
- result.AppendError("step type is not supported");
- return;
+ } else if (m_step_type == eStepTypeOut) {
+ new_plan_sp = thread->QueueThreadPlanForStepOut(
+ abort_other_plans, nullptr, false, bool_stop_other_threads,
+ eVoteYes, eVoteNoOpinion,
+ thread->GetSelectedFrameIndex(DoNoSelectMostRelevantFrame),
+ new_plan_status, m_options.m_step_out_avoid_no_debug);
+ } else if (m_step_type == eStepTypeScripted) {
+ ScriptedMetadata scripted_metadata(m_class_options.GetName(),
+ m_class_options.GetStructuredData());
+ new_plan_sp = thread->QueueThreadPlanForStepScripted(
+ abort_other_plans, scripted_metadata, bool_stop_other_threads,
+ new_plan_status);
+ } else {
+ result.AppendError("step type is not supported");
+ return;
+ }
}
// If we got a new plan, then set it to be a controlling plan (User level
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
index 09b20fafd5e85..9df79fadb8da2 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
+++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
@@ -23,8 +23,10 @@
#include "lldb/Symbol/VariableList.h"
#include "lldb/Target/DynamicRegisterInfo.h"
#include "lldb/Target/ExecutionContext.h"
+#include "lldb/Target/ScriptedThreadPlan.h"
#include "lldb/Target/StackFrame.h"
#include "lldb/Target/Thread.h"
+#include "lldb/Target/ThreadPlan.h"
#include "lldb/Utility/DataBufferHeap.h"
#include "lldb/Utility/LLDBAssert.h"
#include "lldb/Utility/LLDBLog.h"
@@ -105,6 +107,8 @@ ScriptedFrame::Create(ThreadSP thread_sp,
lldb::user_id_t frame_id = scripted_frame_interface->GetID();
lldb::addr_t pc = scripted_frame_interface->GetPC();
+ lldb::addr_t cfa = scripted_frame_interface->GetCFA();
+
SymbolContext sc;
Address symbol_addr;
if (pc != LLDB_INVALID_ADDRESS) {
@@ -118,18 +122,18 @@ ScriptedFrame::Create(ThreadSP thread_sp,
sc = *maybe_sym_ctx;
return std::make_shared<ScriptedFrame>(thread_sp, scripted_frame_interface,
- frame_id, pc, sc,
+ frame_id, pc, cfa, sc,
owned_script_object_sp);
}
ScriptedFrame::ScriptedFrame(ThreadSP thread_sp,
ScriptedFrameInterfaceSP interface_sp,
lldb::user_id_t id, lldb::addr_t pc,
- SymbolContext &sym_ctx,
+ lldb::addr_t cfa, SymbolContext &sym_ctx,
StructuredData::GenericSP script_object_sp)
: StackFrame(thread_sp, /*frame_idx=*/id,
/*concrete_frame_idx=*/id, /*reg_context_sp=*/nullptr,
- /*cfa=*/0, /*pc=*/pc,
+ /*cfa=*/cfa, /*pc=*/pc,
/*behaves_like_zeroth_frame=*/!id, /*symbol_ctx=*/&sym_ctx),
m_scripted_frame_interface_sp(interface_sp),
m_script_object_sp(script_object_sp) {
@@ -353,3 +357,26 @@ lldb::ValueObjectSP ScriptedFrame::GetValueForVariableExpressionPath(
return m_scripted_frame_interface_sp->GetValueObjectForVariableExpression(
var_expr, options, error);
}
+
+llvm::Expected<lldb::ThreadPlanSP>
+ScriptedFrame::GetThreadPlanForStepType(lldb::StepType step_type) {
+ llvm::Expected<ScriptedMetadata> metadata =
+ m_scripted_frame_interface_sp->GetThreadPlanMetadataForStepType(
+ step_type);
+ if (!metadata)
+ return metadata.takeError();
+
+ // Returning a ScriptedMetadata with an empty class name means that the
+ // scripted frame doesn't know how to step. That's not an error, but don't
+ // try to make a thread plan in this case. The bool operator of
+ // ScriptedMetadata checks the class name...
+
+ ThreadSP thread_sp = GetThread();
+ if (thread_sp && !metadata->GetClassName().empty()) {
+ lldb::ThreadPlanSP new_plan_sp(
+ new ScriptedThreadPlan(*thread_sp.get(), *metadata));
+ return new_plan_sp;
+ }
+
+ return lldb::ThreadPlanSP();
+}
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h b/lldb/source/Plugins/Process/scripted/ScriptedFrame.h
index 679d81bc46bdc..aa67d771ba536 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h
+++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.h
@@ -24,7 +24,7 @@ class ScriptedFrame : public lldb_private::StackFrame {
public:
ScriptedFrame(lldb::ThreadSP thread_sp,
lldb::ScriptedFrameInterfaceSP interface_sp,
- lldb::user_id_t frame_idx, lldb::addr_t pc,
+ lldb::user_id_t frame_idx, lldb::addr_t pc, lldb::addr_t cfa,
SymbolContext &sym_ctx,
StructuredData::GenericSP script_object_sp = nullptr);
@@ -82,6 +82,9 @@ class ScriptedFrame : public lldb_private::StackFrame {
uint32_t options, lldb::VariableSP &var_sp, Status &error,
lldb::DILMode mode = lldb::eDILModeFull) override;
+ llvm::Expected<lldb::ThreadPlanSP>
+ GetThreadPlanForStepType(lldb::StepType step_type) override;
+
bool isA(const void *ClassID) const override {
return ClassID == &ID || StackFrame::isA(ClassID);
}
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp
index 8a6dd2d322992..e4dc97852abbb 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp
@@ -62,6 +62,17 @@ lldb::addr_t ScriptedFramePythonInterface::GetPC() {
return obj->GetUnsignedIntegerValue(LLDB_INVALID_ADDRESS);
}
+lldb::addr_t ScriptedFramePythonInterface::GetCFA() {
+ Status error;
+ StructuredData::ObjectSP obj = Dispatch("get_cfa", error);
+
+ if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
+ error))
+ return LLDB_INVALID_ADDRESS;
+
+ return obj->GetUnsignedIntegerValue(LLDB_INVALID_ADDRESS);
+}
+
std::optional<SymbolContext> ScriptedFramePythonInterface::GetSymbolContext() {
Status error;
auto sym_ctx = Dispatch<SymbolContext>("get_symbol_context", error);
@@ -196,6 +207,66 @@ ScriptedFramePythonInterface::GetValueObjectForVariableExpression(
return val;
}
+llvm::Expected<ScriptedMetadata>
+ScriptedFramePythonInterface::GetThreadPlanMetadataForStepType(
+ lldb::StepType step_type) {
+ Status error;
+
+ ScriptedMetadata no_plan_return("", StructuredData::DictionarySP());
+ StructuredData::DictionarySP dict_sp = Dispatch<StructuredData::DictionarySP>(
+ "get_plan_spec_for_step_type", error, step_type);
+ if (error.Fail()) {
+ // There are two cases here. The `get_plan_spec_for_step_type` didn't
+ // exist, in which case we should return no_plan_return
+ // FIXME - Dispatch should distinguish between these two cases in a way
+ // that's more definitive than this.
+ llvm::StringRef err_str(error.AsCString());
+ if (err_str.contains(
+ "object has no attribute 'get_plan_spec_for_step_type'"))
+ return no_plan_return;
+ else
+ return llvm::createStringError(
+ "error dispatching get_plan_spec_for_step_type: %s",
+ error.AsCString());
+ }
+
+ // The return value is an StructuredData::Dictionary with the class name and
+ // the extra args for the call:
+ if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION,
+ dict_sp, error))
+ return llvm::createStringError(
+ "return from get_plan_spec_for_step_type not a valid object: %s",
+ error.AsCString());
+
+ StructuredData::ObjectSP obj = dict_sp->GetValueForKey("class_name");
+ if (!obj)
+ return llvm::createStringError("Required 'class_name' field not provided.");
+
+ std::string class_string = obj->GetStringValue().str();
+ // Passing out an empty class name is they way to say the frame provider
+ // doesn't know how to step from here, and the regular method should be tried
+ // instead. So we only need to make sure the class exists if we were given a
+ // string:
+ if (!class_string.empty()) {
+ const char *class_str = class_string.c_str();
+ if (!m_interpreter.CheckObjectExists(class_str))
+ return llvm::createStringError(
+ "class_name specified a class: '%s' that does not exist.", class_str);
+ }
+
+ // Look for extra args, this is optional:
+ StructuredData::Dictionary *extra_args_ptr = nullptr;
+ StructuredData::DictionarySP extra_args_sp;
+ if (dict_sp->GetValueForKeyAsDictionary("extra_args", extra_args_ptr))
+ extra_args_sp = std::static_pointer_cast<StructuredData::Dictionary>(
+ extra_args_ptr->shared_from_this());
+
+ // Now make a new thread plan for stepping using the provided class name and
+ // extra args.
+ ScriptedMetadata plan_metadata(class_string, extra_args_sp);
+ return plan_metadata;
+}
+
void ScriptedFramePythonInterface::Initialize() {
PluginManager::RegisterPlugin(
GetPluginNameStatic(),
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.h
index ab48da85c16b5..4668082761d33 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.h
@@ -34,6 +34,8 @@ class ScriptedFramePythonInterface : public ScriptedFrameInterface,
lldb::addr_t GetPC() override;
+ lldb::addr_t GetCFA() override;
+
std::optional<SymbolContext> GetSymbolContext() override;
std::optional<std::string> GetFunctionName() override;
@@ -58,6 +60,8 @@ class ScriptedFramePythonInterface : public ScriptedFrameInterface,
lldb::ValueObjectSP
GetValueObjectForVariableExpression(llvm::StringRef expr, uint32_t options,
Status &status) override;
+ llvm::Expected<ScriptedMetadata>
+ GetThreadPlanMetadataForStepType(lldb::StepType step_type) override;
static void Initialize();
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
index b1a769af96e92..fe87b2b1f1e60 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
@@ -288,6 +288,25 @@ ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DescriptionLevel>(
return static_cast<lldb::DescriptionLevel>(unsigned_val);
}
+template <>
+lldb::StepType
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StepType>(
+ python::PythonObject &p, Status &error) {
+ lldb::StepType ret_val = lldb::eStepTypeNone;
+
+ llvm::Expected<unsigned long long> unsigned_or_err = p.AsUnsignedLongLong();
+ if (!unsigned_or_err) {
+ error = (Status::FromError(unsigned_or_err.takeError()));
+ return ret_val;
+ }
+ unsigned long long unsigned_val = *unsigned_or_err;
+ if (unsigned_val >= lldb::eStepTypeScripted) {
+ error = Status("value too large for lldb::StepType.");
+ return ret_val;
+ }
+ return static_cast<lldb::StepType>(unsigned_val);
+}
+
template <>
lldb::StackFrameListSP
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameListSP>(
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
index aecb7ab68d017..f3510c2d6c3f2 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
@@ -733,6 +733,10 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
return python::SWIGBridge::ToSWIGWrapper(arg);
}
+ python::PythonObject Transform(lldb::StepType arg) {
+ return python::SWIGBridge::ToSWIGWrapper(arg);
+ }
+
python::PythonObject Transform(lldb::ValueObjectSP arg) {
return python::SWIGBridge::ToSWIGWrapper(arg);
}
@@ -956,6 +960,11 @@ lldb::DescriptionLevel
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DescriptionLevel>(
python::PythonObject &p, Status &error);
+template <>
+lldb::StepType
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StepType>(
+ python::PythonObject &p, Status &error);
+
template <>
lldb::StackFrameListSP
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameListSP>(
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
index f09284b3aa697..7b191cd7fd721 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
@@ -106,6 +106,7 @@ class SWIGBridge {
static PythonObject ToSWIGWrapper(lldb::ProcessLaunchInfoSP launch_info_sp);
static PythonObject ToSWIGWrapper(lldb::DataExtractorSP data_extractor_sp);
static PythonObject ToSWIGWrapper(lldb::DescriptionLevel level);
+ static PythonObject ToSWIGWrapper(lldb::StepType step_type);
static PythonObject
ToSWIGWrapper(std::unique_ptr<lldb::SBStructuredData> data_sb);
diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp
index b11fdc6ddbf91..af069169f29d6 100644
--- a/lldb/source/Target/StackFrameList.cpp
+++ b/lldb/source/Target/StackFrameList.cpp
@@ -111,7 +111,9 @@ bool SyntheticStackFrameList::FetchFramesUpTo(
break;
}
StackFrameSP frame_sp = *frame_or_err;
- if (frame_sp->IsSynthetic())
+ // Synthetic frames can provide a CFA. If they haven't, set it to the
+ // frame index which will at least order the frames on this stop.
+ if (frame_sp->IsSynthetic() && !frame_sp->GetStackID().IsValid())
frame_sp->GetStackID().SetCFA(num_synthetic_frames++,
GetThread().GetProcess().get());
// Set the frame list weak pointer so ExecutionContextRef can resolve
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/step_with_type/Makefile b/lldb/test/API/functionalities/scripted_frame_provider/step_with_type/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/step_with_type/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/step_with_type/TestFrameProviderStepping.py b/lldb/test/API/functionalities/scripted_frame_provider/step_with_type/TestFrameProviderStepping.py
new file mode 100644
index 0000000000000..0144215ef97c7
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/step_with_type/TestFrameProviderStepping.py
@@ -0,0 +1,138 @@
+"""
+Test the "step with type" feature of scripted frames.
+"""
+
+import os
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import TestBase
+from lldbsuite.test import lldbutil
+
+
+class TestFrameProviderStepping(TestBase):
+ NO_DEBUG_INFO_TESTCASE = True
+
+ # The frame list IDs used by 'bt --provider' match the descriptor IDs
+ # returned by RegisterScriptedFrameProvider:
+ # 0 = base unwinder, 1 = first provider, 2 = second provider, etc.
+ UNWINDER_FRAME_LIST_ID = 0
+ FIRST_PROVIDER_FRAME_LIST_ID = 1
+ SECOND_PROVIDER_FRAME_LIST_ID = 2
+
+ def setUp(self):
+ TestBase.setUp(self)
+ self.source = lldb.SBFileSpec("main.c")
+
+ @expectedFailureAll(
+ oslist=["linux"],
+ archs=["arm$"],
+ bugnumber="github.com/llvm/llvm-project/issues/191859",
+ )
+ def test_step_with_errors_api(self):
+ """Test that errors in creating the step plan are reported"""
+ self.do_test_with_errors(False)
+
+ def test_step_with_errors_command(self):
+ """Test that errors in creating the step plan are reported"""
+ self.do_test_with_errors(True)
+
+ def test_step_with_type_api(self):
+ """
+ Test that a provider can alter the meaning of step in a
+ frame using the SB API's to step.
+ """
+ self.do_test(False)
+
+ def test_step_with_type_command(self):
+ """
+ Test that a provider can alter the meaning of step in a
+ frame using the command line commands to step.
+ """
+ self.do_test(True)
+
+ def test_with_no_step(self):
+ """Test that a returning an empty class name falls back to the
+ standard stepping algorithms"""
+ self.common_startup("frame_provider.NoStepProvider")
+ # Just do this for the API:
+ error = lldb.SBError()
+ self.thread.StepOver(lldb.eOnlyDuringStepping, error)
+ self.assertSuccess(error)
+ # We should have only stepped once, so the counter should be
+ # at 1
+ self.assertEqual(self.g_counter.signed, 1, "We stepped once")
+
+ def common_startup(self, provider_class):
+ self.build()
+
+ (target, _, self.thread, bkpt) = lldbutil.run_to_source_breakpoint(
+ self, "Stop here to step", self.source
+ )
+
+ # Import and register the provider.
+ script_path = os.path.join(self.getSourceDir(), "frame_provider.py")
+ self.runCmd("command script import " + script_path)
+
+ error = lldb.SBError()
+ provider_id = target.RegisterScriptedFrameProvider(
+ provider_class,
+ lldb.SBStructuredData(),
+ error,
+ )
+ self.assertTrue(
+ error.Success(), f"Should register provider successfully: {error}"
+ )
+ self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero")
+ # Now do some stepping and make sure each one goes twice:
+ self.g_counter = target.FindFirstGlobalVariable("g_counter")
+ self.assertSuccess(self.g_counter.error, "Got g_counter value")
+ self.assertEqual(self.g_counter.signed, 0, "Starts at 0")
+
+ def do_test_with_errors(self, use_command):
+ self.common_startup("frame_provider.BadStepProvider")
+ expected_error = "'frame_provider.Oops' that does not exist"
+
+ if use_command:
+ self.expect("thread step-over", substrs=[expected_error], error=True)
+ else:
+ step_error = lldb.SBError()
+ self.thread.StepOver(lldb.eOnlyDuringStepping, step_error)
+ self.assertTrue(step_error.fail, "Got a failure as expected")
+ self.assertIn(expected_error, step_error.description, "Right error")
+
+ def do_test(
+ self,
+ use_command,
+ ):
+ self.common_startup("frame_provider.CorrectStepProvider")
+
+ if use_command:
+ self.runCmd("thread step-over")
+ else:
+ step_error = lldb.SBError()
+ self.thread.StepOver(lldb.eOnlyDuringStepping, step_error)
+ self.assertSuccess(step_error)
+
+ # We stepped over twice, so counter should be 2:
+ self.assertEqual(self.g_counter.signed, 2, "We stepped twice")
+
+ # Step in steps in twice, so we should be in bar:
+ if use_command:
+ self.runCmd("thread step-in")
+ else:
+ self.thread.StepInto()
+
+ frame_0 = self.thread.frames[0]
+ self.assertEqual(frame_0.name, "bar", "Stepped in twice")
+ # We haven't run the increment the counter yet, so it's still 2:
+ self.assertEqual(self.g_counter.signed, 2, "We stepped twice")
+
+ # Now do a step out and make sure it goes back to main:
+ if use_command:
+ self.runCmd("thread step-out")
+ else:
+ self.thread.StepOut()
+
+ frame_0 = self.thread.frames[0]
+ self.assertEqual(frame_0.name, "main", "Stepped out twice")
+ self.assertEqual(self.g_counter.signed, 3, "Step out twice updated counter")
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/step_with_type/frame_provider.py b/lldb/test/API/functionalities/scripted_frame_provider/step_with_type/frame_provider.py
new file mode 100644
index 0000000000000..56d5733bece1d
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/step_with_type/frame_provider.py
@@ -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_spec_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_spec_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_spec_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)
+ self.queue_thread_plan()
+
+ def queue_thread_plan(self):
+ stop_frame = self.thread.frames[0]
+ target = self.thread.process.target
+ error = lldb.SBError()
+ self.curr_plan = None
+
+ line_entry = stop_frame.GetSymbolContext(
+ lldb.eSymbolContextEverything
+ ).line_entry
+ curr_addr = stop_frame.addr.GetLoadAddress(target)
+ pc_addr = stop_frame.GetPC()
+
+ length = line_entry.end_addr.GetLoadAddress(target) - curr_addr
+
+ if self.step_val == lldb.eStepTypeOver:
+ self.curr_plan = self.thread_plan.QueueThreadPlanForStepOverRange(
+ stop_frame.addr, length, error
+ )
+
+ if self.step_val == lldb.eStepTypeInto:
+ self.curr_plan = self.thread_plan.QueueThreadPlanForStepInRange(
+ stop_frame.addr, length, error
+ )
+
+ if self.step_val == lldb.eStepTypeOut:
+ self.curr_plan = self.thread_plan.QueueThreadPlanForStepOut(0, False, error)
+
+ if self.curr_plan == None:
+ print(f"Didn't make a plan for {self.step_val}")
+
+ if error.Fail():
+ print(f"Couldn't queue run plan for {step_type}: {error.description}")
+ self.thread_plan.SetPlanComplete(False)
+ return
+
+ def explains_stop(self, event: lldb.SBEvent):
+ if self.curr_plan.IsPlanComplete():
+ return True
+ else:
+ return False
+
+ def should_stop(self):
+ if self.thread_plan.IsPlanComplete():
+ return True
+ else:
+ if self.counter == 2:
+ self.thread_plan.SetPlanComplete(True)
+ return True
+ else:
+ self.counter += 1
+ self.queue_thread_plan()
+ return False
+
+ def should_step(self):
+ return False
+
+ def stop_description(self, stream: lldb.SBStream):
+ stream.Print("double-step engage")
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/step_with_type/main.c b/lldb/test/API/functionalities/scripted_frame_provider/step_with_type/main.c
new file mode 100644
index 0000000000000..245fdf67a2056
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/step_with_type/main.c
@@ -0,0 +1,28 @@
+#include <stdio.h>
+
+void bar() {
+ printf("bar\n");
+ return;
+}
+
+int g_counter = 0;
+
+void foo() {
+ bar();
+ g_counter++;
+ printf("foo\n");
+ return;
+}
+
+int main() {
+ printf("main\n");
+
+ foo(); // Stop here to step
+ foo();
+ foo();
+ foo();
+ foo();
+ foo();
+
+ return 0;
+}
More information about the lldb-commits
mailing list