From 654dce6c523612d38e8d53818dbc7c03cbe535a3 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Thu, 16 Apr 2026 14:25:27 -0400 Subject: [PATCH] linux-user/ppc: Fix ppc64 rt_sigframe stack offset The kernel's 64-bit signal delivery (signal_64.c) uses: newsp = frame - __SIGNAL_FRAMESIZE while the 32-bit path (signal_32.c) uses: newsp = frame - (__SIGNAL_FRAMESIZE + 16) The extra 16 bytes in the 32-bit case is to place siginfo and ucontext at the same offsets as older kernels (see the comment in signal_32.c). The 64-bit rt_sigframe starts with ucontext directly and does not need this adjustment. QEMU's setup_rt_frame() unconditionally used (SIGNAL_FRAMESIZE + 16) for both 32-bit and 64-bit, placing the handler's SP 16 bytes too low on ppc64. Signal delivery and return still worked because do_rt_sigreturn had the matching wrong offset, but the vDSO DWARF unwind info encodes the correct kernel offset. This caused any DWARF unwinder (libunwind, libgcc, etc.) to compute a CFA that is 16 bytes off, reading garbage register values from the signal frame. Define RT_SIGFRAME_ADJUST (0 on ppc64, 16 on ppc32) and use it in both setup_rt_frame and do_rt_sigreturn to match the kernel. This was verified by A/B testing with libunwind's test suite: ppc64le: Gtest-bt, Ltest-bt, Gtest-concurrent, Ltest-concurrent, and Ltest-sig-context all change from FAIL to PASS. ppc64be: Gtest-bt, Ltest-bt, and Ltest-sig-context all change from FAIL to PASS. Signed-off-by: Matt Turner Reviewed-by: Peter Maydell Signed-off-by: Helge Deller Cc: qemu-stable@nongnu.org --- linux-user/ppc/signal.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/linux-user/ppc/signal.c b/linux-user/ppc/signal.c index 24e5a02a78..a9c10e0987 100644 --- a/linux-user/ppc/signal.c +++ b/linux-user/ppc/signal.c @@ -210,6 +210,18 @@ QEMU_BUILD_BUG_ON(offsetof(struct target_rt_sigframe, uc.tuc_mcontext) #endif +#ifdef TARGET_PPC64 +#define RT_SIGFRAME_ADJUST 0 +#else +/* + * For 32-bit rt sigframes we have an extra 16 bytes of gap + * on top of __SIGNAL_FRAMESIZE; this is to get the siginfo + * and ucontext in the same positions as in older kernels. + * See Linux's arch/powerpc/kernel/signal_32.c. + */ +#define RT_SIGFRAME_ADJUST 16 +#endif + #if defined(TARGET_PPC64) struct target_func_ptr { @@ -525,7 +537,7 @@ void setup_rt_frame(int sig, struct target_sigaction *ka, env->fpscr = 0; /* Create a stack frame for the caller of the handler. */ - newsp = rt_sf_addr - (SIGNAL_FRAMESIZE + 16); + newsp = rt_sf_addr - (SIGNAL_FRAMESIZE + RT_SIGFRAME_ADJUST); err |= put_user(env->gpr[1], newsp, target_ulong); if (err) @@ -641,7 +653,7 @@ long do_rt_sigreturn(CPUPPCState *env) struct target_rt_sigframe *rt_sf = NULL; target_ulong rt_sf_addr; - rt_sf_addr = env->gpr[1] + SIGNAL_FRAMESIZE + 16; + rt_sf_addr = env->gpr[1] + SIGNAL_FRAMESIZE + RT_SIGFRAME_ADJUST; if (!lock_user_struct(VERIFY_READ, rt_sf, rt_sf_addr, 1)) goto sigsegv;