[Lldb-commits] [lldb] [lldb] Share the address lookup of thread until and StepOverUntil (PR #226978)

Felipe de Azevedo Piovezan via lldb-commits lldb-commits at lists.llvm.org
Mon Sep 28 06:15:05 PDT 2026


https://github.com/felipepiovezan created https://github.com/llvm/llvm-project/pull/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. Before, it resolved to the start of the inlined code, where frame zero is the inlined function, so the until plan ignored the hit and the thread stepped out of the frame.

The wording of errors is that of `thread until`.

>From 3a8954d883c8f166515444b12a4e9c0d51fc1599 Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Mon, 28 Sep 2026 09:52:01 +0100
Subject: [PATCH] [lldb] Share the address lookup of thread until and
 StepOverUntil

`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. Before, it resolved to the start of the
inlined code, where frame zero is the inlined function, so the until
plan ignored the hit and the thread stepped out of the frame.

The wording of errors is that of `thread until`.
---
 lldb/include/lldb/Target/ThreadPlan.h         | 12 ++++
 lldb/source/API/SBThread.cpp                  | 62 ++++-------------
 lldb/source/Commands/CommandObjectThread.cpp  | 66 ++-----------------
 lldb/source/Target/ThreadPlan.cpp             | 53 +++++++++++++++
 .../thread/step_until/TestStepUntilAPI.py     |  4 +-
 5 files changed, 86 insertions(+), 111 deletions(-)

diff --git a/lldb/include/lldb/Target/ThreadPlan.h b/lldb/include/lldb/Target/ThreadPlan.h
index 8b0afe62f63c1..0accfa19f2ccb 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> 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..91cfd0d586333 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,53 @@ 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> addresses) {
+  const SymbolContext &frame_sc =
+      frame.GetSymbolContext(eSymbolContextCompUnit | eSymbolContextFunction);
+  TargetSP target_sp = frame.CalculateTarget();
+  if (!frame_sc.comp_unit || !frame_sc.function || !target_sp)
+    return llvm::createStringError("frame has no function debug information");
+
+  std::vector<addr_t> until_addrs;
+  bool found_target = !addresses.empty();
+  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);
+  };
+
+  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_target = true;
+    const uint32_t found_line = line_entry.line;
+    while (idx != UINT32_MAX) {
+      add_if_in_scope(line_entry.range.GetBaseAddress());
+      idx = frame_sc.comp_unit->FindLineEntry(idx + 1, found_line, &file,
+                                              /*exact=*/true, &line_entry);
+    }
+  }
+
+  for (addr_t address : addresses) {
+    Address addr;
+    if (target_sp->ResolveLoadAddress(address, addr))
+      add_if_in_scope(addr);
+  }
+
+  if (!until_addrs.empty())
+    return until_addrs;
+  if (found_target)
+    return llvm::createStringError(
+        "Until target outside of the current function");
+  return llvm::createStringError("No line entries matching until target");
+}
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