[Lldb-commits] [lldb] 554f4d1 - [lldb][Target] RunThreadPlan to save/restore the ExecutionContext's frame if one exists (#134097)

via lldb-commits lldb-commits at lists.llvm.org
Thu Apr 3 03:10:20 PDT 2025


Author: Michael Buch
Date: 2025-04-03T11:10:16+01:00
New Revision: 554f4d1a5769357ee8438c23f572d595c720ff3c

URL: https://github.com/llvm/llvm-project/commit/554f4d1a5769357ee8438c23f572d595c720ff3c
DIFF: https://github.com/llvm/llvm-project/commit/554f4d1a5769357ee8438c23f572d595c720ff3c.diff

LOG: [lldb][Target] RunThreadPlan to save/restore the ExecutionContext's frame if one exists (#134097)

When using `SBFrame::EvaluateExpression` on a frame that's not the
currently selected frame, we would sometimes run into errors such as:
```
error: error: The context has changed before we could JIT the expression!
error: errored out in DoExecute, couldn't PrepareToExecuteJITExpression
```

During expression parsing, we call `RunStaticInitializers`. On our
internal fork this happens quite frequently because any usage of, e.g.,
function pointers, will inject ptrauth fixup code into the expression.
The static initializers are run using `RunThreadPlan`. The
`ExecutionContext::m_frame_sp` going into the `RunThreadPlan` is the
`SBFrame` that we called `EvaluateExpression` on. LLDB then tries to
save this frame to restore it after the thread-plan ran (the restore
occurs by unconditionally overwriting whatever is in
`ExecutionContext::m_frame_sp`). However, if the `selected_frame_sp` is
not the same as the `SBFrame`, then `RunThreadPlan` would set the
`ExecutionContext`'s frame to a different frame than what we started
with. When we `PrepareToExecuteJITExpression`, LLDB checks whether the
`ExecutionContext` frame changed from when we initially
`EvaluateExpression`, and if did, bails out with the error above.

One such test-case is attached. This currently passes regardless of the
fix because our ptrauth static initializers code isn't upstream yet. But
the plan is to upstream it soon.

This patch addresses the issue by saving/restoring the frame of the
incoming `ExecutionContext`, if such frame exists. Otherwise, fall back
to using the selected frame.

rdar://147456589

Added: 
    lldb/test/API/commands/expression/expr-from-non-zero-frame/Makefile
    lldb/test/API/commands/expression/expr-from-non-zero-frame/TestExprFromNonZeroFrame.py
    lldb/test/API/commands/expression/expr-from-non-zero-frame/main.c

Modified: 
    lldb/source/Target/Process.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 7936cf28467b2..2adda309dea9c 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -5080,7 +5080,13 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
     return eExpressionSetupError;
   }
 
-  StackID ctx_frame_id = selected_frame_sp->GetStackID();
+  // If the ExecutionContext has a frame, we want to make sure to save/restore
+  // that frame into exe_ctx. This can happen when we run expressions from a
+  // non-selected SBFrame, in which case we don't want some thread-plan
+  // to overwrite the ExecutionContext frame.
+  StackID ctx_frame_id = exe_ctx.HasFrameScope()
+                             ? exe_ctx.GetFrameRef().GetStackID()
+                             : selected_frame_sp->GetStackID();
 
   // N.B. Running the target may unset the currently selected thread and frame.
   // We don't want to do that either, so we should arrange to reset them as

diff  --git a/lldb/test/API/commands/expression/expr-from-non-zero-frame/Makefile b/lldb/test/API/commands/expression/expr-from-non-zero-frame/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/commands/expression/expr-from-non-zero-frame/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules

diff  --git a/lldb/test/API/commands/expression/expr-from-non-zero-frame/TestExprFromNonZeroFrame.py b/lldb/test/API/commands/expression/expr-from-non-zero-frame/TestExprFromNonZeroFrame.py
new file mode 100644
index 0000000000000..9b1bcfb177765
--- /dev/null
+++ b/lldb/test/API/commands/expression/expr-from-non-zero-frame/TestExprFromNonZeroFrame.py
@@ -0,0 +1,30 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class ExprFromNonZeroFrame(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test(self):
+        """
+        Tests that we can use SBFrame::EvaluateExpression on a frame
+        that we're not stopped in, even if thread-plans run as part of
+        parsing the expression (e.g., when running static initializers).
+        """
+        self.build()
+
+        (_, _, thread, _) = lldbutil.run_to_source_breakpoint(
+            self, "Break here", lldb.SBFileSpec("main.c")
+        )
+        frame = thread.GetFrameAtIndex(1)
+
+        # Using a function pointer inside the expression ensures we
+        # emit a ptrauth static initializer on arm64e into the JITted
+        # expression. The thread-plan that runs for this static
+        # initializer should save/restore the current execution context
+        # frame (which in this test is frame #1).
+        result = frame.EvaluateExpression("int (*fptr)() = &func; fptr()")
+        self.assertTrue(result.GetError().Success())
+        self.assertEqual(result.GetValueAsSigned(), 5)

diff  --git a/lldb/test/API/commands/expression/expr-from-non-zero-frame/main.c b/lldb/test/API/commands/expression/expr-from-non-zero-frame/main.c
new file mode 100644
index 0000000000000..d1675525cf4f2
--- /dev/null
+++ b/lldb/test/API/commands/expression/expr-from-non-zero-frame/main.c
@@ -0,0 +1,6 @@
+int func(void) {
+  __builtin_printf("Break here");
+  return 5;
+}
+
+int main(int argc, const char *argv[]) { return func(); }


        


More information about the lldb-commits mailing list