code wiki / _hdl_build / nx_engineer_crash.nx
nx_engineer_crash.nx source
↩ module page · 145 lines · 6122 B
1// nx_engineer_crash.nx -- the Engineer organ that finds CRASHES, not just bad exit
2// codes. The gate runner / race gate read wait_exit_code (bits 8-15) and so see a
3// SIGSEGV as exit 0 -- blind to the worst failure. A self-sufficient team must catch
4// its own crashes. This decodes the FULL wait4 status: a process killed by a signal
5// (segfault=11, abort=6, ...) is reported as a CRASH with the signal number, and its
6// captured stdout is preserved so the LAST line localizes where it died.
7//
8// wait4 status (Linux): low 7 bits = terminating signal (0 = exited normally, 0x7f =
9// stopped); bits 8-15 = exit code when exited normally.
10
11import "nx_syscalls.nx"
12
13// run binpath with stdout -> capf (so output survives a crash). Returns: 0..255 =
14// normal exit code; 0 - (1000 + signal) = killed by that signal (CRASH).
15func eng_run(binpath: *u8, capf: *u8) -> i64 {
16 let argv: *i64 = sys_mmap(16) as *i64
17 argv[0] = binpath as i64; argv[1] = 0
18 let envp: *i64 = sys_mmap(16) as *i64
19 envp[0] = ("PATH=/usr/bin:/bin:/usr/local/bin" as *u8) as i64; envp[1] = 0
20 let pid: i64 = sys_fork()
21 if pid < 0 { return 0 - 1 }
22 if pid == 0 {
23 if capf != (0 as *u8) {
24 let fd: i64 = sys_openat_wr(capf, 0x1a4)
25 if fd >= 0 { sys_dup3(fd, 1, 0); sys_close(fd) }
26 }
27 sys_execve(binpath, argv, envp)
28 sys_exit(127)
29 }
30 let status: *i64 = sys_mmap(16) as *i64
31 status[0] = 0
32 sys_wait4(pid, status, 0)
33 let st: i64 = status[0]
34 let sig: i64 = st & 0x7f
35 if sig != 0 { if sig != 0x7f { return 0 - (1000 + sig) } } // killed by a signal
36 return (st >> 8) & 0xff // normal exit code
37}
38
39// the UPSTREAM check the crash detector taught us to add: a crash often starts as a
40// silent compile failure (empty .s linked into a garbage binary). eng_compile runs
41// <compiler> <src> -> sout and returns 0 only if it exited 0 AND produced non-empty
42// asm; -1 = compiler error/crash, -3 = empty output (the failure that segfaults later).
43func eng_compile(compiler: *u8, src: *u8, sout: *u8) -> i64 {
44 let argv: *i64 = sys_mmap(16) as *i64
45 argv[0] = compiler as i64; argv[1] = src as i64; argv[2] = 0
46 let envp: *i64 = sys_mmap(16) as *i64
47 envp[0] = ("PATH=/usr/bin:/bin" as *u8) as i64; envp[1] = 0
48 let pid: i64 = sys_fork()
49 if pid < 0 { return 0 - 1 }
50 if pid == 0 {
51 let fd: i64 = sys_openat_wr(sout, 0x1a4)
52 if fd >= 0 { sys_dup3(fd, 1, 0); sys_close(fd) }
53 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4)
54 if dn >= 0 { sys_dup3(dn, 2, 0); sys_close(dn) }
55 sys_execve(compiler, argv, envp)
56 sys_exit(127)
57 }
58 let status: *i64 = sys_mmap(16) as *i64
59 status[0] = 0
60 sys_wait4(pid, status, 0)
61 let st: i64 = status[0]
62 if (st & 0x7f) != 0 { return 0 - 1 } // compiler killed by a signal
63 if ((st >> 8) & 0xff) != 0 { return 0 - 1 } // compiler exited nonzero (rc!=0)
64 let rfd: i64 = sys_openat_rd(sout) // and the asm must be non-empty
65 if rfd < 0 { return 0 - 3 }
66 let buf: *u8 = sys_mmap(8)
67 let n: i64 = sys_read(rfd, buf, 1)
68 sys_close(rfd)
69 if n <= 0 { return 0 - 3 }
70 return 0
71}
72
73// same contract as eng_compile, but the compiler's stderr is CAPTURED to errout
74// instead of discarded -- so the Engineer's diagnostic ("reserved keyword 'X' ...")
75// becomes a readable artifact the Doctor can diagnose its fix from. Detection still
76// belongs to the Engineer; the Doctor only CONSUMES the artifact (roles stay clean).
77func eng_compile_diag(compiler: *u8, src: *u8, sout: *u8, errout: *u8) -> i64 {
78 let argv: *i64 = sys_mmap(16) as *i64
79 argv[0] = compiler as i64; argv[1] = src as i64; argv[2] = 0
80 let envp: *i64 = sys_mmap(16) as *i64
81 envp[0] = ("PATH=/usr/bin:/bin" as *u8) as i64; envp[1] = 0
82 let pid: i64 = sys_fork()
83 if pid < 0 { return 0 - 1 }
84 if pid == 0 {
85 let fd: i64 = sys_openat_wr(sout, 0x1a4)
86 if fd >= 0 { sys_dup3(fd, 1, 0); sys_close(fd) }
87 let ef: i64 = sys_openat_wr(errout, 0x1a4)
88 if ef >= 0 { sys_dup3(ef, 2, 0); sys_close(ef) }
89 sys_execve(compiler, argv, envp)
90 sys_exit(127)
91 }
92 let status: *i64 = sys_mmap(16) as *i64
93 status[0] = 0
94 sys_wait4(pid, status, 0)
95 let st: i64 = status[0]
96 if (st & 0x7f) != 0 { return 0 - 1 }
97 if ((st >> 8) & 0xff) != 0 { return 0 - 1 }
98 let rfd: i64 = sys_openat_rd(sout)
99 if rfd < 0 { return 0 - 3 }
100 let buf: *u8 = sys_mmap(8)
101 let n: i64 = sys_read(rfd, buf, 1)
102 sys_close(rfd)
103 if n <= 0 { return 0 - 3 }
104 return 0
105}
106
107// link a compiled .s -> ELF (gcc -nostdlib -no-pie -static). 0 = ok, <0 = link fail.
108func eng_link(sin: *u8, eout: *u8) -> i64 {
109 let argv: *i64 = sys_mmap(8 * 8) as *i64
110 argv[0] = ("/usr/bin/gcc" as *u8) as i64
111 argv[1] = ("-nostdlib" as *u8) as i64
112 argv[2] = ("-no-pie" as *u8) as i64
113 argv[3] = ("-static" as *u8) as i64
114 argv[4] = sin as i64
115 argv[5] = ("-o" as *u8) as i64
116 argv[6] = eout as i64
117 argv[7] = 0
118 let envp: *i64 = sys_mmap(16) as *i64
119 envp[0] = ("PATH=/usr/bin:/bin:/usr/local/bin" as *u8) as i64; envp[1] = 0
120 let pid: i64 = sys_fork()
121 if pid < 0 { return 0 - 1 }
122 if pid == 0 {
123 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4)
124 if dn >= 0 { sys_dup3(dn, 2, 0); sys_close(dn) }
125 sys_execve("/usr/bin/gcc" as *u8, argv, envp)
126 sys_exit(127)
127 }
128 let status: *i64 = sys_mmap(16) as *i64
129 status[0] = 0
130 sys_wait4(pid, status, 0)
131 let st: i64 = status[0]
132 if (st & 0x7f) != 0 { return 0 - 1 }
133 if ((st >> 8) & 0xff) != 0 { return 0 - 1 }
134 return 0
135}
136
137func eng_crashed(rc: i64) -> i64 { if rc <= (0 - 1000) { return 1 } return 0 }
138func eng_signal(rc: i64) -> i64 { return (0 - rc) - 1000 }
139func eng_sig_name(sig: i64) -> *u8 {
140 if sig == 11 { return "SIGSEGV (segfault)" as *u8 }
141 if sig == 6 { return "SIGABRT" as *u8 }
142 if sig == 4 { return "SIGILL" as *u8 }
143 if sig == 8 { return "SIGFPE" as *u8 }
144 return "signal" as *u8
145}