[Lldb-commits] [lldb] 5f376e4 - [lldb] Share the address lookup of thread until and StepOverUntil (#226978)
via lldb-commits
lldb-commits at lists.llvm.org
Wed Sep 30 03:39:22 PDT 2026
Author: Felipe de Azevedo Piovezan
Date: 2026-09-30T11:39:15+01:00
New Revision: 5f376e4dc0504c30aeccc5997f72455bc620b0de
URL: https://github.com/llvm/llvm-project/commit/5f376e4dc0504c30aeccc5997f72455bc620b0de
DIFF: https://github.com/llvm/llvm-project/commit/5f376e4dc0504c30aeccc5997f72455bc620b0de.diff
LOG: [lldb] Share the address lookup of thread until and StepOverUntil (#226978)
`thread until` and `SBThread::StepOverUntil` turned their targets into
addresses using different code. This commit introduces a helper function
`GetStepUntilAddresses` to unify behavior and the error message.
It resolves lines as `thread until` did: a line without line table
entries resolves to the nearest following line with entries. For
StepOverUntil, a line that is only the call site of an inlined function
now resolves that way too.
The wording of errors is that of `thread until`.
Added:
Modified:
lldb/include/lldb/Target/ThreadPlan.h
lldb/source/API/SBThread.cpp
lldb/source/Commands/CommandObjectThread.cpp
lldb/source/Target/ThreadPlan.cpp
lldb/test/API/functionalities/thread/step_until/TestStepUntilAPI.py
Removed:
################################################################################
diff --git a/lldb/include/lldb/Target/ThreadPlan.h b/lldb/include/lldb/Target/ThreadPlan.h
index 8b0afe62f63c1..864ea79c54d6f 100644
--- a/lldb/include/lldb/Target/ThreadPlan.h
+++ b/lldb/include/lldb/Target/ThreadPlan.h
@@ -11,6 +11,7 @@
#include <mutex>
#include <string>
+#include <vector>
#include "lldb/Target/Process.h"
#include "lldb/Target/StopInfo.h"
@@ -19,6 +20,8 @@
#include "lldb/Target/ThreadPlanTracer.h"
#include "lldb/Utility/UserID.h"
#include "lldb/lldb-private.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/Support/Error.h"
namespace lldb_private {
@@ -624,6 +627,15 @@ class ThreadPlanNull : public ThreadPlan {
const ThreadPlanNull &operator=(const ThreadPlanNull &) = delete;
};
+/// Returns the load addresses of the line table entries for \p lines in
+/// \p file, and of \p addresses, that are in the function of \p frame. A
+/// line without entries resolves to the nearest following line with
+/// entries. Fails if no address is left.
+llvm::Expected<std::vector<lldb::addr_t>>
+GetStepUntilAddresses(StackFrame &frame, const FileSpec &file,
+ llvm::ArrayRef<uint32_t> lines,
+ llvm::ArrayRef<lldb::addr_t> requested_addresses);
+
} // namespace lldb_private
#endif // LLDB_TARGET_THREADPLAN_H
diff --git a/lldb/source/API/SBThread.cpp b/lldb/source/API/SBThread.cpp
index a44a1efb51e99..e8699d81b3658 100644
--- a/lldb/source/API/SBThread.cpp
+++ b/lldb/source/API/SBThread.cpp
@@ -833,7 +833,6 @@ SBError SBThread::StepOverUntil(lldb::SBFrame &sb_frame,
LLDB_INSTRUMENT_VA(this, sb_frame, sb_file_spec, line);
SBError sb_error;
- char path[PATH_MAX];
llvm::Expected<StoppedExecutionContext> exe_ctx =
GetStoppedExecutionContext(m_opaque_sp);
@@ -843,7 +842,6 @@ SBError SBThread::StepOverUntil(lldb::SBFrame &sb_frame,
StackFrameSP frame_sp(sb_frame.GetFrameSP());
if (exe_ctx->HasThreadScope()) {
- Target *target = exe_ctx->GetTargetPtr();
Thread *thread = exe_ctx->GetThreadPtr();
if (line == 0) {
@@ -892,56 +890,22 @@ SBError SBThread::StepOverUntil(lldb::SBFrame &sb_frame,
}
}
- // Grab the current function, then we will make sure the "until" address is
- // within the function. We discard addresses that are out of the current
- // function, and then if there are no addresses remaining, give an
- // appropriate error message.
-
- bool all_in_function = true;
-
- std::vector<addr_t> step_over_until_addrs;
const bool abort_other_plans = false;
const bool stop_other_threads = false;
- // TODO: Handle SourceLocationSpec column information
- SourceLocationSpec location_spec(
- step_file_spec, line, /*column=*/std::nullopt, /*check_inlines=*/true,
- /*exact_match=*/false);
-
- SymbolContextList sc_list;
- frame_sc.comp_unit->ResolveSymbolContext(location_spec,
- eSymbolContextLineEntry, sc_list);
- for (const SymbolContext &sc : sc_list) {
- addr_t step_addr =
- sc.line_entry.range.GetBaseAddress().GetLoadAddress(target);
- if (step_addr != LLDB_INVALID_ADDRESS) {
- AddressRange unused_range;
- if (frame_sc.function->GetRangeContainingLoadAddress(step_addr, *target,
- unused_range))
- step_over_until_addrs.push_back(step_addr);
- else
- all_in_function = false;
- }
- }
+ llvm::Expected<std::vector<addr_t>> step_over_until_addrs =
+ GetStepUntilAddresses(*frame_sp, step_file_spec, {line}, {});
+ if (!step_over_until_addrs)
+ return Status::FromError(step_over_until_addrs.takeError());
- if (step_over_until_addrs.empty()) {
- if (all_in_function) {
- step_file_spec.GetPath(path, sizeof(path));
- sb_error = Status::FromErrorStringWithFormat(
- "No line entries for %s:%u", path, line);
- } else
- sb_error = Status::FromErrorString(
- "step until target not in current function");
- } else {
- Status new_plan_status;
- ThreadPlanSP new_plan_sp = thread->QueueThreadPlanForStepUntil(
- abort_other_plans, step_over_until_addrs, stop_other_threads,
- frame_sp->GetFrameIndex(), new_plan_status);
-
- if (new_plan_status.Success())
- sb_error = ResumeNewPlan(std::move(*exe_ctx), new_plan_sp.get());
- else
- sb_error = Status::FromErrorString(new_plan_status.AsCString());
- }
+ Status new_plan_status;
+ ThreadPlanSP new_plan_sp = thread->QueueThreadPlanForStepUntil(
+ abort_other_plans, *step_over_until_addrs, stop_other_threads,
+ frame_sp->GetFrameIndex(), new_plan_status);
+
+ if (new_plan_status.Success())
+ sb_error = ResumeNewPlan(std::move(*exe_ctx), new_plan_sp.get());
+ else
+ sb_error = Status::FromErrorString(new_plan_status.AsCString());
} else {
sb_error = Status::FromErrorString("this SBThread object is invalid");
}
diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp
index 74a138690d477..46e5bccbc7ee3 100644
--- a/lldb/source/Commands/CommandObjectThread.cpp
+++ b/lldb/source/Commands/CommandObjectThread.cpp
@@ -1090,8 +1090,6 @@ class CommandObjectThreadUntil : public CommandObjectParsed {
void DoExecute(Args &command, CommandReturnObject &result) override {
bool synchronous_execution = m_interpreter.GetSynchronous();
- Target *target = GetTarget();
-
Process *process = m_exe_ctx.GetProcessPtr();
if (process == nullptr) {
result.AppendError("need a valid process to step");
@@ -1161,9 +1159,6 @@ class CommandObjectThreadUntil : public CommandObjectParsed {
return;
}
- LineEntry function_start;
- std::vector<addr_t> address_list;
-
// Find the beginning & end index of the function, but first make
// sure it is valid:
if (!sc.function) {
@@ -1172,65 +1167,16 @@ class CommandObjectThreadUntil : public CommandObjectParsed {
return;
}
- RangeVector<uint32_t, uint32_t> line_idx_ranges;
- for (const AddressRange &range : sc.function->GetAddressRanges()) {
- auto [begin, end] = line_table->GetLineEntryIndexRange(range);
- line_idx_ranges.Append(begin, end - begin);
- }
- line_idx_ranges.Sort();
-
- bool found_something = false;
-
- // Since not all source lines will contribute code, check if we are
- // setting the breakpoint on the exact line number or the nearest
- // subsequent line number and set breakpoints at all the line table
- // entries of the chosen line number (exact or nearest subsequent).
- for (uint32_t line_number : line_numbers) {
- LineEntry line_entry;
- bool exact = false;
- if (sc.comp_unit->FindLineEntry(0, line_number, nullptr, exact,
- &line_entry) == UINT32_MAX)
- continue;
-
- found_something = true;
- line_number = line_entry.line;
- exact = true;
- uint32_t end_func_idx = line_idx_ranges.GetMaxRangeEnd(0);
- uint32_t idx = sc.comp_unit->FindLineEntry(
- line_idx_ranges.GetMinRangeBase(UINT32_MAX), line_number, nullptr,
- exact, &line_entry);
- while (idx < end_func_idx) {
- if (line_idx_ranges.FindEntryIndexThatContains(idx) != UINT32_MAX) {
- addr_t address =
- line_entry.range.GetBaseAddress().GetLoadAddress(target);
- if (address != LLDB_INVALID_ADDRESS)
- address_list.push_back(address);
- }
- idx = sc.comp_unit->FindLineEntry(idx + 1, line_number, nullptr,
- exact, &line_entry);
- }
- }
-
- for (lldb::addr_t address : m_options.m_until_addrs) {
- AddressRange unused;
- if (sc.function->GetRangeContainingLoadAddress(address, *target,
- unused))
- address_list.push_back(address);
- }
-
- if (address_list.empty()) {
- if (found_something)
- result.AppendErrorWithFormat(
- "Until target outside of the current function");
- else
- result.AppendErrorWithFormat(
- "No line entries matching until target");
-
+ llvm::Expected<std::vector<addr_t>> address_list =
+ GetStepUntilAddresses(*frame, sc.comp_unit->GetPrimaryFile(),
+ line_numbers, m_options.m_until_addrs);
+ if (!address_list) {
+ result.AppendError(llvm::toString(address_list.takeError()));
return;
}
new_plan_sp = thread->QueueThreadPlanForStepUntil(
- abort_other_plans, address_list, m_options.m_stop_others,
+ abort_other_plans, *address_list, m_options.m_stop_others,
m_options.m_frame_idx, new_plan_status);
if (new_plan_sp) {
// User level plans should be controlling plans so they can be
diff --git a/lldb/source/Target/ThreadPlan.cpp b/lldb/source/Target/ThreadPlan.cpp
index e575958deab6f..52ae6c1df6198 100644
--- a/lldb/source/Target/ThreadPlan.cpp
+++ b/lldb/source/Target/ThreadPlan.cpp
@@ -8,8 +8,11 @@
#include "lldb/Target/ThreadPlan.h"
#include "lldb/Core/Debugger.h"
+#include "lldb/Symbol/CompileUnit.h"
+#include "lldb/Symbol/Function.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/RegisterContext.h"
+#include "lldb/Target/StackFrame.h"
#include "lldb/Target/Target.h"
#include "lldb/Target/Thread.h"
#include "lldb/Utility/LLDBLog.h"
@@ -293,3 +296,56 @@ lldb::StateType ThreadPlanNull::GetPlanRunState() {
#endif
return eStateRunning;
}
+
+llvm::Expected<std::vector<addr_t>> lldb_private::GetStepUntilAddresses(
+ StackFrame &frame, const FileSpec &file, llvm::ArrayRef<uint32_t> lines,
+ llvm::ArrayRef<addr_t> requested_addresses) {
+ const SymbolContext &frame_sc =
+ frame.GetSymbolContext(eSymbolContextCompUnit | eSymbolContextFunction);
+ TargetSP target_sp = frame.CalculateTarget();
+ if (!target_sp)
+ return llvm::createStringError("null target from StepUntil frame");
+ if (!frame_sc.comp_unit || !frame_sc.function)
+ return llvm::createStringError("frame has no function debug information");
+
+ std::vector<addr_t> until_addrs;
+ auto add_if_in_scope = [&](const Address &addr) {
+ addr_t load_addr = addr.GetLoadAddress(target_sp.get());
+ AddressRange unused;
+ if (load_addr != LLDB_INVALID_ADDRESS &&
+ frame_sc.function->GetRangeContainingLoadAddress(load_addr, *target_sp,
+ unused))
+ until_addrs.push_back(load_addr);
+ };
+
+ bool found_some_line_table_entry = false;
+ for (uint32_t line : lines) {
+ LineEntry line_entry;
+ uint32_t idx = frame_sc.comp_unit->FindLineEntry(
+ 0, line, &file, /*exact=*/false, &line_entry);
+ if (idx == UINT32_MAX)
+ continue;
+ found_some_line_table_entry = true;
+ const uint32_t actual_line = line_entry.line;
+ while (idx != UINT32_MAX) {
+ add_if_in_scope(line_entry.range.GetBaseAddress());
+ idx = frame_sc.comp_unit->FindLineEntry(idx + 1, actual_line, &file,
+ /*exact=*/true, &line_entry);
+ }
+ }
+
+ for (addr_t address : requested_addresses) {
+ Address addr;
+ if (target_sp->ResolveLoadAddress(address, addr))
+ add_if_in_scope(addr);
+ }
+
+ if (!until_addrs.empty())
+ return until_addrs;
+ // Historically, LLDB has emphasized the error when "only lines were
+ // requested, but none of those lines had a line table entry".
+ if (requested_addresses.empty() && !found_some_line_table_entry)
+ return llvm::createStringError("No line entries matching until target");
+ return llvm::createStringError(
+ "Until target outside of the current function");
+}
diff --git a/lldb/test/API/functionalities/thread/step_until/TestStepUntilAPI.py b/lldb/test/API/functionalities/thread/step_until/TestStepUntilAPI.py
index 8181f27a0d669..8ee9a2265147d 100644
--- a/lldb/test/API/functionalities/thread/step_until/TestStepUntilAPI.py
+++ b/lldb/test/API/functionalities/thread/step_until/TestStepUntilAPI.py
@@ -123,7 +123,7 @@ def test_bad_line(self):
self, "At the start", self.main_spec
)
self.assertIn(
- "step until target not in current function",
+ "Until target outside of the current function",
thread.StepOverUntil(
self.frame(), self.main_spec, self.in_foo
).GetCString(),
@@ -145,7 +145,7 @@ def test_bad_line_discontinuous(self):
self.skipTest(f"failed to build with linker script.")
self.assertIn(
- "step until target not in current function",
+ "Until target outside of the current function",
thread.StepOverUntil(
self.frame(), self.main_spec, self.in_foo
).GetCString(),
More information about the lldb-commits
mailing list