code wiki / (root) / nx_argv.nx

nx_argv.nx source

↩ module page · 284 lines · 10545 B

1// nx_argv.nx -- Linux RV64 entry-stack builder. 2// 3// At program entry the Linux kernel hands the runtime a stack 4// laid out per System V ABI: 5// 6// sp -> argc (i64) 7// argv[0] (pointer to NUL-terminated string) 8// argv[1] 9// ... 10// argv[argc-1] 11// NULL 12// envp[0] 13// envp[1] 14// ... 15// envp[K-1] 16// NULL 17// auxv entries (Elf64_auxv_t = i64 a_type, i64 a_un.a_val) 18// AT_NULL terminator (a_type=0, a_un=0) 19// (string area: argv/envp string bodies) 20// 21// Without this layout, a simulated program crashes the moment it 22// reads its first argument. Setting it up correctly lets us pass 23// arguments through nxc_native_wrap to the simulated program, which 24// is required for any meaningful test harness. 25// 26// Pairs with nx_rv64_sim (consumes the stack via sp register) + 27// nxc_native_wrap (wires this in front of nx_rv64_run). 28// 29// Reference: System V ABI for RV64, "Process Initialization" section 30// (https://github.com/riscv-non-isa/riscv-elf-psabi-doc). 31 32// nx_safety_envelope: 33// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 34// sil_target: SIL1 35// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 36// verdict: NOT_YET_EVALUATED 37 38import "syscalls.nx" 39import "runtime.nx" 40const NX_MAGIC_4096: i64 = 4096 41const NX_MAGIC_1048576: i64 = 1048576 42 43// AT_* auxv types. We populate the few that real programs read. 44const NX_AT_NULL: i64 = 0 // end-of-vector marker 45const NX_AT_PHDR: i64 = 3 // address of program headers 46const NX_AT_PHENT: i64 = 4 // size of one phdr entry 47const NX_AT_PHNUM: i64 = 5 // count of phdrs 48const NX_AT_PAGESZ: i64 = 6 // system page size 49const NX_AT_BASE: i64 = 7 // dynamic linker base addr 50const NX_AT_FLAGS: i64 = 8 // (always 0 in our case) 51const NX_AT_ENTRY: i64 = 9 // program entry point 52const NX_AT_UID: i64 = 11 53const NX_AT_EUID: i64 = 12 54const NX_AT_GID: i64 = 13 55const NX_AT_EGID: i64 = 14 56const NX_AT_HWCAP: i64 = 16 // CPU feature bits 57const NX_AT_CLKTCK: i64 = 17 // clock_t per second 58const NX_AT_RANDOM: i64 = 25 // 16-byte random seed (stack canary) 59 60// strlen canonical in runtime.nx. 61 62// Build the entry stack for a simulated RV64 program in `mem` of 63// total size `mem_size`. 64// 65// Returns the simulated `sp` value (an offset into `mem`) the caller 66// should set on the simulated CPU. 67// 68// Inputs: 69// mem -- HOST pointer to the simulated memory region. We 70// write into the TOP of this region (top-down stack). 71// mem_size -- size in bytes of the simulated region. 72// load_base -- virtual address the simulated memory starts at. 73// Returned sp is in the simulated address space, so: 74// sim_sp = load_base + (host_sp - mem) 75// argc -- number of argv entries. 76// argv_host -- HOST array of pointers to NUL-terminated strings 77// (length argc). 78// envc -- number of envp entries. 79// envp_host -- HOST array of pointers to envp strings (length envc). 80// entry -- program entry point (used for AT_ENTRY). 81// 82// Returns -1 on overflow. 83// 84// Layout is built from the TOP of memory downward: 85// 1. Copy argv/envp string bodies (NUL-terminated). 86// 2. 16-byte random seed (NX_AT_RANDOM). 87// 3. Align to 16 bytes. 88// 4. AuxV entries (terminated by AT_NULL). 89// 5. envp pointer array + NULL terminator. 90// 6. argv pointer array + NULL terminator. 91// 7. argc (i64). 92// 93// All pointers stored in the simulated stack are in the simulated 94// virtual address space (load_base + offset_into_mem). 95func nx_argv_build_stack( 96 mem: *u8, 97 mem_size: i64, 98 load_base: i64, 99 argc: i64, 100 argv_host: *i64, 101 envc: i64, 102 envp_host: *i64, 103 entry: i64 104) -> i64 { 105 // Stage 1: copy strings into top of memory. We allocate a 106 // pointer table that holds the SIMULATED address of each string. 107 let argv_sim_raw: *u8 = sys_mmap((argc + 1) * 8) 108 let argv_sim: *i64 = argv_sim_raw as *i64 109 let envp_sim_raw: *u8 = sys_mmap((envc + 1) * 8) 110 let envp_sim: *i64 = envp_sim_raw as *i64 111 112 var top: i64 = mem_size 113 // Walk argv string bodies. 114 var i: i64 = 0 115 while i < argc { 116 let s_host: *u8 = (argv_host[i]) as *u8 117 let n: i64 = strlen(s_host) 118 top = top - (n + 1) 119 if top < 0 { return -1 } 120 // Copy s_host[0..=n] into mem[top..top+n+1]. 121 var k: i64 = 0 122 while k <= n { 123 mem[top + k] = s_host[k] 124 k = k + 1 125 } 126 argv_sim[i] = load_base + top 127 i = i + 1 128 } 129 argv_sim[argc] = 0 130 // Walk envp string bodies. 131 var j: i64 = 0 132 while j < envc { 133 let e_host: *u8 = (envp_host[j]) as *u8 134 let n2: i64 = strlen(e_host) 135 top = top - (n2 + 1) 136 if top < 0 { return -1 } 137 var k2: i64 = 0 138 while k2 <= n2 { 139 mem[top + k2] = e_host[k2] 140 k2 = k2 + 1 141 } 142 envp_sim[j] = load_base + top 143 j = j + 1 144 } 145 envp_sim[envc] = 0 146 147 // Stage 2: 16-byte random seed (AT_RANDOM target). 148 top = top - 16 149 if top < 0 { return -1 } 150 var r: i64 = 0 151 while r < 16 { 152 // Deterministic seed for reproducibility under F6. Real 153 // builds plug the kernel-provided seed in here. 154 mem[top + r] = 0 155 r = r + 1 156 } 157 let at_random_addr: i64 = load_base + top 158 159 // Stage 3: pre-compute total down-stack size of the rest, then 160 // pick `top` so that the final sp lands on a 16-byte boundary 161 // (System V ABI requirement). 162 let n_aux: i64 = 4 163 let rest: i64 = n_aux * 16 + (envc + 1) * 8 + (argc + 1) * 8 + 8 164 let target_sp: i64 = (top - rest) & (~15) 165 top = target_sp + rest 166 167 // Stage 4: build auxv entries. Each entry is two i64s. Order 168 // doesn't matter, but AT_NULL must be last. 169 // Entries: PAGESZ, ENTRY, RANDOM, AT_NULL. 170 top = top - n_aux * 16 171 if top < 0 { return -1 } 172 let aux: *i64 = ((mem as i64) + top) as *i64 173 aux[0] = NX_AT_PAGESZ; aux[1] = NX_MAGIC_4096 174 aux[2] = NX_AT_ENTRY; aux[3] = entry 175 aux[4] = NX_AT_RANDOM; aux[5] = at_random_addr 176 aux[6] = NX_AT_NULL; aux[7] = 0 177 178 // Stage 5: envp pointer array + NULL terminator. 179 top = top - (envc + 1) * 8 180 if top < 0 { return -1 } 181 var ev: i64 = 0 182 while ev <= envc { 183 let dst: *i64 = (((mem as i64) + top + ev * 8) as *i64) 184 *dst = envp_sim[ev] 185 ev = ev + 1 186 } 187 188 // Stage 6: argv pointer array + NULL terminator. 189 top = top - (argc + 1) * 8 190 if top < 0 { return -1 } 191 var av: i64 = 0 192 while av <= argc { 193 let dst2: *i64 = (((mem as i64) + top + av * 8) as *i64) 194 *dst2 = argv_sim[av] 195 av = av + 1 196 } 197 198 // Stage 7: argc. 199 top = top - 8 200 if top < 0 { return -1 } 201 let argc_dst: *i64 = (((mem as i64) + top) as *i64) 202 *argc_dst = argc 203 204 return load_base + top 205} 206 207// Convenience: build a single-arg stack from a host string pointer 208// (e.g. the simulated program's argv[0] = path passed to wrap). 209func nx_argv_build_single(mem: *u8, mem_size: i64, load_base: i64, argv0: *u8, entry: i64) -> i64 { 210 let av_raw: *u8 = sys_mmap(8) 211 let av: *i64 = av_raw as *i64 212 av[0] = argv0 as i64 213 let envp_raw: *u8 = sys_mmap(8) 214 let envp: *i64 = envp_raw as *i64 215 return nx_argv_build_stack(mem, mem_size, load_base, 1, av, 0, envp, entry) 216} 217 218// ---- self-test --------------------------------------------------- 219 220func main() -> i64 { 221 // 1 MiB simulated memory at virtual base 0x10000. 222 let mem_size: i64 = NX_MAGIC_1048576 223 let mem: *u8 = sys_mmap(mem_size) 224 var i: i64 = 0 225 while i < mem_size { mem[i] = 0; i = i + 1 } 226 227 // Pretend argv = ["nxc.elf", "-O", "input.nx"]. 228 let arg0: *u8 = sys_mmap(16) 229 arg0[0] = 0x6E; arg0[1] = 0x78; arg0[2] = 0x63; arg0[3] = 0x2E 230 arg0[4] = 0x65; arg0[5] = 0x6C; arg0[6] = 0x66; arg0[7] = 0 231 let arg1: *u8 = sys_mmap(8) 232 arg1[0] = 0x2D; arg1[1] = 0x4F; arg1[2] = 0 233 let arg2: *u8 = sys_mmap(16) 234 arg2[0] = 0x69; arg2[1] = 0x6E; arg2[2] = 0x70; arg2[3] = 0x75 235 arg2[4] = 0x74; arg2[5] = 0x2E; arg2[6] = 0x6E; arg2[7] = 0x78; arg2[8] = 0 236 let argv_raw: *u8 = sys_mmap(32) 237 let argv: *i64 = argv_raw as *i64 238 argv[0] = arg0 as i64 239 argv[1] = arg1 as i64 240 argv[2] = arg2 as i64 241 242 // No envp. 243 let envp_raw: *u8 = sys_mmap(8) 244 let envp: *i64 = envp_raw as *i64 245 246 let load_base: i64 = 0x10000 247 let entry: i64 = 0x10100 248 let sim_sp: i64 = nx_argv_build_stack(mem, mem_size, load_base, 3, argv, 0, envp, entry) 249 if sim_sp < 0 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 250 251 // sim_sp should be near the top, well below load_base + mem_size. 252 let max_sp: i64 = load_base + mem_size 253 if sim_sp >= max_sp { return __syscall(93, 2, 0, 0, 0, 0, 0) } 254 if sim_sp < load_base { return __syscall(93, 3, 0, 0, 0, 0, 0) } 255 256 // Read back argc at simulated sp -> host sp. 257 let host_sp: i64 = (mem as i64) + (sim_sp - load_base) 258 let argc_p: *i64 = host_sp as *i64 259 if *argc_p != 3 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 260 261 // argv[0] sim address should resolve to "nxc.elf" in host memory. 262 let argv0_sim: i64 = *((host_sp + 8) as *i64) 263 if argv0_sim < load_base { return __syscall(93, 5, 0, 0, 0, 0, 0) } 264 if argv0_sim >= max_sp { return __syscall(93, 6, 0, 0, 0, 0, 0) } 265 let argv0_host: *u8 = (((mem as i64) + (argv0_sim - load_base)) as *u8) 266 if argv0_host[0] != 0x6E { return __syscall(93, 7, 0, 0, 0, 0, 0) } 267 if argv0_host[6] != 0x66 { return __syscall(93, 8, 0, 0, 0, 0, 0) } 268 if argv0_host[7] != 0 { return __syscall(93, 9, 0, 0, 0, 0, 0) } 269 270 // After argc + 4*argv pointers (3 strings + NULL) + 1*envp NULL 271 // we should hit the auxv entries, the first of which is PAGESZ. 272 let aux_off: i64 = host_sp + 8 + 4 * 8 + 1 * 8 273 let aux: *i64 = aux_off as *i64 274 if aux[0] != NX_AT_PAGESZ { return __syscall(93, 10, 0, 0, 0, 0, 0) } 275 if aux[1] != NX_MAGIC_4096 { return __syscall(93, 11, 0, 0, 0, 0, 0) } 276 if aux[2] != NX_AT_ENTRY { return __syscall(93, 12, 0, 0, 0, 0, 0) } 277 if aux[3] != entry { return __syscall(93, 13, 0, 0, 0, 0, 0) } 278 if aux[6] != NX_AT_NULL { return __syscall(93, 14, 0, 0, 0, 0, 0) } 279 280 // sp must be 16-byte aligned (System V ABI). 281 if (sim_sp & 15) != 0 { return __syscall(93, 15, 0, 0, 0, 0, 0) } 282 283 return 0 284}