code wiki / (root) / nx_nxld.nx

nx_nxld.nx source

↩ module page · 760 lines · 27171 B

1// nxld.nx -- Nishi sovereign linker. 2// 3// Replaces gcc-ld in verify.sh. Reads RV64 object files (ELF 4// relocatable), resolves symbols across them, and produces a 5// statically-linked RV64 Linux ELF executable. 6// 7// Scope (v0.0.1): 8// - Single-output static linking (no .so / dynamic linking) 9// - RV64 only (Sv39 virtual memory layout) 10// - ELF relocations: R_RISCV_64, R_RISCV_BRANCH, R_RISCV_JAL, 11// R_RISCV_CALL, R_RISCV_HI20, R_RISCV_LO12_I, R_RISCV_LO12_S, 12// R_RISCV_PCREL_HI20, R_RISCV_PCREL_LO12_I 13// - Single .text section + single .data section + single .bss 14// - Entry point = _start (RV64 Linux convention) 15// - Output base vaddr = 0x10000 (matches elf_writer.nx) 16// 17// Out of scope (v0.0.1): 18// - Section ordering directives 19// - Linker scripts 20// - LTO bytecode handling 21// - Position-independent code 22// - Garbage collection of unreferenced sections 23// - Debug info preservation (DWARF stripped) 24// 25// These are deliberate v0.0.1 limits. v0.1.0 closes them. 26// 27// Pipeline: 28// 1. Open + parse each input ELF (header, sections, symbols, relocs) 29// 2. Group sections by type: .text, .data, .bss 30// 3. Resolve undefined symbols across input objects 31// 4. Lay out output sections (text starts at 0x10000 + 0x1000 header) 32// 5. Apply relocations now that final addresses are known 33// 6. Build output ELF (header + program headers + sections) 34// 7. Write to output file 35 36// nx_safety_envelope: 37// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 38// sil_target: SIL1 39// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 40// verdict: NOT_YET_EVALUATED 41 42import "nx_syscalls.nx" 43 44// === ELF constants (matches elf_writer.nx encoding) === 45 46const ELF_MAGIC0: i64 = 0x7F 47const ELF_MAGIC1: i64 = 0x45 // 'E' 48const ELF_MAGIC2: i64 = 0x4C // 'L' 49const ELF_MAGIC3: i64 = 0x46 // 'F' 50const EI_CLASS64: i64 = 2 51const EI_DATA2LSB: i64 = 1 52const ET_EXEC: i64 = 2 53const ET_REL: i64 = 1 54const EM_RISCV: i64 = 0xF3 55 56const SHT_NULL: i64 = 0 57const SHT_PROGBITS: i64 = 1 58const SHT_SYMTAB: i64 = 2 59const SHT_STRTAB: i64 = 3 60const SHT_RELA: i64 = 4 61const SHT_NOBITS: i64 = 8 62 63const PT_LOAD: i64 = 1 64const PF_R: i64 = 4 65const PF_W: i64 = 2 66const PF_X: i64 = 1 67 68// RISC-V relocations (subset for v0.0.1) 69const R_RISCV_64: i64 = 2 70const R_RISCV_BRANCH: i64 = 16 71const R_RISCV_JAL: i64 = 17 72const R_RISCV_CALL: i64 = 18 73const R_RISCV_PCREL_HI20: i64 = 23 74const R_RISCV_PCREL_LO12_I: i64 = 24 75const R_RISCV_HI20: i64 = 26 76const R_RISCV_LO12_I: i64 = 27 77const R_RISCV_LO12_S: i64 = 28 78 79// === in-memory representation === 80 81struct ElfObject { 82 base: *u8, // mmap'd file start 83 length: i64, 84 e_type: i64, // ET_REL etc. 85 e_machine: i64, 86 e_shoff: i64, 87 e_shnum: i64, 88 e_shentsize: i64, 89 e_shstrndx: i64, 90 text_off: i64, // offset into file of .text section data 91 text_len: i64, // byte length of .text 92 data_off: i64, // offset into file of .data section 93 data_len: i64, 94 symtab_off: i64, // offset into file of symbol table 95 symtab_len: i64, 96 strtab_off: i64, // offset of string table for symtab 97 strtab_len: i64, 98 rela_text_off: i64, // offset of relocations against .text 99 rela_text_len: i64, 100} 101 102struct Symbol { 103 name: *u8, // string table entry 104 name_len: i64, 105 value: i64, // section-relative offset 106 section_idx: i64, // section number; 0 = SHN_UNDEF 107 bind: i64, // STB_LOCAL=0, STB_GLOBAL=1 108 type_: i64, // STT_FUNC=2, STT_OBJECT=1 109 resolved_addr: i64, // final virtual address after layout 110} 111 112// === reader === 113 114func read_u16_le(buf: *u8, off: i64) -> i64 { 115 return buf[off] | (buf[off + 1] << 8) 116} 117 118func read_u32_le(buf: *u8, off: i64) -> i64 { 119 var v: i64 = 0 120 var i: i64 = 3 121 while i >= 0 { 122 v = (v << 8) | buf[off + i] 123 i = i - 1 124 } 125 return v 126} 127 128func read_u64_le(buf: *u8, off: i64) -> i64 { 129 var v: i64 = 0 130 var i: i64 = 7 131 while i >= 0 { 132 v = (v << 8) | buf[off + i] 133 i = i - 1 134 } 135 return v 136} 137 138// Compare a null-terminated section name `np` against literal `lit` 139// of length `lit_len`. Returns 1 on match, 0 otherwise. Match 140// requires both the literal AND the null terminator at np[lit_len] 141// (so .text doesn't accidentally match .text.cold etc.) 142func sec_name_eq(np: *u8, lit: *u8, lit_len: i64) -> i64 { 143 var i: i64 = 0 144 while i < lit_len { 145 if np[i] != lit[i] { return 0 } 146 i = i + 1 147 } 148 if np[lit_len] != 0 { return 0 } 149 return 1 150} 151 152// Validate magic + class + endianness, parse the header. Returns 153// 0 on success, negative on validation failure. 154func nxld_open(obj: *ElfObject, base: *u8, length: i64) -> i64 { 155 if length < 64 { return -1 } 156 if base[0] != ELF_MAGIC0 { return -2 } 157 if base[1] != ELF_MAGIC1 { return -3 } 158 if base[2] != ELF_MAGIC2 { return -4 } 159 if base[3] != ELF_MAGIC3 { return -5 } 160 if base[4] != EI_CLASS64 { return -6 } 161 if base[5] != EI_DATA2LSB { return -7 } 162 163 obj.base = base 164 obj.length = length 165 obj.e_type = read_u16_le(base, 16) 166 obj.e_machine = read_u16_le(base, 18) 167 obj.e_shoff = read_u64_le(base, 40) 168 obj.e_shentsize = read_u16_le(base, 58) 169 obj.e_shnum = read_u16_le(base, 60) 170 obj.e_shstrndx = read_u16_le(base, 62) 171 172 if obj.e_machine != EM_RISCV { return -8 } 173 174 obj.text_off = 0; obj.text_len = 0 175 obj.data_off = 0; obj.data_len = 0 176 obj.symtab_off = 0; obj.symtab_len = 0 177 obj.strtab_off = 0; obj.strtab_len = 0 178 obj.rela_text_off = 0; obj.rela_text_len = 0 179 180 // Bounds-check the section header table. Header-only ELFs 181 // (e_shnum == 0) skip the section walk entirely. 182 if obj.e_shnum == 0 { return 0 } 183 if obj.e_shoff + obj.e_shnum * obj.e_shentsize > length { return -9 } 184 if obj.e_shstrndx >= obj.e_shnum { return -10 } 185 186 // The section-header string table (.shstrtab) holds the names 187 // of all sections. Resolve its file offset from sh[shstrndx]. 188 let shstr_hdr: i64 = obj.e_shoff + obj.e_shstrndx * obj.e_shentsize 189 let shstr_off: i64 = read_u64_le(base, shstr_hdr + 24) 190 let shstr_len: i64 = read_u64_le(base, shstr_hdr + 32) 191 if shstr_off + shstr_len > length { return -11 } 192 193 // Walk every section header, identify sections by name. 194 var i: i64 = 0 195 while i < obj.e_shnum { 196 let hdr: i64 = obj.e_shoff + i * obj.e_shentsize 197 let name_idx: i64 = read_u32_le(base, hdr + 0) 198 let sh_type: i64 = read_u32_le(base, hdr + 4) 199 let sh_off: i64 = read_u64_le(base, hdr + 24) 200 let sh_size: i64 = read_u64_le(base, hdr + 32) 201 202 // Resolve the section name into shstrtab. 203 let name_ptr: *u8 = (base as i64 + shstr_off + name_idx) as *u8 204 205 // Match well-known section names. String compare via 206 // byte-by-byte; sections with longer names won't match 207 // any of these short prefixes. 208 if sec_name_eq(name_ptr, ".text", 5) == 1 { 209 obj.text_off = sh_off 210 obj.text_len = sh_size 211 } 212 if sec_name_eq(name_ptr, ".data", 5) == 1 { 213 obj.data_off = sh_off 214 obj.data_len = sh_size 215 } 216 if sh_type == SHT_SYMTAB { 217 obj.symtab_off = sh_off 218 obj.symtab_len = sh_size 219 } 220 if sh_type == SHT_STRTAB { 221 // .strtab is the symbol-table string table; .shstrtab 222 // is the section-header string table. We want the 223 // first STRTAB that ISN'T shstrtab. 224 if i != obj.e_shstrndx { 225 obj.strtab_off = sh_off 226 obj.strtab_len = sh_size 227 } 228 } 229 if sec_name_eq(name_ptr, ".rela.text", 10) == 1 { 230 obj.rela_text_off = sh_off 231 obj.rela_text_len = sh_size 232 } 233 i = i + 1 234 } 235 return 0 236} 237 238// === symbol table walker ============================================= 239// 240// ELF64 symbol table entry layout (24 bytes per Elf64_Sym): 241// u32 st_name (offset into strtab) 242// u8 st_info (high 4 bits = bind, low 4 bits = type) 243// u8 st_other 244// u16 st_shndx (section index, or SHN_UNDEF=0/SHN_ABS=0xFFF1) 245// u64 st_value (section-relative offset / abs value) 246// u64 st_size (object size in bytes; 0 for funcs without size info) 247// 248// We extract every defined symbol into the caller-supplied Symbol 249// pool. Undefined symbols (st_shndx == 0) get section_idx = 0 250// so the caller can collect cross-object references for resolution. 251 252const SYMBOL_BYTES: i64 = 24 253const STN_UNDEF: i64 = 0 254const STB_LOCAL: i64 = 0 255const STB_GLOBAL: i64 = 1 256const STB_WEAK: i64 = 2 257const STT_NOTYPE: i64 = 0 258const STT_OBJECT: i64 = 1 259const STT_FUNC: i64 = 2 260const STT_SECTION: i64 = 3 261 262// Number of symbols in the object file. Returns 0 if no symtab 263// section was identified during nxld_open(). 264func nxld_symbol_count(obj: *ElfObject) -> i64 { 265 if obj.symtab_len == 0 { return 0 } 266 return obj.symtab_len / SYMBOL_BYTES 267} 268 269// Read symbol at `idx` into the caller-supplied Symbol struct. 270// Returns 0 on success, -1 on out-of-bounds. Resolves the symbol 271// name pointer into the object's strtab section. 272func nxld_read_symbol(obj: *ElfObject, idx: i64, sym: *Symbol) -> i64 { 273 let n: i64 = nxld_symbol_count(obj) 274 if idx < 0 { return -1 } 275 if idx >= n { return -1 } 276 let sym_off: i64 = obj.symtab_off + idx * SYMBOL_BYTES 277 let name_idx: i64 = read_u32_le(obj.base, sym_off) 278 let st_info: i64 = obj.base[sym_off + 4] 279 let st_shndx: i64 = read_u16_le(obj.base, sym_off + 6) 280 let st_value: i64 = read_u64_le(obj.base, sym_off + 8) 281 282 sym.value = st_value 283 sym.section_idx = st_shndx 284 sym.bind = (st_info >> 4) & 0xF 285 sym.type_ = st_info & 0xF 286 sym.resolved_addr = 0 287 288 // Resolve the name into strtab. name_len is computed by walking 289 // until a NUL terminator. 290 if obj.strtab_len > 0 { 291 sym.name = (obj.base as i64 + obj.strtab_off + name_idx) as *u8 292 var k: i64 = 0 293 while sym.name[k] != 0 { k = k + 1 } 294 sym.name_len = k 295 } 296 if obj.strtab_len == 0 { 297 sym.name = 0 as *u8 298 sym.name_len = 0 299 } 300 return 0 301} 302 303// Symbol-name equality vs a literal. Same shape as sec_name_eq. 304func sym_name_eq(np: *u8, np_len: i64, lit: *u8, lit_len: i64) -> i64 { 305 if np_len != lit_len { return 0 } 306 var i: i64 = 0 307 while i < lit_len { 308 if np[i] != lit[i] { return 0 } 309 i = i + 1 310 } 311 return 1 312} 313 314// Find a symbol by name. Returns symbol index (>= 0) or -1. 315// O(N) scan; for production use a hashtable. 316func nxld_find_symbol(obj: *ElfObject, name: *u8, name_len: i64) -> i64 { 317 let n: i64 = nxld_symbol_count(obj) 318 var sym_raw: *u8 = sys_mmap(64) 319 let sym: *Symbol = sym_raw as *Symbol 320 var i: i64 = 0 321 while i < n { 322 let rc: i64 = nxld_read_symbol(obj, i, sym) 323 if rc == 0 { 324 if sym_name_eq(sym.name, sym.name_len, name, name_len) == 1 { 325 return i 326 } 327 } 328 i = i + 1 329 } 330 return -1 331} 332 333// === relocation reader =============================================== 334// 335// ELF64 relocation entry layout (24 bytes per Elf64_Rela): 336// u64 r_offset (where in target section to apply the reloc) 337// u64 r_info (high 32 bits = symbol idx, low 32 bits = type) 338// i64 r_addend (signed value added to the resolution) 339// 340// We extract relocations from the .rela.text section recorded in 341// nxld_open. The link-time pass walks these, looks up the 342// referenced symbol's resolved address, computes the value to 343// patch in (per type), and writes it into the section data. 344 345const RELOC_BYTES: i64 = 24 346 347struct Reloc { 348 offset: i64, // where in section to apply 349 sym_idx: i64, // index into the symbol table 350 rtype: i64, // R_RISCV_* 351 addend: i64, 352} 353 354// Number of .rela.text relocations. 0 if no relocations were 355// recorded during nxld_open. 356func nxld_reloc_count(obj: *ElfObject) -> i64 { 357 if obj.rela_text_len == 0 { return 0 } 358 return obj.rela_text_len / RELOC_BYTES 359} 360 361// Read relocation `idx` into the caller-supplied Reloc. 362// Returns 0 on success, -1 on out-of-bounds. 363func nxld_read_reloc(obj: *ElfObject, idx: i64, r: *Reloc) -> i64 { 364 let n: i64 = nxld_reloc_count(obj) 365 if idx < 0 { return -1 } 366 if idx >= n { return -1 } 367 let off: i64 = obj.rela_text_off + idx * RELOC_BYTES 368 let r_offset: i64 = read_u64_le(obj.base, off) 369 let r_info: i64 = read_u64_le(obj.base, off + 8) 370 let r_addend: i64 = read_u64_le(obj.base, off + 16) 371 372 r.offset = r_offset 373 r.sym_idx = (r_info >> 32) & 0xFFFFFFFF 374 r.rtype = r_info & 0xFFFFFFFF 375 r.addend = r_addend 376 return 0 377} 378 379// === relocation apply pass =========================================== 380// 381// Walks every relocation in obj.rela_text and patches the 32-bit 382// instruction at obj.text + reloc.offset based on the resolved 383// symbol's address + the relocation type's encoding rules. 384// 385// Pre-condition: each Symbol's resolved_addr field has been set 386// by the layout pass (final virtual address of the symbol's 387// definition). 388// 389// Inputs: 390// obj -- the input ELF object 391// text_buf -- mutable copy of the .text bytes 392// syms_pool -- array of Symbol[N] indexed by symtab idx, with 393// resolved_addr already filled in 394// text_vaddr -- virtual address of text_buf[0] in the linked output 395// 396// Returns 0 on success, negative on unsupported relocation kind 397// or unresolved symbol. 398// 399// RISC-V instruction encoding cheatsheet (LE 32-bit words): 400// I-type addi/jalr: imm[11:0] @ bits 31:20 401// S-type sd/sb: imm[11:5] @ 31:25, imm[4:0] @ 11:7 402// B-type beq/bne: imm[12,10:5] @ 31:25, imm[4:1,11] @ 11:7 403// U-type lui/auipc: imm[31:12] @ 31:12 404// J-type jal: imm[20,10:1,11,19:12] @ 31:12 405 406func write_u32_le(buf: *u8, off: i64, v: i64) -> i64 { 407 buf[off + 0] = v & 0xFF 408 buf[off + 1] = (v >> 8) & 0xFF 409 buf[off + 2] = (v >> 16) & 0xFF 410 buf[off + 3] = (v >> 24) & 0xFF 411 return 0 412} 413 414// Patch the I-type immediate (12 bits, sign-extended) into a word. 415// Used by addi / jalr / lo12_i. 416func patch_i_imm(word: i64, imm: i64) -> i64 { 417 let kept: i64 = word & 0x000FFFFF // clear bits 31:20 418 let imm12: i64 = imm & 0xFFF 419 return kept | (imm12 << 20) 420} 421 422// Patch the S-type split immediate (12 bits) into a word. 423// Used by sd / sb / lo12_s. 424func patch_s_imm(word: i64, imm: i64) -> i64 { 425 let kept: i64 = word & 0x01FFF07F // clear 31:25 + 11:7 426 let hi: i64 = (imm >> 5) & 0x7F 427 let lo: i64 = imm & 0x1F 428 return kept | (hi << 25) | (lo << 7) 429} 430 431// Patch the U-type immediate (high 20 bits). Used by auipc / lui. 432func patch_u_imm(word: i64, imm_hi: i64) -> i64 { 433 let kept: i64 = word & 0x00000FFF // clear 31:12 434 return kept | ((imm_hi & 0xFFFFF) << 12) 435} 436 437// Patch the J-type bit-scrambled 21-bit signed immediate. 438func patch_j_imm(word: i64, imm: i64) -> i64 { 439 let kept: i64 = word & 0x00000FFF 440 let b20: i64 = (imm >> 20) & 1 441 let b10_1: i64 = (imm >> 1) & 0x3FF 442 let b11: i64 = (imm >> 11) & 1 443 let b19_12: i64 = (imm >> 12) & 0xFF 444 let scrambled: i64 = (b20 << 31) | (b10_1 << 21) | (b11 << 20) | (b19_12 << 12) 445 return kept | scrambled 446} 447 448// Patch the B-type bit-scrambled 13-bit signed immediate. 449func patch_b_imm(word: i64, imm: i64) -> i64 { 450 let kept: i64 = word & 0x01FFF07F 451 let b12: i64 = (imm >> 12) & 1 452 let b10_5: i64 = (imm >> 5) & 0x3F 453 let b4_1: i64 = (imm >> 1) & 0xF 454 let b11: i64 = (imm >> 11) & 1 455 let scrambled: i64 = (b12 << 31) | (b10_5 << 25) | (b4_1 << 8) | (b11 << 7) 456 return kept | scrambled 457} 458 459// "Hi 20 + Lo 12" split that handles sign extension correctly: 460// the lo12 immediate is sign-extended at use, so if its top bit 461// is set we must add 0x800 to the hi20 to compensate. 462func split_hi20(value: i64) -> i64 { 463 return (value + 0x800) >> 12 464} 465func split_lo12(value: i64) -> i64 { 466 let hi: i64 = split_hi20(value) 467 return value - (hi << 12) 468} 469 470// Apply one relocation in-place to text_buf. 471// Returns 0 on success, -1 on unknown rtype, -2 on unresolved sym. 472func nxld_apply_one(r: *Reloc, text_buf: *u8, text_vaddr: i64, 473 syms_pool: *Symbol, n_syms: i64) -> i64 { 474 if r.sym_idx >= n_syms { return -2 } 475 let lbase: i64 = syms_pool as i64 476 let sym: *Symbol = (lbase + r.sym_idx * 56) as *Symbol 477 let resolved: i64 = sym.resolved_addr + r.addend 478 479 if r.rtype == R_RISCV_64 { 480 // 8-byte absolute write 481 text_buf[r.offset + 0] = resolved & 0xFF 482 text_buf[r.offset + 1] = (resolved >> 8) & 0xFF 483 text_buf[r.offset + 2] = (resolved >> 16) & 0xFF 484 text_buf[r.offset + 3] = (resolved >> 24) & 0xFF 485 text_buf[r.offset + 4] = (resolved >> 32) & 0xFF 486 text_buf[r.offset + 5] = (resolved >> 40) & 0xFF 487 text_buf[r.offset + 6] = (resolved >> 48) & 0xFF 488 text_buf[r.offset + 7] = (resolved >> 56) & 0xFF 489 return 0 490 } 491 492 // For all instruction-patch relocations, read the current word. 493 let cur_off: i64 = r.offset 494 let cur_word: i64 = 495 text_buf[cur_off] 496 | (text_buf[cur_off + 1] << 8) 497 | (text_buf[cur_off + 2] << 16) 498 | (text_buf[cur_off + 3] << 24) 499 var patched: i64 = 0 500 501 if r.rtype == R_RISCV_BRANCH { 502 let pc: i64 = text_vaddr + cur_off 503 let off: i64 = resolved - pc 504 patched = patch_b_imm(cur_word, off) 505 write_u32_le(text_buf, cur_off, patched) 506 return 0 507 } 508 if r.rtype == R_RISCV_JAL { 509 let pc: i64 = text_vaddr + cur_off 510 let off: i64 = resolved - pc 511 patched = patch_j_imm(cur_word, off) 512 write_u32_le(text_buf, cur_off, patched) 513 return 0 514 } 515 if r.rtype == R_RISCV_CALL { 516 // 2-instruction sequence: auipc rd, hi20 ; jalr rd, rd, lo12 517 let pc: i64 = text_vaddr + cur_off 518 let off: i64 = resolved - pc 519 let hi: i64 = split_hi20(off) 520 let lo: i64 = split_lo12(off) 521 patched = patch_u_imm(cur_word, hi) 522 write_u32_le(text_buf, cur_off, patched) 523 let next_off: i64 = cur_off + 4 524 let next_word: i64 = 525 text_buf[next_off] 526 | (text_buf[next_off + 1] << 8) 527 | (text_buf[next_off + 2] << 16) 528 | (text_buf[next_off + 3] << 24) 529 let next_patched: i64 = patch_i_imm(next_word, lo) 530 write_u32_le(text_buf, next_off, next_patched) 531 return 0 532 } 533 if r.rtype == R_RISCV_HI20 { 534 patched = patch_u_imm(cur_word, split_hi20(resolved)) 535 write_u32_le(text_buf, cur_off, patched) 536 return 0 537 } 538 if r.rtype == R_RISCV_LO12_I { 539 patched = patch_i_imm(cur_word, split_lo12(resolved)) 540 write_u32_le(text_buf, cur_off, patched) 541 return 0 542 } 543 if r.rtype == R_RISCV_LO12_S { 544 patched = patch_s_imm(cur_word, split_lo12(resolved)) 545 write_u32_le(text_buf, cur_off, patched) 546 return 0 547 } 548 if r.rtype == R_RISCV_PCREL_HI20 { 549 let pc: i64 = text_vaddr + cur_off 550 let off: i64 = resolved - pc 551 patched = patch_u_imm(cur_word, split_hi20(off)) 552 write_u32_le(text_buf, cur_off, patched) 553 return 0 554 } 555 if r.rtype == R_RISCV_PCREL_LO12_I { 556 let pc: i64 = text_vaddr + cur_off 557 let off: i64 = resolved - pc 558 patched = patch_i_imm(cur_word, split_lo12(off)) 559 write_u32_le(text_buf, cur_off, patched) 560 return 0 561 } 562 return -1 563} 564 565// === layout pass ===================================================== 566// 567// Assigns final virtual addresses to sections + resolves every 568// defined symbol's resolved_addr. Single-object linking only for 569// v0.0.1 (no inter-object symbol resolution yet). 570// 571// Memory layout matches elf_writer.nx for consistency: 572// 0x10000 ELF header (64 bytes) 573// 0x10040 program header (56 bytes) 574// 0x10078 .text section starts 575// 0x10078+text_len .data section (round to next 16-byte boundary) 576// 577// Returns the start vaddr of .text after layout (typically 0x10078). 578const NXLD_BASE_VADDR: i64 = 0x10000 579const NXLD_HEADER_BYTES: i64 = 120 // 64 ELF hdr + 56 phdr 580 581func nxld_layout(obj: *ElfObject, syms_pool: *Symbol, n_syms: i64, 582 out_text_vaddr: *i64, out_data_vaddr: *i64) -> i64 { 583 let text_vaddr: i64 = NXLD_BASE_VADDR + NXLD_HEADER_BYTES 584 *out_text_vaddr = text_vaddr 585 586 // .data follows .text, 16-byte aligned. 587 let after_text: i64 = text_vaddr + obj.text_len 588 let aligned: i64 = (after_text + 15) & ~15 589 *out_data_vaddr = aligned 590 591 // Resolve every defined symbol. For v0.0.1 we treat any symbol 592 // with section_idx > 0 as living in either .text or .data based 593 // on the section we recorded. A real linker would carry per- 594 // section vaddrs through; here single-section .text covers the 595 // common case (most NishiLang functions live in .text). 596 var i: i64 = 0 597 let lbase: i64 = syms_pool as i64 598 while i < n_syms { 599 let sym: *Symbol = (lbase + i * 56) as *Symbol 600 if sym.section_idx == 0 { 601 // Undefined symbol -- needs cross-object resolution 602 // (out of scope for v0.0.1 single-object linker). 603 sym.resolved_addr = 0 604 } 605 if sym.section_idx > 0 { 606 // For v0.0.1: assume the section is .text. A real 607 // linker would map section_idx -> assigned vaddr base. 608 sym.resolved_addr = text_vaddr + sym.value 609 } 610 i = i + 1 611 } 612 return 0 613} 614 615// === top-level link entry ============================================ 616// 617// Take a single input ELF (relocatable .o), apply its relocations, 618// emit a statically-linked ELF executable to fd. 619// 620// Returns the byte count written, or negative on error. 621 622import "elf_writer.nx" 623 624func nxld_link(obj: *ElfObject, fd: i64) -> i64 { 625 // Pre-flight: object must have a .text section. 626 if obj.text_len == 0 { return -100 } 627 628 // Load symbols into a working pool. 629 let n_syms: i64 = nxld_symbol_count(obj) 630 let syms_raw: *u8 = sys_mmap(n_syms * 56 + 64) 631 let syms: *Symbol = syms_raw as *Symbol 632 var i: i64 = 0 633 let lbase: i64 = syms as i64 634 while i < n_syms { 635 let s: *Symbol = (lbase + i * 56) as *Symbol 636 nxld_read_symbol(obj, i, s) 637 i = i + 1 638 } 639 640 // Layout: assign vaddrs + resolve symbols. 641 let tva_raw: *u8 = sys_mmap(16) 642 let dva_raw: *u8 = sys_mmap(16) 643 let tva: *i64 = tva_raw as *i64 644 let dva: *i64 = dva_raw as *i64 645 nxld_layout(obj, syms, n_syms, tva, dva) 646 647 // Allocate a writable copy of the .text bytes (relocations 648 // patch into this). 649 let text_buf: *u8 = sys_mmap(obj.text_len + 64) 650 var k: i64 = 0 651 while k < obj.text_len { 652 text_buf[k] = obj.base[obj.text_off + k] 653 k = k + 1 654 } 655 656 // Apply relocations. 657 let n_relocs: i64 = nxld_reloc_count(obj) 658 let reloc_raw: *u8 = sys_mmap(64) 659 let reloc: *Reloc = reloc_raw as *Reloc 660 var ri: i64 = 0 661 while ri < n_relocs { 662 nxld_read_reloc(obj, ri, reloc) 663 let arc: i64 = nxld_apply_one(reloc, text_buf, *tva, syms, n_syms) 664 if arc < 0 { return -200 + arc } 665 ri = ri + 1 666 } 667 668 // Emit ELF wrapping the patched text. elf_writer's write_elf 669 // handles the standard 0x10000 base; matches our layout. 670 return write_elf(text_buf, obj.text_len, fd) 671} 672 673// === self-test === 674 675func main() -> i64 { 676 // Build a minimal valid ELF64 RV64 header in a buffer + verify 677 // nxld_open accepts it. 678 let buf: *u8 = sys_mmap(128) 679 buf[0] = ELF_MAGIC0; buf[1] = ELF_MAGIC1 680 buf[2] = ELF_MAGIC2; buf[3] = ELF_MAGIC3 681 buf[4] = EI_CLASS64; buf[5] = EI_DATA2LSB 682 buf[6] = 1 // EI_VERSION 683 // e_type = ET_REL 684 buf[16] = 1; buf[17] = 0 685 // e_machine = EM_RISCV (0xF3) 686 buf[18] = 0xF3; buf[19] = 0 687 // e_version = 1 688 buf[20] = 1; buf[21] = 0; buf[22] = 0; buf[23] = 0 689 690 let obj: *ElfObject = sys_mmap(128) as *ElfObject 691 let rc: i64 = nxld_open(obj, buf, 64) 692 if rc != 0 { return __syscall(93, 100 + (0 - rc), 0, 0, 0, 0, 0) } 693 if obj.e_type != ET_REL { return __syscall(93, 50, 0, 0, 0, 0, 0) } 694 if obj.e_machine != EM_RISCV { return __syscall(93, 51, 0, 0, 0, 0, 0) } 695 696 // Symbol table API: with no symtab section in our minimal 697 // fixture, count is 0 and reads are out of bounds. 698 if nxld_symbol_count(obj) != 0 { 699 return __syscall(93, 52, 0, 0, 0, 0, 0) 700 } 701 let sym_raw: *u8 = sys_mmap(64) 702 let sym: *Symbol = sym_raw as *Symbol 703 if nxld_read_symbol(obj, 0, sym) != -1 { 704 return __syscall(93, 53, 0, 0, 0, 0, 0) 705 } 706 707 // Relocation API: with no .rela.text either, count 0 + 708 // reads -1. 709 if nxld_reloc_count(obj) != 0 { 710 return __syscall(93, 54, 0, 0, 0, 0, 0) 711 } 712 let rel_raw: *u8 = sys_mmap(64) 713 let rel: *Reloc = rel_raw as *Reloc 714 if nxld_read_reloc(obj, 0, rel) != -1 { 715 return __syscall(93, 55, 0, 0, 0, 0, 0) 716 } 717 718 // Layout pass: with no symbols + zero-length text in our 719 // minimal ELF fixture, the layout returns the standard 720 // base+header offsets and resolves zero symbols. 721 let tva_raw: *u8 = sys_mmap(16) 722 let dva_raw: *u8 = sys_mmap(16) 723 let tva: *i64 = tva_raw as *i64 724 let dva: *i64 = dva_raw as *i64 725 let lo_syms_raw: *u8 = sys_mmap(64) 726 let lo_syms: *Symbol = lo_syms_raw as *Symbol 727 nxld_layout(obj, lo_syms, 0, tva, dva) 728 if *tva != 0x10078 { 729 return __syscall(93, 58, 0, 0, 0, 0, 0) 730 } 731 732 // Apply pass: build a synthetic R_RISCV_HI20 relocation against 733 // a fake symbol resolved to 0x12345000. Verify the patched 734 // word's high 20 bits hold the expected value. 735 let text_buf: *u8 = sys_mmap(64) 736 text_buf[0] = 0x37; text_buf[1] = 0x00; text_buf[2] = 0x00; text_buf[3] = 0x00 737 // Synthetic Symbol[1] with resolved_addr = 0x12345000. 738 let syms_raw: *u8 = sys_mmap(128) 739 let syms: *Symbol = syms_raw as *Symbol 740 let s0: *Symbol = syms 741 let lbase: i64 = syms as i64 742 let s1: *Symbol = (lbase + 56) as *Symbol 743 s1.resolved_addr = 0x12345000 744 rel.offset = 0 745 rel.sym_idx = 1 746 rel.rtype = R_RISCV_HI20 747 rel.addend = 0 748 let arc: i64 = nxld_apply_one(rel, text_buf, 0x10000, syms, 2) 749 if arc != 0 { return __syscall(93, 56, 0, 0, 0, 0, 0) } 750 // Patched word's high 20 bits should encode 0x12345 (after sign 751 // adjust: low12 of 0x12345000 = 0; no adjustment needed). 752 let pword: i64 = text_buf[0] | (text_buf[1] << 8) 753 | (text_buf[2] << 16) | (text_buf[3] << 24) 754 let patched_hi: i64 = (pword >> 12) & 0xFFFFF 755 if patched_hi != 0x12345 { 756 return __syscall(93, 57, 0, 0, 0, 0, 0) 757 } 758 759 return __syscall(93, 42, 0, 0, 0, 0, 0) 760}