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.
1078 lines
30 KiB
Plaintext
1078 lines
30 KiB
Plaintext
// os — process and filesystem facade. The body of each call lands
|
|
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
|
|
// depending on how the program was linked.
|
|
|
|
@symbol("rt_syscall") fn syscall0(num: i64) i64;
|
|
@symbol("rt_syscall") fn syscall1(num: i64, a: i64) i64;
|
|
@symbol("rt_syscall") fn syscall2(num: i64, a: i64, b: i64) i64;
|
|
@symbol("rt_syscall") fn syscall3(num: i64, a: i64, b: i64, c: i64) i64;
|
|
|
|
@symbol("rt_alloc") fn alloc(n: u64) *void;
|
|
@symbol("rt_free") fn free(p: *void, n: u64) void;
|
|
@symbol("rt_abort") fn abort(msg: str) void;
|
|
|
|
// Hare-style runtime check. Caller passes a message that's printed
|
|
// to stderr before exit(1).
|
|
export fn assert(cond: bool, msg: str) void = {
|
|
if (!cond) { abort(msg); };
|
|
};
|
|
|
|
def SYS_READ: i64 = 0;
|
|
def SYS_WRITE: i64 = 1;
|
|
def SYS_OPEN: i64 = 2;
|
|
def SYS_CLOSE: i64 = 3;
|
|
def SYS_LSEEK: i64 = 8;
|
|
def SYS_EXIT: i64 = 60;
|
|
|
|
// open(2) flags. Linux values, matching <fcntl.h>.
|
|
def O_RDONLY: i32 = 0;
|
|
def O_WRONLY: i32 = 1;
|
|
def O_RDWR: i32 = 2;
|
|
def O_CREAT: i32 = 64; // 0x40
|
|
def O_TRUNC: i32 = 512; // 0x200
|
|
|
|
// lseek(2) whence.
|
|
def SEEK_SET: i32 = 0;
|
|
def SEEK_CUR: i32 = 1;
|
|
def SEEK_END: i32 = 2;
|
|
|
|
export fn exit(code: i32) void = {
|
|
syscall1(SYS_EXIT, code: i64);
|
|
};
|
|
|
|
// Raw, non-fallible primitives. These return Linux's int conventions
|
|
// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a
|
|
// Hare-style fallible API use the wrappers below.
|
|
export fn write(fd: i32, buf: *u8, n: u64) i64 = {
|
|
return syscall3(SYS_WRITE, fd: i64, buf: i64, n: i64);
|
|
};
|
|
|
|
export fn read(fd: i32, buf: *u8, n: u64) i64 = {
|
|
return syscall3(SYS_READ, fd: i64, buf: i64, n: i64);
|
|
};
|
|
|
|
export fn close(fd: i32) i32 = {
|
|
return syscall1(SYS_CLOSE, fd: i64): i32;
|
|
};
|
|
|
|
// Fallible wrappers. The error variant is a plain str (Plan 9 errstr
|
|
// model, see lib/errors); the sum type makes success/failure explicit
|
|
// without overloading length-zero.
|
|
export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
|
let r: i64 = read(fd, buf, n);
|
|
if (r < 0) { return "read failed"; };
|
|
return r;
|
|
};
|
|
|
|
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
|
let r: i64 = write(fd, buf, n);
|
|
if (r < 0) { return "write failed"; };
|
|
return r;
|
|
};
|
|
|
|
// open — Linux open(2). Path must be NUL-terminated; callers using ww
|
|
// `str` must ensure the bytes are followed by a 0 byte (literals are,
|
|
// arena-copied paths usually are by construction). Returns -errno on
|
|
// failure, fd otherwise. Higher-level callers prefer `tryopen`.
|
|
export fn open(path: *u8, flags: i32, mode: i32) i32 = {
|
|
return syscall3(SYS_OPEN, path: i64, flags: i64, mode: i64): i32;
|
|
};
|
|
|
|
export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | str) = {
|
|
let fd: i32 = open(path, flags, mode);
|
|
if (fd < 0) { return "open failed"; };
|
|
return fd;
|
|
};
|
|
|
|
// lseek — set/inspect the fd's position. Returns the new offset or
|
|
// a negative errno. We use this for fstat-free file-size discovery
|
|
// (open ⇒ lseek to end ⇒ lseek back).
|
|
export fn lseek(fd: i32, off: i64, whence: i32) i64 = {
|
|
return syscall3(SYS_LSEEK, fd: i64, off, whence: i64);
|
|
};
|
|
|
|
// filesize — convenience: returns the byte length of an open fd by
|
|
// seeking to the end and back. -1 on error.
|
|
export fn filesize(fd: i32) i64 = {
|
|
let end: i64 = lseek(fd, 0i64, SEEK_END);
|
|
if (end < 0) { return -1i64; };
|
|
let r: i64 = lseek(fd, 0i64, SEEK_SET);
|
|
if (r < 0) { return -1i64; };
|
|
return end;
|
|
};
|
|
|
|
// readfull — keep reading until `n` bytes have arrived or the fd
|
|
// closes early. Returns bytes read (0..=n) or -1 on read error.
|
|
export fn readfull(fd: i32, buf: *u8, n: u64) i64 = {
|
|
let got: u64 = 0u64;
|
|
for (got < n) {
|
|
let r: i64 = read(fd, buf + got, n - got);
|
|
if (r < 0) { return -1i64; };
|
|
if (r == 0) { return got: i64; }; // short read: caller decides
|
|
got += r: u64;
|
|
};
|
|
return got: i64;
|
|
};
|
|
|
|
// writefull — keep writing until `n` bytes have been accepted or the
|
|
// fd refuses progress. Returns bytes written or -1.
|
|
export fn writefull(fd: i32, buf: *u8, n: u64) i64 = {
|
|
let sent: u64 = 0u64;
|
|
for (sent < n) {
|
|
let r: i64 = write(fd, buf + sent, n - sent);
|
|
if (r < 0) { return -1i64; };
|
|
if (r == 0) { return sent: i64; };
|
|
sent += r: u64;
|
|
};
|
|
return sent: i64;
|
|
};
|
|
|
|
// selfhost/cmd/wwc/mem.ww — port of cmd/wwc/mem.c.
|
|
//
|
|
// Bump arena allocator. Backed by the runtime page allocator
|
|
// (rt_alloc / rt_free), no libc. Each chunk is mmap'd; when the
|
|
// current chunk runs out we link a fresh one. Freeing the arena
|
|
// unmaps the chain.
|
|
//
|
|
// Memory handed out is 16-byte aligned. The C version under
|
|
// cmd/wwc/ is retained until the three-stage bootstrap diffs clean.
|
|
|
|
use os;
|
|
|
|
def ALIGN: u64 = 16u64;
|
|
def INIT_CHUNK: u64 = 65536u64;
|
|
def MAX_CHUNK: u64 = 4194304u64;
|
|
def ARENA_SZ: u64 = 48u64; // sizeof(arena), kept in sync below
|
|
|
|
type arena = struct {
|
|
buf: *u8,
|
|
off: u64,
|
|
cap: u64,
|
|
next: *arena,
|
|
total: u64,
|
|
};
|
|
|
|
fn roundup(n: u64, a: u64) u64 = {
|
|
return (n + a - 1u64) & ~(a - 1u64);
|
|
};
|
|
|
|
export fn newarena() *arena = {
|
|
let a: *arena = os.alloc(ARENA_SZ): *arena;
|
|
a.buf = os.alloc(INIT_CHUNK): *u8;
|
|
a.off = 0u64;
|
|
a.cap = INIT_CHUNK;
|
|
a.next = nil;
|
|
a.total = 0u64;
|
|
return a;
|
|
};
|
|
|
|
// Grow: link a fresh chunk in front of the head. We push the old
|
|
// chunk into `next` so the head always describes the current bump
|
|
// region. Chunk size doubles up to MAX_CHUNK.
|
|
fn grow(a: *arena, need: u64) bool = {
|
|
let want: u64 = a.cap * 2u64;
|
|
if (want < need) { want = need; };
|
|
if (want > MAX_CHUNK) { want = MAX_CHUNK; };
|
|
if (want < need) { return false; }; // single allocation too big
|
|
|
|
let old: *arena = os.alloc(ARENA_SZ): *arena;
|
|
old.buf = a.buf;
|
|
old.off = a.off;
|
|
old.cap = a.cap;
|
|
old.next = a.next;
|
|
old.total = 0u64;
|
|
|
|
a.buf = os.alloc(want): *u8;
|
|
a.off = 0u64;
|
|
a.cap = want;
|
|
a.next = old;
|
|
return true;
|
|
};
|
|
|
|
export fn amalloc(a: *arena, n: u64) *void = {
|
|
let need: u64 = roundup(n, ALIGN);
|
|
if (need > a.cap - a.off) {
|
|
if (!grow(a, need)) { return nil; };
|
|
};
|
|
let p: *u8 = a.buf + a.off;
|
|
a.off += need;
|
|
a.total += need;
|
|
// Zero the region. Plan 9 amalloc zeroes; we mirror that here so
|
|
// the checker can assume freshly allocated nodes start at 0.
|
|
let i: u64 = 0u64;
|
|
for (i < need) {
|
|
p[i] = 0u8;
|
|
i += 1u64;
|
|
};
|
|
return p: *void;
|
|
};
|
|
|
|
// astrndup — copy `n` bytes into the arena and produce a NUL-terminated
|
|
// view. Returns a `str` whose ptr is arena-owned and whose len is `n`
|
|
// (the trailing NUL is past `len`, so callers reading exactly n bytes
|
|
// see no padding). Used by the lexer to capture token text.
|
|
export fn astrndup(a: *arena, src: *u8, n: u64) str = {
|
|
let p: *u8 = amalloc(a, n + 1u64): *u8;
|
|
let i: u64 = 0u64;
|
|
for (i < n) {
|
|
p[i] = src[i];
|
|
i += 1u64;
|
|
};
|
|
p[n] = 0u8;
|
|
let r: str;
|
|
r.ptr = p;
|
|
r.len = n: i32;
|
|
return r;
|
|
};
|
|
|
|
export fn freearena(a: *arena) void = {
|
|
for (a != nil) {
|
|
let next: *arena = a.next;
|
|
os.free(a.buf: *void, a.cap);
|
|
os.free(a: *void, ARENA_SZ);
|
|
a = next;
|
|
};
|
|
};
|
|
|
|
// 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;
|
|
};
|
|
|
|
// 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;
|
|
};
|
|
|
|
// 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;
|
|
};
|
|
|
|
// 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;
|
|
};
|
|
|
|
// 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;
|
|
};
|
|
|