From de3dfc8317a24b093b1bf8275c28e2aaeea8c700 Mon Sep 17 00:00:00 2001 From: Michael Buch Date: Thu, 3 Apr 2025 11:10:16 +0100 Subject: [PATCH 1/2] [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 (cherry picked from commit 554f4d1a5769357ee8438c23f572d595c720ff3c) --- lldb/source/Target/Process.cpp | 8 ++++- .../expr-from-non-zero-frame/Makefile | 3 ++ .../TestExprFromNonZeroFrame.py | 30 +++++++++++++++++++ .../expr-from-non-zero-frame/main.c | 6 ++++ 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 lldb/test/API/commands/expression/expr-from-non-zero-frame/Makefile create mode 100644 lldb/test/API/commands/expression/expr-from-non-zero-frame/TestExprFromNonZeroFrame.py create mode 100644 lldb/test/API/commands/expression/expr-from-non-zero-frame/main.c diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index 9060a43deac31..59ccda22c2739 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -5200,7 +5200,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(); } From c3f0a5d2931bd6836443533830f372cbff9d820f Mon Sep 17 00:00:00 2001 From: Michael Buch Date: Thu, 3 Apr 2025 12:21:41 +0100 Subject: [PATCH 2/2] [lldb][test] TestExprFromNonZeroFrame.py: fix windows build On Windows this test was failing to link with following error: ``` make: Entering directory 'C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/lldb-test-build.noindex/commands/expression/expr-from-non-zero-frame/TestExprFromNonZeroFrame.test' C:\Users\tcwg\llvm-worker\lldb-aarch64-windows\build\bin\clang.exe -gdwarf -O0 -IC:\Users\tcwg\llvm-worker\lldb-aarch64-windows\llvm-project\lldb\packages\Python\lldbsuite\test\make/../../../../..//include -IC:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/tools/lldb/include -IC:\Users\tcwg\llvm-worker\lldb-aarch64-windows\llvm-project\lldb\test\API\commands\expression\expr-from-non-zero-frame -IC:\Users\tcwg\llvm-worker\lldb-aarch64-windows\llvm-project\lldb\packages\Python\lldbsuite\test\make -include C:\Users\tcwg\llvm-worker\lldb-aarch64-windows\llvm-project\lldb\packages\Python\lldbsuite\test\make/test_common.h -fno-limit-debug-info -MT main.o -MD -MP -MF main.d -c -o main.o C:\Users\tcwg\llvm-worker\lldb-aarch64-windows\llvm-project\lldb\test\API\commands\expression\expr-from-non-zero-frame/main.c C:\Users\tcwg\llvm-worker\lldb-aarch64-windows\build\bin\clang.exe main.o -gdwarf -O0 -IC:\Users\tcwg\llvm-worker\lldb-aarch64-windows\llvm-project\lldb\packages\Python\lldbsuite\test\make/../../../../..//include -IC:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/tools/lldb/include -IC:\Users\tcwg\llvm-worker\lldb-aarch64-windows\llvm-project\lldb\test\API\commands\expression\expr-from-non-zero-frame -IC:\Users\tcwg\llvm-worker\lldb-aarch64-windows\llvm-project\lldb\packages\Python\lldbsuite\test\make -include C:\Users\tcwg\llvm-worker\lldb-aarch64-windows\llvm-project\lldb\packages\Python\lldbsuite\test\make/test_common.h -fno-limit-debug-info -fuse-ld=lld --driver-mode=g++ -o "a.out" lld-link: error: undefined symbol: printf >>> referenced by main.o:(func) clang: error: linker command failed with exit code 1 (use -v to see invocation) make: *** [Makefile.rules:530: a.out] Error 1 make: Leaving directory 'C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/lldb-test-build.noindex/commands/expression/expr-from-non-zero-frame/TestExprFromNonZeroFrame.test' ``` (cherry picked from commit 739fe980802e17e49ab9cc2e4c18a48c88e15ef5) --- .../expr-from-non-zero-frame/TestExprFromNonZeroFrame.py | 2 +- .../API/commands/expression/expr-from-non-zero-frame/main.c | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) 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 index 9b1bcfb177765..623c5b87f14c7 100644 --- 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 @@ -16,7 +16,7 @@ def test(self): self.build() (_, _, thread, _) = lldbutil.run_to_source_breakpoint( - self, "Break here", lldb.SBFileSpec("main.c") + self, "return 5", lldb.SBFileSpec("main.c") ) frame = thread.GetFrameAtIndex(1) 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 index d1675525cf4f2..abd52aeeb5b0b 100644 --- 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 @@ -1,6 +1,3 @@ -int func(void) { - __builtin_printf("Break here"); - return 5; -} +int func(void) { return 5; } int main(int argc, const char *argv[]) { return func(); }