https://github.com/jasonmolenda updated https://github.com/llvm/llvm-project/pull/138805
>From 4fc9acd03a58a3617b113352c48e5dd2fdb58eda Mon Sep 17 00:00:00 2001 From: Jason Molenda <jmole...@apple.com> Date: Tue, 6 May 2025 22:37:17 -0700 Subject: [PATCH 01/14] [lldb] Provide lr value in faulting frame on arm64 When a frameless function faults or is interrupted asynchronously, the UnwindPlan MAY have no register location rule for the return address register (lr on arm64); the value is simply live in the lr register when it was interrupted, and the frame below this on the stack -- e.g. sigtramp on a Unix system -- has the full register context, including that register. RegisterContextUnwind::SavedLocationForRegister, when asked to find the caller's pc value, will first see if there is a pc register location. If there isn't, on a Return Address Register architecture like arm/mips/riscv, we rewrite the register request from "pc" to "RA register", and search for a location. On frame 0 (the live frame) and an interrupted frame, the UnwindPlan may have no register location rule for the RA Reg, that is valid. A frameless function that never calls another may simply keep the return address in the live register the whole way. Our instruction emulation unwind plans explicitly add a rule (see Pavel's May 2024 change https://github.com/llvm/llvm-project/pull/91321 ), but an UnwindPlan sourced from debug_frame may not. I've got a case where this exactly happens - clang debug_frame for arm64 where there is no register location for the lr in a frameless function. There is a fault in the middle of this frameless function and we only get the lr value from the fault handler below this frame if lr has a register location of `IsSame`, in line with Pavel's 2024 change. Similar to how we see a request of the RA Reg from frame 0 after failing to find an unwind location for the pc register, the same style of special casing is needed when this is a function that was interrupted. Without this change, we can find the pc of the frame that was executing when it was interrupted, but we need $lr to find its caller, and we don't descend down to the trap handler to get that value, truncating the stack. rdar://145614545 --- lldb/source/Target/RegisterContextUnwind.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lldb/source/Target/RegisterContextUnwind.cpp b/lldb/source/Target/RegisterContextUnwind.cpp index 3ed49e12476dd..23a86bee2518b 100644 --- a/lldb/source/Target/RegisterContextUnwind.cpp +++ b/lldb/source/Target/RegisterContextUnwind.cpp @@ -1377,6 +1377,7 @@ RegisterContextUnwind::SavedLocationForRegister( } } + // Check if the active_row has a register location listed. if (regnum.IsValid() && active_row->GetRegisterInfo(regnum.GetAsKind(unwindplan_registerkind), unwindplan_regloc)) { @@ -1390,11 +1391,10 @@ RegisterContextUnwind::SavedLocationForRegister( // This is frame 0 and we're retrieving the PC and it's saved in a Return // Address register and it hasn't been saved anywhere yet -- that is, // it's still live in the actual register. Handle this specially. - if (!have_unwindplan_regloc && return_address_reg.IsValid() && - IsFrameZero()) { - if (return_address_reg.GetAsKind(eRegisterKindLLDB) != - LLDB_INVALID_REGNUM) { + return_address_reg.GetAsKind(eRegisterKindLLDB) != + LLDB_INVALID_REGNUM) { + if (IsFrameZero()) { lldb_private::UnwindLLDB::ConcreteRegisterLocation new_regloc; new_regloc.type = UnwindLLDB::ConcreteRegisterLocation:: eRegisterInLiveRegisterContext; @@ -1408,6 +1408,17 @@ RegisterContextUnwind::SavedLocationForRegister( return_address_reg.GetAsKind(eRegisterKindLLDB), return_address_reg.GetAsKind(eRegisterKindLLDB)); return UnwindLLDB::RegisterSearchResult::eRegisterFound; + } else if (BehavesLikeZerothFrame()) { + // This function was interrupted asynchronously -- it faulted, + // an async interrupt, a timer fired, a debugger expression etc. + // The caller's pc is in the Return Address register, but the + // UnwindPlan for this function may have no location rule for + // the RA reg. + // This means that the caller's return address is in the RA reg + // when the function was interrupted--descend down one stack frame + // to retrieve it from the trap handler's saved context. + unwindplan_regloc.SetSame(); + have_unwindplan_regloc = true; } } >From b10162deb49ecddca6439665c2b8ea1995fdd81f Mon Sep 17 00:00:00 2001 From: Jason Molenda <jmole...@apple.com> Date: Wed, 7 May 2025 23:24:17 -0700 Subject: [PATCH 02/14] Add the sources for an API test case to be written --- .../interrupt-and-trap-funcs.s | 92 +++++++++++++++++++ .../macosx/unwind-frameless-faulted/main.c | 6 ++ 2 files changed, 98 insertions(+) create mode 100644 lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s create mode 100644 lldb/test/API/macosx/unwind-frameless-faulted/main.c diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s new file mode 100644 index 0000000000000..23eb35c2b31ca --- /dev/null +++ b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s @@ -0,0 +1,92 @@ +#define DW_CFA_register 0x9 +#define ehframe_x22 22 +#define ehframe_x23 23 +#define ehframe_pc 32 + + .section __TEXT,__text,regular,pure_instructions + +//-------------------------------------- +// to_be_interrupted() a frameless function that does a non-ABI +// function call ("is interrupted/traps" simulated) to trap(). +// Before it branches to trap(), it puts its return address in +// x23. trap() knows to branch back to $x23 when it has finished. +//-------------------------------------- + .globl _to_be_interrupted + .p2align 2 +_to_be_interrupted: + .cfi_startproc + + // This is a garbage entry to ensure that eh_frame is emitted, + // it isn't used for anything. If there's no eh_frame, lldb + // can do an assembly emulation scan and add a rule for $lr + // which won't expose the issue at hand. + .cfi_escape DW_CFA_register, ehframe_x22, ehframe_x23 + mov x24, x0 + add x24, x24, #1 + + adrp x23, L_.return@PAGE ; put return address in x4 + add x23, x23, L_.return@PAGEOFF + + b _trap ; branch to trap handler, fake async interrupt + +L_.return: + mov x0, x24 + ret + .cfi_endproc + + + +//-------------------------------------- +// trap() trap handler function, sets up stack frame +// with special unwind rule for the pc value of the +// "interrupted" stack frame (it's in x23), then calls +// break_to_debugger(). +//-------------------------------------- + .globl _trap + .p2align 2 +_trap: + .cfi_startproc + .cfi_signal_frame + + // The pc value when we were interrupted is in x23 + .cfi_escape DW_CFA_register, ehframe_pc, ehframe_x23 + + // standard prologue save of fp & lr so we can call puts() + sub sp, sp, #32 + stp x29, x30, [sp, #16] + add x29, sp, #16 + .cfi_def_cfa w29, 16 + .cfi_offset w30, -8 + .cfi_offset w29, -16 + + bl _break_to_debugger + + ldp x29, x30, [sp, #16] + add sp, sp, #32 + + // jump back to $x23 to resume execution of to_be_interrupted + br x23 + .cfi_endproc + +//-------------------------------------- +// break_to_debugger() executes a BRK instruction +//-------------------------------------- + .globl _break_to_debugger + .p2align 2 +_break_to_debugger: + .cfi_startproc + + // standard prologue save of fp & lr so we can call puts() + sub sp, sp, #32 + stp x29, x30, [sp, #16] + add x29, sp, #16 + .cfi_def_cfa w29, 16 + .cfi_offset w30, -8 + .cfi_offset w29, -16 + + brk #0xf000 ;; __builtin_debugtrap aarch64 instruction + + ldp x29, x30, [sp, #16] + add sp, sp, #32 + ret + .cfi_endproc diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/main.c b/lldb/test/API/macosx/unwind-frameless-faulted/main.c new file mode 100644 index 0000000000000..e121809f25478 --- /dev/null +++ b/lldb/test/API/macosx/unwind-frameless-faulted/main.c @@ -0,0 +1,6 @@ +int to_be_interrupted(int); +int main() { + int c = 10; + c = to_be_interrupted(c); + return c; +} >From 508b9cb404072263a6ed52a75b3b7aad5d949510 Mon Sep 17 00:00:00 2001 From: Jason Molenda <jmole...@apple.com> Date: Wed, 7 May 2025 23:24:58 -0700 Subject: [PATCH 03/14] Add a log msg when a frame is marked as a trap handler via the UnwindPlan. And use the trap handler flag for zeroth frame so we can properly unwind a frameless function that was interrupted while in the trap handler function. --- lldb/source/Target/RegisterContextUnwind.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lldb/source/Target/RegisterContextUnwind.cpp b/lldb/source/Target/RegisterContextUnwind.cpp index 51c0f56e9bc1e..cf4b96c6eda9f 100644 --- a/lldb/source/Target/RegisterContextUnwind.cpp +++ b/lldb/source/Target/RegisterContextUnwind.cpp @@ -248,6 +248,7 @@ void RegisterContextUnwind::InitializeZerothFrame() { active_row = m_full_unwind_plan_sp->GetRowForFunctionOffset(m_current_offset); row_register_kind = m_full_unwind_plan_sp->GetRegisterKind(); + PropagateTrapHandlerFlagFromUnwindPlan(m_full_unwind_plan_sp); if (active_row && log) { StreamString active_row_strm; active_row->Dump(active_row_strm, m_full_unwind_plan_sp.get(), &m_thread, @@ -1933,6 +1934,7 @@ void RegisterContextUnwind::PropagateTrapHandlerFlagFromUnwindPlan( } m_frame_type = eTrapHandlerFrame; + UnwindLogMsg("This frame is marked as a trap handler via its UnwindPlan"); if (m_current_offset_backed_up_one != m_current_offset) { // We backed up the pc by 1 to compute the symbol context, but >From d8843fe86c0eaaa1483bed0317f832b7630d8548 Mon Sep 17 00:00:00 2001 From: Jason Molenda <jmole...@apple.com> Date: Wed, 7 May 2025 23:28:40 -0700 Subject: [PATCH 04/14] Update assembly a bit. --- .../interrupt-and-trap-funcs.s | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s index 23eb35c2b31ca..708ad9ed56a1c 100644 --- a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s +++ b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s @@ -51,7 +51,8 @@ _trap: // The pc value when we were interrupted is in x23 .cfi_escape DW_CFA_register, ehframe_pc, ehframe_x23 - // standard prologue save of fp & lr so we can call puts() + // standard prologue save of fp & lr so we can call + // break_to_debugger() sub sp, sp, #32 stp x29, x30, [sp, #16] add x29, sp, #16 @@ -76,17 +77,7 @@ _trap: _break_to_debugger: .cfi_startproc - // standard prologue save of fp & lr so we can call puts() - sub sp, sp, #32 - stp x29, x30, [sp, #16] - add x29, sp, #16 - .cfi_def_cfa w29, 16 - .cfi_offset w30, -8 - .cfi_offset w29, -16 - brk #0xf000 ;; __builtin_debugtrap aarch64 instruction - ldp x29, x30, [sp, #16] - add sp, sp, #32 ret .cfi_endproc >From 1d90aa48c92d9f8ef43c889223c0804ac1491d53 Mon Sep 17 00:00:00 2001 From: Jason Molenda <jmole...@apple.com> Date: Thu, 8 May 2025 18:18:14 -0700 Subject: [PATCH 05/14] Add unwind rules for x0 (volatile) and x20 (non-voltile) to trap() and break_to_debugger() for fun, and add unwind instructions for the epilogue of trap(). When stopped in `break_to_debugger`, ``` * frame #0: 0x00000001000003e8 a.out`break_to_debugger + 4 frame #1: 0x00000001000003d8 a.out`trap + 16 frame #2: 0x00000001000003c0 a.out`to_be_interrupted + 20 frame #3: 0x0000000100000398 a.out`main + 32 ``` Normally we can't provide a volatile register (e.g. x0) up the stack. And we can provide a non-volatile register (e.g. x20) up the stack. I added an IsSame rule for trap() and break_to_debugger() for x0, so it CAN be passed up the stack. And I added an Undefined rule for x20 to trap() so it CAN'T be provided up the stack. If a user selects `to_be_interrupted` and does `register read`, they'll get x0 and they won't get x20. From `main`, they will not get x0 or x20 (x0 because `to_be_interrupted` doesn't have an IsSame rule). --- .../interrupt-and-trap-funcs.s | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s index 708ad9ed56a1c..b50c4734f18f3 100644 --- a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s +++ b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s @@ -1,4 +1,6 @@ #define DW_CFA_register 0x9 +#define ehframe_x0 0 +#define ehframe_x20 20 #define ehframe_x22 22 #define ehframe_x23 23 #define ehframe_pc 32 @@ -7,9 +9,9 @@ //-------------------------------------- // to_be_interrupted() a frameless function that does a non-ABI -// function call ("is interrupted/traps" simulated) to trap(). -// Before it branches to trap(), it puts its return address in -// x23. trap() knows to branch back to $x23 when it has finished. +// function call to trap(), simulating an async signal/interrup/exception/fault. +// Before it branches to trap(), put the return address in x23. +// trap() knows to branch back to $x23 when it has finished. //-------------------------------------- .globl _to_be_interrupted .p2align 2 @@ -51,6 +53,15 @@ _trap: // The pc value when we were interrupted is in x23 .cfi_escape DW_CFA_register, ehframe_pc, ehframe_x23 + // For fun, mark x0 as unmodified so the caller can + // retrieve the value if it wants. + .cfi_same_value ehframe_x0 + + // Mark x20 as undefined. This is a callee-preserved + // (non-volatile) register by the SysV AArch64 ABI, but + // it's be fun to see lldb not passing a value past this. + .cfi_undefined ehframe_x20 + // standard prologue save of fp & lr so we can call // break_to_debugger() sub sp, sp, #32 @@ -63,7 +74,11 @@ _trap: bl _break_to_debugger ldp x29, x30, [sp, #16] + .cfi_same_value x29 + .cfi_same_value x30 + .cfi_def_cfa sp, 32 add sp, sp, #32 + .cfi_same_value sp // jump back to $x23 to resume execution of to_be_interrupted br x23 @@ -77,6 +92,10 @@ _trap: _break_to_debugger: .cfi_startproc + // For fun, mark x0 as unmodified so the caller can + // retrieve the value if it wants. + .cfi_same_value ehframe_x0 + brk #0xf000 ;; __builtin_debugtrap aarch64 instruction ret >From 303edd47f7bcfb6af0a9947ca7b0b9a0c5bec589 Mon Sep 17 00:00:00 2001 From: Jason Molenda <jmole...@apple.com> Date: Thu, 8 May 2025 18:46:14 -0700 Subject: [PATCH 06/14] small comment improvement --- .../unwind-frameless-faulted/interrupt-and-trap-funcs.s | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s index b50c4734f18f3..38be06a681bf2 100644 --- a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s +++ b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s @@ -18,10 +18,10 @@ _to_be_interrupted: .cfi_startproc - // This is a garbage entry to ensure that eh_frame is emitted, - // it isn't used for anything. If there's no eh_frame, lldb - // can do an assembly emulation scan and add a rule for $lr - // which won't expose the issue at hand. + // This is a garbage entry to ensure that eh_frame is emitted. + // If there's no eh_frame, lldb can use the assembly emulation scan, + // which always includes a rule for $lr, and we won't replicate the + // bug we're testing for. .cfi_escape DW_CFA_register, ehframe_x22, ehframe_x23 mov x24, x0 add x24, x24, #1 >From 620eace34ae149b8c513767935118b6e870646fc Mon Sep 17 00:00:00 2001 From: Jason Molenda <jmole...@apple.com> Date: Thu, 8 May 2025 18:47:54 -0700 Subject: [PATCH 07/14] more comment fix --- .../macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s index 38be06a681bf2..b5c5cb79656c3 100644 --- a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s +++ b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s @@ -59,7 +59,8 @@ _trap: // Mark x20 as undefined. This is a callee-preserved // (non-volatile) register by the SysV AArch64 ABI, but - // it's be fun to see lldb not passing a value past this. + // it'll be fun to see lldb not passing a value past this + // point on the stack. .cfi_undefined ehframe_x20 // standard prologue save of fp & lr so we can call >From 6185c513beaa2f63dc04b54f723b209de8af2286 Mon Sep 17 00:00:00 2001 From: Jason Molenda <jmole...@apple.com> Date: Thu, 8 May 2025 22:36:21 -0700 Subject: [PATCH 08/14] Add test case Makefile and API test python code. Also fix one bug in the by-hand unwind state in the assembly file. --- .../macosx/unwind-frameless-faulted/Makefile | 12 ++++ .../TestUnwindFramelessFaulted.py | 59 +++++++++++++++++++ .../interrupt-and-trap-funcs.s | 1 + 3 files changed, 72 insertions(+) create mode 100644 lldb/test/API/macosx/unwind-frameless-faulted/Makefile create mode 100644 lldb/test/API/macosx/unwind-frameless-faulted/TestUnwindFramelessFaulted.py diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/Makefile b/lldb/test/API/macosx/unwind-frameless-faulted/Makefile new file mode 100644 index 0000000000000..76548928a3622 --- /dev/null +++ b/lldb/test/API/macosx/unwind-frameless-faulted/Makefile @@ -0,0 +1,12 @@ +C_SOURCES := main.c + +interrupt-and-trap-funcs.o: interrupt-and-trap-funcs.s + $(CC) $(CFLAGS) -c -o interrupt-and-trap-funcs.o $(SRCDIR)/interrupt-and-trap-funcs.s + +include Makefile.rules + +a.out: interrupt-and-trap-funcs.o + +# Needs to come after include +OBJECTS += interrupt-and-trap-funcs.o + diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/TestUnwindFramelessFaulted.py b/lldb/test/API/macosx/unwind-frameless-faulted/TestUnwindFramelessFaulted.py new file mode 100644 index 0000000000000..34e7fbfffacde --- /dev/null +++ b/lldb/test/API/macosx/unwind-frameless-faulted/TestUnwindFramelessFaulted.py @@ -0,0 +1,59 @@ +"""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 + + @skipUnlessDarwin + 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. + while ( + process.GetState() == lldb.eStateStopped + and thread.GetFrameAtIndex(0).name in correct_frames + ): + 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 thread.GetFrameAtIndex(0).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 + + if self.TraceOn(): + print("StepInstruction") + thread.StepInstruction(False) diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s index b5c5cb79656c3..5bc991fc0f67d 100644 --- a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s +++ b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s @@ -80,6 +80,7 @@ _trap: .cfi_def_cfa sp, 32 add sp, sp, #32 .cfi_same_value sp + .cfi_def_cfa sp, 0 // jump back to $x23 to resume execution of to_be_interrupted br x23 >From d239b7a84c1e489b2630aa63358061435a749ce7 Mon Sep 17 00:00:00 2001 From: Jason Molenda <jmole...@apple.com> Date: Thu, 8 May 2025 22:49:20 -0700 Subject: [PATCH 09/14] Add tests for x0 and x20, which I treat in normally ABI-wrong ways in these hand-written UnwindPlans. --- .../TestUnwindFramelessFaulted.py | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/TestUnwindFramelessFaulted.py b/lldb/test/API/macosx/unwind-frameless-faulted/TestUnwindFramelessFaulted.py index 34e7fbfffacde..d899b7a2f7369 100644 --- a/lldb/test/API/macosx/unwind-frameless-faulted/TestUnwindFramelessFaulted.py +++ b/lldb/test/API/macosx/unwind-frameless-faulted/TestUnwindFramelessFaulted.py @@ -34,17 +34,15 @@ def test_frameless_faulted_unwind(self): # Instruction step through the binary until we are in a function not # listed in correct_frames. - while ( - process.GetState() == lldb.eStateStopped - and thread.GetFrameAtIndex(0).name in correct_frames - ): + frame = thread.GetFrameAtIndex(0) + while process.GetState() == lldb.eStateStopped and frame.name in correct_frames: 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 thread.GetFrameAtIndex(0).name == name: + if frame.name == name: starting_index = idx # Test that all frames after the current frame listed in @@ -54,6 +52,32 @@ def test_frameless_faulted_unwind(self): 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) + if self.TraceOn(): print("StepInstruction") thread.StepInstruction(False) + frame = thread.GetFrameAtIndex(0) >From 33563049b37479db9ebebdad0a1c982deb4c6f14 Mon Sep 17 00:00:00 2001 From: Jason Molenda <jmole...@apple.com> Date: Thu, 8 May 2025 22:52:04 -0700 Subject: [PATCH 10/14] Remove some Mach-O/Darwinisms, get closer to this compiling on linux? I think maybe the only difference is that the symbol names are prefixed with _ on darwin? --- .../unwind-frameless-faulted/interrupt-and-trap-funcs.s | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s index 5bc991fc0f67d..2ba6a711767b3 100644 --- a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s +++ b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s @@ -5,7 +5,7 @@ #define ehframe_x23 23 #define ehframe_pc 32 - .section __TEXT,__text,regular,pure_instructions + .text //-------------------------------------- // to_be_interrupted() a frameless function that does a non-ABI @@ -14,7 +14,6 @@ // trap() knows to branch back to $x23 when it has finished. //-------------------------------------- .globl _to_be_interrupted - .p2align 2 _to_be_interrupted: .cfi_startproc @@ -45,7 +44,6 @@ L_.return: // break_to_debugger(). //-------------------------------------- .globl _trap - .p2align 2 _trap: .cfi_startproc .cfi_signal_frame @@ -90,7 +88,6 @@ _trap: // break_to_debugger() executes a BRK instruction //-------------------------------------- .globl _break_to_debugger - .p2align 2 _break_to_debugger: .cfi_startproc >From 80248f5a5841645a1da0026799c040d4db7ab6f3 Mon Sep 17 00:00:00 2001 From: Jason Molenda <jmole...@apple.com> Date: Fri, 9 May 2025 18:44:27 -0700 Subject: [PATCH 11/14] Adapt test case to build & run on linux or Darwin. There's a slight syntax diff for the ADRP + ADD pair and the label name. And the function names in Darwin are prepended with an _ and not on linux. clang on darwin runs the .s file through the preprocessor, but it doesn't seem to on linux. I renamed interrupt-and-trap-funcs.s to interrupt-and-trap-funcs.c and run it through the preprocessor then assemble it in Makefile now. The linux version would probably run on other AArch64 systems like FreeBSD but I haven't checked those. --- .../API/macosx/unwind-frameless-faulted/Makefile | 5 +++-- .../TestUnwindFramelessFaulted.py | 5 ++++- ...d-trap-funcs.s => interrupt-and-trap-funcs.c} | 16 ++++++++++++---- .../API/macosx/unwind-frameless-faulted/main.c | 10 ++++++++++ 4 files changed, 29 insertions(+), 7 deletions(-) rename lldb/test/API/macosx/unwind-frameless-faulted/{interrupt-and-trap-funcs.s => interrupt-and-trap-funcs.c} (88%) diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/Makefile b/lldb/test/API/macosx/unwind-frameless-faulted/Makefile index 76548928a3622..fca47ae47491c 100644 --- a/lldb/test/API/macosx/unwind-frameless-faulted/Makefile +++ b/lldb/test/API/macosx/unwind-frameless-faulted/Makefile @@ -1,7 +1,8 @@ C_SOURCES := main.c -interrupt-and-trap-funcs.o: interrupt-and-trap-funcs.s - $(CC) $(CFLAGS) -c -o interrupt-and-trap-funcs.o $(SRCDIR)/interrupt-and-trap-funcs.s +interrupt-and-trap-funcs.o: interrupt-and-trap-funcs.c + $(CC) $(CFLAGS) -E -o interrupt-and-trap-funcs.s $(SRCDIR)/interrupt-and-trap-funcs.c + $(CC) $(CFLAGS) -c -o interrupt-and-trap-funcs.o interrupt-and-trap-funcs.s include Makefile.rules diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/TestUnwindFramelessFaulted.py b/lldb/test/API/macosx/unwind-frameless-faulted/TestUnwindFramelessFaulted.py index d899b7a2f7369..fe4b9b52abebb 100644 --- a/lldb/test/API/macosx/unwind-frameless-faulted/TestUnwindFramelessFaulted.py +++ b/lldb/test/API/macosx/unwind-frameless-faulted/TestUnwindFramelessFaulted.py @@ -10,7 +10,10 @@ class TestUnwindFramelessFaulted(TestBase): NO_DEBUG_INFO_TESTCASE = True - @skipUnlessDarwin + @skipIf( + oslist=no_match([lldbplatformutil.getDarwinOSTriples(), "linux"]), + archs=no_match(["aarch64", "arm64", "arm64e"]), + ) def test_frameless_faulted_unwind(self): self.build() diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.c similarity index 88% rename from lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s rename to lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.c index 2ba6a711767b3..23c6cfd688969 100644 --- a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s +++ b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.c @@ -6,7 +6,6 @@ #define ehframe_pc 32 .text - //-------------------------------------- // to_be_interrupted() a frameless function that does a non-ABI // function call to trap(), simulating an async signal/interrup/exception/fault. @@ -25,12 +24,21 @@ mov x24, x0 add x24, x24, #1 - adrp x23, L_.return@PAGE ; put return address in x4 +#if defined(__APPLE__) + adrp x23, L_.return@PAGE // put return address in x4 add x23, x23, L_.return@PAGEOFF +#else + adrp x23, .L.return + add x23, x23, :lo12:.L.return +#endif - b _trap ; branch to trap handler, fake async interrupt + b _trap // branch to trap handler, fake async interrupt +#if defined(__APPLE__) L_.return: +#else +.L.return: +#endif mov x0, x24 ret .cfi_endproc @@ -95,7 +103,7 @@ L_.return: // retrieve the value if it wants. .cfi_same_value ehframe_x0 - brk #0xf000 ;; __builtin_debugtrap aarch64 instruction + brk #0xf000 // __builtin_debugtrap aarch64 instruction ret .cfi_endproc diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/main.c b/lldb/test/API/macosx/unwind-frameless-faulted/main.c index e121809f25478..27a2f228b5feb 100644 --- a/lldb/test/API/macosx/unwind-frameless-faulted/main.c +++ b/lldb/test/API/macosx/unwind-frameless-faulted/main.c @@ -1,6 +1,16 @@ +#if defined(__APPLE__) int to_be_interrupted(int); +#else +int _to_be_interrupted(int); +#endif + int main() { int c = 10; +#if defined(__APPLE__) c = to_be_interrupted(c); +#else + c = _to_be_interrupted(c); + #endif + return c; } >From 98be98ce9eb68dd89179ed1dc1905db9e5fd9cbf Mon Sep 17 00:00:00 2001 From: Jason Molenda <jmole...@apple.com> Date: Fri, 9 May 2025 18:48:00 -0700 Subject: [PATCH 12/14] Move this test from macosx/ to functionalities/unwind because it can run on darwin or linux aarch64. --- .../unwind/frameless-faulted}/Makefile | 0 .../unwind/frameless-faulted}/TestUnwindFramelessFaulted.py | 0 .../unwind/frameless-faulted}/interrupt-and-trap-funcs.c | 0 .../unwind/frameless-faulted}/main.c | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename lldb/test/API/{macosx/unwind-frameless-faulted => functionalities/unwind/frameless-faulted}/Makefile (100%) rename lldb/test/API/{macosx/unwind-frameless-faulted => functionalities/unwind/frameless-faulted}/TestUnwindFramelessFaulted.py (100%) rename lldb/test/API/{macosx/unwind-frameless-faulted => functionalities/unwind/frameless-faulted}/interrupt-and-trap-funcs.c (100%) rename lldb/test/API/{macosx/unwind-frameless-faulted => functionalities/unwind/frameless-faulted}/main.c (100%) diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/Makefile b/lldb/test/API/functionalities/unwind/frameless-faulted/Makefile similarity index 100% rename from lldb/test/API/macosx/unwind-frameless-faulted/Makefile rename to lldb/test/API/functionalities/unwind/frameless-faulted/Makefile diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/TestUnwindFramelessFaulted.py b/lldb/test/API/functionalities/unwind/frameless-faulted/TestUnwindFramelessFaulted.py similarity index 100% rename from lldb/test/API/macosx/unwind-frameless-faulted/TestUnwindFramelessFaulted.py rename to lldb/test/API/functionalities/unwind/frameless-faulted/TestUnwindFramelessFaulted.py diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.c b/lldb/test/API/functionalities/unwind/frameless-faulted/interrupt-and-trap-funcs.c similarity index 100% rename from lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.c rename to lldb/test/API/functionalities/unwind/frameless-faulted/interrupt-and-trap-funcs.c diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/main.c b/lldb/test/API/functionalities/unwind/frameless-faulted/main.c similarity index 100% rename from lldb/test/API/macosx/unwind-frameless-faulted/main.c rename to lldb/test/API/functionalities/unwind/frameless-faulted/main.c >From 2e1250cf6bdaed75a3829a8cf465b2fb798505d3 Mon Sep 17 00:00:00 2001 From: Jason Molenda <jmole...@apple.com> Date: Fri, 9 May 2025 18:51:15 -0700 Subject: [PATCH 13/14] typeo --- lldb/test/API/functionalities/unwind/frameless-faulted/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/test/API/functionalities/unwind/frameless-faulted/main.c b/lldb/test/API/functionalities/unwind/frameless-faulted/main.c index 27a2f228b5feb..1a016ea61affb 100644 --- a/lldb/test/API/functionalities/unwind/frameless-faulted/main.c +++ b/lldb/test/API/functionalities/unwind/frameless-faulted/main.c @@ -10,7 +10,7 @@ int main() { c = to_be_interrupted(c); #else c = _to_be_interrupted(c); - #endif +#endif return c; } >From 2d30011693cf3c3aa1ebdfde57a2c43687b37757 Mon Sep 17 00:00:00 2001 From: Jason Molenda <jmole...@apple.com> Date: Fri, 9 May 2025 19:05:15 -0700 Subject: [PATCH 14/14] Disabling clang-format of the assembly file called ".c" Add two more checks for non-ABI compliant register availability from the custom UnwindPlans. --- .../TestUnwindFramelessFaulted.py | 37 ++++++++++++++----- .../interrupt-and-trap-funcs.c | 7 ++++ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/lldb/test/API/functionalities/unwind/frameless-faulted/TestUnwindFramelessFaulted.py b/lldb/test/API/functionalities/unwind/frameless-faulted/TestUnwindFramelessFaulted.py index fe4b9b52abebb..dfb42c56164ac 100644 --- a/lldb/test/API/functionalities/unwind/frameless-faulted/TestUnwindFramelessFaulted.py +++ b/lldb/test/API/functionalities/unwind/frameless-faulted/TestUnwindFramelessFaulted.py @@ -55,31 +55,50 @@ def test_frameless_faulted_unwind(self): 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. + # 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. + # 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) + if self.TraceOn(): print("StepInstruction") thread.StepInstruction(False) diff --git a/lldb/test/API/functionalities/unwind/frameless-faulted/interrupt-and-trap-funcs.c b/lldb/test/API/functionalities/unwind/frameless-faulted/interrupt-and-trap-funcs.c index 23c6cfd688969..d32f01a2ea429 100644 --- a/lldb/test/API/functionalities/unwind/frameless-faulted/interrupt-and-trap-funcs.c +++ b/lldb/test/API/functionalities/unwind/frameless-faulted/interrupt-and-trap-funcs.c @@ -1,3 +1,10 @@ +// This is assembly code that needs to be run +// through the preprocessor, for simplicity of +// preprocessing it's named .c to start with. +// +// clang-format off + + #define DW_CFA_register 0x9 #define ehframe_x0 0 #define ehframe_x20 20 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits