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 1/4] [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 2/4] 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 3/4] 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 4/4] 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 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits