Skip to content

[lldb] Expose QueueThreadPlanForStepSingleInstruction function to SBThreadPlan #137904

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lldb/include/lldb/API/SBThreadPlan.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ class LLDB_API SBThreadPlan {
SBThreadPlan QueueThreadPlanForStepOut(uint32_t frame_idx_to_step_to,
bool first_insn, SBError &error);

SBThreadPlan QueueThreadPlanForStepSingleInstruction(bool step_over,
SBError &error);

SBThreadPlan QueueThreadPlanForRunToAddress(SBAddress address);
SBThreadPlan QueueThreadPlanForRunToAddress(SBAddress address,
SBError &error);
Expand Down
23 changes: 23 additions & 0 deletions lldb/source/API/SBThreadPlan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,29 @@ SBThreadPlan::QueueThreadPlanForStepOut(uint32_t frame_idx_to_step_to,
return SBThreadPlan();
}

SBThreadPlan
SBThreadPlan::QueueThreadPlanForStepSingleInstruction(bool step_over,
SBError &error) {
LLDB_INSTRUMENT_VA(this, step_over, error);

ThreadPlanSP thread_plan_sp(GetSP());
if (thread_plan_sp) {
Status plan_status;
SBThreadPlan plan(
thread_plan_sp->GetThread().QueueThreadPlanForStepSingleInstruction(
step_over, false, false, plan_status));

if (plan_status.Fail())
error.SetErrorString(plan_status.AsCString());
else
plan.GetSP()->SetPrivate(true);

return plan;
}

return SBThreadPlan();
}

SBThreadPlan
SBThreadPlan::QueueThreadPlanForRunToAddress(SBAddress sb_address) {
LLDB_INSTRUMENT_VA(this, sb_address);
Expand Down
20 changes: 20 additions & 0 deletions lldb/test/API/functionalities/step_scripted/Steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,26 @@ def queue_child_thread_plan(self):
return self.thread_plan.QueueThreadPlanForStepScripted("Steps.StepOut")


class StepSingleInstruction(StepWithChild):
def __init__(self, thread_plan, dict):
super().__init__(thread_plan)

def queue_child_thread_plan(self):
return self.thread_plan.QueueThreadPlanForStepSingleInstruction(
False, lldb.SBError()
)


class StepSingleInstructionWithStepOver(StepWithChild):
def __init__(self, thread_plan, dict):
super().__init__(thread_plan)

def queue_child_thread_plan(self):
return self.thread_plan.QueueThreadPlanForStepSingleInstruction(
True, lldb.SBError()
)


# This plan does a step-over until a variable changes value.
class StepUntil(StepWithChild):
def __init__(self, thread_plan, args_data):
Expand Down
42 changes: 42 additions & 0 deletions lldb/test/API/functionalities/step_scripted/TestStepScripted.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,48 @@ def step_out_with_scripted_plan(self, name):
stop_desc = thread.GetStopDescription(1000)
self.assertIn("Stepping out from", stop_desc, "Got right description")

def run_until_branch_instruction(self):
self.build()
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
self, "Break on branch instruction", self.main_source_file
)

# Check that we landed in a call instruction
frame = thread.GetFrameAtIndex(0)
current_instruction = target.ReadInstructions(frame.GetPCAddress(), 1)[0]
self.assertEqual(
lldb.eInstructionControlFlowKindCall,
current_instruction.GetControlFlowKind(target),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW I don't think is implemented for arm targets

)
return (target, process, thread, bkpt)

def test_step_single_instruction(self):
(target, process, thread, bkpt) = self.run_until_branch_instruction()

err = thread.StepUsingScriptedThreadPlan("Steps.StepSingleInstruction")
self.assertSuccess(err)

# Verify that stepping a single instruction after "foo();" steps into `foo`
frame = thread.GetFrameAtIndex(0)
self.assertEqual("foo", frame.GetFunctionName())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case of step-i, we are really trying to assert that we step just one instruction. That's actually not all that hard to test exactly. Just add some simple arithmetic computation to the test, so it won't have branches, and then stop at the beginning of the computation, run your step-i plan, and assert that you did get to the NEXT instruction on the instruction list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that testing a branch is more powerful though, because in a case of a non branch instruction both stepi and nexti would step into the same instruction


def test_step_single_instruction_with_step_over(self):
(target, process, thread, bkpt) = self.run_until_branch_instruction()

frame = thread.GetFrameAtIndex(0)
next_instruction = target.ReadInstructions(frame.GetPCAddress(), 2)[1]
next_instruction_address = next_instruction.GetAddress()

err = thread.StepUsingScriptedThreadPlan(
"Steps.StepSingleInstructionWithStepOver"
)
self.assertSuccess(err)

# Verify that stepping over an instruction doesn't step into `foo`
frame = thread.GetFrameAtIndex(0)
self.assertEqual("main", frame.GetFunctionName())
Copy link
Collaborator

@jimingham jimingham Apr 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the thread plan failed to move at all, this assert would pass. Figuring out where you go with nexti over a function call is a little trickier than stepi, so it's probably okay here to just assert that you didn't step into foo but the PC did change.

Copy link
Collaborator

@jimingham jimingham Apr 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might also assert before the nexti that you really are at a branch (you can get the instruction list for the function, find the instruction you are stopped at and ask it IsBranch. It seems really unlikely that any compiler/architecture would would emit more than a single branch and link for a call to a void (*)(void), but it's better not to rely on that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, I didn't have any other straightfoward idea for a 100% way to break on a branch instruction but adding a check is a good idea

self.assertEqual(next_instruction_address, frame.GetPCAddress())

def test_misspelled_plan_name(self):
"""Test that we get a useful error if we misspell the plan class name"""
self.build()
Expand Down
2 changes: 1 addition & 1 deletion lldb/test/API/functionalities/step_scripted/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ void foo() {
}

int main() {
foo();
foo(); // Break on branch instruction.
return 0;
}
Loading