/* * vlibc — setjmp/longjmp + sigsetjmp/siglongjmp test (todo 4). * * Exercises the non-local jump implementation end to end: * * 1. setjmp returns 0 on the initial call and 42 after longjmp(env, 42); * 2. longjmp(env, 0) makes setjmp return 1, not 0 (POSIX coercion); * 3. a volatile local survives a jump across a real call boundary; * 4. FP control state (MXCSR and the x87 control word) round-trips: * changed at the jump site, must come back as saved; * 5. sigsetjmp(env, 1) captures a blocked SIGUSR1 mask that siglongjmp * restores; sigsetjmp(env2, 0) must NOT restore it (flag logic); * 6. _setjmp/_longjmp basic round-trip. * * The signal mask is manipulated with raw SYS_rt_sigprocmask (no signal.h: * that header belongs to a later todo), and all diagnostics go through raw * SYS_write (no stdio: this test must not depend on host libc headers, which * the -Iinclude search path could otherwise shadow). With `-f`, only the * POSIX coercion failure scenario runs: longjmp(env, 0) must return 1 — a 0 * return is the defect. * * Not part of the library proper; compiled manually for this todo (the * tests/ + make check wiring is owned by a later todo). */ #include #include "../src/internal/syscall.h" /* x86_64 signal number (kernel UAPI ): SIGUSR1 = 10. */ #define TEST_SIGUSR1 10 /* rt_sigprocmask how codes (kernel UAPI). */ #define TEST_SIG_BLOCK 0 #define TEST_SIG_UNBLOCK 1 #define TEST_SIG_SETMASK 2 /* x86_64 Linux sigset_t is a single 64-bit word. */ #define TEST_SIGSETSIZE 8 static int failures; /* * rt_sigprocmask with the kernel argument registers pinned by hand. The * internal __syscall4() wrapper hands its 4th argument to the compiler as a * generic "r" operand, which the compiler may place in r8 — but the x86_64 * syscall ABI delivers the 4th argument in r10, so the kernel then sees * garbage in sigsetsize and rejects the call. Pin r10 here (the same * technique src/internal/syscall.h uses for __syscall6); this test is * self-contained and must be deterministic regardless of register * allocation. */ static long test_rt_sigprocmask(long how, const unsigned long *set, unsigned long *oldset) // NOLINT(readability-non-const-parameter) { register long rdx __asm__("rdx") = (long)oldset; register long rsi __asm__("rsi") = (long)set; register long rdi __asm__("rdi") = how; register long r10 __asm__("r10") = TEST_SIGSETSIZE; register long rax __asm__("rax") = SYS_rt_sigprocmask; __asm__ volatile("syscall" : "+a"(rax) : "r"(rdi), "r"(rsi), "r"(rdx), "r"(r10) : "rcx", "r11", "memory"); return rax; } /* Write a NUL-terminated string to fd via the raw syscall layer. */ static void say(int fd, const char *s) { long n = 0; while (s[n] != '\0') { n++; } __syscall3(SYS_write, fd, (long)s, n); } /* Write v in decimal to fd. */ static void say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters) { char buf[24]; int i = (int)sizeof(buf); buf[--i] = '\0'; do { buf[--i] = (char)('0' + (v % 10)); v /= 10; } while (v != 0); __syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i)); } static void check(int cond, const char *what) { if (cond) { say(1, "PASS: "); say(1, what); say(1, "\n"); } else { say(2, "FAIL: "); say(2, what); say(2, "\n"); failures++; } } /* Raw FP control-state access (the public owns this later). */ static unsigned read_mxcsr(void) { unsigned x; __asm__ volatile("stmxcsr %0" : "=m"(x)); return x; } static void write_mxcsr(unsigned x) { __asm__ volatile("ldmxcsr %0" : : "m"(x)); } static unsigned short read_x87_cw(void) { unsigned short c; __asm__ volatile("fnstcw %0" : "=m"(c)); return c; } static void write_x87_cw(unsigned short c) { __asm__ volatile("fldcw %0" : : "m"(c)); } /* Query the current signal mask via raw rt_sigprocmask (SIG_BLOCK + NULL). */ static unsigned long query_mask(void) { unsigned long old = 0; test_rt_sigprocmask(TEST_SIG_BLOCK, 0, &old); return old; } /* * Must stay a real function: the volatile-local test needs the longjmp to * cross an actual call boundary, not an inlined body. */ static __attribute__((noinline)) void jump_out(jmp_buf env, int val) { longjmp(env, val); } /* 1. Initial return 0, then longjmp(env, 42) -> 42. */ static void basic_roundtrip(void) { jmp_buf env; int r = setjmp(env); if (r == 0) { longjmp(env, 42); check(0, "longjmp returned to its caller (noreturn violated)"); return; } check(r == 42, "longjmp(env,42) -> setjmp returned 42"); } /* 2. longjmp(env, 0) -> setjmp returns 1, never 0. */ static void coercion_test(void) { jmp_buf env; int r = setjmp(env); if (r == 0) { longjmp(env, 0); check(0, "longjmp returned to its caller (noreturn violated)"); return; } check(r == 1, "longjmp(env,0) -> setjmp returned 1, not 0 (POSIX coercion)"); } /* 3. Volatile locals survive a jump across a call boundary. */ static void volatile_local_test(void) { jmp_buf env; volatile int local = 1; int r = setjmp(env); if (r == 0) { local = 99; jump_out(env, 5); check(0, "longjmp returned to its caller (noreturn violated)"); return; } check(r == 5 && local == 99, "volatile local survives longjmp across a call boundary"); } /* 4. FP control state round-trips through setjmp/longjmp. */ static void fp_state_test(void) { jmp_buf env; unsigned mx = read_mxcsr(); unsigned short cw = read_x87_cw(); int r = setjmp(env); if (r == 0) { /* Toggle the MXCSR rounding-control bits and the x87 PC+RC bits. */ write_mxcsr(mx ^ 0x6000U); write_x87_cw((unsigned short)(cw ^ 0x0f00U)); longjmp(env, 3); check(0, "longjmp returned to its caller (noreturn violated)"); return; } check(r == 3, "FP-state test: longjmp(env,3) -> setjmp returned 3"); check(read_mxcsr() == mx, "mxcsr restored by longjmp"); check(read_x87_cw() == cw, "x87 control word restored by longjmp"); } /* 5. sigsetjmp/siglongjmp round-trip a blocked mask; savemask=0 skips it. */ static void sigmask_test(void) { sigjmp_buf env; sigjmp_buf env2; unsigned long block = 1UL << (TEST_SIGUSR1 - 1); unsigned long orig = query_mask(); int r; /* Block SIGUSR1 (raw rt_sigprocmask), then save the masked state. */ test_rt_sigprocmask(TEST_SIG_BLOCK, &block, 0); r = sigsetjmp(env, 1); if (r == 0) { /* Drop the block; siglongjmp must restore it. */ test_rt_sigprocmask(TEST_SIG_UNBLOCK, &block, 0); siglongjmp(env, 7); check(0, "siglongjmp returned to its caller (noreturn violated)"); return; } check(r == 7, "siglongjmp(env,7) -> sigsetjmp returned 7"); check((query_mask() & block) != 0, "siglongjmp restored the blocked SIGUSR1 mask"); /* Negative: savemask == 0 -> the mask must NOT be restored. */ r = sigsetjmp(env2, 0); if (r == 0) { test_rt_sigprocmask(TEST_SIG_UNBLOCK, &block, 0); siglongjmp(env2, 8); check(0, "siglongjmp returned to its caller (noreturn violated)"); return; } check(r == 8, "siglongjmp(env2,8) -> sigsetjmp returned 8"); check((query_mask() & block) == 0, "sigsetjmp(env2,0): mask NOT restored (flag logic)"); /* Leave the process mask as it was found. */ test_rt_sigprocmask(TEST_SIG_SETMASK, &orig, 0); } /* 6. _setjmp/_longjmp basic round-trip. */ static void underscore_forms_test(void) { jmp_buf env; int r = _setjmp(env); if (r == 0) { _longjmp(env, 9); check(0, "_longjmp returned to its caller (noreturn violated)"); return; } 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. */ static int failure_scenario(void) { jmp_buf env; int r = setjmp(env); if (r == 0) { longjmp(env, 0); return 1; /* longjmp returned: noreturn violated */ } if (r == 1) { say(1, "longjmp(env,0) -> setjmp returned 1 (a 0 return would be the defect)\n"); return 0; } say(1, "longjmp(env,0) -> setjmp returned "); say_dec(1, (unsigned long)r); say(1, ", want 1\n"); return 1; } int main(int argc, char **argv) { if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f') { return failure_scenario(); } basic_roundtrip(); coercion_test(); volatile_local_test(); fp_state_test(); sigmask_test(); underscore_forms_test(); rbx_callee_saved_test(); if (failures > 0) { say(2, "FAILED ("); say_dec(2, (unsigned long)failures); say(2, " check(s))\n"); return 1; } say(1, "all setjmp tests passed\n"); return 0; }