[libunwind] [WIP][PAC][libunwind] Handle LR and IP signing around sigreturn frame (PR #184661)
Anatoly Trosinenko via cfe-commits
cfe-commits at lists.llvm.org
Thu Mar 5 03:57:06 PST 2026
================
@@ -2944,8 +2950,14 @@ int UnwindCursor<A, R>::stepThroughSigReturn(Registers_arm64 &) {
static_cast<pint_t>(i * 8));
_registers.setRegister(UNW_AARCH64_X0 + i, value);
}
- _registers.setSP(_addressSpace.get64(sigctx + kOffsetSp));
- _registers.setIP(_addressSpace.get64(sigctx + kOffsetPc));
+ uint64_t sp = _addressSpace.get64(sigctx + kOffsetSp);
+ uint64_t ip = _addressSpace.get64(sigctx + kOffsetPc);
+#if defined(_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING)
+ ip = (uint64_t)ptrauth_sign_unauthenticated((void *)ip,
+ ptrauth_key_return_address, sp);
----------------
atrosinenko wrote:
As far as I can see, this value is read from an `mcontext_t` structure written to the stack by the Linux kernel itself. This is the value that was in IP at the time regular execution flow was interrupted by the kernel to invoke SIGSEGV handler.
Here is what I see in the debugger running inside QEMU just before SIGSEGV (full system emulation, not qemu-user; simulated CPU is `neoverse-v1` and the guest kernel is rebuilt with narrower virtual addresses / wider PAC field):
```
(gdb) x/7i Signaller
0x5555560ff0 <_Z9Signallerv>: pacibsp
0x5555560ff4 <_Z9Signallerv+4>: str x30, [sp, #-16]!
0x5555560ff8 <_Z9Signallerv+8>: adrp x8, 0x5555581000
0x5555560ffc <_Z9Signallerv+12>: ldr x8, [x8, #2408]
=> 0x5555561000 <_Z9Signallerv+16>: str xzr, [x8]
0x5555561004 <_Z9Signallerv+20>: ldr x30, [sp], #16
0x5555561008 <_Z9Signallerv+24>: retab
(gdb) p/x $sp
$1 = 0x7ffffff290
(gdb) p/x $lr
$2 = 0x28c6d555561040
```
After stepping to the next instruction, signal handler is invoked:
```
(gdb) x/4i $pc
=> 0x5555560f8c <_Z7Handleri>: pacibsp
0x5555560f90 <_Z7Handleri+4>: sub sp, sp, #0x30
0x5555560f94 <_Z7Handleri+8>: stp x29, x30, [sp, #32]
0x5555560f98 <_Z7Handleri+12>: add x29, sp, #0x20
(gdb) p/x $sp
$4 = 0x7fffffe030
(gdb) p/x $lr
$5 = 0x7ff7fbadc4
(gdb) x/2i $lr
0x7ff7fbadc4 <__restore_rt>: mov x8, #0x8b // #139
0x7ff7fbadc8 <__restore_rt+4>: svc #0x0
```
Note that SP was decremented by 0x1260 and LR was arranged so that when our signal handler returns, execution is transferred to `__restore_rt` (as far as I understand, in this particular case `__restore_rt` is implemented by musl libc and its address is passed to the kernel in `sa_restorer` field of `struct sigaction` by the libc itself). LR is not signed at this point but this is correct: it is signed by the `Handler`'s prologue as usual. Invocation of `Handler` is unwound as usual thanks to CFI information emitted for `Handler` and then it is `__restore_rt` frame that `setInfoForSigReturn` and `stepThroughSigReturn` deal with.
Here is what is stored on the stack (by the kernel, I assume) at several offsets mentioned in `stepThroughSigReturn` function's implementation for AArch64:
```
# SP:
(gdb) p/x *(uintptr_t *)($sp + 304 + 256)
$7 = 0x7ffffff290
# PC:
(gdb) p/x *(uintptr_t *)($sp + 304 + 264)
$8 = 0x5555561000
(gdb) x/3i 0x5555561000
0x5555561000 <_Z9Signallerv+16>: str xzr, [x8]
0x5555561004 <_Z9Signallerv+20>: ldr x30, [sp], #16
0x5555561008 <_Z9Signallerv+24>: retab
# LR:
(gdb) p/x *(uintptr_t *)($sp + 304 + 8 + 30 * 8)
$9 = 0x28c6d555561040
(gdb) x/4i (0x28c6d555561040 & 0x7fffffffff)
0x5555561040 <_Z6Calleri+52>: b 0x5555561044 <_Z6Calleri+56>
0x5555561044 <_Z6Calleri+56>: ldp x29, x30, [sp, #16]
0x5555561048 <_Z6Calleri+60>: add sp, sp, #0x20
0x555556104c <_Z6Calleri+64>: retab
```
The registers' values are stored exactly as they were immediately before the execution of faulted `str` instruction.
https://github.com/llvm/llvm-project/pull/184661
More information about the cfe-commits
mailing list