[Lldb-commits] [lldb] 150f9dc - [lldb] Reject thread until targets outside of an inline frame's function (#227800)

via lldb-commits lldb-commits at lists.llvm.org
Thu Oct 1 01:55:08 PDT 2026


Author: Felipe de Azevedo Piovezan
Date: 2026-10-01T08:54:56Z
New Revision: 150f9dcdb4f06219f36031ab6d5b56fdc8499e88

URL: https://github.com/llvm/llvm-project/commit/150f9dcdb4f06219f36031ab6d5b56fdc8499e88
DIFF: https://github.com/llvm/llvm-project/commit/150f9dcdb4f06219f36031ab6d5b56fdc8499e88.diff

LOG: [lldb] Reject thread until targets outside of an inline frame's function (#227800)

`thread until -f N <line>` rejects a line outside of the function of
frame N.

Before this patch, for an inline frame, it checked the concrete
function, not the inlined function, so it accepted lines of other
inlined functions. The until plan then ignored a hit of such a target,
because it is not in the scope of frame N.

This commit changes GetStepUntilAddresses to only accepts an address if
its innermost inlined function is the one of frame N, as it does for a
regular frame.

As a before/after example, consider this backtrace:

```
  frame #0: 0x00000001000003c0 deep`sink(x=81) at deep.c:4:6 [opt]
  frame #1: 0x0000000100000418 deep`level3(a=48) at deep.c:11:3 [opt] [inlined]
  frame #2: 0x0000000100000404 deep`level2(b=37) at deep.c:20:3 [opt] [inlined]
  frame #3: 0x00000001000003f0 deep`level1(c=42) at deep.c:28:3 [opt] [inlined]
  frame #4: 0x00000001000003dc deep`main at deep.c:33:3 [opt]
```

This patch changes behavior as follows:

```
command                             gdb        lldb before    lldb after
until -f 1 <later line in frame 2>  that line  process exits  error
until -f 2 <later line in frame 1>  that line  process exits  error

Added: 
    lldb/test/API/functionalities/inline-frame-pc/Makefile
    lldb/test/API/functionalities/inline-frame-pc/TestInlineFrameUntil.py
    lldb/test/API/functionalities/inline-frame-pc/main.c

Modified: 
    lldb/include/lldb/Target/StackFrame.h
    lldb/include/lldb/Target/ThreadPlan.h
    lldb/source/Target/StackFrame.cpp
    lldb/source/Target/ThreadPlan.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Target/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h
index e6ac3d1ad5275..8fa0eef067f4a 100644
--- a/lldb/include/lldb/Target/StackFrame.h
+++ b/lldb/include/lldb/Target/StackFrame.h
@@ -231,6 +231,12 @@ class StackFrame : public ExecutionContextScope,
   ///   not be provided.
   virtual Block *GetFrameBlock();
 
+  /// Let F be this frame's function. Returns true if \p addr points to code
+  /// inside F.
+  /// Note: if some other function F2 has been inlined into F, and \p
+  /// addr points to code of the inlined copy of F2, this returns false.
+  bool IsAddressInFrameScope(const Address &addr);
+
   /// Get the RegisterContext for this frame, if possible.
   ///
   /// Returns a shared pointer to the RegisterContext for this stack frame.

diff  --git a/lldb/include/lldb/Target/ThreadPlan.h b/lldb/include/lldb/Target/ThreadPlan.h
index 864ea79c54d6f..0a663da896e97 100644
--- a/lldb/include/lldb/Target/ThreadPlan.h
+++ b/lldb/include/lldb/Target/ThreadPlan.h
@@ -628,7 +628,7 @@ class ThreadPlanNull : public ThreadPlan {
 };
 
 /// 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
+/// \p file, and of \p addresses, that are in the scope 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>>

diff  --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp
index 59eda286e487f..6696077582eef 100644
--- a/lldb/source/Target/StackFrame.cpp
+++ b/lldb/source/Target/StackFrame.cpp
@@ -302,6 +302,21 @@ Block *StackFrame::GetFrameBlock() {
   return nullptr;
 }
 
+bool StackFrame::IsAddressInFrameScope(const Address &addr) {
+  Block *frame_block = GetFrameBlock();
+  Block *addr_block = addr.CalculateSymbolContextBlock();
+  if (!frame_block || !addr_block)
+    return false;
+  // Do they represent the same concrete function?
+  if (addr_block->CalculateSymbolContextFunction() !=
+      frame_block->CalculateSymbolContextFunction())
+    return false;
+
+  // Do they represent the same inlined function?
+  return addr_block->GetContainingInlinedBlock() ==
+         frame_block->GetContainingInlinedBlock();
+}
+
 // Get the symbol context if we already haven't done so by resolving the
 // PC address as much as possible. This way when we pass around a
 // StackFrame object, everyone will have as much information as possible and no

diff  --git a/lldb/source/Target/ThreadPlan.cpp b/lldb/source/Target/ThreadPlan.cpp
index 52ae6c1df6198..57a1b597975c5 100644
--- a/lldb/source/Target/ThreadPlan.cpp
+++ b/lldb/source/Target/ThreadPlan.cpp
@@ -311,10 +311,7 @@ llvm::Expected<std::vector<addr_t>> lldb_private::GetStepUntilAddresses(
   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))
+    if (load_addr != LLDB_INVALID_ADDRESS && frame.IsAddressInFrameScope(addr))
       until_addrs.push_back(load_addr);
   };
 

diff  --git a/lldb/test/API/functionalities/inline-frame-pc/Makefile b/lldb/test/API/functionalities/inline-frame-pc/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/functionalities/inline-frame-pc/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules

diff  --git a/lldb/test/API/functionalities/inline-frame-pc/TestInlineFrameUntil.py b/lldb/test/API/functionalities/inline-frame-pc/TestInlineFrameUntil.py
new file mode 100644
index 0000000000000..2188614b4fcda
--- /dev/null
+++ b/lldb/test/API/functionalities/inline-frame-pc/TestInlineFrameUntil.py
@@ -0,0 +1,57 @@
+"""
+Test thread until with inlined functions.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestInlineFrameUntil(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def run_to_sink(self):
+        self.build()
+        _, _, thread, _ = lldbutil.run_to_name_breakpoint(self, "sink")
+        return thread
+
+    def until_from_frame(self, frame_idx, marker):
+        thread = self.run_to_sink()
+        self.runCmd(f"thread until -f {frame_idx} {line_number('main.c', marker)}")
+        return thread.GetFrameAtIndex(0)
+
+    def check_stops_at(self, frame_idx, marker):
+        frame = self.until_from_frame(frame_idx, marker)
+        self.assertEqual(frame.GetLineEntry().GetLine(), line_number("main.c", marker))
+
+    def check_rejected(self, frame_idx, marker):
+        self.run_to_sink()
+        self.expect(
+            f"thread until -f {frame_idx} {line_number('main.c', marker)}",
+            error=True,
+            substrs=["Until target outside of the current function"],
+        )
+
+    def test_until_in_same_inline_frame(self):
+        self.check_stops_at(1, "// until in level3")
+
+    def test_until_in_parent_inline_frame(self):
+        self.check_stops_at(2, "// until in level2")
+
+    def test_until_in_inlining_caller(self):
+        self.check_rejected(1, "// until in level2")
+
+    def test_until_in_inlined_callee(self):
+        self.check_rejected(2, "// until in level3")
+
+    def test_step_over_until_in_inlining_caller(self):
+        thread = self.run_to_sink()
+        error = thread.StepOverUntil(
+            thread.GetFrameAtIndex(1),
+            lldb.SBFileSpec("main.c"),
+            line_number("main.c", "// until in level2"),
+        )
+        self.assertIn(
+            "Until target outside of the current function", error.GetCString()
+        )

diff  --git a/lldb/test/API/functionalities/inline-frame-pc/main.c b/lldb/test/API/functionalities/inline-frame-pc/main.c
new file mode 100644
index 0000000000000..2ab720a609f13
--- /dev/null
+++ b/lldb/test/API/functionalities/inline-frame-pc/main.c
@@ -0,0 +1,30 @@
+volatile int g;
+
+__attribute__((noinline)) void sink(int x) { g = x; }
+
+static inline __attribute__((always_inline)) void level3(int a) {
+  g = a; // before sink
+  sink(a);
+  g = a + 1; // break in level3
+  g = a + 2; // until in level3
+}
+
+static inline __attribute__((always_inline)) void level2(int b) {
+  g = b;
+  level3(b + 1);
+  g = b + 1; // until in level2
+}
+
+static inline __attribute__((always_inline)) void level1(int c) {
+  g = c;
+  level2(c + 1);
+}
+
+__attribute__((noinline)) void outer(void) {
+  level1(42); // some comment to make clang-format happy.
+}
+
+int main(void) {
+  outer();
+  return 0;
+}


        


More information about the lldb-commits mailing list