348 lines
9.0 KiB
C
348 lines
9.0 KiB
C
/*
|
|
* 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 <setjmp.h>
|
|
|
|
#include "../src/internal/syscall.h"
|
|
|
|
/* x86_64 signal number (kernel UAPI <asm/signal.h>): 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 <fenv.h> 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");
|
|
}
|
|
|
|
/*
|
|
* 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();
|
|
|
|
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;
|
|
}
|