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}