ww: import toolchain — C bootstrap + ww-side self-host (phases 0-10)
C bootstrap (phases 0-9):
cmd/wwc, cmd/6c, cmd/6a, cmd/6l, cmd/ww, rt, lib/*.
ww-side self-host (phase 10):
selfhost/cmd/wwc — ww-cgen frontend; bootstrap fixed point.
selfhost/cmd/6a — assembler; byte-identical to C 6a (test 991).
selfhost/cmd/6l — linker w/ archive (.a) support; byte-identical
to C 6l (test 992).
selfhost/cmd/ww — driver (build/run/version); byte-identical to
C ww (test 993).
make test: 15/15. make bootstrap: ww2.s == ww3.s, ww2.o == ww3.o,
ww2 == ww3 byte-identical, with the full ww-tooled chain.
This commit is contained in:
1077
selfhost/cmd/6l/main.combined.ww
Normal file
1077
selfhost/cmd/6l/main.combined.ww
Normal file
File diff suppressed because it is too large
Load Diff
120
selfhost/cmd/6l/main.ww
Normal file
120
selfhost/cmd/6l/main.ww
Normal file
@@ -0,0 +1,120 @@
|
||||
// selfhost/cmd/6l/main.ww — port of cmd/6l/main.c.
|
||||
//
|
||||
// 6l = amd64 static linker. Reads relocatable ELF .o files and
|
||||
// SysV `ar` archives, resolves symbols, applies relocations, writes
|
||||
// a static ELF executable.
|
||||
//
|
||||
// 6l_ww -o out file1.o file2.o libwwrt.a ...
|
||||
|
||||
use os;
|
||||
use mem;
|
||||
use sym;
|
||||
use obj;
|
||||
use pass;
|
||||
use out;
|
||||
|
||||
def BASE: u64 = 4194304u64; // 0x400000
|
||||
def CODE_VA_OFF: u64 = 4096u64; // .text starts at base + 0x1000
|
||||
|
||||
// Linker context lives in main's frame; arena gets passed in.
|
||||
fn make_lnk(a: *arena) *lnk = {
|
||||
let l: *lnk = amalloc(a, 96u64): *lnk;
|
||||
l.a = a;
|
||||
return l;
|
||||
};
|
||||
|
||||
fn streq_cs(a: *u8, lit: str) bool = {
|
||||
let n: u64 = lit.len: u64;
|
||||
let i: u64 = 0u64;
|
||||
for (i < n) {
|
||||
let li: i32 = i: i32;
|
||||
if (a[i] != lit[li]) { return false; };
|
||||
i += 1u64;
|
||||
};
|
||||
if (a[i] != 0u8) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn main(argc: i32, argv: **u8) i32 = {
|
||||
|
||||
let out_path: *u8 = nil;
|
||||
// Inputs: store as **u8 (heap'd from a fixed-size buffer).
|
||||
let max_inputs: i32 = 64;
|
||||
let inputs: **u8 = os.alloc((max_inputs: u64) * 8u64): **u8;
|
||||
let ninputs: i32 = 0;
|
||||
|
||||
let i: i32 = 1;
|
||||
for (i < argc) {
|
||||
let a: *u8 = argv[i];
|
||||
if (streq_cs(a, "-o")) {
|
||||
i += 1;
|
||||
if (i >= argc) {
|
||||
os.write(2, "6l: -o requires argument\n".ptr, 25u64);
|
||||
return 2;
|
||||
};
|
||||
out_path = argv[i];
|
||||
} else { if (a[0u64] == 45u8) {
|
||||
os.write(2, "6l: unknown flag\n".ptr, 17u64);
|
||||
return 2;
|
||||
} else {
|
||||
if (ninputs >= max_inputs) {
|
||||
os.write(2, "6l: too many inputs\n".ptr, 20u64);
|
||||
return 2;
|
||||
};
|
||||
inputs[ninputs] = a;
|
||||
ninputs += 1;
|
||||
}; };
|
||||
i += 1;
|
||||
};
|
||||
|
||||
if (out_path == nil) {
|
||||
os.write(2, "usage: 6l_ww -o exe file1.o [file2.o...]\n".ptr, 41u64);
|
||||
return 2;
|
||||
};
|
||||
if (ninputs == 0) {
|
||||
os.write(2, "6l: no inputs\n".ptr, 14u64);
|
||||
return 2;
|
||||
};
|
||||
|
||||
let a: *arena = newarena();
|
||||
let l: *lnk = make_lnk(a);
|
||||
|
||||
// Seed _start so a libwwrt-style start.o is recognised as wanted.
|
||||
l_intern(l, "_start");
|
||||
|
||||
let k: i32 = 0;
|
||||
for (k < ninputs) {
|
||||
if (l_load(l, inputs[k]) != 0) {
|
||||
return 1;
|
||||
};
|
||||
k += 1;
|
||||
};
|
||||
|
||||
if (l_resolve(l) != 0) { return 1; };
|
||||
if (l_relocate(l, BASE + CODE_VA_OFF) != 0) { return 1; };
|
||||
|
||||
let entry_sym: *lsym = l_lookup(l, "_start");
|
||||
if (entry_sym == nil) { entry_sym = l_lookup(l, "main"); }
|
||||
else { if (entry_sym.defined == 0) { entry_sym = l_lookup(l, "main"); }; };
|
||||
if (entry_sym == nil) {
|
||||
os.write(2, "6l: no _start or main symbol\n".ptr, 29u64);
|
||||
return 1;
|
||||
};
|
||||
if (entry_sym.defined == 0) {
|
||||
os.write(2, "6l: no _start or main symbol\n".ptr, 29u64);
|
||||
return 1;
|
||||
};
|
||||
|
||||
// Open output: O_WRONLY|O_CREAT|O_TRUNC, mode 0755.
|
||||
let flags: i32 = os.O_WRONLY | os.O_CREAT | os.O_TRUNC;
|
||||
let fd: i32 = os.open(out_path, flags, 493i32); // 0o755
|
||||
if (fd < 0) {
|
||||
os.write(2, "6l: cannot open output\n".ptr, 23u64);
|
||||
return 1;
|
||||
};
|
||||
|
||||
let entry_va: u64 = BASE + CODE_VA_OFF + entry_sym.val;
|
||||
let rc: i32 = l_emit_elf(l, fd, BASE, entry_va);
|
||||
os.close(fd);
|
||||
return rc;
|
||||
};
|
||||
486
selfhost/cmd/6l/obj.ww
Normal file
486
selfhost/cmd/6l/obj.ww
Normal file
@@ -0,0 +1,486 @@
|
||||
// selfhost/cmd/6l/obj.ww — port of cmd/6l/obj.c.
|
||||
//
|
||||
// Loads relocatable ELF64 .o files emitted by 6a, appends .text to
|
||||
// the combined image, and pulls in symbols + relocations with
|
||||
// offsets adjusted to the combined section.
|
||||
//
|
||||
// Also handles SysV `ar` archives (libwwrt.a). The two-pass loader
|
||||
// indexes members on the first pass and iteratively pulls members
|
||||
// that define currently-undefined symbols on subsequent passes.
|
||||
|
||||
use os;
|
||||
use mem;
|
||||
use sym;
|
||||
|
||||
def ET_REL: i32 = 1;
|
||||
def EM_X86_64: i32 = 62;
|
||||
def SHT_PROGBITS: i32 = 1;
|
||||
def SHT_SYMTAB: i32 = 2;
|
||||
def SHT_STRTAB: i32 = 3;
|
||||
def SHT_RELA: i32 = 4;
|
||||
|
||||
// ---- little-endian byte readers ----------------------------------------
|
||||
// 6a/6l use straight LE on amd64. Reading via byte offsets keeps us off
|
||||
// the cgen's u16 field-load story for now (MOVZBQ exists; MOVZWQ doesn't).
|
||||
|
||||
fn rd_u16(p: *u8, off: u64) u16 = {
|
||||
let b0: u16 = p[off]: u16;
|
||||
let b1: u16 = p[off + 1u64]: u16;
|
||||
return b0 | (b1 << 8u16);
|
||||
};
|
||||
|
||||
fn rd_u32(p: *u8, off: u64) u32 = {
|
||||
let b0: u32 = p[off]: u32;
|
||||
let b1: u32 = p[off + 1u64]: u32;
|
||||
let b2: u32 = p[off + 2u64]: u32;
|
||||
let b3: u32 = p[off + 3u64]: u32;
|
||||
return b0 | (b1 << 8u32) | (b2 << 16u32) | (b3 << 24u32);
|
||||
};
|
||||
|
||||
fn rd_u64(p: *u8, off: u64) u64 = {
|
||||
let lo: u64 = rd_u32(p, off): u64;
|
||||
let hi: u64 = rd_u32(p, off + 4u64): u64;
|
||||
return lo | (hi << 32u64);
|
||||
};
|
||||
|
||||
// ---- ELF64 section header offsets (40 bytes total) --------------------
|
||||
def SHDR_SIZE: u64 = 64u64; // sizeof(Shdr) per ELF64 spec
|
||||
def SHDR_NAME: u64 = 0u64;
|
||||
def SHDR_TYPE: u64 = 4u64;
|
||||
def SHDR_OFFSET: u64 = 24u64;
|
||||
def SHDR_SIZE_F: u64 = 32u64;
|
||||
def SHDR_LINK: u64 = 40u64;
|
||||
|
||||
// ELF64 ehdr field offsets
|
||||
def EHDR_SIZE: u64 = 64u64;
|
||||
def EHDR_TYPE: u64 = 16u64;
|
||||
def EHDR_MACHINE: u64 = 18u64;
|
||||
def EHDR_SHOFF: u64 = 40u64;
|
||||
def EHDR_SHENTSIZE: u64 = 58u64;
|
||||
def EHDR_SHNUM: u64 = 60u64;
|
||||
def EHDR_SHSTRNDX: u64 = 62u64;
|
||||
|
||||
// ELF64 sym entry: 24 bytes
|
||||
def SYM_SIZE: u64 = 24u64;
|
||||
def SYM_NAME: u64 = 0u64;
|
||||
def SYM_INFO: u64 = 4u64;
|
||||
def SYM_SHNDX: u64 = 6u64;
|
||||
def SYM_VALUE: u64 = 8u64;
|
||||
|
||||
// ELF64 RELA entry: 24 bytes
|
||||
def RELA_SIZE: u64 = 24u64;
|
||||
def RELA_OFFSET: u64 = 0u64;
|
||||
def RELA_INFO: u64 = 8u64;
|
||||
def RELA_ADDEND: u64 = 16u64;
|
||||
|
||||
// ---- file slurp --------------------------------------------------------
|
||||
|
||||
fn read_all(path_cs: *u8) (*u8, u64) = {
|
||||
let fd: i32 = os.open(path_cs, os.O_RDONLY, 0i32);
|
||||
if (fd < 0) { return nil, 0u64; };
|
||||
let n: i64 = os.filesize(fd);
|
||||
if (n < 0i64) { os.close(fd); return nil, 0u64; };
|
||||
let buf: *u8 = os.alloc(n: u64): *u8;
|
||||
let got: i64 = os.readfull(fd, buf, n: u64);
|
||||
os.close(fd);
|
||||
if (got != n) { return nil, 0u64; };
|
||||
return buf, n: u64;
|
||||
};
|
||||
|
||||
// ---- text buffer growth ------------------------------------------------
|
||||
|
||||
fn emit_text(l: *lnk, src: *u8, n: u64) void = {
|
||||
if (l.textlen + n > l.textcap) {
|
||||
let nc: u64 = l.textcap;
|
||||
if (nc == 0u64) { nc = 4096u64; };
|
||||
for (nc < l.textlen + n) { nc = nc * 2u64; };
|
||||
// Grow by mmap'ing a fresh region and copying. The old buffer
|
||||
// is leaked into the page allocator; for a linker run this is
|
||||
// trivial waste.
|
||||
let nb: *u8 = os.alloc(nc): *u8;
|
||||
let i: u64 = 0u64;
|
||||
for (i < l.textlen) {
|
||||
nb[i] = l.text[i];
|
||||
i += 1u64;
|
||||
};
|
||||
l.text = nb;
|
||||
l.textcap = nc;
|
||||
};
|
||||
let i: u64 = 0u64;
|
||||
for (i < n) {
|
||||
l.text[l.textlen + i] = src[i];
|
||||
i += 1u64;
|
||||
};
|
||||
l.textlen += n;
|
||||
};
|
||||
|
||||
// ---- C-string helpers --------------------------------------------------
|
||||
|
||||
fn cstrlen(p: *u8) u64 = {
|
||||
let n: u64 = 0u64;
|
||||
for (p[n] != 0u8) { n += 1u64; };
|
||||
return n;
|
||||
};
|
||||
|
||||
fn cstr_eq(p: *u8, lit: str) bool = {
|
||||
let n: u64 = lit.len: u64;
|
||||
let i: u64 = 0u64;
|
||||
for (i < n) {
|
||||
let li: i32 = i: i32;
|
||||
if (p[i] != lit[li]) { return false; };
|
||||
i += 1u64;
|
||||
};
|
||||
if (p[i] != 0u8) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// Build a ww str from a NUL-terminated *u8 (for passing to l_intern).
|
||||
fn cstr_to_str(a: *arena, p: *u8) str = {
|
||||
let n: u64 = cstrlen(p);
|
||||
return astrndup(a, p, n);
|
||||
};
|
||||
|
||||
// ---- archive (SysV ar) types and helpers -------------------------------
|
||||
//
|
||||
// Each archive member starts with a 60-byte ar_hdr. The fields we care
|
||||
// about are the first byte (member type) and the size at offset 48 (a
|
||||
// 10-byte, space-padded decimal). Member bodies are 2-byte aligned.
|
||||
|
||||
type defent = struct {
|
||||
name: str,
|
||||
dnext: *defent,
|
||||
};
|
||||
|
||||
type armember = struct {
|
||||
data: *u8, // arena copy of the member's ELF bytes
|
||||
size: u64,
|
||||
defs: *defent, // linked list of defined globals
|
||||
loaded: i32,
|
||||
mnext: *armember,
|
||||
};
|
||||
|
||||
fn is_archive(p: *u8, len: u64) bool = {
|
||||
if (len < 8u64) { return false; };
|
||||
if (p[0u64] != 33u8) { return false; }; // '!'
|
||||
if (p[1u64] != 60u8) { return false; }; // '<'
|
||||
if (p[2u64] != 97u8) { return false; }; // 'a'
|
||||
if (p[3u64] != 114u8) { return false; }; // 'r'
|
||||
if (p[4u64] != 99u8) { return false; }; // 'c'
|
||||
if (p[5u64] != 104u8) { return false; }; // 'h'
|
||||
if (p[6u64] != 62u8) { return false; }; // '>'
|
||||
if (p[7u64] != 10u8) { return false; }; // '\n'
|
||||
return true;
|
||||
};
|
||||
|
||||
// ar_field — parse a space-padded decimal integer of width n.
|
||||
fn ar_field(p: *u8, n: u64) u64 = {
|
||||
let v: u64 = 0u64;
|
||||
let i: u64 = 0u64;
|
||||
for (i < n) {
|
||||
let c: u8 = p[i];
|
||||
if (c < 48u8) { return v; }; // space, NUL, etc.
|
||||
if (c > 57u8) { return v; };
|
||||
v = v * 10u64 + ((c - 48u8): u64);
|
||||
i += 1u64;
|
||||
};
|
||||
return v;
|
||||
};
|
||||
|
||||
// elf_globals — return a linked list of names of globally-defined
|
||||
// (STB_GLOBAL) symbols whose section is `.text`. Names are arena
|
||||
// copies, so the source ELF buffer can be freed afterward.
|
||||
fn elf_globals(a: *arena, buf: *u8, len: u64) *defent = {
|
||||
if (len < EHDR_SIZE) { return nil; };
|
||||
if (buf[0u64] != 127u8) { return nil; };
|
||||
if (buf[1u64] != 69u8) { return nil; };
|
||||
if (buf[2u64] != 76u8) { return nil; };
|
||||
if (buf[3u64] != 70u8) { return nil; };
|
||||
|
||||
let shoff: u64 = rd_u64(buf, EHDR_SHOFF);
|
||||
let shnum: u32 = rd_u16(buf, EHDR_SHNUM): u32;
|
||||
let shstrndx: u32 = rd_u16(buf, EHDR_SHSTRNDX): u32;
|
||||
|
||||
let shstr_sh_off: u64 = rd_u64(buf, shoff + (shstrndx: u64) * SHDR_SIZE + SHDR_OFFSET);
|
||||
let shstr: *u8 = buf + shstr_sh_off;
|
||||
|
||||
let idx_text: i32 = -1;
|
||||
let idx_symtab: i32 = -1;
|
||||
let i: u32 = 0u32;
|
||||
for (i < shnum) {
|
||||
let sh_off: u64 = shoff + (i: u64) * SHDR_SIZE;
|
||||
let sh_type: u32 = rd_u32(buf, sh_off + SHDR_TYPE);
|
||||
let sh_name: u32 = rd_u32(buf, sh_off + SHDR_NAME);
|
||||
let nm: *u8 = shstr + (sh_name: u64);
|
||||
if (sh_type == SHT_PROGBITS: u32) {
|
||||
if (cstr_eq(nm, ".text")) { idx_text = i: i32; };
|
||||
};
|
||||
if (sh_type == SHT_SYMTAB: u32) { idx_symtab = i: i32; };
|
||||
i += 1u32;
|
||||
};
|
||||
if (idx_text < 0) { return nil; };
|
||||
if (idx_symtab < 0) { return nil; };
|
||||
|
||||
let sym_sh: u64 = shoff + (idx_symtab: u64) * SHDR_SIZE;
|
||||
let sym_off: u64 = rd_u64(buf, sym_sh + SHDR_OFFSET);
|
||||
let sym_size: u64 = rd_u64(buf, sym_sh + SHDR_SIZE_F);
|
||||
let sym_link: u32 = rd_u32(buf, sym_sh + SHDR_LINK);
|
||||
let nsyms: u64 = sym_size / SYM_SIZE;
|
||||
|
||||
let str_sh: u64 = shoff + (sym_link: u64) * SHDR_SIZE;
|
||||
let str_off: u64 = rd_u64(buf, str_sh + SHDR_OFFSET);
|
||||
let strtab: *u8 = buf + str_off;
|
||||
|
||||
let head: *defent = nil;
|
||||
let si: u64 = 1u64;
|
||||
for (si < nsyms) {
|
||||
let sym_p: u64 = sym_off + si * SYM_SIZE;
|
||||
let st_name: u32 = rd_u32(buf, sym_p + SYM_NAME);
|
||||
let st_info: u8 = buf[sym_p + SYM_INFO];
|
||||
let st_shndx: u16 = rd_u16(buf, sym_p + SYM_SHNDX);
|
||||
let bind: u32 = (st_info: u32) >> 4u32;
|
||||
// STB_GLOBAL = 1; defined in .text.
|
||||
if (bind == 1u32) {
|
||||
if (st_shndx != 0u16) {
|
||||
if ((st_shndx: i32) == idx_text) {
|
||||
let nm_p: *u8 = strtab + (st_name: u64);
|
||||
if (nm_p[0u64] != 0u8) {
|
||||
let nm: str = cstr_to_str(a, nm_p);
|
||||
let de: *defent = amalloc(a, 32u64): *defent;
|
||||
de.name = nm;
|
||||
de.dnext = head;
|
||||
head = de;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
si += 1u64;
|
||||
};
|
||||
return head;
|
||||
};
|
||||
|
||||
// member_defines_undef — true if any of m's defined globals matches a
|
||||
// currently-undefined symbol in the linker's symbol table. Names not
|
||||
// already interned are uninteresting (the link doesn't need them yet).
|
||||
fn member_defines_undef(l: *lnk, m: *armember) bool = {
|
||||
let de: *defent = m.defs;
|
||||
for (de != nil) {
|
||||
let s: *lsym = l_lookup(l, de.name);
|
||||
if (s != nil) {
|
||||
if (s.defined == 0) { return true; };
|
||||
};
|
||||
de = de.dnext;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// load_archive — port of cmd/6l/obj.c:load_archive.
|
||||
//
|
||||
// Pass 1 indexes every regular member. Pass 2 iteratively pulls in any
|
||||
// member that supplies a currently-undefined symbol; each pull may
|
||||
// introduce fresh undefs, so we loop until quiescent.
|
||||
fn load_archive(l: *lnk, path_cs: *u8, buf: *u8, len: u64) i32 = {
|
||||
let head: *armember = nil;
|
||||
let tail: *armember = nil;
|
||||
let pos: u64 = 8u64; // past "!<arch>\n"
|
||||
for (pos + 60u64 <= len) {
|
||||
let hdr_size: u64 = ar_field(buf + pos + 48u64, 10u64);
|
||||
let hdr_end: u64 = pos + 60u64;
|
||||
if (hdr_end + hdr_size > len) { break; };
|
||||
let first: u8 = buf[pos];
|
||||
// Skip the symbol table ('/'), long-name table ('//'), and
|
||||
// any padding entries (NUL or space leading byte).
|
||||
if (first != 47u8) { if (first != 0u8) { if (first != 32u8) {
|
||||
let m: *armember = amalloc(l.a, 48u64): *armember;
|
||||
m.size = hdr_size;
|
||||
let mb: *u8 = amalloc(l.a, hdr_size): *u8;
|
||||
let i: u64 = 0u64;
|
||||
for (i < hdr_size) {
|
||||
mb[i] = buf[hdr_end + i];
|
||||
i += 1u64;
|
||||
};
|
||||
m.data = mb;
|
||||
m.defs = elf_globals(l.a, mb, hdr_size);
|
||||
m.loaded = 0;
|
||||
m.mnext = nil;
|
||||
if (head == nil) { head = m; }
|
||||
else { tail.mnext = m; };
|
||||
tail = m;
|
||||
}; }; };
|
||||
pos = hdr_end + hdr_size;
|
||||
if ((hdr_size & 1u64) != 0u64) { pos = pos + 1u64; };
|
||||
};
|
||||
|
||||
let changed: i32 = 1;
|
||||
for (changed != 0) {
|
||||
changed = 0;
|
||||
let m: *armember = head;
|
||||
for (m != nil) {
|
||||
if (m.loaded == 0) {
|
||||
if (member_defines_undef(l, m)) {
|
||||
if (load_image(l, path_cs, m.data, m.size) == 0) {
|
||||
m.loaded = 1;
|
||||
changed = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
m = m.mnext;
|
||||
};
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
|
||||
// ---- main loader -------------------------------------------------------
|
||||
|
||||
export fn l_load(l: *lnk, path_cs: *u8) i32 = {
|
||||
let bufp: *u8;
|
||||
let buflen: u64;
|
||||
bufp, buflen = read_all(path_cs);
|
||||
if (bufp == nil) {
|
||||
os.write(2, "6l: cannot read object\n".ptr, 23u64);
|
||||
return -1;
|
||||
};
|
||||
if (is_archive(bufp, buflen)) {
|
||||
return load_archive(l, path_cs, bufp, buflen);
|
||||
};
|
||||
return load_image(l, path_cs, bufp, buflen);
|
||||
};
|
||||
|
||||
fn load_image(l: *lnk, path_cs: *u8, buf: *u8, len: u64) i32 = {
|
||||
if (len < EHDR_SIZE) { return -1; };
|
||||
// magic: 0x7f, 'E', 'L', 'F'
|
||||
if (buf[0u64] != 127u8) { return -1; };
|
||||
if (buf[1u64] != 69u8) { return -1; };
|
||||
if (buf[2u64] != 76u8) { return -1; };
|
||||
if (buf[3u64] != 70u8) { return -1; };
|
||||
if (buf[4u64] != 2u8) { return -1; }; // ELFCLASS64
|
||||
if (rd_u16(buf, EHDR_TYPE) != ET_REL: u16) { return -1; };
|
||||
if (rd_u16(buf, EHDR_MACHINE) != EM_X86_64: u16) { return -1; };
|
||||
|
||||
let shoff: u64 = rd_u64(buf, EHDR_SHOFF);
|
||||
let shnum: u32 = rd_u16(buf, EHDR_SHNUM): u32;
|
||||
let shstrndx: u32 = rd_u16(buf, EHDR_SHSTRNDX): u32;
|
||||
|
||||
let shstr_sh_off: u64 = rd_u64(buf, shoff + (shstrndx: u64) * SHDR_SIZE + SHDR_OFFSET);
|
||||
let shstr: *u8 = buf + shstr_sh_off;
|
||||
|
||||
// find .text, .symtab, .rela.text
|
||||
let idx_text: i32 = -1;
|
||||
let idx_symtab: i32 = -1;
|
||||
let idx_rela: i32 = -1;
|
||||
let i: u32 = 0u32;
|
||||
for (i < shnum) {
|
||||
let sh_off: u64 = shoff + (i: u64) * SHDR_SIZE;
|
||||
let sh_type: u32 = rd_u32(buf, sh_off + SHDR_TYPE);
|
||||
let sh_name: u32 = rd_u32(buf, sh_off + SHDR_NAME);
|
||||
let nm: *u8 = shstr + (sh_name: u64);
|
||||
if (sh_type == SHT_PROGBITS: u32) {
|
||||
if (cstr_eq(nm, ".text")) { idx_text = i: i32; };
|
||||
};
|
||||
if (sh_type == SHT_SYMTAB: u32) { idx_symtab = i: i32; };
|
||||
if (sh_type == SHT_RELA: u32) {
|
||||
if (cstr_eq(nm, ".rela.text")) { idx_rela = i: i32; };
|
||||
};
|
||||
i += 1u32;
|
||||
};
|
||||
if (idx_text < 0) {
|
||||
os.write(2, "6l: missing .text\n".ptr, 18u64);
|
||||
return -1;
|
||||
};
|
||||
if (idx_symtab < 0) {
|
||||
os.write(2, "6l: missing .symtab\n".ptr, 20u64);
|
||||
return -1;
|
||||
};
|
||||
|
||||
let text_sh: u64 = shoff + (idx_text: u64) * SHDR_SIZE;
|
||||
let text_off: u64 = rd_u64(buf, text_sh + SHDR_OFFSET);
|
||||
let text_size: u64 = rd_u64(buf, text_sh + SHDR_SIZE_F);
|
||||
|
||||
let sym_sh: u64 = shoff + (idx_symtab: u64) * SHDR_SIZE;
|
||||
let sym_off: u64 = rd_u64(buf, sym_sh + SHDR_OFFSET);
|
||||
let sym_size: u64 = rd_u64(buf, sym_sh + SHDR_SIZE_F);
|
||||
let sym_link: u32 = rd_u32(buf, sym_sh + SHDR_LINK);
|
||||
let nsyms: u64 = sym_size / SYM_SIZE;
|
||||
|
||||
let str_sh: u64 = shoff + (sym_link: u64) * SHDR_SIZE;
|
||||
let str_off: u64 = rd_u64(buf, str_sh + SHDR_OFFSET);
|
||||
let strtab: *u8 = buf + str_off;
|
||||
|
||||
// Track this object.
|
||||
let ob: *lobj = amalloc(l.a, 64u64): *lobj;
|
||||
ob.path = cstr_to_str(l.a, path_cs);
|
||||
ob.buf = buf;
|
||||
ob.len = len;
|
||||
ob.text_off = l.textlen;
|
||||
ob.text_size = text_size;
|
||||
ob.onext = l.objs;
|
||||
l.objs = ob;
|
||||
|
||||
// Append .text bytes to the combined image.
|
||||
emit_text(l, buf + text_off, text_size);
|
||||
|
||||
// Walk symbols. We don't keep a per-object map[] of *lsym. Instead
|
||||
// the reloc loop re-walks symtab and re-interns by name. Simpler
|
||||
// than dancing around the cgen's u64-shift gaps.
|
||||
let si: u64 = 1u64; // skip index 0 (always undef sentinel)
|
||||
for (si < nsyms) {
|
||||
let sym_p: u64 = sym_off + si * SYM_SIZE;
|
||||
let st_name: u32 = rd_u32(buf, sym_p + SYM_NAME);
|
||||
let st_shndx: u16 = rd_u16(buf, sym_p + SYM_SHNDX);
|
||||
let st_value: u64 = rd_u64(buf, sym_p + SYM_VALUE);
|
||||
let nm_p: *u8 = strtab + (st_name: u64);
|
||||
if (nm_p[0u64] != 0u8) {
|
||||
let nm: str = cstr_to_str(l.a, nm_p);
|
||||
let gs: *lsym = l_intern(l, nm);
|
||||
if (st_shndx != 0u16) {
|
||||
if ((st_shndx: i32) == idx_text) {
|
||||
if (gs.defined != 0) {
|
||||
os.write(2, "6l: duplicate symbol\n".ptr, 21u64);
|
||||
l.errs += 1;
|
||||
} else {
|
||||
gs.defined = 1;
|
||||
gs.owner = ob;
|
||||
gs.idx_in_owner = si: i32;
|
||||
gs.val = ob.text_off + st_value;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
si += 1u64;
|
||||
};
|
||||
|
||||
// Per-object relocation collection.
|
||||
if (idx_rela >= 0) {
|
||||
let rela_sh: u64 = shoff + (idx_rela: u64) * SHDR_SIZE;
|
||||
let rela_off: u64 = rd_u64(buf, rela_sh + SHDR_OFFSET);
|
||||
let rela_size: u64 = rd_u64(buf, rela_sh + SHDR_SIZE_F);
|
||||
let nrel: u64 = rela_size / RELA_SIZE;
|
||||
let ri: u64 = 0u64;
|
||||
for (ri < nrel) {
|
||||
let rp: u64 = rela_off + ri * RELA_SIZE;
|
||||
let r_off: u64 = rd_u64(buf, rp + RELA_OFFSET);
|
||||
let r_info: u64 = rd_u64(buf, rp + RELA_INFO);
|
||||
let r_addend: u64 = rd_u64(buf, rp + RELA_ADDEND);
|
||||
let r_sym_idx: u32 = (r_info >> 32u64): u32;
|
||||
let r_kind: i32 = ((r_info & 4294967295u64): u32): i32;
|
||||
let nr: *lrel = amalloc(l.a, 48u64): *lrel;
|
||||
nr.off = ob.text_off + r_off;
|
||||
nr.kind = r_kind;
|
||||
nr.addend = r_addend: i64;
|
||||
// Look up the referenced sym by name (re-walk symtab).
|
||||
if ((r_sym_idx: u64) < nsyms) {
|
||||
let s_p: u64 = sym_off + (r_sym_idx: u64) * SYM_SIZE;
|
||||
let s_name: u32 = rd_u32(buf, s_p + SYM_NAME);
|
||||
let s_nm: *u8 = strtab + (s_name: u64);
|
||||
if (s_nm[0u64] != 0u8) {
|
||||
let nm: str = cstr_to_str(l.a, s_nm);
|
||||
nr.sym = l_intern(l, nm);
|
||||
};
|
||||
};
|
||||
nr.rnext = l.rels;
|
||||
l.rels = nr;
|
||||
ri += 1u64;
|
||||
};
|
||||
};
|
||||
|
||||
return 0;
|
||||
};
|
||||
91
selfhost/cmd/6l/out.ww
Normal file
91
selfhost/cmd/6l/out.ww
Normal file
@@ -0,0 +1,91 @@
|
||||
// selfhost/cmd/6l/out.ww — port of cmd/6l/out.c.
|
||||
//
|
||||
// Emit a static ELF64 executable. File layout (per the C original):
|
||||
// [0..64) Ehdr
|
||||
// [64..120) Phdr (one PT_LOAD)
|
||||
// [120..0x1000) zero pad
|
||||
// [0x1000..) .text bytes
|
||||
// Single PT_LOAD covers the whole file, R+X. No interpreter, no .bss.
|
||||
|
||||
use os;
|
||||
use sym;
|
||||
|
||||
def ET_EXEC: u16 = 2u16;
|
||||
def EM_X86_64_W: u16 = 62u16;
|
||||
def EV_CURRENT: u32 = 1u32;
|
||||
def ELFCLASS64: u8 = 2u8;
|
||||
def ELFDATA2LSB: u8 = 1u8;
|
||||
def PT_LOAD: u32 = 1u32;
|
||||
def PF_X: u32 = 1u32;
|
||||
def PF_R: u32 = 4u32;
|
||||
|
||||
def TEXT_OFF: u64 = 4096u64; // 0x1000
|
||||
|
||||
// ---- little-endian byte writers ----------------------------------------
|
||||
|
||||
fn wr_u16(buf: *u8, off: u64, v: u16) void = {
|
||||
buf[off] = (v & 255u16): u8;
|
||||
buf[off + 1u64] = ((v >> 8u16) & 255u16): u8;
|
||||
};
|
||||
|
||||
fn wr_u32(buf: *u8, off: u64, v: u32) void = {
|
||||
buf[off] = (v & 255u32): u8;
|
||||
buf[off + 1u64] = ((v >> 8u32) & 255u32): u8;
|
||||
buf[off + 2u64] = ((v >> 16u32) & 255u32): u8;
|
||||
buf[off + 3u64] = ((v >> 24u32) & 255u32): u8;
|
||||
};
|
||||
|
||||
fn wr_u64(buf: *u8, off: u64, v: u64) void = {
|
||||
wr_u32(buf, off, (v & 4294967295u64): u32);
|
||||
wr_u32(buf, off + 4u64, ((v >> 32u64) & 4294967295u64): u32);
|
||||
};
|
||||
|
||||
// ---- emit ---------------------------------------------------------------
|
||||
|
||||
export fn l_emit_elf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
||||
let filesz: u64 = TEXT_OFF + l.textlen;
|
||||
|
||||
// One contiguous header buffer covering [0..0x1000), then .text.
|
||||
let hdr: *u8 = os.alloc(TEXT_OFF): *u8; // zero-initialised by mmap
|
||||
|
||||
// --- Ehdr (64 bytes) ---
|
||||
hdr[0u64] = 127u8; // 0x7f
|
||||
hdr[1u64] = 69u8; // 'E'
|
||||
hdr[2u64] = 76u8; // 'L'
|
||||
hdr[3u64] = 70u8; // 'F'
|
||||
hdr[4u64] = ELFCLASS64;
|
||||
hdr[5u64] = ELFDATA2LSB;
|
||||
hdr[6u64] = EV_CURRENT: u8;
|
||||
wr_u16(hdr, 16u64, ET_EXEC); // e_type
|
||||
wr_u16(hdr, 18u64, EM_X86_64_W); // e_machine
|
||||
wr_u32(hdr, 20u64, EV_CURRENT); // e_version
|
||||
wr_u64(hdr, 24u64, entry); // e_entry
|
||||
wr_u64(hdr, 32u64, 64u64); // e_phoff = sizeof(Ehdr)
|
||||
wr_u64(hdr, 40u64, 0u64); // e_shoff
|
||||
wr_u32(hdr, 48u64, 0u32); // e_flags
|
||||
wr_u16(hdr, 52u64, 64u16); // e_ehsize
|
||||
wr_u16(hdr, 54u64, 56u16); // e_phentsize
|
||||
wr_u16(hdr, 56u64, 1u16); // e_phnum
|
||||
wr_u16(hdr, 58u64, 0u16); // e_shentsize
|
||||
wr_u16(hdr, 60u64, 0u16); // e_shnum
|
||||
wr_u16(hdr, 62u64, 0u16); // e_shstrndx
|
||||
|
||||
// --- Phdr (56 bytes) at offset 64 ---
|
||||
wr_u32(hdr, 64u64, PT_LOAD); // p_type
|
||||
wr_u32(hdr, 68u64, PF_R | PF_X); // p_flags
|
||||
wr_u64(hdr, 72u64, 0u64); // p_offset
|
||||
wr_u64(hdr, 80u64, base); // p_vaddr
|
||||
wr_u64(hdr, 88u64, base); // p_paddr
|
||||
wr_u64(hdr, 96u64, filesz); // p_filesz
|
||||
wr_u64(hdr, 104u64, filesz); // p_memsz
|
||||
wr_u64(hdr, 112u64, TEXT_OFF); // p_align
|
||||
|
||||
// Write [0..0x1000) then .text.
|
||||
let n1: i64 = os.writefull(fd, hdr, TEXT_OFF);
|
||||
if (n1 != TEXT_OFF: i64) { return -1; };
|
||||
if (l.textlen > 0u64) {
|
||||
let n2: i64 = os.writefull(fd, l.text, l.textlen);
|
||||
if (n2 != l.textlen: i64) { return -1; };
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
64
selfhost/cmd/6l/pass.ww
Normal file
64
selfhost/cmd/6l/pass.ww
Normal file
@@ -0,0 +1,64 @@
|
||||
// selfhost/cmd/6l/pass.ww — port of cmd/6l/pass.c.
|
||||
//
|
||||
// Resolution + relocation. l_resolve flags every undefined symbol
|
||||
// referenced by a relocation. l_relocate walks the rel list and
|
||||
// patches the .text bytes in place once the final virtual base is
|
||||
// known. Supported relocation kinds: PC32 (=2), PLT32 (=4); both
|
||||
// are 32-bit PC-relative displacements (PLT32 == PC32 for static).
|
||||
|
||||
use os;
|
||||
use sym;
|
||||
|
||||
def R_X86_64_PC32: i32 = 2;
|
||||
def R_X86_64_PLT32: i32 = 4;
|
||||
|
||||
export fn l_resolve(l: *lnk) i32 = {
|
||||
let r: *lrel = l.rels;
|
||||
for (r != nil) {
|
||||
if (r.sym != nil) {
|
||||
if (r.sym.defined == 0) {
|
||||
os.write(2, "6l: undefined reference to '".ptr, 28u64);
|
||||
let nm: str = r.sym.name;
|
||||
os.write(2, nm.ptr, nm.len: u64);
|
||||
os.write(2, "'\n".ptr, 2u64);
|
||||
l.errs += 1;
|
||||
};
|
||||
};
|
||||
r = r.rnext;
|
||||
};
|
||||
return l.errs;
|
||||
};
|
||||
|
||||
fn patch_u32(p: *u8, v: u32) void = {
|
||||
p[0] = (v & 255u32): u8;
|
||||
p[1] = ((v >> 8u32) & 255u32): u8;
|
||||
p[2] = ((v >> 16u32) & 255u32): u8;
|
||||
p[3] = ((v >> 24u32) & 255u32): u8;
|
||||
};
|
||||
|
||||
export fn l_relocate(l: *lnk, base: u64) i32 = {
|
||||
let r: *lrel = l.rels;
|
||||
for (r != nil) {
|
||||
if (r.sym != nil) {
|
||||
if (r.sym.defined != 0) {
|
||||
let k: i32 = r.kind;
|
||||
if (k == R_X86_64_PC32) {
|
||||
let site: u64 = base + r.off;
|
||||
let target: i64 = (base + r.sym.val): i64;
|
||||
let rel: i64 = (target - site: i64) + r.addend;
|
||||
patch_u32(l.text + r.off, rel: u32);
|
||||
} else { if (k == R_X86_64_PLT32) {
|
||||
let site: u64 = base + r.off;
|
||||
let target: i64 = (base + r.sym.val): i64;
|
||||
let rel: i64 = (target - site: i64) + r.addend;
|
||||
patch_u32(l.text + r.off, rel: u32);
|
||||
} else {
|
||||
os.write(2, "6l: unsupported reloc kind\n".ptr, 27u64);
|
||||
l.errs += 1;
|
||||
};};
|
||||
};
|
||||
};
|
||||
r = r.rnext;
|
||||
};
|
||||
return l.errs;
|
||||
};
|
||||
75
selfhost/cmd/6l/sym.ww
Normal file
75
selfhost/cmd/6l/sym.ww
Normal file
@@ -0,0 +1,75 @@
|
||||
// selfhost/cmd/6l/sym.ww — port of cmd/6l/sym.c.
|
||||
//
|
||||
// Linker symbol table. Singly-linked list, usually a few hundred
|
||||
// entries; hashing isn't worth it yet.
|
||||
|
||||
use mem;
|
||||
|
||||
type lsym = struct {
|
||||
name: str,
|
||||
val: u64, // offset within combined .text once linked
|
||||
defined: i32, // 1 if some lobj defines this symbol
|
||||
owner: *lobj,
|
||||
idx_in_owner: i32,
|
||||
snext: *lsym,
|
||||
};
|
||||
|
||||
type lrel = struct {
|
||||
off: u64, // offset within combined .text
|
||||
kind: i32, // R_X86_64_*
|
||||
sym: *lsym,
|
||||
addend: i64,
|
||||
rnext: *lrel,
|
||||
};
|
||||
|
||||
type lobj = struct {
|
||||
path: str,
|
||||
buf: *u8, // object bytes
|
||||
len: u64,
|
||||
text_off: u64, // offset of .text in combined output
|
||||
text_size: u64,
|
||||
onext: *lobj,
|
||||
};
|
||||
|
||||
type lnk = struct {
|
||||
a: *arena,
|
||||
objs: *lobj,
|
||||
syms: *lsym,
|
||||
rels: *lrel,
|
||||
text: *u8, // combined .text
|
||||
textcap: u64,
|
||||
textlen: u64,
|
||||
errs: i32,
|
||||
};
|
||||
|
||||
fn streq(a: str, b: str) bool = {
|
||||
if (a.len != b.len) { return false; };
|
||||
let i: i32 = 0;
|
||||
for (i < a.len) {
|
||||
if (a[i] != b[i]) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
export fn l_intern(l: *lnk, name: str) *lsym = {
|
||||
let s: *lsym = l.syms;
|
||||
for (s != nil) {
|
||||
if (streq(s.name, name)) { return s; };
|
||||
s = s.snext;
|
||||
};
|
||||
let n: *lsym = amalloc(l.a, 64u64): *lsym;
|
||||
n.name = name;
|
||||
n.snext = l.syms;
|
||||
l.syms = n;
|
||||
return n;
|
||||
};
|
||||
|
||||
export fn l_lookup(l: *lnk, name: str) *lsym = {
|
||||
let s: *lsym = l.syms;
|
||||
for (s != nil) {
|
||||
if (streq(s.name, name)) { return s; };
|
||||
s = s.snext;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
Reference in New Issue
Block a user