code wiki / (root) / nxld.nx

nxld.nx source

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