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}