Browse Source

m68k: make the __jmp_buf layout match what setjmp.S actually saves

uClibc-ng's m68k setjmp.S (David McCullough, 2002) saves "d2-d7/a2-a7" as one
moveml block at JB_REGS plus the return PC at JB_PC -- it keeps only the
call-saved registers (d0/d1/a0/a1 and fp0/fp1 are caller-saved).  But
bits/setjmp.h still carried glibc's struct, which is laid out for glibc's C
setjmp.c (it saves d1-d7/a1-a5 and fills __fp/__sp explicitly).  So the member
names did not line up with the saved data:

  - the stack pointer (a7) actually lands in __aregs[4], while __fp/__sp were
    never written (they even sat past JB_SIZE);
  - JB_SIZE (52) disagreed with sizeof(struct) (60), so the sigjmp_buf
    __mask_was_saved flag -- written at JB_SIZE by bsd-_setjmp.S -- and the C
    struct's view of it ended up at different offsets.

This broke two things on m68k:
  - jmpbuf-unwind.h read the SP from __sp (uninitialised) and __aregs[5] (the
    saved PC), so NPTL forced-unwind cancellation longjmped on the first frame
    and never ran the -fexceptions cleanup handlers (tst-cancelx16/18/20/21,
    tst-cleanupx1/3, tst-oncex3/4);
  - the JB_SIZE vs sizeof mismatch corrupted the saved-signal-mask handling
    (tst-vfork-longjmp).

Fix the root cause instead of reading the SP from a magic register slot:
redefine the struct to mirror setjmp.S (__dregs[6]=d2-d7, __aregs[5]=a2-a6,
__sp=a7, __pc=return address, __fpregs=fp2-fp7), so every member is meaningful
and sizeof(__jmp_buf) == JB_SIZE.  jmpbuf-unwind.h then reads __sp like every
other target, and __mask_was_saved lands where bsd-_setjmp.S writes it.  Also
correct JB_SIZE for the FPU build: only fp2-fp7 are saved, so 52 + 6*12 = 124
(76 previously under-counted the fmovem area).

Verified on m68k/kernel-5.4 under qemu-system-m68k: the full test suite drops
from 11 failures to 1 (only tst-cancelx4, an unrelated blocked-in-syscall
case, remains); all setjmp/sigsetjmp/cleanup/once/vfork tests pass.

Signed-off-by: Ramin Moussavi <ramin.moussavi@yacoub.de>
Ramin Moussavi 6 days ago
parent
commit
77c371e095

+ 11 - 10
libc/sysdeps/linux/m68k/bits/setjmp.h

@@ -26,20 +26,21 @@
 
 #include <features.h>
 
+/* uClibc's m68k setjmp.S stores "%d2-%d7/%a2-%a7" as one moveml block at
+   JB_REGS, followed by the return PC at JB_PC.  Only the call-saved registers
+   are kept (d0/d1/a0/a1 and fp0/fp1 are caller-saved), so these members mirror
+   that on-stack layout -- in particular the stack pointer is %a7, saved as the
+   last address register, hence its own __sp member here.  */
 typedef struct
   {
-    /* There are eight 4-byte data registers, but D0 is not saved.  */
-    long int __dregs[7];
-
-    /* There are six 4-byte address registers, plus the FP and SP.  */
-    int *__aregs[6];
-    int *__fp;
-    int *__sp;
+    long int __dregs[6];	/* %d2-%d7 */
+    int *__aregs[5];		/* %a2-%a6 */
+    int *__sp;			/* %a7, the stack pointer */
+    int *__pc;			/* return address */
 
 #if defined __HAVE_68881__ || defined __UCLIBC_HAS_FPU__
-    /* There are eight floating point registers which
-       are saved in IEEE 96-bit extended format.  */
-    char __fpregs[8 * (96 / 8)];
+    /* %fp2-%fp7, IEEE 96-bit extended format (8 bytes each on ColdFire).  */
+    char __fpregs[6 * (96 / 8)];
 #endif
 
   } __jmp_buf[1];

+ 3 - 1
libc/sysdeps/linux/m68k/jmpbuf-offsets.h

@@ -12,8 +12,10 @@
 #define JB_PC     48
 #define JB_FPREGS 52
 
+/* JB_SIZE == sizeof(__jmp_buf): 12 regs + PC (= 52), plus the saved
+   %fp2-%fp7 (6 * 12 bytes) when an FPU is present.  */
 #if defined __HAVE_68881__ || defined __UCLIBC_HAS_FPU__
-# define JB_SIZE 76
+# define JB_SIZE 124
 #else
 # define JB_SIZE 52
 #endif

+ 1 - 1
libc/sysdeps/linux/m68k/jmpbuf-unwind.h

@@ -20,7 +20,7 @@
 /* Test if longjmp to JMPBUF would unwind the frame
    containing a local variable at ADDRESS.  */
 #define _JMPBUF_UNWINDS(jmpbuf, address) \
-  ((void *) (address) < (void *) (jmpbuf)->__aregs[5])
+  ((void *) (address) < (void *) (jmpbuf)->__sp)
 
 #ifdef __UCLIBC_HAS_THREADS_NATIVE__
 #include <stdint.h>