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}