llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-lldb Author: Charles Zablit (charles-zablit) <details> <summary>Changes</summary> `ABIWindows_x86_64::CreateDefaultUnwindPlan()` uses a "CFA = rbp + 16" as a placeholder, however, `Windows-x86_64` does not use rbp as a frame pointer. With this bogus rbp based CFA, the unwinder could not produce the caller frame, so stepping out of such a function fails with "Could not create return address breakpoint". This patch uses the `Windows-x86_64` correct rule for the fallback: [the return address is at the top of the stack](https://learn.microsoft.com/en-us/cpp/build/x64-software-conventions?view=msvc-170), so `CFA = rsp + 8` and `pc = [CFA - 8]`. This patch also ports `lang/c/trampoline_stepping` from Swiftlang. --- Full diff: https://github.com/llvm/llvm-project/pull/210076.diff 4 Files Affected: - (modified) lldb/source/Plugins/ABI/X86/ABIWindows_x86_64.cpp (+2-8) - (added) lldb/test/API/lang/c/trampoline_stepping/Makefile (+3) - (added) lldb/test/API/lang/c/trampoline_stepping/TestTrampolineStepping.py (+100) - (added) lldb/test/API/lang/c/trampoline_stepping/main.c (+52) ``````````diff diff --git a/lldb/source/Plugins/ABI/X86/ABIWindows_x86_64.cpp b/lldb/source/Plugins/ABI/X86/ABIWindows_x86_64.cpp index 079b22a307602..be3c83880bd60 100644 --- a/lldb/source/Plugins/ABI/X86/ABIWindows_x86_64.cpp +++ b/lldb/source/Plugins/ABI/X86/ABIWindows_x86_64.cpp @@ -751,21 +751,15 @@ UnwindPlanSP ABIWindows_x86_64::CreateFunctionEntryUnwindPlan() { // Windows-x86_64 doesn't use %rbp // No available Unwind information for Windows-x86_64 (section .pdata) -// Let's use SysV-x86_64 one for now UnwindPlanSP ABIWindows_x86_64::CreateDefaultUnwindPlan() { - uint32_t fp_reg_num = dwarf_rbp; uint32_t sp_reg_num = dwarf_rsp; uint32_t pc_reg_num = dwarf_rip; UnwindPlan::Row row; - - const int32_t ptr_size = 8; - row.GetCFAValue().SetIsRegisterPlusOffset(dwarf_rbp, 2 * ptr_size); row.SetOffset(0); row.SetUnspecifiedRegistersAreUndefined(true); - - row.SetRegisterLocationToAtCFAPlusOffset(fp_reg_num, ptr_size * -2, true); - row.SetRegisterLocationToAtCFAPlusOffset(pc_reg_num, ptr_size * -1, true); + row.GetCFAValue().SetIsRegisterPlusOffset(sp_reg_num, 8); + row.SetRegisterLocationToAtCFAPlusOffset(pc_reg_num, -8, false); row.SetRegisterLocationToIsCFAPlusOffset(sp_reg_num, 0, true); auto plan_sp = std::make_shared<UnwindPlan>(eRegisterKindDWARF); diff --git a/lldb/test/API/lang/c/trampoline_stepping/Makefile b/lldb/test/API/lang/c/trampoline_stepping/Makefile new file mode 100644 index 0000000000000..10495940055b6 --- /dev/null +++ b/lldb/test/API/lang/c/trampoline_stepping/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules diff --git a/lldb/test/API/lang/c/trampoline_stepping/TestTrampolineStepping.py b/lldb/test/API/lang/c/trampoline_stepping/TestTrampolineStepping.py new file mode 100644 index 0000000000000..bbf1dc8cb7fea --- /dev/null +++ b/lldb/test/API/lang/c/trampoline_stepping/TestTrampolineStepping.py @@ -0,0 +1,100 @@ +"""Test that stepping in/out of trampolines works as expected.""" + +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestTrampoline(TestBase): + def setup(self, bkpt_str): + self.build() + + _, _, thread, _ = lldbutil.run_to_source_breakpoint( + self, bkpt_str, lldb.SBFileSpec("main.c") + ) + return thread + + def test_direct_call(self): + thread = self.setup("Break here for direct") + + # Sanity check that we start out in the correct function. + name = thread.frames[0].GetFunctionName() + self.assertIn("direct_trampoline_call", name) + + # Check that stepping in will take us directly to the trampoline target. + thread.StepInto() + name = thread.frames[0].GetFunctionName() + self.assertIn("foo", name) + + # Check that stepping out takes us back to the trampoline caller. + thread.StepOut() + name = thread.frames[0].GetFunctionName() + self.assertIn("direct_trampoline_call", name) + + # Check that stepping over the end of the trampoline target + # takes us back to the trampoline caller. + thread.StepInto() + thread.StepOver() + name = thread.frames[0].GetFunctionName() + self.assertIn("direct_trampoline_call", name) + + def test_chained_call(self): + thread = self.setup("Break here for chained") + + # Sanity check that we start out in the correct function. + name = thread.frames[0].GetFunctionName() + self.assertIn("chained_trampoline_call", name) + + # Check that stepping in will take us directly to the trampoline target. + thread.StepInto() + name = thread.frames[0].GetFunctionName() + self.assertIn("foo", name) + + # Check that stepping out takes us back to the trampoline caller. + thread.StepOut() + name = thread.frames[0].GetFunctionName() + self.assertIn("chained_trampoline_call", name) + + # Check that stepping over the end of the trampoline target + # takes us back to the trampoline caller. + thread.StepInto() + thread.StepOver() + name = thread.frames[0].GetFunctionName() + self.assertIn("chained_trampoline_call", name) + + def test_trampoline_after_nodebug(self): + thread = self.setup("Break here for nodebug then trampoline") + + # Sanity check that we start out in the correct function. + name = thread.frames[0].GetFunctionName() + self.assertIn("trampoline_after_nodebug", name) + + # Check that stepping in will take us directly to the trampoline target. + thread.StepInto() + name = thread.frames[0].GetFunctionName() + self.assertIn("foo", name) + + # Check that stepping out takes us back to the trampoline caller. + thread.StepOut() + name = thread.frames[0].GetFunctionName() + self.assertIn("trampoline_after_nodebug", name) + + # Check that stepping over the end of the trampoline target + # takes us back to the trampoline caller. + thread.StepInto() + thread.StepOver() + name = thread.frames[0].GetFunctionName() + self.assertIn("trampoline_after_nodebug", name) + + def test_unused_target(self): + thread = self.setup("Break here for unused") + + # Sanity check that we start out in the correct function. + name = thread.frames[0].GetFunctionName() + self.assertIn("unused_target", name) + + # Check that stepping into a trampoline that doesn't call its target + # jumps back to its caller. + thread.StepInto() + name = thread.frames[0].GetFunctionName() + self.assertIn("unused_target", name) diff --git a/lldb/test/API/lang/c/trampoline_stepping/main.c b/lldb/test/API/lang/c/trampoline_stepping/main.c new file mode 100644 index 0000000000000..cb98be00ca1f6 --- /dev/null +++ b/lldb/test/API/lang/c/trampoline_stepping/main.c @@ -0,0 +1,52 @@ +void foo(void) {} + +__attribute__((transparent_stepping)) +void bar(void) { + foo(); +} + +__attribute__((transparent_stepping)) +void baz(void) { + bar(); +} + +__attribute__((nodebug)) +void nodebug(void) {} + +__attribute__((transparent_stepping)) +void nodebug_then_trampoline(void) { + nodebug(); + baz(); +} + +__attribute__((transparent_stepping)) +void doesnt_call_trampoline(void) {} + +void direct_trampoline_call(void) { + bar(); // Break here for direct + bar(); +} + +void chained_trampoline_call(void) { + baz(); // Break here for chained + baz(); +} + +void trampoline_after_nodebug(void) { + nodebug_then_trampoline(); // Break here for nodebug then trampoline + nodebug_then_trampoline(); +} + +void unused_target(void) { + doesnt_call_trampoline(); // Break here for unused +} + + +int main(void) { + direct_trampoline_call(); + chained_trampoline_call(); + trampoline_after_nodebug(); + unused_target(); + return 0; +} + `````````` </details> https://github.com/llvm/llvm-project/pull/210076 _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
