| """Test that lldb backtraces a frameless function that faults correctly.""" |
| |
| import lldbsuite.test.lldbutil as lldbutil |
| from lldbsuite.test.lldbtest import * |
| from lldbsuite.test.decorators import * |
| import shutil |
| import os |
| |
| |
| class TestUnwindFramelessFaulted(TestBase): |
| NO_DEBUG_INFO_TESTCASE = True |
| |
| @skipIf(oslist=no_match([lldbplatformutil.getDarwinOSTriples()])) |
| @skipIf(archs=no_match(["aarch64", "arm64", "arm64e"])) |
| |
| # The static linker in Xcode 15.0-15.2 on macOS 14 will mislink |
| # the eh_frame addresses; ld-classic in those tools is one workaround. |
| # This issue was fixed in Xcode 15.3, but it's not straightforward |
| # to test for the linker version or Xcode version so tie this to |
| # macOS 15 which uses Xcode 16 and does not have the issues. |
| @skipIf(macos_version=["<", "15.0"]) |
| |
| def test_frameless_faulted_unwind(self): |
| self.build() |
| |
| (target, process, thread, bp) = lldbutil.run_to_name_breakpoint( |
| self, "main", only_one_thread=False |
| ) |
| |
| # The test program will have a backtrace like this at its deepest: |
| # |
| # * frame #0: 0x0000000102adc468 a.out`break_to_debugger + 4 |
| # frame #1: 0x0000000102adc458 a.out`trap + 16 |
| # frame #2: 0x0000000102adc440 a.out`to_be_interrupted + 20 |
| # frame #3: 0x0000000102adc418 a.out`main at main.c:4:7 |
| # frame #4: 0x0000000193b7eb4c dyld`start + 6000 |
| |
| correct_frames = ["break_to_debugger", "trap", "to_be_interrupted", "main"] |
| |
| # Keep track of when main has branch & linked, instruction step until we're |
| # back in main() |
| main_has_bl_ed = False |
| |
| # Instruction step through the binary until we are in a function not |
| # listed in correct_frames. |
| frame = thread.GetFrameAtIndex(0) |
| step_count = 0 |
| max_step_count = 200 |
| while ( |
| process.GetState() == lldb.eStateStopped |
| and frame.name in correct_frames |
| and step_count < max_step_count |
| ): |
| starting_index = 0 |
| if self.TraceOn(): |
| self.runCmd("bt") |
| |
| # Find which index into correct_frames the current stack frame is |
| for idx, name in enumerate(correct_frames): |
| if frame.name == name: |
| starting_index = idx |
| |
| # Test that all frames after the current frame listed in |
| # correct_frames appears in the backtrace. |
| frame_idx = 0 |
| for expected_frame in correct_frames[starting_index:]: |
| self.assertEqual(thread.GetFrameAtIndex(frame_idx).name, expected_frame) |
| frame_idx = frame_idx + 1 |
| |
| # When we're at our deepest level, test that register passing of |
| # x0 and x20 follow the by-hand UnwindPlan rules. |
| # In this test program, we can get x0 in the middle of the stack |
| # and we CAN'T get x20. The opposites of the normal AArch64 SysV |
| # ABI. |
| if frame.name == "break_to_debugger": |
| tbi_frame = thread.GetFrameAtIndex(2) |
| self.assertEqual(tbi_frame.name, "to_be_interrupted") |
| # The original argument to to_be_interrupted(), 10 |
| # Normally can't get x0 mid-stack, but UnwindPlans have |
| # special rules to make this possible. |
| x0_reg = tbi_frame.register["x0"] |
| self.assertTrue(x0_reg.IsValid()) |
| self.assertEqual(x0_reg.GetValueAsUnsigned(), 10) |
| # The incremented return value from to_be_interrupted(), 11 |
| x24_reg = tbi_frame.register["x24"] |
| self.assertTrue(x24_reg.IsValid()) |
| self.assertEqual(x24_reg.GetValueAsUnsigned(), 11) |
| # x20 can normally be fetched mid-stack, but the UnwindPlan |
| # has a rule saying it can't be fetched. |
| x20_reg = tbi_frame.register["x20"] |
| self.assertTrue(x20_reg.error.fail) |
| |
| trap_frame = thread.GetFrameAtIndex(1) |
| self.assertEqual(trap_frame.name, "trap") |
| # Confirm that we can fetch x0 from trap() which |
| # is normally not possible w/ SysV AbI, but special |
| # UnwindPlans in use. |
| x0_reg = trap_frame.register["x0"] |
| self.assertTrue(x0_reg.IsValid()) |
| self.assertEqual(x0_reg.GetValueAsUnsigned(), 10) |
| x1_reg = trap_frame.register["x1"] |
| self.assertTrue(x1_reg.error.fail) |
| |
| main_frame = thread.GetFrameAtIndex(3) |
| self.assertEqual(main_frame.name, "main") |
| # x20 can normally be fetched mid-stack, but the UnwindPlan |
| # has a rule saying it can't be fetched. |
| x20_reg = main_frame.register["x20"] |
| self.assertTrue(x20_reg.error.fail) |
| # x21 can be fetched mid-stack. |
| x21_reg = main_frame.register["x21"] |
| self.assertTrue(x21_reg.error.success) |
| |
| # manually move past the BRK instruction in |
| # break_to_debugger(). lldb-server doesn't |
| # advance past the builtin_debugtrap() BRK |
| # instruction. |
| if ( |
| thread.GetStopReason() == lldb.eStopReasonException |
| and frame.name == "break_to_debugger" |
| ): |
| frame.SetPC(frame.GetPC() + 4) |
| |
| if self.TraceOn(): |
| print("StepInstruction") |
| thread.StepInstruction(False) |
| frame = thread.GetFrameAtIndex(0) |
| step_count = step_count + 1 |