diff --git a/src/setjmp/x86_64/sigsetjmp.s b/src/setjmp/x86_64/sigsetjmp.s index 404fd63..5c423a3 100644 --- a/src/setjmp/x86_64/sigsetjmp.s +++ b/src/setjmp/x86_64/sigsetjmp.s @@ -28,6 +28,12 @@ * * The mask slot is written through &env[11]; on success the flag is set and * 0 is returned in eax, exactly like plain setjmp. + * + * rbx carries the env base across the syscall (rdi/rsi/rdx/r10 hold the + * syscall arguments, rax the number, rcx/r11 are clobbered by the kernel). + * rbx is callee-saved: its original value was stored in env[0] above, so it + * is reloaded from there BEFORE the ret, leaving every callee-saved register + * intact on the ordinary return path as the SysV AMD64 ABI requires. */ .global __sigsetjmp @@ -51,7 +57,7 @@ sigsetjmp: fnstcw 72(%rdi) test %esi,%esi jz 1f - mov %rdi,%rbx /* env base (rbx is restored from the buffer later) */ + mov %rdi,%rbx /* env base (scratch; caller rbx is in env[0]) */ mov $14,%eax /* SYS_rt_sigprocmask */ xor %edi,%edi /* how = SIG_BLOCK(0) */ xor %esi,%esi /* set = NULL (query only) */ @@ -59,6 +65,7 @@ sigsetjmp: mov $8,%r10d /* sigsetsize (x86_64 sigset_t = one word) */ syscall movq $1,80(%rbx) /* mask saved: set the flag */ + mov 0(%rbx),%rbx /* restore the caller's rbx before returning */ 1: xor %eax,%eax ret diff --git a/tests/test_setjmp.c b/tests/test_setjmp.c index a8f19ed..0e69f96 100644 --- a/tests/test_setjmp.c +++ b/tests/test_setjmp.c @@ -294,6 +294,30 @@ underscore_forms_test(void) check(r == 9, "_longjmp(env,9) -> _setjmp returned 9"); } +/* + * 7. The initial sigsetjmp(env, 1) return must preserve the caller's rbx. + * A canary is planted in rbx via inline assembly (declared clobbered, so + * the compiler holds nothing else there), sigsetjmp returns normally, and + * rbx is read back raw: the SysV AMD64 ABI requires the function to leave + * every callee-saved register intact on the ordinary return path, and the + * compiler assumes exactly that when allocating registers around the call, + * so nothing reloads rbx between the plant and the read. + */ +static __attribute__((noinline)) void +rbx_callee_saved_test(void) +{ + sigjmp_buf env; + unsigned long after = 0; + int r; + + __asm__ volatile("mov $0x12345678, %%rbx" ::: "rbx"); + r = sigsetjmp(env, 1); + __asm__ volatile("mov %%rbx, %0" : "=r"(after)); + + check(r == 0, "sigsetjmp(env,1) initial return is 0 (rbx canary scenario)"); + check(after == 0x12345678UL, "caller rbx preserved across sigsetjmp(env,1) initial return"); +} + /* * Failure scenario (-f): longjmp(env, 0) must make setjmp return 1. Exits 0 * only when the coercion behaved exactly as POSIX specifies. @@ -334,6 +358,7 @@ main(int argc, char **argv) fp_state_test(); sigmask_test(); underscore_forms_test(); + rbx_callee_saved_test(); if (failures > 0) {