[Lldb-commits] [lldb] [lldb] Put the thread until backstop in the caller of the concrete frame (PR #226075)
via lldb-commits
lldb-commits at lists.llvm.org
Thu Sep 24 01:55:45 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lldb
Author: Felipe de Azevedo Piovezan (felipepiovezan)
<details>
<summary>Changes</summary>
This commit fixes a bug with thread-until, and makes LLDB's behavior similar to GDB's in code with inlined functions.
For 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 that line
until -f 2 <later line in frame 1> that line process exits that line
until -f 1 <line that already ran> main returns process exits main returns
```
TestInlineFrameUntil covers these cases.
`thread until -f N` put its return breakpoint at the PC of frame N+1. When frame N+1 is an inline frame, that PC is the start of the inlined code of frame N, which already ran; in other words, this breakpoint will never be hit and the process runs to exit.
Also, an until breakpoint counted as reached only in the inline scope of frame N, so a target in another inline scope of the same concrete frame was ignored.
The return breakpoint now goes in the first frame after N that is in a different concrete frame. An until breakpoint now counts as reached when frame zero has the CFA of frame N.
---
Full diff: https://github.com/llvm/llvm-project/pull/226075.diff
4 Files Affected:
- (modified) lldb/source/Target/ThreadPlanStepUntil.cpp (+13-3)
- (added) lldb/test/API/functionalities/inline-frame-pc/Makefile (+3)
- (added) lldb/test/API/functionalities/inline-frame-pc/TestInlineFrameUntil.py (+38)
- (added) lldb/test/API/functionalities/inline-frame-pc/main.c (+28)
``````````diff
diff --git a/lldb/source/Target/ThreadPlanStepUntil.cpp b/lldb/source/Target/ThreadPlanStepUntil.cpp
index 11def7a304f97..89b35cd993968 100644
--- a/lldb/source/Target/ThreadPlanStepUntil.cpp
+++ b/lldb/source/Target/ThreadPlanStepUntil.cpp
@@ -43,9 +43,17 @@ ThreadPlanStepUntil::ThreadPlanStepUntil(Thread &thread,
// Find the return address and set a breakpoint there:
// FIXME - can we do this more securely if we know first_insn?
- StackFrameSP return_frame_sp(thread.GetStackFrameAtIndex(frame_idx + 1));
+ // Inline frames have the PC of their concrete frame, so the return
+ // breakpoint goes in the caller of the concrete frame. Artificial frames
+ // share the concrete frame index of the frame they return to.
+ uint32_t return_frame_idx = frame_idx + 1;
+ StackFrameSP return_frame_sp =
+ thread.GetStackFrameAtIndex(return_frame_idx);
+ while (return_frame_sp && !frame_sp->IsArtificial() &&
+ return_frame_sp->GetConcreteFrameIndex() ==
+ frame_sp->GetConcreteFrameIndex())
+ return_frame_sp = thread.GetStackFrameAtIndex(++return_frame_idx);
if (return_frame_sp) {
- // TODO: add inline functionality
m_return_addr = return_frame_sp->GetStackID().GetPC();
Breakpoint *return_bp =
target_sp->CreateBreakpoint(m_return_addr, true, false).get();
@@ -197,7 +205,9 @@ void ThreadPlanStepUntil::AnalyzeStop() {
StackID frame_zero_id =
thread.GetStackFrameAtIndex(0)->GetStackID();
- if (frame_zero_id == m_stack_id)
+ // Inline frames have the CFA of their concrete frame.
+ if (frame_zero_id.GetCallFrameAddressWithoutMetadata() ==
+ m_stack_id.GetCallFrameAddressWithoutMetadata())
done = true;
else if (frame_zero_id.IsYoungerThan(m_stack_id))
done = false;
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..0bfb74f7816af
--- /dev/null
+++ b/lldb/test/API/functionalities/inline-frame-pc/TestInlineFrameUntil.py
@@ -0,0 +1,38 @@
+"""
+Test thread until from an inline frame.
+"""
+
+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 until_from_frame(self, frame_idx, marker):
+ self.build()
+ _, _, thread, _ = lldbutil.run_to_name_breakpoint(self, "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 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_stops_at(1, "// until in level2")
+
+ def test_until_in_inlined_callee(self):
+ self.check_stops_at(2, "// until in level3")
+
+ def test_until_target_already_ran(self):
+ frame = self.until_from_frame(1, "// before sink")
+ self.assertEqual(frame.GetFunctionName(), "main")
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..b9e765f204945
--- /dev/null
+++ b/lldb/test/API/functionalities/inline-frame-pc/main.c
@@ -0,0 +1,28 @@
+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); }
+
+int main(void) {
+ outer();
+ return 0;
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/226075
More information about the lldb-commits
mailing list