[Lldb-commits] [lldb] [lldb] Step through inlined code when stepping out of inline frame N > 0 (PR #226910)

Felipe de Azevedo Piovezan via lldb-commits lldb-commits at lists.llvm.org
Mon Sep 28 00:48:11 PDT 2026


https://github.com/felipepiovezan created https://github.com/llvm/llvm-project/pull/226910

To step out of an inline frame N > 0, ThreadPlanStepOut first steps out to frame N, then queues a plan that steps through the inlined block. Due to a bug, ShouldStop marks the plan complete right after it queues that plan, so the thread stops as soon as it reaches frame N, with no stop reason.

In fact, the comment next to that code describes the intended behavior while implementing something different: only call the StepOut plan done if the "step out from line" plan cannot be queued.

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                  before                          after
frame select 1; finish   level3 line 12, no stop reason  level2 line 21, step out
frame select 2; finish   level3 line 12, no stop reason  level1 line 29, step out
```

>From 3473b4523a9b88b45224d605fe2d188a9c0dc2a9 Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Sun, 27 Sep 2026 08:42:53 +0100
Subject: [PATCH] [lldb] Step through inlined code when stepping out of inline
 frame N > 0

To step out of an inline frame N > 0, ThreadPlanStepOut first steps out
to frame N, then queues a plan that steps through the inlined block. Due
to a bug, ShouldStop marks the plan complete right after it queues that
plan, so the thread stops as soon as it reaches frame N, with no stop
reason.

In fact, the comment next to that code describes the intended behavior
while implementing something different: only call the StepOut plan done
if the "step out from line" plan cannot be queued.

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                  before                          after
frame select 1; finish   level3 line 12, no stop reason  level2 line 21, step out
frame select 2; finish   level3 line 12, no stop reason  level1 line 29, step out
```

TestThreadStepOutInline covers these cases.
---
 lldb/source/Target/ThreadPlanStepOut.cpp      | 12 ++++----
 .../thread/step_out_inline/Makefile           |  3 ++
 .../TestThreadStepOutInline.py                | 30 +++++++++++++++++++
 .../thread/step_out_inline/main.c             | 18 +++++++++++
 4 files changed, 56 insertions(+), 7 deletions(-)
 create mode 100644 lldb/test/API/functionalities/thread/step_out_inline/Makefile
 create mode 100644 lldb/test/API/functionalities/thread/step_out_inline/TestThreadStepOutInline.py
 create mode 100644 lldb/test/API/functionalities/thread/step_out_inline/main.c

diff --git a/lldb/source/Target/ThreadPlanStepOut.cpp b/lldb/source/Target/ThreadPlanStepOut.cpp
index a98eb862a7e27..34608000f1d4f 100644
--- a/lldb/source/Target/ThreadPlanStepOut.cpp
+++ b/lldb/source/Target/ThreadPlanStepOut.cpp
@@ -379,14 +379,12 @@ bool ThreadPlanStepOut::ShouldStop(Event *event_ptr) {
   bool done = false;
   if (m_step_out_to_inline_plan_sp) {
     if (m_step_out_to_inline_plan_sp->MischiefManaged()) {
+      m_step_out_to_inline_plan_sp.reset();
       // Now step through the inlined stack we are in:
-      if (QueueInlinedStepPlan(true)) {
-        // If we can't queue a plan to do this, then just call ourselves done.
-        m_step_out_to_inline_plan_sp.reset();
-        SetPlanComplete(false);
-        return true;
-      } else
-        done = true;
+      if (QueueInlinedStepPlan(true))
+        return false;
+      // If we can't queue a plan to do this, then just call ourselves done.
+      done = true;
     } else
       return m_step_out_to_inline_plan_sp->ShouldStop(event_ptr);
   } else if (m_step_through_inline_plan_sp) {
diff --git a/lldb/test/API/functionalities/thread/step_out_inline/Makefile b/lldb/test/API/functionalities/thread/step_out_inline/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/functionalities/thread/step_out_inline/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/thread/step_out_inline/TestThreadStepOutInline.py b/lldb/test/API/functionalities/thread/step_out_inline/TestThreadStepOutInline.py
new file mode 100644
index 0000000000000..c66dc73eb3e11
--- /dev/null
+++ b/lldb/test/API/functionalities/thread/step_out_inline/TestThreadStepOutInline.py
@@ -0,0 +1,30 @@
+"""
+Test stepping out of an inline frame that is not frame zero.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestThreadStepOutInline(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def step_out_of_frame(self, frame_idx, function, marker):
+        self.build()
+        _, _, thread, _ = lldbutil.run_to_name_breakpoint(self, "sink")
+        self.assertTrue(thread.GetFrameAtIndex(frame_idx).IsInlined())
+
+        thread.StepOutOfFrame(thread.GetFrameAtIndex(frame_idx))
+
+        self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonPlanComplete)
+        frame = thread.GetFrameAtIndex(0)
+        self.assertEqual(frame.GetFunctionName(), function)
+        self.assertEqual(frame.GetLineEntry().GetLine(), line_number("main.c", marker))
+
+    def test_step_out_of_frame_1(self):
+        self.step_out_of_frame(1, "level1", "// after level2")
+
+    def test_step_out_of_frame_2(self):
+        self.step_out_of_frame(2, "main", "// after level1")
diff --git a/lldb/test/API/functionalities/thread/step_out_inline/main.c b/lldb/test/API/functionalities/thread/step_out_inline/main.c
new file mode 100644
index 0000000000000..beb1353abb37b
--- /dev/null
+++ b/lldb/test/API/functionalities/thread/step_out_inline/main.c
@@ -0,0 +1,18 @@
+volatile int g;
+
+__attribute__((noinline)) void sink(int x) { g = x; }
+
+static inline __attribute__((always_inline)) void level2(int b) {
+  sink(b);
+  g = b + 1;
+}
+
+static inline __attribute__((always_inline)) void level1(int c) {
+  level2(c + 1);
+  g = c + 1; // after level2
+}
+
+int main(void) {
+  level1(42);
+  return 0; // after level1
+}



More information about the lldb-commits mailing list