[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