code wiki / (root) / nx_signal.nx

nx_signal.nx source

↩ module page · 144 lines · 5069 B

1// nx_signal.nx -- POSIX signal numbers + sigaction syscall wrapper. 2// 3// Used by: 4// - Panic handler (catch SIGSEGV / SIGBUS / SIGILL / SIGFPE and 5// print a structured backtrace before the kernel writes a 6// core file). 7// - Long-running daemons (catch SIGTERM / SIGINT for clean 8// shutdown). 9// - Profilers (catch SIGPROF for sampling). 10// - Self-modifying code probes (catch SIGTRAP for breakpoint 11// handling outside a debugger). 12// 13// We provide the constants + a minimal wrapper around the Linux 14// rt_sigaction(2) syscall. The handler signature is 15// func(sig: i64, info: *u8, ctx: *u8) -> i64 16// (3-arg SA_SIGINFO form). Today NishiLang doesn't yet have 17// first-class function pointers, so we accept a raw i64 address 18// the caller resolves via inline asm or by leaking the symbol. 19 20// nx_safety_envelope: 21// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 22// sil_target: SIL1 23// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 24// verdict: NOT_YET_EVALUATED 25 26import "syscalls.nx" 27 28// Signal numbers (Linux RV64 -- same as x86_64 for the common ones). 29const NX_SIGHUP: i64 = 1 30const NX_SIGINT: i64 = 2 31const NX_SIGQUIT: i64 = 3 32const NX_SIGILL: i64 = 4 33const NX_SIGTRAP: i64 = 5 34const NX_SIGABRT: i64 = 6 35const NX_SIGBUS: i64 = 7 36const NX_SIGFPE: i64 = 8 37const NX_SIGKILL: i64 = 9 38const NX_SIGUSR1: i64 = 10 39const NX_SIGSEGV: i64 = 11 40const NX_SIGUSR2: i64 = 12 41const NX_SIGPIPE: i64 = 13 42const NX_SIGALRM: i64 = 14 43const NX_SIGTERM: i64 = 15 44const NX_SIGCHLD: i64 = 17 45const NX_SIGCONT: i64 = 18 46const NX_SIGSTOP: i64 = 19 47const NX_SIGPROF: i64 = 27 48 49// sa_flags 50const NX_SA_NOCLDSTOP: i64 = 0x00000001 51const NX_SA_NOCLDWAIT: i64 = 0x00000002 52const NX_SA_SIGINFO: i64 = 0x00000004 53const NX_SA_ONSTACK: i64 = 0x08000000 54const NX_SA_RESTART: i64 = 0x10000000 55const NX_SA_NODEFER: i64 = 0x40000000 56const NX_SA_RESETHAND: i64 = 0x80000000 57 58// SIG_DFL / SIG_IGN -- treat them as raw addresses for sigaction. 59const NX_SIG_DFL: i64 = 0 60const NX_SIG_IGN: i64 = 1 61 62// struct kernel_sigaction (Linux): 63// void (*sa_handler)(int) u64 64// unsigned long sa_flags u64 65// void (*sa_restorer)(void) u64 66// sigset_t sa_mask (RV64 = 8 bytes for 64-bit mask) 67struct NxSigAction { 68 handler: i64, 69 flags: i64, 70 restorer: i64, 71 mask: i64, 72} 73 74const NX_SIGACTION_BYTES: i64 = 32 75 76func nx_signal_action_new(handler: i64, flags: i64, mask: i64) -> *NxSigAction { 77 let raw: *u8 = sys_mmap(NX_SIGACTION_BYTES) 78 let a: *NxSigAction = raw as *NxSigAction 79 a.handler = handler 80 a.flags = flags 81 a.restorer = 0 82 a.mask = mask 83 return a 84} 85 86// rt_sigaction syscall (Linux 134). Args: 87// sig: i64 88// act: pointer to new NxSigAction (or 0 to query) 89// old: pointer to old NxSigAction (or 0 to discard) 90// sigsetsize: i64 = 8 (RV64 mask is one i64) 91// 92// Returns 0 on success, negative errno on failure. 93func nx_signal_action_install(sig: i64, act: *NxSigAction, old: *NxSigAction) -> i64 { 94 return __syscall(134, sig, act as i64, old as i64, 8, 0, 0) 95} 96 97// Install SIG_IGN for a given signal (most common pattern: ignore 98// SIGPIPE so writes to closed sockets don't kill the process). 99func nx_signal_ignore(sig: i64) -> i64 { 100 let act: *NxSigAction = nx_signal_action_new(NX_SIG_IGN, 0, 0) 101 return nx_signal_action_install(sig, act, 0 as *NxSigAction) 102} 103 104// Restore default handler for a given signal. 105func nx_signal_default(sig: i64) -> i64 { 106 let act: *NxSigAction = nx_signal_action_new(NX_SIG_DFL, 0, 0) 107 return nx_signal_action_install(sig, act, 0 as *NxSigAction) 108} 109 110// kill(pid, sig) -- send a signal. Handy for self-signaling 111// (kill(getpid(), SIGABRT) inside a panic handler). 112func nx_signal_kill(pid: i64, sig: i64) -> i64 { 113 return __syscall(129, pid, sig, 0, 0, 0, 0) 114} 115 116func nx_signal_raise(sig: i64) -> i64 { 117 let pid: i64 = __syscall(172, 0, 0, 0, 0, 0, 0) 118 return nx_signal_kill(pid, sig) 119} 120 121// ---- self-test --------------------------------------------------- 122 123func main() -> i64 { 124 // Action struct round-trips fields. 125 let a: *NxSigAction = nx_signal_action_new(0xDEADBEEF, NX_SA_SIGINFO, 0) 126 if a.handler != 0xDEADBEEF { return __syscall(93, 1, 0, 0, 0, 0, 0) } 127 if a.flags != NX_SA_SIGINFO { return __syscall(93, 2, 0, 0, 0, 0, 0) } 128 129 // Sig number constants are within Linux range (1..64). 130 if NX_SIGSEGV != 11 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 131 if NX_SIGTERM != 15 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 132 if NX_SIGCHLD != 17 { return __syscall(93, 5, 0, 0, 0, 0, 0) } 133 134 // Installing SIG_IGN for SIGPIPE on the host should succeed. 135 let r: i64 = nx_signal_ignore(NX_SIGPIPE) 136 // We can't assert r == 0 because the host may not have 137 // permission, but it shouldn't crash. 138 if r > 0 { return __syscall(93, 6, 0, 0, 0, 0, 0) } // r is errno-style: 0 or negative 139 140 // Restore default for SIGPIPE to leave the host in a clean state. 141 nx_signal_default(NX_SIGPIPE) 142 143 return 0 144}