From: Matt Turner <[email protected]>

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 <[email protected]>
Reviewed-by: Peter Maydell <[email protected]>
Signed-off-by: Helge Deller <[email protected]>
Cc: [email protected]
(cherry picked from commit 654dce6c523612d38e8d53818dbc7c03cbe535a3)
Signed-off-by: Michael Tokarev <[email protected]>

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;
 
-- 
2.47.3


Reply via email to