nxld.nx
buildroot/runtime/nxld.nx
about
nxld.nx -- Nishi sovereign linker.
Replaces gcc-ld in verify.sh. Reads RV64 object files (ELF
relocatable), resolves symbols across them, and produces a
statically-linked RV64 Linux ELF executable.
Scope (v0.0.1):
- Single-output static linking (no .so / dynamic linking)
- RV64 only (Sv39 virtual memory layout)
- ELF relocations: R_RISCV_64, R_RISCV_BRANCH, R_RISCV_JAL,
R_RISCV_CALL, R_RISCV_HI20, R_RISCV_LO12_I, R_RISCV_LO12_S,
R_RISCV_PCREL_HI20, R_RISCV_PCREL_LO12_I
- Single .text section + single .data section + single .bss
- Entry point = _start (RV64 Linux convention)
- Output base vaddr = 0x10000 (matches elf_writer.nx)
Out of scope (v0.0.1):
- Section ordering directives
- Linker scripts
- LTO bytecode handling
- Position-independent code
- Garbage collection of unreferenced sections
- Debug info preservation (DWARF stripped)
These are deliberate v0.0.1 limits. v0.1.0 closes them.
Pipeline:
1. Open + parse each input ELF (header, sections, symbols, relocs)
2. Group sections by type: .text, .data, .bss
3. Resolve undefined symbols across input objects
4. Lay out output sections (text starts at 0x10000 + 0x1000 header)
5. Apply relocations now that final addresses are known
6. Build output ELF (header + program headers + sections)
7. Write to output file
dependencies 2 imports · 1 importers
imports: syscalls.nxelf_writer.nx
imported by: nxld_main.nx
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| 75 | struct ElfObject { |
| 96 | struct Symbol { |
| 341 | struct Reloc { |
consts
| 40 | const ELF_MAGIC0: i64 = 0x7F |
| 41 | const ELF_MAGIC1: i64 = 0x45 // 'E' |
| 42 | const ELF_MAGIC2: i64 = 0x4C // 'L' |
| 43 | const ELF_MAGIC3: i64 = 0x46 // 'F' |
| 44 | const EI_CLASS64: i64 = 2 |
| 45 | const EI_DATA2LSB: i64 = 1 |
| 46 | const ET_EXEC: i64 = 2 |
| 47 | const ET_REL: i64 = 1 |
| 48 | const EM_RISCV: i64 = 0xF3 |
| 50 | const SHT_NULL: i64 = 0 |
| 51 | const SHT_PROGBITS: i64 = 1 |
| 52 | const SHT_SYMTAB: i64 = 2 |
| 53 | const SHT_STRTAB: i64 = 3 |
| 54 | const SHT_RELA: i64 = 4 |
| 55 | const SHT_NOBITS: i64 = 8 |
| 57 | const PT_LOAD: i64 = 1 |
| 58 | const PF_R: i64 = 4 |
| 59 | const PF_W: i64 = 2 |
| 60 | const PF_X: i64 = 1 |
| 63 | const R_RISCV_64: i64 = 2 |
| 64 | const R_RISCV_BRANCH: i64 = 16 |
| 65 | const R_RISCV_JAL: i64 = 17 |
| 66 | const R_RISCV_CALL: i64 = 18 |
| 67 | const R_RISCV_PCREL_HI20: i64 = 23 |
| 68 | const R_RISCV_PCREL_LO12_I: i64 = 24 |
| 69 | const R_RISCV_HI20: i64 = 26 |
| 70 | const R_RISCV_LO12_I: i64 = 27 |
| 71 | const R_RISCV_LO12_S: i64 = 28 |
| 246 | const SYMBOL_BYTES: i64 = 24 |
| 247 | const STN_UNDEF: i64 = 0 |
| 248 | const STB_LOCAL: i64 = 0 |
| 249 | const STB_GLOBAL: i64 = 1 |
| 250 | const STB_WEAK: i64 = 2 |
| 251 | const STT_NOTYPE: i64 = 0 |
| 252 | const STT_OBJECT: i64 = 1 |
| 253 | const STT_FUNC: i64 = 2 |
| 254 | const STT_SECTION: i64 = 3 |
| 339 | const RELOC_BYTES: i64 = 24 |
| 572 | const NXLD_BASE_VADDR: i64 = 0x10000 |
| 573 | const NXLD_HEADER_BYTES: i64 = 120 // 64 ELF hdr + 56 phdr |
functions
| 108 | func read_u16_le(buf: *u8, off: i64) -> i64 { |
| 112 | func read_u32_le(buf: *u8, off: i64) -> i64 { |
| 122 | func read_u64_le(buf: *u8, off: i64) -> i64 { |
| 136 | func sec_name_eq(np: *u8, lit: *u8, lit_len: i64) -> i64 {
called by 1: nxld_open |
| 148 | func nxld_open(obj: *ElfObject, base: *u8, length: i64) -> i64 { |
| 258 | func nxld_symbol_count(obj: *ElfObject) -> i64 { |
| 266 | func nxld_read_symbol(obj: *ElfObject, idx: i64, sym: *Symbol) -> i64 {
called by 3: nxld_find_symbolnxld_linkmain calls 4: nxld_symbol_countread_u32_leread_u16_leread_u64_le |
| 298 | func sym_name_eq(np: *u8, np_len: i64, lit: *u8, lit_len: i64) -> i64 {
called by 1: nxld_find_symbol |
| 310 | func nxld_find_symbol(obj: *ElfObject, name: *u8, name_len: i64) -> i64 { |
| 350 | func nxld_reloc_count(obj: *ElfObject) -> i64 { |
| 357 | func nxld_read_reloc(obj: *ElfObject, idx: i64, r: *Reloc) -> i64 { |
| 400 | func write_u32_le(buf: *u8, off: i64, v: i64) -> i64 {
called by 1: nxld_apply_one |
| 410 | func patch_i_imm(word: i64, imm: i64) -> i64 {
called by 1: nxld_apply_one |
| 418 | func patch_s_imm(word: i64, imm: i64) -> i64 {
called by 1: nxld_apply_one |
| 426 | func patch_u_imm(word: i64, imm_hi: i64) -> i64 {
called by 1: nxld_apply_one |
| 432 | func patch_j_imm(word: i64, imm: i64) -> i64 {
called by 1: nxld_apply_one |
| 443 | func patch_b_imm(word: i64, imm: i64) -> i64 {
called by 1: nxld_apply_one |
| 456 | func split_hi20(value: i64) -> i64 { |
| 459 | func split_lo12(value: i64) -> i64 { |
| 466 | func nxld_apply_one(r: *Reloc, text_buf: *u8, text_vaddr: i64,
called by 2: nxld_linkmain calls 8: patch_b_immwrite_u32_lepatch_j_immsplit_hi20split_lo12patch_u_imm+2 |
| 575 | func nxld_layout(obj: *ElfObject, syms_pool: *Symbol, n_syms: i64, |
| 618 | func nxld_link(obj: *ElfObject, fd: i64) -> i64 {
called by 1: main calls 7: nxld_symbol_countnxld_read_symbolnxld_layoutnxld_reloc_countnxld_read_relocnxld_apply_one+1 |
| 669 | func main() -> i64 { |