feat(setjmp): x86_64 setjmp/longjmp/sigsetjmp
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
#ifndef VLIBC_SETJMP_H
|
||||
#define VLIBC_SETJMP_H
|
||||
|
||||
/*
|
||||
* vlibc — <setjmp.h>.
|
||||
*
|
||||
* Non-local jumps. setjmp(env) saves the calling environment and returns 0;
|
||||
* longjmp(env, val) makes that setjmp return val, with 0 coerced to 1 so the
|
||||
* two returns are distinguishable. sigsetjmp/siglongjmp additionally save and
|
||||
* restore the signal mask when the buffer's saved-mask flag is set.
|
||||
*
|
||||
* jmp_buf is an array type (C23 7.13.1 requires it); both types below are
|
||||
* arrays of unsigned long. The word layout is ABI: the x86_64 assembly in
|
||||
* src/setjmp/x86_64/{setjmp,longjmp,sigsetjmp,siglongjmp}.s reads and writes
|
||||
* exactly these slots. This comment is the single authoritative map — keep it
|
||||
* and those files in sync:
|
||||
*
|
||||
* [0] rbx callee-saved general registers (System V AMD64 ABI)
|
||||
* [1] rbp
|
||||
* [2] r12
|
||||
* [3] r13
|
||||
* [4] r14
|
||||
* [5] r15
|
||||
* [6] rsp the caller's stack pointer (just past the return address)
|
||||
* [7] rip the return address, where longjmp resumes execution
|
||||
* [8] mxcsr SSE control/status register (stmxcsr/ldmxcsr)
|
||||
* [9] x87 cw x87 control word (fnstcw/fldcw)
|
||||
*
|
||||
* sigjmp_buf extends the base layout with two more words:
|
||||
*
|
||||
* [10] saved-mask flag (1 when the signal mask was saved)
|
||||
* [11] signal mask (the x86_64 Linux sigset_t: a single 64-bit word)
|
||||
*
|
||||
* Because the base layout occupies words 0..9 of both types, the plain
|
||||
* setjmp/longjmp assembly works verbatim on sigjmp_buf storage as well.
|
||||
*/
|
||||
|
||||
#include <vlibc/features.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Word counts and per-slot indices (see the authoritative map above). */
|
||||
#define VLIBC_JMP_BUF_WORDS 10
|
||||
#define VLIBC_JMP_RBX 0
|
||||
#define VLIBC_JMP_RBP 1
|
||||
#define VLIBC_JMP_R12 2
|
||||
#define VLIBC_JMP_R13 3
|
||||
#define VLIBC_JMP_R14 4
|
||||
#define VLIBC_JMP_R15 5
|
||||
#define VLIBC_JMP_RSP 6
|
||||
#define VLIBC_JMP_RIP 7
|
||||
#define VLIBC_JMP_MXCSR 8
|
||||
#define VLIBC_JMP_X87CW 9
|
||||
|
||||
#define VLIBC_SIGJMP_BUF_WORDS 12
|
||||
#define VLIBC_SIGJMP_FLAG 10
|
||||
#define VLIBC_SIGJMP_MASK 11
|
||||
|
||||
typedef unsigned long jmp_buf[VLIBC_JMP_BUF_WORDS];
|
||||
typedef unsigned long sigjmp_buf[VLIBC_SIGJMP_BUF_WORDS];
|
||||
|
||||
/*
|
||||
* Save the calling environment and return 0; after longjmp(env, val) the
|
||||
* matching setjmp invocation returns val. A macro (C23 7.13.1: usable without
|
||||
* a prototype in scope); it expands to _setjmp(env).
|
||||
*/
|
||||
#define setjmp(env) _setjmp(env)
|
||||
|
||||
/*
|
||||
* Like setjmp, plus the current signal mask is saved into env when savemask is
|
||||
* nonzero (siglongjmp then restores it). Macro expanding to
|
||||
* __sigsetjmp(env, savemask).
|
||||
*/
|
||||
#define sigsetjmp(env, savemask) __sigsetjmp(env, savemask)
|
||||
|
||||
/*
|
||||
* The save behind the setjmp macro; identical semantics (POSIX _setjmp).
|
||||
*/
|
||||
int
|
||||
_setjmp(jmp_buf env); // NOLINT(bugprone-reserved-identifier)
|
||||
|
||||
/*
|
||||
* Restore the environment saved by _setjmp/setjmp and make it return val, with
|
||||
* 0 coerced to 1. Does not return.
|
||||
*/
|
||||
__attribute__((noreturn)) void
|
||||
_longjmp(jmp_buf env, int val); // NOLINT(bugprone-reserved-identifier)
|
||||
|
||||
/*
|
||||
* ISO C longjmp: same as _longjmp.
|
||||
*/
|
||||
__attribute__((noreturn)) void
|
||||
longjmp(jmp_buf env, int val);
|
||||
|
||||
/*
|
||||
* The save behind the sigsetjmp macro. Implementation-reserved name, so the
|
||||
* public API keeps only the macro; declared because the macro expansion must
|
||||
* have a prototype in scope (C23 removed implicit declarations).
|
||||
*/
|
||||
int
|
||||
__sigsetjmp(sigjmp_buf env, int savemask); // NOLINT(bugprone-reserved-identifier)
|
||||
|
||||
/*
|
||||
* Restore the environment saved by sigsetjmp and make it return val, with 0
|
||||
* coerced to 1; when the buffer's saved-mask flag is set, the signal mask is
|
||||
* restored first. Does not return.
|
||||
*/
|
||||
__attribute__((noreturn)) void
|
||||
siglongjmp(sigjmp_buf env, int val);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* VLIBC_SETJMP_H */
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* vlibc — x86_64 longjmp (todo 4).
|
||||
*
|
||||
* longjmp(env, val) / _longjmp(env, val): restore the environment saved by
|
||||
* setjmp and make that setjmp return val — with 0 coerced to 1 (POSIX:
|
||||
* longjmp(env, 0) must return 1 so it is distinguishable from the initial
|
||||
* setjmp return of 0). Never returns.
|
||||
*
|
||||
* Restores the FP control state (MXCSR and x87 control word), the
|
||||
* callee-saved registers, then jumps to the saved rip with the saved rsp,
|
||||
* exactly as if setjmp had returned val.
|
||||
*
|
||||
* Registers: env arrives in rdi and is moved to r12, a callee-saved scratch
|
||||
* register that is itself restored from the buffer, so it can serve as the
|
||||
* base for every load. val rides in rax, which is never restored from the
|
||||
* buffer, and is therefore the value setjmp observes. The saved rsp and rip
|
||||
* travel through rdx and rcx (both caller-saved, safe to clobber) until the
|
||||
* final `mov %rdx,%rsp; jmp *%rcx`.
|
||||
*/
|
||||
|
||||
.global longjmp
|
||||
.global _longjmp
|
||||
.type longjmp,@function
|
||||
.type _longjmp,@function
|
||||
longjmp:
|
||||
_longjmp:
|
||||
mov %rsi,%rax /* val; coerce 0 to 1 below */
|
||||
test %rax,%rax
|
||||
jnz 1f
|
||||
inc %rax
|
||||
1:
|
||||
mov %rdi,%r12 /* env base (r12 is restored from the buffer later) */
|
||||
ldmxcsr 64(%r12)
|
||||
fldcw 72(%r12)
|
||||
mov 0(%r12),%rbx
|
||||
mov 8(%r12),%rbp
|
||||
mov 32(%r12),%r14
|
||||
mov 40(%r12),%r15
|
||||
mov 48(%r12),%rdx /* saved rsp */
|
||||
mov 56(%r12),%rcx /* saved rip */
|
||||
mov 24(%r12),%r13
|
||||
mov 16(%r12),%r12 /* last read of env */
|
||||
mov %rdx,%rsp
|
||||
jmp *%rcx
|
||||
|
||||
.size longjmp,.-longjmp
|
||||
.size _longjmp,.-_longjmp
|
||||
.section .note.GNU-stack,"",@progbits
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* vlibc — x86_64 setjmp (todo 4).
|
||||
*
|
||||
* setjmp(env) / _setjmp(env): save the calling environment into env and
|
||||
* return 0. Both entry points share one body; the setjmp macro in
|
||||
* include/setjmp.h expands to _setjmp, while the plain symbol is kept so
|
||||
* address-takers and #undef-users still link.
|
||||
*
|
||||
* jmp_buf slot map (the authoritative layout; see include/setjmp.h):
|
||||
*
|
||||
* [0] rbx [1] rbp [2] r12 [3] r13 [4] r14
|
||||
* [5] r15 [6] rsp [7] rip [8] mxcsr [9] x87 control word
|
||||
*
|
||||
* The saved rsp is the caller's stack pointer (rsp + 8, past the return
|
||||
* address pushed by the call); the saved rip is that return address, so
|
||||
* longjmp can resume exactly as if setjmp had returned. The FP control
|
||||
* state (MXCSR and the x87 control word) is saved as well, so a longjmp out
|
||||
* of code that changed rounding/trap state restores it — this must be
|
||||
* assembly: C cannot access the register file.
|
||||
*
|
||||
* Only the caller's callee-saved registers are preserved by this function
|
||||
* itself (rbx is saved into the buffer before any use); caller-saved
|
||||
* registers are untouched, and the return value 0 is delivered in eax.
|
||||
*/
|
||||
|
||||
.global setjmp
|
||||
.global _setjmp
|
||||
.type setjmp,@function
|
||||
.type _setjmp,@function
|
||||
setjmp:
|
||||
_setjmp:
|
||||
mov %rbx,0(%rdi)
|
||||
mov %rbp,8(%rdi)
|
||||
mov %r12,16(%rdi)
|
||||
mov %r13,24(%rdi)
|
||||
mov %r14,32(%rdi)
|
||||
mov %r15,40(%rdi)
|
||||
lea 8(%rsp),%rdx
|
||||
mov %rdx,48(%rdi)
|
||||
mov (%rsp),%rax
|
||||
mov %rax,56(%rdi)
|
||||
stmxcsr 64(%rdi)
|
||||
fnstcw 72(%rdi)
|
||||
xor %eax,%eax
|
||||
ret
|
||||
|
||||
.size setjmp,.-setjmp
|
||||
.size _setjmp,.-_setjmp
|
||||
.section .note.GNU-stack,"",@progbits
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* vlibc — x86_64 siglongjmp (todo 4).
|
||||
*
|
||||
* siglongjmp(env, val): like longjmp, but when the buffer's saved-mask flag
|
||||
* (word 10, see include/setjmp.h) is set, first restore the signal mask
|
||||
* stored at word 11 with rt_sigprocmask(SIG_SETMASK). Never returns.
|
||||
*
|
||||
* The syscall is made inline here: siglongjmp is naked assembly that must
|
||||
* not call into C while the register set is half-restored. The kernel ABI
|
||||
* facts hardcoded below mirror src/internal/syscall.h:
|
||||
*
|
||||
* SYS_rt_sigprocmask = 14, SIG_SETMASK = 2, sigsetsize = 8
|
||||
* (x86_64 Linux sigset_t is a single 64-bit word)
|
||||
*
|
||||
* Register discipline matches longjmp.s: env moves to r12 (callee-saved
|
||||
* scratch, restored from the buffer last), val rides in rax — stashed in
|
||||
* r13 across the syscall, which clobbers rax — and the saved rsp/rip travel
|
||||
* through rdx/rcx until the final `mov %rdx,%rsp; jmp *%rcx`.
|
||||
*/
|
||||
|
||||
.global siglongjmp
|
||||
.type siglongjmp,@function
|
||||
siglongjmp:
|
||||
mov %rsi,%rax /* val; coerce 0 to 1 below */
|
||||
test %rax,%rax
|
||||
jnz 1f
|
||||
inc %rax
|
||||
1:
|
||||
mov %rdi,%r12 /* env base (r12 is restored from the buffer later) */
|
||||
cmpl $0,80(%r12) /* saved-mask flag */
|
||||
jz 2f
|
||||
mov %rax,%r13 /* val must survive the syscall */
|
||||
mov $14,%eax /* SYS_rt_sigprocmask */
|
||||
mov $2,%edi /* SIG_SETMASK */
|
||||
lea 88(%r12),%rsi /* set = &env[11] */
|
||||
xor %edx,%edx /* oldset = NULL */
|
||||
mov $8,%r10d /* sigsetsize (x86_64 sigset_t = one word) */
|
||||
syscall
|
||||
mov %r13,%rax /* val back in rax */
|
||||
2:
|
||||
ldmxcsr 64(%r12)
|
||||
fldcw 72(%r12)
|
||||
mov 0(%r12),%rbx
|
||||
mov 8(%r12),%rbp
|
||||
mov 32(%r12),%r14
|
||||
mov 40(%r12),%r15
|
||||
mov 48(%r12),%rdx /* saved rsp */
|
||||
mov 56(%r12),%rcx /* saved rip */
|
||||
mov 24(%r12),%r13
|
||||
mov 16(%r12),%r12 /* last read of env */
|
||||
mov %rdx,%rsp
|
||||
jmp *%rcx
|
||||
|
||||
.size siglongjmp,.-siglongjmp
|
||||
.section .note.GNU-stack,"",@progbits
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* vlibc — x86_64 sigsetjmp (todo 4).
|
||||
*
|
||||
* __sigsetjmp(env, savemask) / sigsetjmp(env, savemask): like setjmp, plus
|
||||
* the current signal mask is saved into env when savemask is nonzero. Both
|
||||
* entry points share one body; the sigsetjmp macro in include/setjmp.h
|
||||
* expands to __sigsetjmp, while the plain symbol is kept so address-takers
|
||||
* and #undef-users still link.
|
||||
*
|
||||
* Slot map (authoritative; see include/setjmp.h): a sigjmp_buf holds the
|
||||
* base jmp_buf layout at words 0..9 (same offsets as plain setjmp, so the
|
||||
* shared longjmp restore logic works on either storage), plus:
|
||||
*
|
||||
* [10] saved-mask flag (1 when the signal mask was saved)
|
||||
* [11] signal mask (the x86_64 Linux sigset_t: a single 64-bit word)
|
||||
*
|
||||
* The saved-mask flag is cleared before anything else, so a buffer saved
|
||||
* with savemask == 0 — or jumped to by plain longjmp — never presents a
|
||||
* stale flag to siglongjmp. When savemask is nonzero, the mask is queried
|
||||
* with a raw rt_sigprocmask syscall made inline right here: this function
|
||||
* must stay pure assembly, because the saved rip/rsp resume DIRECTLY in the
|
||||
* caller (an intermediate C frame would have its return-address slot above
|
||||
* the saved stack pointer, where intervening calls would clobber it before
|
||||
* the longjmp). The kernel ABI facts below mirror src/internal/syscall.h:
|
||||
*
|
||||
* SYS_rt_sigprocmask = 14, SIG_BLOCK = 0 (NULL newset queries),
|
||||
* sigsetsize = 8 (x86_64 sigset_t is one 64-bit word)
|
||||
*
|
||||
* The mask slot is written through &env[11]; on success the flag is set and
|
||||
* 0 is returned in eax, exactly like plain setjmp.
|
||||
*/
|
||||
|
||||
.global __sigsetjmp
|
||||
.global sigsetjmp
|
||||
.type __sigsetjmp,@function
|
||||
.type sigsetjmp,@function
|
||||
__sigsetjmp:
|
||||
sigsetjmp:
|
||||
movq $0,80(%rdi) /* clear the saved-mask flag */
|
||||
mov %rbx,0(%rdi)
|
||||
mov %rbp,8(%rdi)
|
||||
mov %r12,16(%rdi)
|
||||
mov %r13,24(%rdi)
|
||||
mov %r14,32(%rdi)
|
||||
mov %r15,40(%rdi)
|
||||
lea 8(%rsp),%rdx
|
||||
mov %rdx,48(%rdi)
|
||||
mov (%rsp),%rax
|
||||
mov %rax,56(%rdi)
|
||||
stmxcsr 64(%rdi)
|
||||
fnstcw 72(%rdi)
|
||||
test %esi,%esi
|
||||
jz 1f
|
||||
mov %rdi,%rbx /* env base (rbx is restored from the buffer later) */
|
||||
mov $14,%eax /* SYS_rt_sigprocmask */
|
||||
xor %edi,%edi /* how = SIG_BLOCK(0) */
|
||||
xor %esi,%esi /* set = NULL (query only) */
|
||||
lea 88(%rbx),%rdx /* oldset = &env[11] */
|
||||
mov $8,%r10d /* sigsetsize (x86_64 sigset_t = one word) */
|
||||
syscall
|
||||
movq $1,80(%rbx) /* mask saved: set the flag */
|
||||
1:
|
||||
xor %eax,%eax
|
||||
ret
|
||||
|
||||
.size __sigsetjmp,.-__sigsetjmp
|
||||
.size sigsetjmp,.-sigsetjmp
|
||||
.section .note.GNU-stack,"",@progbits
|
||||
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user