/* * vlibc — signal handling test (todo 28). * * Exercises the whole 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 #include #include "../src/internal/syscall.h" /* Kernel timespec (vlibc 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; }