feat(signal): signal handling and sigset_t
This commit is contained in:
@@ -0,0 +1,479 @@
|
||||
/*
|
||||
* vlibc — signal handling test (todo 28).
|
||||
*
|
||||
* Exercises the whole <signal.h> surface end to end:
|
||||
*
|
||||
* 1. sigaction installs a SIGUSR1 handler; raise() delivers it; the
|
||||
* handler runs exactly once and the process resumes after it (the
|
||||
* SA_RESTORER return trampoline works). Reading the action back with
|
||||
* act == NULL returns the same handler with SA_RESTORER masked out.
|
||||
* 2. sigprocmask SIG_BLOCK + raise makes the signal pending;
|
||||
* sigpending/sigismember report it; SIG_UNBLOCK delivers it to the
|
||||
* handler.
|
||||
* 3. sigwait consumes a blocked+pending SIGUSR1 (handler never runs).
|
||||
* 4. sigqueue(self, SIGUSR2, value) with an SA_SIGINFO handler: the
|
||||
* 3-arg handler runs and sees si_code == SI_QUEUE (-1).
|
||||
* 5. alarm(1) + pause(): the SIGALRM handler runs and pause returns -1.
|
||||
* 6. signal() installs a BSD (SA_RESTART) handler and returns the
|
||||
* previous sigaction-installed handler.
|
||||
* 7. abort() in a forked child kills it with SIGABRT (raw fork/wait4,
|
||||
* independent of the parallel fork todo).
|
||||
* 8. kill(pid, SIGKILL) on a sleeping child: reaped WIFSIGNALED with
|
||||
* WTERMSIG == SIGKILL.
|
||||
*
|
||||
* Level-2 gated section (compile with -DVLIBC_LEVEL=2):
|
||||
* 9. sigaltstack installs an alternate stack and an SA_ONSTACK SIGUSR2
|
||||
* handler provably runs on it (its locals land inside the stack).
|
||||
* 10. ualarm fires SIGALRM into the installed handler.
|
||||
* 11. siginterrupt clears and restores SA_RESTART.
|
||||
* 12. psignal/psiginfo write their stderr diagnostics.
|
||||
*
|
||||
* The -f mode runs the failure shapes — sigaction with sig -1/0/SIGKILL,
|
||||
* sigprocmask with an invalid how, sigaddset/sigismember with out-of-range
|
||||
* signals, killpg with a negative pgrp — asserting return values only
|
||||
* (errno is never read: vlibc's errno slot collides with the host TCB,
|
||||
* so every library errno write is bracketed with a save/restore of the
|
||||
* slot, house pattern). -f exits via raw SYS_exit_group.
|
||||
*
|
||||
* No host libc headers are included (the -Iinclude path would shadow
|
||||
* GCC's internal headers); diagnostics go through raw SYS_write. Not part
|
||||
* of the library proper; compiled manually for this todo (the tests/ +
|
||||
* make check wiring is owned by a later todo).
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "../src/internal/syscall.h"
|
||||
|
||||
/* Kernel timespec (vlibc <time.h> is not included by this TU). */
|
||||
struct t28_timespec
|
||||
{
|
||||
long tv_sec;
|
||||
long tv_nsec;
|
||||
};
|
||||
|
||||
static int failures;
|
||||
|
||||
static volatile sig_atomic_t usr1_count;
|
||||
static volatile sig_atomic_t alarm_count;
|
||||
static volatile sig_atomic_t bsd_count;
|
||||
static volatile sig_atomic_t usr2_count;
|
||||
static volatile int usr2_code;
|
||||
static volatile sig_atomic_t astk_fired;
|
||||
static volatile unsigned long astk_local_addr;
|
||||
|
||||
/* Write a NUL-terminated string to fd via the raw syscall layer. The
|
||||
* optimize attribute keeps GCC from lowering the length loop into a
|
||||
* strlen call, which would leave a vlibc-owned symbol undefined in this
|
||||
* host-linked standalone binary (house idiom, see src/string). */
|
||||
static __attribute__((optimize("no-tree-loop-distribute-patterns"))) void
|
||||
say(int fd, const char *s)
|
||||
{
|
||||
long n = 0;
|
||||
|
||||
while (s[n] != '\0')
|
||||
{
|
||||
n++;
|
||||
}
|
||||
__syscall3(SYS_write, fd, (long)s, n);
|
||||
}
|
||||
|
||||
static void
|
||||
check(int cond, const char *what)
|
||||
{
|
||||
if (cond)
|
||||
{
|
||||
say(1, "PASS: ");
|
||||
}
|
||||
else
|
||||
{
|
||||
say(1, "FAIL: ");
|
||||
failures++;
|
||||
}
|
||||
say(1, what);
|
||||
say(1, "\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Host-TCB slot-1 bracket: the library's errno write on a negative path
|
||||
* lands at %fs:0+8, glibc's dtv pointer. Save and restore it around each
|
||||
* such call; only vlibc/raw-syscall code runs in between (task 13
|
||||
* technique).
|
||||
*/
|
||||
static unsigned long
|
||||
tcb_slot1(void)
|
||||
{
|
||||
return *(unsigned long *)((char *)__builtin_thread_pointer() + 8);
|
||||
}
|
||||
|
||||
static void
|
||||
tcb_slot1_set(unsigned long v)
|
||||
{
|
||||
*(unsigned long *)((char *)__builtin_thread_pointer() + 8) = v;
|
||||
}
|
||||
|
||||
/* Terminate the current thread via the raw syscall (never returns). */
|
||||
static void
|
||||
t28_child_exit(int code)
|
||||
{
|
||||
__syscall1(SYS_exit, code);
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
static void
|
||||
h_usr1(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
usr1_count++;
|
||||
}
|
||||
|
||||
static void
|
||||
h_alarm(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
alarm_count++;
|
||||
}
|
||||
|
||||
static void
|
||||
h_bsd(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
bsd_count++;
|
||||
}
|
||||
|
||||
/* SA_SIGINFO handler: records si_code (kernel SI_QUEUE == -1 for sigqueue). */
|
||||
static void
|
||||
h_usr2_info(int sig, siginfo_t *si, void *ctx)
|
||||
{
|
||||
(void)sig;
|
||||
(void)ctx;
|
||||
usr2_count++;
|
||||
usr2_code = si->si_code;
|
||||
}
|
||||
|
||||
/* 1. sigaction install + raise + readback. */
|
||||
static void
|
||||
scenario_sigaction_raise(void)
|
||||
{
|
||||
struct sigaction act;
|
||||
struct sigaction old;
|
||||
|
||||
usr1_count = 0;
|
||||
act.sa_handler = h_usr1;
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = 0;
|
||||
check(sigaction(SIGUSR1, &act, NULL) == 0, "sigaction installs a SIGUSR1 handler");
|
||||
check(raise(SIGUSR1) == 0, "raise(SIGUSR1) delivers synchronously");
|
||||
check(usr1_count == 1, "SIGUSR1 handler ran exactly once and returned (resumed)");
|
||||
check(sigaction(SIGUSR1, NULL, &old) == 0 && old.sa_handler == h_usr1 &&
|
||||
(old.sa_flags & SA_RESTORER) == 0,
|
||||
"sigaction act==NULL reads back the handler, SA_RESTORER masked out");
|
||||
}
|
||||
|
||||
/* 2. Block, pend, inspect, unblock-deliver. */
|
||||
static void
|
||||
scenario_sigprocmask_pending(void)
|
||||
{
|
||||
sigset_t block;
|
||||
sigset_t pend;
|
||||
sig_atomic_t before = usr1_count;
|
||||
|
||||
sigemptyset(&block);
|
||||
sigaddset(&block, SIGUSR1);
|
||||
check(sigprocmask(SIG_BLOCK, &block, NULL) == 0, "sigprocmask SIG_BLOCK SIGUSR1");
|
||||
check(raise(SIGUSR1) == 0, "raise while blocked succeeds (signal pends)");
|
||||
sigemptyset(&pend);
|
||||
check(sigpending(&pend) == 0, "sigpending reports pending signals");
|
||||
check(sigismember(&pend, SIGUSR1) == 1, "sigpending contains SIGUSR1");
|
||||
check(sigismember(&pend, SIGALRM) == 0, "sigpending holds no unrelated signal");
|
||||
check(sigprocmask(SIG_UNBLOCK, &block, NULL) == 0,
|
||||
"sigprocmask SIG_UNBLOCK runs the pending handler");
|
||||
check(usr1_count == before + 1, "the pending SIGUSR1 was delivered on unblock");
|
||||
}
|
||||
|
||||
/* 3. sigwait consumes a blocked signal; the handler never runs. */
|
||||
static void
|
||||
scenario_sigwait(void)
|
||||
{
|
||||
sigset_t set;
|
||||
int got = -1;
|
||||
sig_atomic_t before = usr1_count;
|
||||
|
||||
sigemptyset(&set);
|
||||
sigaddset(&set, SIGUSR1);
|
||||
check(sigprocmask(SIG_BLOCK, &set, NULL) == 0, "sigwait: SIGUSR1 blocked");
|
||||
check(raise(SIGUSR1) == 0, "sigwait: pending SIGUSR1 raised");
|
||||
check(sigwait(&set, &got) == 0, "sigwait returns 0 (signal consumed)");
|
||||
check(got == SIGUSR1, "sigwait reports SIGUSR1");
|
||||
check(usr1_count == before, "sigwait consumed the signal: handler did not run");
|
||||
check(sigprocmask(SIG_UNBLOCK, &set, NULL) == 0, "sigwait: mask restored after");
|
||||
}
|
||||
|
||||
/* 4. sigqueue + SA_SIGINFO delivery (payload union + kernel siginfo). */
|
||||
static void
|
||||
scenario_sigqueue(void)
|
||||
{
|
||||
struct sigaction act;
|
||||
union sigval val;
|
||||
long self = __syscall0(SYS_getpid);
|
||||
|
||||
usr2_count = 0;
|
||||
usr2_code = 0;
|
||||
act.sa_sigaction = h_usr2_info;
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = SA_SIGINFO;
|
||||
check(sigaction(SIGUSR2, &act, NULL) == 0, "install an SA_SIGINFO SIGUSR2 handler");
|
||||
val.sival_int = 42;
|
||||
check(sigqueue((pid_t)self, SIGUSR2, val) == 0, "sigqueue(self, SIGUSR2) delivers");
|
||||
check(usr2_count == 1, "SA_SIGINFO handler ran once (3-arg form)");
|
||||
check(usr2_code == -1, "SA_SIGINFO handler saw si_code SI_QUEUE (-1)");
|
||||
}
|
||||
|
||||
/* 5. alarm(1) + pause(): the handler runs, pause returns -1/EINTR. */
|
||||
static void
|
||||
scenario_alarm_pause(void)
|
||||
{
|
||||
struct sigaction act;
|
||||
unsigned long saved;
|
||||
|
||||
alarm_count = 0;
|
||||
act.sa_handler = h_alarm;
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = 0;
|
||||
check(sigaction(SIGALRM, &act, NULL) == 0, "install a SIGALRM handler");
|
||||
check(alarm(1) == 0, "alarm(1) arms with no previous alarm");
|
||||
saved = tcb_slot1(); /* pause() reports via errno EINTR (library write) */
|
||||
check(pause() == -1, "pause() returns -1 after the SIGALRM handler ran");
|
||||
tcb_slot1_set(saved);
|
||||
check(alarm_count == 1, "SIGALRM handler ran during pause");
|
||||
alarm(0); /* cancel any leftover of the 1 s alarm */
|
||||
}
|
||||
|
||||
/* 6. signal(): BSD handler install, returns the previous disposition. */
|
||||
static void
|
||||
scenario_signal_fn(void)
|
||||
{
|
||||
void (*prev)(int);
|
||||
struct sigaction cur;
|
||||
|
||||
bsd_count = 0;
|
||||
prev = signal(SIGUSR1, h_bsd);
|
||||
check(prev == h_usr1, "signal() returns the previous (sigaction) handler");
|
||||
check(raise(SIGUSR1) == 0, "signal()-installed handler receives raise()");
|
||||
check(bsd_count == 1, "signal() BSD handler ran once");
|
||||
check(sigaction(SIGUSR1, NULL, &cur) == 0 && (cur.sa_flags & SA_RESTART) != 0,
|
||||
"signal() installed with SA_RESTART (BSD semantics)");
|
||||
}
|
||||
|
||||
/* 7. abort() in a forked child: dies by SIGABRT (raw fork + wait4). */
|
||||
static void
|
||||
scenario_abort_child(void)
|
||||
{
|
||||
long pid = __syscall0(SYS_fork);
|
||||
int st = 0;
|
||||
long r;
|
||||
|
||||
if (pid == 0)
|
||||
{
|
||||
abort();
|
||||
t28_child_exit(0);
|
||||
}
|
||||
if (pid > 0)
|
||||
{
|
||||
r = __syscall4(SYS_wait4, pid, (long)&st, 0, 0);
|
||||
check(r == pid && WIFSIGNALED(st) && WTERMSIG(st) == SIGABRT,
|
||||
"abort() kills the child with SIGABRT");
|
||||
}
|
||||
else
|
||||
{
|
||||
check(0, "fork for the abort child");
|
||||
}
|
||||
}
|
||||
|
||||
/* 8. kill(pid, SIGKILL) on a sleeping child: reaped WIFSIGNALED. */
|
||||
static void
|
||||
scenario_kill_child(void)
|
||||
{
|
||||
long pid = __syscall0(SYS_fork);
|
||||
int st = 0;
|
||||
long r;
|
||||
|
||||
if (pid == 0)
|
||||
{
|
||||
struct t28_timespec ts = {.tv_sec = 10, .tv_nsec = 0};
|
||||
|
||||
__syscall2(SYS_nanosleep, (long)&ts, 0);
|
||||
t28_child_exit(0);
|
||||
}
|
||||
if (pid > 0)
|
||||
{
|
||||
check(kill((pid_t)pid, SIGKILL) == 0, "kill(pid, SIGKILL) succeeds");
|
||||
r = __syscall4(SYS_wait4, pid, (long)&st, 0, 0);
|
||||
check(r == pid && WIFSIGNALED(st) && WTERMSIG(st) == SIGKILL,
|
||||
"SIGKILLed child is reaped WIFSIGNALED WTERMSIG 9");
|
||||
}
|
||||
else
|
||||
{
|
||||
check(0, "fork for the SIGKILL child");
|
||||
}
|
||||
}
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
/* Level 2 (muslmimic): sigaltstack / ualarm / siginterrupt / psignal. */
|
||||
|
||||
static char t28_alt_stack[SIGSTKSZ * 2];
|
||||
|
||||
/* SA_ONSTACK handler: records where its own frame landed. */
|
||||
static void
|
||||
h_astk(int sig)
|
||||
{
|
||||
volatile unsigned long local = 0;
|
||||
|
||||
(void)sig;
|
||||
astk_local_addr = (unsigned long)&local;
|
||||
astk_fired = 1;
|
||||
}
|
||||
|
||||
/* 9. Alternate stack: an SA_ONSTACK handler provably runs on it. */
|
||||
static void
|
||||
scenario_sigaltstack(void)
|
||||
{
|
||||
struct sigaction act;
|
||||
stack_t ss;
|
||||
stack_t cur;
|
||||
stack_t disable;
|
||||
unsigned long base = (unsigned long)t28_alt_stack;
|
||||
int on_range;
|
||||
|
||||
astk_fired = 0;
|
||||
astk_local_addr = 0;
|
||||
ss.ss_sp = t28_alt_stack;
|
||||
ss.ss_flags = 0;
|
||||
ss.ss_size = sizeof t28_alt_stack;
|
||||
check(sigaltstack(&ss, NULL) == 0, "sigaltstack installs an alternate stack");
|
||||
check(sigaltstack(NULL, &cur) == 0, "sigaltstack reads the current stack back");
|
||||
check(cur.ss_sp == t28_alt_stack && cur.ss_flags == 0 && cur.ss_size == sizeof t28_alt_stack,
|
||||
"sigaltstack readback matches the installed descriptor");
|
||||
|
||||
act.sa_handler = h_astk;
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = SA_ONSTACK;
|
||||
check(sigaction(SIGUSR2, &act, NULL) == 0, "install an SA_ONSTACK SIGUSR2 handler");
|
||||
check(raise(SIGUSR2) == 0, "raise SIGUSR2 with the alternate stack armed");
|
||||
on_range =
|
||||
astk_fired == 1 && astk_local_addr >= base && astk_local_addr < base + sizeof t28_alt_stack;
|
||||
check(on_range, "SA_ONSTACK handler ran with its frame on the alternate stack");
|
||||
|
||||
disable.ss_sp = NULL;
|
||||
disable.ss_flags = SS_DISABLE;
|
||||
disable.ss_size = 0;
|
||||
check(sigaltstack(&disable, NULL) == 0, "sigaltstack disables the alternate stack");
|
||||
act.sa_handler = SIG_DFL;
|
||||
act.sa_flags = 0;
|
||||
sigaction(SIGUSR2, &act, NULL);
|
||||
}
|
||||
|
||||
/* 10. ualarm fires SIGALRM into the installed handler. */
|
||||
static void
|
||||
scenario_ualarm(void)
|
||||
{
|
||||
int i;
|
||||
int fired = 0;
|
||||
|
||||
alarm_count = 0;
|
||||
check(ualarm(200000, 0) != (useconds_t)-1, "ualarm(200 ms, 0) arms an itimer");
|
||||
for (i = 0; i < 5000 && alarm_count == 0; i++)
|
||||
{
|
||||
struct t28_timespec ts = {.tv_sec = 0, .tv_nsec = 1000000L}; /* 1 ms */
|
||||
|
||||
__syscall2(SYS_nanosleep, (long)&ts, 0);
|
||||
}
|
||||
fired = alarm_count > 0;
|
||||
check(fired, "ualarm fired SIGALRM within 5 s");
|
||||
ualarm(0, 0); /* cancel the timer */
|
||||
}
|
||||
|
||||
/* 11. siginterrupt toggles SA_RESTART on the current action. */
|
||||
static void
|
||||
scenario_siginterrupt(void)
|
||||
{
|
||||
struct sigaction cur;
|
||||
|
||||
check(siginterrupt(SIGUSR1, 1) == 0, "siginterrupt(sig, 1) succeeds");
|
||||
check(sigaction(SIGUSR1, NULL, &cur) == 0 && (cur.sa_flags & SA_RESTART) == 0,
|
||||
"siginterrupt(sig, 1) cleared SA_RESTART");
|
||||
check(siginterrupt(SIGUSR1, 0) == 0, "siginterrupt(sig, 0) succeeds");
|
||||
check(sigaction(SIGUSR1, NULL, &cur) == 0 && (cur.sa_flags & SA_RESTART) != 0,
|
||||
"siginterrupt(sig, 0) restored SA_RESTART");
|
||||
}
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
|
||||
/* The failure scenarios, run alone under -f (writes errno; see top). */
|
||||
static int
|
||||
failure_scenarios(void)
|
||||
{
|
||||
struct sigaction act;
|
||||
sigset_t set;
|
||||
unsigned long saved = tcb_slot1();
|
||||
|
||||
act.sa_handler = SIG_DFL;
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = 0;
|
||||
check(sigaction(-1, &act, NULL) == -1, "-f: sigaction(-1, ...) returns -1");
|
||||
check(sigaction(0, &act, NULL) == -1, "-f: sigaction(0, ...) returns -1");
|
||||
check(sigaction(SIGKILL, &act, NULL) == -1, "-f: sigaction(SIGKILL, catch) returns -1");
|
||||
sigemptyset(&set);
|
||||
check(sigprocmask(99, &set, NULL) == -1, "-f: sigprocmask(invalid how) returns -1");
|
||||
check(sigaddset(&set, 0) == -1, "-f: sigaddset(sig 0) returns -1");
|
||||
check(sigaddset(&set, NSIG) == -1, "-f: sigaddset(sig NSIG) returns -1");
|
||||
check(sigismember(&set, 0) == -1, "-f: sigismember(sig 0) returns -1");
|
||||
check(killpg(-5, SIGUSR1) == -1, "-f: killpg(negative pgrp) returns -1");
|
||||
|
||||
tcb_slot1_set(saved);
|
||||
return failures;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
||||
{
|
||||
/*
|
||||
* The failure scenarios write errno inside the library; under the
|
||||
* host libc that slot is glibc's private TLS state, so leave via
|
||||
* the raw syscall without running host cleanup.
|
||||
*/
|
||||
int rc = failure_scenarios();
|
||||
|
||||
__syscall1(SYS_exit_group, rc);
|
||||
return rc; /* not reached */
|
||||
}
|
||||
|
||||
scenario_sigaction_raise();
|
||||
scenario_sigprocmask_pending();
|
||||
scenario_sigwait();
|
||||
scenario_sigqueue();
|
||||
scenario_alarm_pause();
|
||||
scenario_signal_fn();
|
||||
scenario_abort_child();
|
||||
scenario_kill_child();
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
scenario_sigaltstack();
|
||||
scenario_ualarm();
|
||||
scenario_siginterrupt();
|
||||
psignal(SIGUSR1, "t28 psignal");
|
||||
psiginfo(NULL, "t28 psiginfo(NULL)");
|
||||
#endif
|
||||
|
||||
if (failures > 0)
|
||||
{
|
||||
say(2, "FAILED (");
|
||||
say(2, "some checks failed");
|
||||
say(2, ")\n");
|
||||
return 1;
|
||||
}
|
||||
say(1, "all signal tests passed\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user