ww: rename toolchain to w-prefix + hare-style build/run/test driver
Plan 9-style w-prefix on the per-arch tools, disambiguating from the
real Plan 9 6c/6a/6l in ref/plan9front/:
cmd/wwc/ → cmd/wcc/ libwwc.a → libwcc.a
cmd/6{c,a,l} → cmd/w6{c,a,l} binary names too
test/wwc/ → test/wcc/ 6 test files w/ w6 prefix
selfhost/cmd mirror in lockstep
bootstrap/amd64/{w6c,w6a,w6l} snapshot binaries (gitignored)
WW_6{C,A,L} → WW_W6{C,A,L} env-var overrides
Plan 9 source-tree refs ("Plan 9 6c shape", ref/plan9front/, etc.)
preserved. Hare-style driver, both C and ww sides:
ww test [path] discover *_test.ww in a directory module, run
each; single-file mode for `ww test foo.ww`
Module-by-name `ww build foo` resolves to foo.ww or foo/foo.ww
via search path (cwd : -I dirs : $WW_LIB)
Default-to-cwd `ww build` / `ww test` build the cwd module
Run pass-through `ww run path arg1 arg2` reaches the program
lib/os: getcwd (79) and getdents64 (217) syscalls power `.` resolution
and directory enumeration on the ww side.
Makefile: wwstage tool deps now include lib/os/os.ww (+ lib/strconv
for wwdump_ww) so lib/* edits force their rebuild instead of leaving
stale binaries — surfaced when test 995 first failed against a stale
w6c_ww built before the lib/os additions.
Test 993 byte-identical parity gate (C-side ww vs ww-side ww_ww on a
build corpus) stays green; all 19 tests pass.
This commit is contained in:
262
cmd/w6l/dyn.c
Normal file
262
cmd/w6l/dyn.c
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* dyn.c — load a shared object (ET_DYN) so the linker knows which
|
||||
* symbols it exports and which DT_NEEDED entry to record. We do not
|
||||
* pull bytes from the .so; the dynamic loader maps it at runtime.
|
||||
*
|
||||
* Each call appends one Lso to lnk->sos. `l_so_provides` answers
|
||||
* "does this .so export the named symbol?" — l_resolve uses that to
|
||||
* promote unresolved references to dynamic.
|
||||
*/
|
||||
#include "l.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define ET_DYN 3
|
||||
|
||||
#define SHT_DYNAMIC 6
|
||||
#define SHT_DYNSYM 11
|
||||
/* GNU extensions, sh_type values. */
|
||||
#define SHT_GNU_VERDEF 0x6ffffffd
|
||||
#define SHT_GNU_VERNEED 0x6ffffffe
|
||||
#define SHT_GNU_VERSYM 0x6fffffff
|
||||
|
||||
#define DT_NULL 0
|
||||
#define DT_SONAME 14
|
||||
#define DT_STRTAB 5
|
||||
|
||||
/* Versym special values: 0 = local, 1 = base/global. */
|
||||
#define VER_NDX_LOCAL 0
|
||||
#define VER_NDX_GLOBAL 1
|
||||
#define VER_FLG_BASE 1
|
||||
#define VERSYM_HIDDEN 0x8000
|
||||
#define VERSYM_VERSION 0x7fff
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
u8 e_ident[16];
|
||||
u16 e_type, e_machine;
|
||||
u32 e_version;
|
||||
u64 e_entry, e_phoff, e_shoff;
|
||||
u32 e_flags;
|
||||
u16 e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx;
|
||||
} Ehdr;
|
||||
|
||||
typedef struct {
|
||||
u32 sh_name, sh_type;
|
||||
u64 sh_flags, sh_addr, sh_offset, sh_size;
|
||||
u32 sh_link, sh_info;
|
||||
u64 sh_addralign, sh_entsize;
|
||||
} Shdr;
|
||||
|
||||
typedef struct {
|
||||
u32 st_name;
|
||||
u8 st_info, st_other;
|
||||
u16 st_shndx;
|
||||
u64 st_value, st_size;
|
||||
} Sym64;
|
||||
|
||||
typedef struct {
|
||||
i64 d_tag;
|
||||
u64 d_val;
|
||||
} Dyn64;
|
||||
|
||||
typedef struct {
|
||||
u16 vd_version;
|
||||
u16 vd_flags;
|
||||
u16 vd_ndx;
|
||||
u16 vd_cnt;
|
||||
u32 vd_hash;
|
||||
u32 vd_aux;
|
||||
u32 vd_next;
|
||||
} Verdef;
|
||||
|
||||
typedef struct {
|
||||
u32 vda_name;
|
||||
u32 vda_next;
|
||||
} Verdaux;
|
||||
#pragma pack(pop)
|
||||
|
||||
/* ELF binding values. STB_GLOBAL = 1, STB_WEAK = 2. */
|
||||
#define ST_BIND(i) ((i) >> 4)
|
||||
|
||||
int
|
||||
l_load_so(Lnk *l, const char *path)
|
||||
{
|
||||
u8 *buf;
|
||||
u64 len;
|
||||
if (l_read_all(path, &buf, &len) < 0) {
|
||||
fprintf(stderr, "w6l: %s: cannot read\n", path);
|
||||
return -1;
|
||||
}
|
||||
if (len < sizeof(Ehdr)) {
|
||||
fprintf(stderr, "w6l: %s: short ELF\n", path);
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
Ehdr *eh = (Ehdr *)buf;
|
||||
if (memcmp(eh->e_ident, "\x7f""ELF", 4) != 0
|
||||
|| eh->e_ident[4] != 2
|
||||
|| eh->e_machine != 62 /* EM_X86_64 */
|
||||
|| eh->e_type != ET_DYN) {
|
||||
fprintf(stderr, "w6l: %s: not an amd64 ET_DYN\n", path);
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
if (eh->e_shoff == 0 || eh->e_shnum == 0) {
|
||||
fprintf(stderr, "w6l: %s: stripped .so unsupported\n", path);
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
Shdr *sh = (Shdr *)(buf + eh->e_shoff);
|
||||
int idx_dynsym = -1, idx_dynamic = -1;
|
||||
int idx_versym = -1, idx_verdef = -1;
|
||||
for (u16 i = 0; i < eh->e_shnum; i++) {
|
||||
if (sh[i].sh_type == SHT_DYNSYM) idx_dynsym = i;
|
||||
if (sh[i].sh_type == SHT_DYNAMIC) idx_dynamic = i;
|
||||
if (sh[i].sh_type == SHT_GNU_VERSYM) idx_versym = i;
|
||||
if (sh[i].sh_type == SHT_GNU_VERDEF) idx_verdef = i;
|
||||
}
|
||||
if (idx_dynsym < 0) {
|
||||
fprintf(stderr, "w6l: %s: no .dynsym\n", path);
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
int idx_dynstr = sh[idx_dynsym].sh_link;
|
||||
const char *str = (const char *)(buf + sh[idx_dynstr].sh_offset);
|
||||
Sym64 *syms = (Sym64 *)(buf + sh[idx_dynsym].sh_offset);
|
||||
u64 nsyms = sh[idx_dynsym].sh_size / sizeof(Sym64);
|
||||
|
||||
/* SONAME: the .dynamic section's strings live in the section pointed
|
||||
* at by its sh_link (not necessarily .dynstr — though usually). */
|
||||
const char *soname = NULL;
|
||||
if (idx_dynamic >= 0) {
|
||||
int idx_dstr = sh[idx_dynamic].sh_link;
|
||||
const char *dstr = (const char *)(buf + sh[idx_dstr].sh_offset);
|
||||
Dyn64 *d = (Dyn64 *)(buf + sh[idx_dynamic].sh_offset);
|
||||
u64 nd = sh[idx_dynamic].sh_size / sizeof(Dyn64);
|
||||
for (u64 i = 0; i < nd; i++) {
|
||||
if (d[i].d_tag == DT_NULL) break;
|
||||
if (d[i].d_tag == DT_SONAME) {
|
||||
soname = dstr + d[i].d_val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (soname == NULL) {
|
||||
const char *bn = strrchr(path, '/');
|
||||
soname = bn ? bn + 1 : path;
|
||||
}
|
||||
|
||||
/* Build verdef-index → version-name table. The version name is in
|
||||
* the first Verdaux (the rest are predecessor names — version
|
||||
* inheritance for stable ABI within a release line). For our
|
||||
* purposes only the leading name matters. */
|
||||
const char **verdef_names = NULL;
|
||||
int verdef_max = 0;
|
||||
if (idx_verdef >= 0) {
|
||||
const u8 *vbase = buf + sh[idx_verdef].sh_offset;
|
||||
const char *vstr = (const char *)
|
||||
(buf + sh[sh[idx_verdef].sh_link].sh_offset);
|
||||
/* First pass: discover max ndx so we can size the table. */
|
||||
u64 vd_off = 0;
|
||||
while (vd_off < sh[idx_verdef].sh_size) {
|
||||
Verdef *vd = (Verdef *)(vbase + vd_off);
|
||||
if ((int)vd->vd_ndx > verdef_max)
|
||||
verdef_max = vd->vd_ndx;
|
||||
if (vd->vd_next == 0) break;
|
||||
vd_off += vd->vd_next;
|
||||
}
|
||||
verdef_names = calloc((size_t)verdef_max + 1,
|
||||
sizeof *verdef_names);
|
||||
vd_off = 0;
|
||||
while (vd_off < sh[idx_verdef].sh_size) {
|
||||
Verdef *vd = (Verdef *)(vbase + vd_off);
|
||||
Verdaux *va = (Verdaux *)((u8 *)vd + vd->vd_aux);
|
||||
verdef_names[vd->vd_ndx] = vstr + va->vda_name;
|
||||
if (vd->vd_next == 0) break;
|
||||
vd_off += vd->vd_next;
|
||||
}
|
||||
}
|
||||
|
||||
/* Versym is one u16 per .dynsym entry. */
|
||||
const u16 *versym = NULL;
|
||||
if (idx_versym >= 0)
|
||||
versym = (const u16 *)(buf + sh[idx_versym].sh_offset);
|
||||
|
||||
Lso *so = calloc(1, sizeof *so);
|
||||
so->path = strdup(path);
|
||||
so->soname = strdup(soname);
|
||||
so->exports = calloc((size_t)nsyms + 1, sizeof *so->exports);
|
||||
so->versions = calloc((size_t)nsyms + 1, sizeof *so->versions);
|
||||
|
||||
int n = 0;
|
||||
for (u64 i = 1; i < nsyms; i++) {
|
||||
if (syms[i].st_shndx == 0) continue; /* SHN_UNDEF */
|
||||
u8 b = ST_BIND(syms[i].st_info);
|
||||
if (b != 1 && b != 2) continue; /* GLOBAL or WEAK */
|
||||
const char *nm = str + syms[i].st_name;
|
||||
if (nm[0] == '\0') continue;
|
||||
|
||||
/* Skip non-default versions: when a name has multiple version
|
||||
* definitions, the loader binds an unversioned reference to
|
||||
* the one whose Versym entry has the hidden bit clear. */
|
||||
const char *vername = NULL;
|
||||
if (versym != NULL) {
|
||||
u16 v = versym[i];
|
||||
if (v & VERSYM_HIDDEN) continue; /* non-default */
|
||||
u16 vidx = v & VERSYM_VERSION;
|
||||
if (vidx == VER_NDX_LOCAL) continue; /* not exported */
|
||||
if (vidx == VER_NDX_GLOBAL) {
|
||||
vername = NULL; /* unversioned */
|
||||
} else if (verdef_names != NULL
|
||||
&& (int)vidx <= verdef_max
|
||||
&& verdef_names[vidx] != NULL) {
|
||||
/* index 1 in glibc's Verdef is the SONAME with
|
||||
* VER_FLG_BASE — we skip its export entries
|
||||
* as a side effect of vidx==1 mapping to the
|
||||
* BASE name (e.g. "libc.so.6"), which never
|
||||
* appears as a reference target. Treat any
|
||||
* lookup that lands on the BASE entry as
|
||||
* unversioned. */
|
||||
if (vidx == 1)
|
||||
vername = NULL;
|
||||
else
|
||||
vername = verdef_names[vidx];
|
||||
}
|
||||
}
|
||||
|
||||
so->exports[n] = strdup(nm);
|
||||
so->versions[n] = vername ? strdup(vername) : NULL;
|
||||
n++;
|
||||
}
|
||||
so->exports[n] = NULL;
|
||||
so->versions[n] = NULL;
|
||||
|
||||
if (verdef_names != NULL) free(verdef_names);
|
||||
|
||||
so->next = l->sos;
|
||||
l->sos = so;
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
l_so_provides(Lso *so, const char *name)
|
||||
{
|
||||
const char *v;
|
||||
return l_so_provides_v(so, name, &v);
|
||||
}
|
||||
|
||||
int
|
||||
l_so_provides_v(Lso *so, const char *name, const char **out_version)
|
||||
{
|
||||
if (so == NULL || so->exports == NULL) return 0;
|
||||
for (int i = 0; so->exports[i]; i++) {
|
||||
if (strcmp(so->exports[i], name) != 0) continue;
|
||||
*out_version = so->versions ? so->versions[i] : NULL;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
644
cmd/w6l/dynout.c
Normal file
644
cmd/w6l/dynout.c
Normal file
@@ -0,0 +1,644 @@
|
||||
/*
|
||||
* dynout.c — emit a dynamic-linked ELF executable.
|
||||
*
|
||||
* The shape we produce is the simplest valid one: PT_INTERP +
|
||||
* PT_DYNAMIC + DT_BIND_NOW so the loader resolves every PLT slot at
|
||||
* startup (no lazy binding, no PLT0 trampoline). Symbol versioning
|
||||
* is omitted; modern glibc tolerates unversioned references by
|
||||
* binding to each symbol's "default" version. SysV .hash, not
|
||||
* .gnu.hash. Non-PIE, fixed base.
|
||||
*
|
||||
* File layout:
|
||||
* [0] Ehdr
|
||||
* [64] Phdrs (PT_LOAD R+X, PT_LOAD R+W, PT_INTERP, PT_DYNAMIC)
|
||||
* [interp_off] "/lib64/ld-linux-x86-64.so.2\0"
|
||||
* [dynstr_off] .dynstr
|
||||
* [dynsym_off] .dynsym
|
||||
* [hash_off] .hash
|
||||
* [relaplt_off] .rela.plt
|
||||
* [pad to 0x1000]
|
||||
* [text_off] .text
|
||||
* [plt_off] .plt
|
||||
* [pad to next page]
|
||||
* [gotplt_off] .got.plt (writable; mapped by PT_LOAD #2)
|
||||
* [dynamic_off] .dynamic (writable; covered by PT_DYNAMIC)
|
||||
*
|
||||
* Each PLT entry is 8 bytes: `jmpq *(rip+disp)` (6 bytes) + 2 bytes
|
||||
* pad so the next entry stays naturally aligned.
|
||||
*/
|
||||
#include "l.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ELF constants */
|
||||
#define ET_EXEC 2
|
||||
#define EM_X86_64 62
|
||||
#define EV_CURRENT 1
|
||||
#define ELFCLASS64 2
|
||||
#define ELFDATA2LSB 1
|
||||
|
||||
#define PT_LOAD 1
|
||||
#define PT_DYNAMIC 2
|
||||
#define PT_INTERP 3
|
||||
#define PF_X 1
|
||||
#define PF_W 2
|
||||
#define PF_R 4
|
||||
|
||||
#define DT_NULL 0
|
||||
#define DT_NEEDED 1
|
||||
#define DT_PLTRELSZ 2
|
||||
#define DT_PLTGOT 3
|
||||
#define DT_HASH 4
|
||||
#define DT_STRTAB 5
|
||||
#define DT_SYMTAB 6
|
||||
#define DT_STRSZ 10
|
||||
#define DT_SYMENT 11
|
||||
#define DT_PLTREL 20
|
||||
#define DT_RELA 7
|
||||
#define DT_JMPREL 23
|
||||
#define DT_BIND_NOW 24
|
||||
/* GNU extensions for symbol versioning. */
|
||||
#define DT_VERSYM 0x6ffffff0
|
||||
#define DT_VERNEED 0x6ffffffe
|
||||
#define DT_VERNEEDNUM 0x6fffffff
|
||||
|
||||
#define VER_NDX_LOCAL 0
|
||||
#define VER_NDX_GLOBAL 1
|
||||
|
||||
#define R_X86_64_PC32 2
|
||||
#define R_X86_64_PLT32 4
|
||||
#define R_X86_64_JUMP_SLOT 7
|
||||
|
||||
#define STB_GLOBAL 1
|
||||
#define STT_FUNC 2
|
||||
#define ST_INFO(b,t) (((b) << 4) | ((t) & 0xf))
|
||||
|
||||
#define INTERP "/lib64/ld-linux-x86-64.so.2"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
u8 e_ident[16];
|
||||
u16 e_type, e_machine;
|
||||
u32 e_version;
|
||||
u64 e_entry, e_phoff, e_shoff;
|
||||
u32 e_flags;
|
||||
u16 e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx;
|
||||
} Ehdr;
|
||||
|
||||
typedef struct {
|
||||
u32 p_type, p_flags;
|
||||
u64 p_offset, p_vaddr, p_paddr;
|
||||
u64 p_filesz, p_memsz, p_align;
|
||||
} Phdr;
|
||||
|
||||
typedef struct {
|
||||
u32 st_name;
|
||||
u8 st_info, st_other;
|
||||
u16 st_shndx;
|
||||
u64 st_value, st_size;
|
||||
} Sym64;
|
||||
|
||||
typedef struct {
|
||||
u64 r_offset;
|
||||
u64 r_info;
|
||||
i64 r_addend;
|
||||
} Rela64;
|
||||
|
||||
typedef struct {
|
||||
i64 d_tag;
|
||||
u64 d_val;
|
||||
} Dyn64;
|
||||
#pragma pack(pop)
|
||||
|
||||
#define ELF64_R_INFO(s,t) (((u64)(s) << 32) | ((u32)(t)))
|
||||
|
||||
/* SysV ELF hash (the older format; .gnu.hash is faster but more code). */
|
||||
static u32
|
||||
elf_hash(const char *name)
|
||||
{
|
||||
u32 h = 0, g;
|
||||
for (const u8 *s = (const u8 *)name; *s; s++) {
|
||||
h = (h << 4) + *s;
|
||||
g = h & 0xf0000000u;
|
||||
if (g) h ^= g >> 24;
|
||||
h &= ~g;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
/* Patch a 4-byte little-endian field in `buf` at offset `off`. */
|
||||
static void
|
||||
poke32(u8 *buf, u64 off, u32 v)
|
||||
{
|
||||
buf[off + 0] = (u8)(v);
|
||||
buf[off + 1] = (u8)(v >> 8);
|
||||
buf[off + 2] = (u8)(v >> 16);
|
||||
buf[off + 3] = (u8)(v >> 24);
|
||||
}
|
||||
|
||||
#define PLT_STUB_BYTES 8 /* jmpq *disp(%rip) + 2 nop pad */
|
||||
|
||||
int
|
||||
l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
{
|
||||
const int N = l->dyn_n;
|
||||
|
||||
/* ---- Pass 1: collect dynamic symbol names + .dynstr layout ---- */
|
||||
|
||||
/* dynstr layout: [0]='\0', then DT_NEEDED soname strings, then
|
||||
* one symbol name per dynamic Lsym. We index dyn syms by
|
||||
* plt_idx (assigned in l_resolve). Build an array sorted by
|
||||
* plt_idx so we can walk in slot order. */
|
||||
Lsym **dynsyms = calloc((size_t)N, sizeof *dynsyms);
|
||||
for (Lsym *s = l->syms; s; s = s->next) {
|
||||
if (s->is_dyn && s->plt_idx >= 0 && s->plt_idx < N)
|
||||
dynsyms[s->plt_idx] = s;
|
||||
}
|
||||
for (int i = 0; i < N; i++) {
|
||||
if (dynsyms[i] == NULL) {
|
||||
fprintf(stderr, "w6l: dynout: no sym for plt_idx %d\n", i);
|
||||
free(dynsyms);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Count Lso's that any dyn sym references; only those need DT_NEEDED. */
|
||||
int nsos = 0;
|
||||
for (Lso *so = l->sos; so; so = so->next) {
|
||||
int used = 0;
|
||||
for (int i = 0; i < N; i++)
|
||||
if (dynsyms[i]->dyn_lib == so) { used = 1; break; }
|
||||
if (used) nsos++;
|
||||
}
|
||||
Lso **sos_used = calloc((size_t)nsos, sizeof *sos_used);
|
||||
{
|
||||
int idx = 0;
|
||||
for (Lso *so = l->sos; so; so = so->next) {
|
||||
int used = 0;
|
||||
for (int i = 0; i < N; i++)
|
||||
if (dynsyms[i]->dyn_lib == so) { used = 1; break; }
|
||||
if (used) sos_used[idx++] = so;
|
||||
}
|
||||
}
|
||||
|
||||
/* Build .dynstr in a growable buffer. */
|
||||
u8 *dynstr = NULL;
|
||||
u64 dynstr_cap = 0, dynstr_len = 0;
|
||||
#define DSTR_PUT(s) do { \
|
||||
size_t _n = strlen(s) + 1; \
|
||||
if (dynstr_len + _n > dynstr_cap) { \
|
||||
dynstr_cap = dynstr_cap ? dynstr_cap * 2 : 256; \
|
||||
while (dynstr_cap < dynstr_len + _n) dynstr_cap *= 2; \
|
||||
dynstr = realloc(dynstr, dynstr_cap); \
|
||||
} \
|
||||
memcpy(dynstr + dynstr_len, s, _n); \
|
||||
dynstr_len += _n; \
|
||||
} while (0)
|
||||
|
||||
DSTR_PUT(""); /* leading null entry */
|
||||
|
||||
u32 *soname_str = calloc((size_t)nsos, sizeof *soname_str);
|
||||
for (int i = 0; i < nsos; i++) {
|
||||
soname_str[i] = (u32)dynstr_len;
|
||||
DSTR_PUT(sos_used[i]->soname);
|
||||
}
|
||||
u32 *symname_str = calloc((size_t)N, sizeof *symname_str);
|
||||
for (int i = 0; i < N; i++) {
|
||||
symname_str[i] = (u32)dynstr_len;
|
||||
DSTR_PUT(dynsyms[i]->name);
|
||||
}
|
||||
|
||||
/* ---- Versioning: group dyn syms by (lib, version) ----
|
||||
*
|
||||
* For every sym whose dyn_version is non-NULL, there's a
|
||||
* Vernaux record under that lib's Verneed. The vna_other
|
||||
* value (assigned starting at 2; 1 is reserved for "global,
|
||||
* unversioned") becomes that sym's .gnu.version entry.
|
||||
* Unversioned syms get .gnu.version = 1.
|
||||
*
|
||||
* vlibs is parallel-indexed with sos_used so we can look
|
||||
* up the SONAME's dynstr offset directly.
|
||||
*/
|
||||
struct vlib_ver { const char *name; u32 dynstr_off; u16 vna_other; };
|
||||
struct vlib { int sos_idx; int n_versions; struct vlib_ver *versions; };
|
||||
struct vlib *vlibs = calloc((size_t)nsos, sizeof *vlibs);
|
||||
int n_vlibs = 0;
|
||||
|
||||
for (int i = 0; i < nsos; i++) {
|
||||
int has = 0;
|
||||
for (int j = 0; j < N; j++) {
|
||||
if (dynsyms[j]->dyn_lib == sos_used[i]
|
||||
&& dynsyms[j]->dyn_version != NULL) {
|
||||
has = 1; break;
|
||||
}
|
||||
}
|
||||
if (!has) continue;
|
||||
struct vlib *vl = &vlibs[n_vlibs];
|
||||
vl->sos_idx = i;
|
||||
vl->versions = calloc((size_t)N, sizeof *vl->versions);
|
||||
vl->n_versions = 0;
|
||||
for (int j = 0; j < N; j++) {
|
||||
if (dynsyms[j]->dyn_lib != sos_used[i]) continue;
|
||||
const char *vname = dynsyms[j]->dyn_version;
|
||||
if (vname == NULL) continue;
|
||||
int seen = 0;
|
||||
for (int k = 0; k < vl->n_versions; k++) {
|
||||
if (strcmp(vl->versions[k].name, vname) == 0) {
|
||||
seen = 1; break;
|
||||
}
|
||||
}
|
||||
if (!seen) {
|
||||
vl->versions[vl->n_versions].name = vname;
|
||||
vl->n_versions++;
|
||||
}
|
||||
}
|
||||
n_vlibs++;
|
||||
}
|
||||
|
||||
/* Assign vna_other indices starting at 2. */
|
||||
u16 next_vna = 2;
|
||||
for (int i = 0; i < n_vlibs; i++)
|
||||
for (int k = 0; k < vlibs[i].n_versions; k++)
|
||||
vlibs[i].versions[k].vna_other = next_vna++;
|
||||
|
||||
/* Add version name strings to .dynstr. */
|
||||
for (int i = 0; i < n_vlibs; i++) {
|
||||
for (int k = 0; k < vlibs[i].n_versions; k++) {
|
||||
vlibs[i].versions[k].dynstr_off = (u32)dynstr_len;
|
||||
DSTR_PUT(vlibs[i].versions[k].name);
|
||||
}
|
||||
}
|
||||
|
||||
/* Per-dyn-sym versym index: 1 (global) for unversioned, else
|
||||
* the matched Vernaux's vna_other. */
|
||||
u16 *versym_for = calloc((size_t)N, sizeof *versym_for);
|
||||
for (int j = 0; j < N; j++) {
|
||||
const char *vname = dynsyms[j]->dyn_version;
|
||||
if (vname == NULL) { versym_for[j] = VER_NDX_GLOBAL; continue; }
|
||||
int matched = 0;
|
||||
for (int i = 0; i < n_vlibs && !matched; i++) {
|
||||
if (sos_used[vlibs[i].sos_idx] != dynsyms[j]->dyn_lib)
|
||||
continue;
|
||||
for (int k = 0; k < vlibs[i].n_versions; k++) {
|
||||
if (strcmp(vlibs[i].versions[k].name, vname) == 0) {
|
||||
versym_for[j] = vlibs[i].versions[k].vna_other;
|
||||
matched = 1; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!matched) {
|
||||
fprintf(stderr, "w6l: dynout: unmatched version %s for %s\n",
|
||||
vname, dynsyms[j]->name);
|
||||
versym_for[j] = VER_NDX_GLOBAL;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Pass 2: compute byte sizes of every section ---- */
|
||||
|
||||
const u64 ehdr_sz = sizeof(Ehdr);
|
||||
const int n_phdrs = 4;
|
||||
const u64 phdr_sz = (u64)n_phdrs * sizeof(Phdr);
|
||||
|
||||
const u64 interp_sz = strlen(INTERP) + 1;
|
||||
|
||||
/* nsyms = 1 (undef at index 0) + N */
|
||||
const int nsyms_total = 1 + N;
|
||||
const u64 dynsym_sz = (u64)nsyms_total * sizeof(Sym64);
|
||||
const u64 dynstr_sz = dynstr_len;
|
||||
|
||||
/* SysV hash: nbuckets + nchain + buckets[] + chain[]. We use one
|
||||
* bucket; loader scans the whole chain. Cheap to compute, easy to
|
||||
* validate. */
|
||||
const u32 nbuckets = 1;
|
||||
const u32 nchain = (u32)nsyms_total;
|
||||
const u64 hash_sz = (2 + nbuckets + nchain) * 4;
|
||||
|
||||
const u64 relaplt_sz = (u64)N * sizeof(Rela64);
|
||||
|
||||
const u64 plt_sz = (u64)N * PLT_STUB_BYTES;
|
||||
const u64 gotplt_sz = (3 + (u64)N) * 8;
|
||||
|
||||
/* .gnu.version: one Elf64_Half per .dynsym entry. */
|
||||
const u64 versym_sz = (u64)nsyms_total * 2;
|
||||
|
||||
/* .gnu.version_r: per lib, 16-byte Verneed plus 16-byte Vernaux
|
||||
* for each version under it. */
|
||||
u64 verneed_sz = 0;
|
||||
for (int i = 0; i < n_vlibs; i++)
|
||||
verneed_sz += 16 + 16 * (u64)vlibs[i].n_versions;
|
||||
|
||||
/* dynamic entries: NEEDED*nsos, HASH, STRTAB, SYMTAB, STRSZ, SYMENT,
|
||||
* PLTGOT, PLTRELSZ, PLTREL, JMPREL, BIND_NOW, [VERSYM, VERNEED,
|
||||
* VERNEEDNUM], NULL. The version trio is conditional on having any
|
||||
* versioned references. */
|
||||
const int with_ver = (n_vlibs > 0);
|
||||
const u64 ndyn = (u64)nsos + 11 + (with_ver ? 3 : 0);
|
||||
const u64 dynamic_sz = ndyn * sizeof(Dyn64);
|
||||
|
||||
/* ---- Pass 3: assign file offsets and virtual addresses ----
|
||||
* Everything from the Ehdr through .text+.plt is in the R+X
|
||||
* load segment at base+0..text_end. .got.plt and .dynamic land
|
||||
* in the R+W segment at the next page boundary. */
|
||||
|
||||
u64 off = ehdr_sz + phdr_sz;
|
||||
const u64 interp_off = off; off += interp_sz;
|
||||
off = (off + 7) & ~(u64)7;
|
||||
const u64 dynstr_off = off; off += dynstr_sz;
|
||||
off = (off + 7) & ~(u64)7;
|
||||
const u64 dynsym_off = off; off += dynsym_sz;
|
||||
const u64 hash_off = off; off += hash_sz;
|
||||
off = (off + 1) & ~(u64)1;
|
||||
const u64 versym_off = off; off += versym_sz;
|
||||
off = (off + 3) & ~(u64)3;
|
||||
const u64 verneed_off = off; off += verneed_sz;
|
||||
off = (off + 7) & ~(u64)7;
|
||||
const u64 relaplt_off = off; off += relaplt_sz;
|
||||
|
||||
/* Pad to 0x1000 so .text is page-aligned (matters for the loader
|
||||
* mapping our R+X PT_LOAD). */
|
||||
const u64 page = 0x1000;
|
||||
const u64 text_off = (off + page - 1) & ~(page - 1);
|
||||
const u64 plt_off = text_off + l->textlen;
|
||||
const u64 rx_end = plt_off + plt_sz;
|
||||
|
||||
/* Page-align the writable segment. We skip a page of file bytes;
|
||||
* the data lands at file offset gotplt_off, vaddr at base+gotplt_va. */
|
||||
const u64 gotplt_off = (rx_end + page - 1) & ~(page - 1);
|
||||
const u64 dynamic_off = gotplt_off + gotplt_sz;
|
||||
const u64 file_end = dynamic_off + dynamic_sz;
|
||||
|
||||
/* Virtual addresses mirror file offsets within their segment.
|
||||
* The R+W segment in particular needs vaddr = base + gotplt_off
|
||||
* so file offset and vaddr modulo page agree (loader requirement). */
|
||||
const u64 interp_va = base + interp_off;
|
||||
const u64 dynstr_va = base + dynstr_off;
|
||||
const u64 dynsym_va = base + dynsym_off;
|
||||
const u64 hash_va = base + hash_off;
|
||||
const u64 versym_va = base + versym_off;
|
||||
const u64 verneed_va = base + verneed_off;
|
||||
const u64 relaplt_va = base + relaplt_off;
|
||||
const u64 text_va = base + text_off;
|
||||
const u64 plt_va = base + plt_off;
|
||||
const u64 gotplt_va = base + gotplt_off;
|
||||
const u64 dynamic_va = base + dynamic_off;
|
||||
(void)dynstr_va; (void)hash_va; (void)plt_va;
|
||||
|
||||
/* ---- Pass 4: build each section into a buffer ---- */
|
||||
|
||||
/* .dynsym */
|
||||
Sym64 *dynsym = calloc((size_t)nsyms_total, sizeof *dynsym);
|
||||
for (int i = 0; i < N; i++) {
|
||||
Sym64 *e = &dynsym[1 + i];
|
||||
e->st_name = symname_str[i];
|
||||
e->st_info = ST_INFO(STB_GLOBAL, STT_FUNC);
|
||||
e->st_other = 0;
|
||||
e->st_shndx = 0; /* SHN_UNDEF */
|
||||
e->st_value = 0;
|
||||
e->st_size = 0;
|
||||
dynsyms[i]->dynsym_idx = 1 + i;
|
||||
}
|
||||
|
||||
/* .hash (SysV format, 1 bucket). */
|
||||
u32 *hash = calloc(2 + nbuckets + nchain, 4);
|
||||
hash[0] = nbuckets;
|
||||
hash[1] = nchain;
|
||||
/* buckets[0] = first entry that lives in this bucket; we put
|
||||
* everything in bucket 0, so the bucket head is symbol 1. */
|
||||
hash[2] = nsyms_total > 1 ? 1 : 0;
|
||||
/* chain[i] = next sym in the bucket. Last one terminates with 0. */
|
||||
for (int i = 1; i < nsyms_total; i++) {
|
||||
u32 next = (i + 1 < nsyms_total) ? (u32)(i + 1) : 0;
|
||||
hash[2 + nbuckets + i] = next;
|
||||
}
|
||||
/* elf_hash is also used by .gnu.version_r for vna_hash below. */
|
||||
|
||||
/* .rela.plt */
|
||||
Rela64 *relaplt = calloc((size_t)N, sizeof *relaplt);
|
||||
for (int i = 0; i < N; i++) {
|
||||
relaplt[i].r_offset = gotplt_va + (3 + (u64)i) * 8;
|
||||
relaplt[i].r_info = ELF64_R_INFO(1 + i, R_X86_64_JUMP_SLOT);
|
||||
relaplt[i].r_addend = 0;
|
||||
}
|
||||
|
||||
/* .gnu.version: u16 per .dynsym entry. [0] = LOCAL, [1+i] = the
|
||||
* versym index we computed for dyn sym i. */
|
||||
u16 *versym = calloc((size_t)nsyms_total, 2);
|
||||
versym[0] = VER_NDX_LOCAL;
|
||||
for (int i = 0; i < N; i++)
|
||||
versym[1 + i] = versym_for[i];
|
||||
|
||||
/* .gnu.version_r: chain of Verneed records, one per versioned lib,
|
||||
* each with a chain of Vernaux records, one per version under it.
|
||||
* We write directly into a u8 buffer with little-endian poke
|
||||
* helpers to avoid alignment concerns. */
|
||||
u8 *verneed = NULL;
|
||||
if (verneed_sz > 0) {
|
||||
verneed = calloc((size_t)verneed_sz, 1);
|
||||
u64 vnoff = 0;
|
||||
for (int i = 0; i < n_vlibs; i++) {
|
||||
struct vlib *vl = &vlibs[i];
|
||||
u64 vn_start = vnoff;
|
||||
/* Verneed header (16 bytes). */
|
||||
u8 *vn = verneed + vnoff;
|
||||
/* vn_version = 1, vn_cnt = nversions */
|
||||
vn[0] = 1; vn[1] = 0;
|
||||
vn[2] = (u8)(vl->n_versions);
|
||||
vn[3] = (u8)(vl->n_versions >> 8);
|
||||
poke32(vn, 4, soname_str[vl->sos_idx]); /* vn_file */
|
||||
poke32(vn, 8, 16); /* vn_aux */
|
||||
/* vn_next set after we know the vernaux count */
|
||||
vnoff += 16;
|
||||
for (int k = 0; k < vl->n_versions; k++) {
|
||||
u8 *va = verneed + vnoff;
|
||||
poke32(va, 0, elf_hash(vl->versions[k].name));
|
||||
/* vna_flags = 0 */
|
||||
va[4] = 0; va[5] = 0;
|
||||
/* vna_other (versym index) */
|
||||
va[6] = (u8)(vl->versions[k].vna_other);
|
||||
va[7] = (u8)(vl->versions[k].vna_other >> 8);
|
||||
poke32(va, 8, vl->versions[k].dynstr_off);
|
||||
poke32(va, 12,
|
||||
(k + 1 < vl->n_versions) ? 16u : 0u);
|
||||
vnoff += 16;
|
||||
}
|
||||
/* Now patch vn_next at vn_start+12. */
|
||||
poke32(verneed, vn_start + 12,
|
||||
(i + 1 < n_vlibs)
|
||||
? (u32)(vnoff - vn_start) : 0u);
|
||||
}
|
||||
}
|
||||
|
||||
/* .plt — `jmpq *got.plt[3+i](%rip)` per stub.
|
||||
* Encoding: FF 25 <disp32>. The disp is computed from the address
|
||||
* of the *next* instruction (RIP after the 6-byte jmp) to the
|
||||
* GOT slot. */
|
||||
u8 *plt = calloc((size_t)plt_sz, 1);
|
||||
for (int i = 0; i < N; i++) {
|
||||
u64 stub_va = plt_va + (u64)i * PLT_STUB_BYTES;
|
||||
u64 next_ip = stub_va + 6;
|
||||
u64 slot_va = gotplt_va + (3 + (u64)i) * 8;
|
||||
i64 disp = (i64)slot_va - (i64)next_ip;
|
||||
u8 *p = plt + (u64)i * PLT_STUB_BYTES;
|
||||
p[0] = 0xff;
|
||||
p[1] = 0x25;
|
||||
poke32(p, 2, (u32)(i32)disp);
|
||||
/* p[6], p[7] left as zero — pad. */
|
||||
}
|
||||
|
||||
/* .got.plt — first three slots are reserved.
|
||||
* [0] = address of .dynamic (loader reads this).
|
||||
* [1] = link_map * (loader writes at startup).
|
||||
* [2] = dl_runtime_resolve (loader writes; unused with BIND_NOW). */
|
||||
u8 *gotplt = calloc((size_t)gotplt_sz, 1);
|
||||
{
|
||||
u64 v = dynamic_va;
|
||||
for (int b = 0; b < 8; b++) gotplt[b] = (u8)(v >> (b * 8));
|
||||
}
|
||||
/* [3..3+N-1] left zero; loader fills via R_X86_64_JUMP_SLOT. */
|
||||
|
||||
/* .dynamic */
|
||||
Dyn64 *dynamic = calloc((size_t)ndyn, sizeof *dynamic);
|
||||
{
|
||||
int k = 0;
|
||||
for (int i = 0; i < nsos; i++) {
|
||||
dynamic[k].d_tag = DT_NEEDED;
|
||||
dynamic[k].d_val = soname_str[i];
|
||||
k++;
|
||||
}
|
||||
dynamic[k].d_tag = DT_HASH; dynamic[k].d_val = hash_va; k++;
|
||||
dynamic[k].d_tag = DT_STRTAB; dynamic[k].d_val = dynstr_va; k++;
|
||||
dynamic[k].d_tag = DT_SYMTAB; dynamic[k].d_val = dynsym_va; k++;
|
||||
dynamic[k].d_tag = DT_STRSZ; dynamic[k].d_val = dynstr_sz; k++;
|
||||
dynamic[k].d_tag = DT_SYMENT; dynamic[k].d_val = sizeof(Sym64); k++;
|
||||
dynamic[k].d_tag = DT_PLTGOT; dynamic[k].d_val = gotplt_va; k++;
|
||||
dynamic[k].d_tag = DT_PLTRELSZ; dynamic[k].d_val = relaplt_sz; k++;
|
||||
dynamic[k].d_tag = DT_PLTREL; dynamic[k].d_val = DT_RELA; k++;
|
||||
dynamic[k].d_tag = DT_JMPREL; dynamic[k].d_val = relaplt_va; k++;
|
||||
dynamic[k].d_tag = DT_BIND_NOW; dynamic[k].d_val = 0; k++;
|
||||
if (with_ver) {
|
||||
dynamic[k].d_tag = DT_VERSYM;
|
||||
dynamic[k].d_val = versym_va;
|
||||
k++;
|
||||
dynamic[k].d_tag = DT_VERNEED;
|
||||
dynamic[k].d_val = verneed_va;
|
||||
k++;
|
||||
dynamic[k].d_tag = DT_VERNEEDNUM;
|
||||
dynamic[k].d_val = (u64)n_vlibs;
|
||||
k++;
|
||||
}
|
||||
dynamic[k].d_tag = DT_NULL; dynamic[k].d_val = 0; k++;
|
||||
if ((u64)k != ndyn) {
|
||||
fprintf(stderr, "w6l: dynamic entry count mismatch\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Pass 5: patch .text relocations targeting dynamic syms ---
|
||||
* The site is the existing PC32/PLT32 displacement field. Target
|
||||
* is the address of the symbol's PLT stub. */
|
||||
for (Lrel *r = l->rels; r; r = r->next) {
|
||||
if (r->sym == NULL || !r->sym->is_dyn) continue;
|
||||
if (r->kind != R_X86_64_PC32 && r->kind != R_X86_64_PLT32) {
|
||||
fprintf(stderr, "w6l: dynamic reloc kind %d unsupported\n",
|
||||
r->kind);
|
||||
free(dynsyms); free(sos_used); free(soname_str);
|
||||
free(symname_str); free(dynstr); free(dynsym);
|
||||
free(hash); free(relaplt); free(plt); free(gotplt);
|
||||
free(dynamic);
|
||||
return 1;
|
||||
}
|
||||
u64 site = text_va + r->off;
|
||||
u64 stub = plt_va + (u64)r->sym->plt_idx * PLT_STUB_BYTES;
|
||||
i64 disp = (i64)stub - (i64)site + r->addend;
|
||||
poke32(l->text, r->off, (u32)(i32)disp);
|
||||
}
|
||||
|
||||
/* ---- Pass 6: emit ---- */
|
||||
|
||||
Ehdr eh = {0};
|
||||
memcpy(eh.e_ident, "\x7f""ELF", 4);
|
||||
eh.e_ident[4] = ELFCLASS64;
|
||||
eh.e_ident[5] = ELFDATA2LSB;
|
||||
eh.e_ident[6] = EV_CURRENT;
|
||||
eh.e_type = ET_EXEC;
|
||||
eh.e_machine = EM_X86_64;
|
||||
eh.e_version = EV_CURRENT;
|
||||
eh.e_entry = entry;
|
||||
eh.e_phoff = ehdr_sz;
|
||||
eh.e_ehsize = sizeof(Ehdr);
|
||||
eh.e_phentsize = sizeof(Phdr);
|
||||
eh.e_phnum = (u16)n_phdrs;
|
||||
|
||||
Phdr ph[4] = {0};
|
||||
/* PT_LOAD #1 — R+X covering everything from Ehdr through .plt. */
|
||||
ph[0].p_type = PT_LOAD;
|
||||
ph[0].p_flags = PF_R | PF_X;
|
||||
ph[0].p_offset = 0;
|
||||
ph[0].p_vaddr = base;
|
||||
ph[0].p_paddr = base;
|
||||
ph[0].p_filesz = rx_end;
|
||||
ph[0].p_memsz = rx_end;
|
||||
ph[0].p_align = page;
|
||||
|
||||
/* PT_LOAD #2 — R+W covering .got.plt and .dynamic. */
|
||||
ph[1].p_type = PT_LOAD;
|
||||
ph[1].p_flags = PF_R | PF_W;
|
||||
ph[1].p_offset = gotplt_off;
|
||||
ph[1].p_vaddr = gotplt_va;
|
||||
ph[1].p_paddr = gotplt_va;
|
||||
ph[1].p_filesz = file_end - gotplt_off;
|
||||
ph[1].p_memsz = file_end - gotplt_off;
|
||||
ph[1].p_align = page;
|
||||
|
||||
/* PT_INTERP. */
|
||||
ph[2].p_type = PT_INTERP;
|
||||
ph[2].p_flags = PF_R;
|
||||
ph[2].p_offset = interp_off;
|
||||
ph[2].p_vaddr = interp_va;
|
||||
ph[2].p_paddr = interp_va;
|
||||
ph[2].p_filesz = interp_sz;
|
||||
ph[2].p_memsz = interp_sz;
|
||||
ph[2].p_align = 1;
|
||||
|
||||
/* PT_DYNAMIC. */
|
||||
ph[3].p_type = PT_DYNAMIC;
|
||||
ph[3].p_flags = PF_R | PF_W;
|
||||
ph[3].p_offset = dynamic_off;
|
||||
ph[3].p_vaddr = dynamic_va;
|
||||
ph[3].p_paddr = dynamic_va;
|
||||
ph[3].p_filesz = dynamic_sz;
|
||||
ph[3].p_memsz = dynamic_sz;
|
||||
ph[3].p_align = 8;
|
||||
|
||||
fwrite(&eh, 1, sizeof eh, f);
|
||||
fwrite(ph, 1, sizeof ph, f);
|
||||
|
||||
/* helper: pad to absolute offset `to` */
|
||||
#define PAD_TO(to) do { \
|
||||
long _here = ftell(f); \
|
||||
for (long _i = _here; _i < (long)(to); _i++) fputc(0, f); \
|
||||
} while (0)
|
||||
|
||||
PAD_TO(interp_off); fwrite(INTERP, 1, interp_sz, f);
|
||||
PAD_TO(dynstr_off); fwrite(dynstr, 1, dynstr_sz, f);
|
||||
PAD_TO(dynsym_off); fwrite(dynsym, 1, dynsym_sz, f);
|
||||
PAD_TO(hash_off); fwrite(hash, 4, 2 + nbuckets + nchain, f);
|
||||
PAD_TO(versym_off); fwrite(versym, 2, (size_t)nsyms_total, f);
|
||||
if (verneed_sz > 0) {
|
||||
PAD_TO(verneed_off); fwrite(verneed, 1, verneed_sz, f);
|
||||
}
|
||||
PAD_TO(relaplt_off); fwrite(relaplt, 1, relaplt_sz, f);
|
||||
PAD_TO(text_off); fwrite(l->text, 1, l->textlen, f);
|
||||
PAD_TO(plt_off); fwrite(plt, 1, plt_sz, f);
|
||||
PAD_TO(gotplt_off); fwrite(gotplt, 1, gotplt_sz, f);
|
||||
PAD_TO(dynamic_off); fwrite(dynamic, 1, dynamic_sz, f);
|
||||
|
||||
for (int i = 0; i < n_vlibs; i++) free(vlibs[i].versions);
|
||||
free(vlibs); free(versym_for); free(versym);
|
||||
if (verneed) free(verneed);
|
||||
free(dynsyms); free(sos_used); free(soname_str);
|
||||
free(symname_str); free(dynstr); free(dynsym);
|
||||
free(hash); free(relaplt); free(plt); free(gotplt); free(dynamic);
|
||||
return 0;
|
||||
}
|
||||
104
cmd/w6l/l.h
Normal file
104
cmd/w6l/l.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* l.h — w6l-private header. Loads relocatable ELF64 .o files (the
|
||||
* format produced by w6a) and links them into a static executable.
|
||||
*
|
||||
* No archives yet (phase 8). No dynamic linking ever.
|
||||
*/
|
||||
#ifndef SIX_L_H
|
||||
#define SIX_L_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
typedef int64_t i64;
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef struct Lsym Lsym;
|
||||
typedef struct Lrel Lrel;
|
||||
typedef struct Lobj Lobj;
|
||||
typedef struct Lso Lso;
|
||||
typedef struct Lnk Lnk;
|
||||
|
||||
struct Lsym {
|
||||
const char *name;
|
||||
u64 val; /* offset within combined .text once linked */
|
||||
int defined; /* 1 if a Lobj defines this symbol */
|
||||
Lobj *owner;
|
||||
int idx_in_owner;
|
||||
/* Dynamic-linking fields. Set by l_resolve when an undefined sym
|
||||
* is provided by some loaded Lso. Patched-in PLT slot index lets
|
||||
* the relocator route PC32/PLT32 references through the stub. */
|
||||
int is_dyn;
|
||||
Lso *dyn_lib;
|
||||
const char *dyn_version; /* matched export's version, NULL if none */
|
||||
int plt_idx; /* 0..dyn_n-1, -1 if no PLT slot */
|
||||
int dynsym_idx; /* index in emitted .dynsym, -1 otherwise */
|
||||
Lsym *next;
|
||||
};
|
||||
|
||||
struct Lrel {
|
||||
u64 off; /* offset within combined .text */
|
||||
int kind; /* R_X86_64_* */
|
||||
Lsym *sym;
|
||||
i64 addend;
|
||||
Lrel *next;
|
||||
};
|
||||
|
||||
struct Lobj {
|
||||
const char *path;
|
||||
u8 *buf; /* mmapped or read-in object bytes */
|
||||
u64 len;
|
||||
u64 text_off; /* offset of .text in combined output */
|
||||
u64 text_size;
|
||||
Lobj *next;
|
||||
};
|
||||
|
||||
struct Lso {
|
||||
const char *path; /* full filesystem path used to load */
|
||||
const char *soname; /* DT_SONAME, or basename if missing */
|
||||
char **exports; /* NULL-terminated list of GLOBAL/WEAK syms */
|
||||
char **versions; /* parallel to exports[]; NULL for unversioned,
|
||||
* else strdup'd version name e.g. "GLIBC_2.2.5" */
|
||||
Lso *next;
|
||||
};
|
||||
|
||||
struct Lnk {
|
||||
Lobj *objs;
|
||||
Lso *sos;
|
||||
Lsym *syms;
|
||||
Lrel *rels;
|
||||
u8 *text; /* combined .text */
|
||||
u64 textcap, textlen;
|
||||
int errs;
|
||||
int dyn_n; /* number of symbols routed through PLT */
|
||||
};
|
||||
|
||||
/* obj.c */
|
||||
int l_load(Lnk*, const char *path);
|
||||
int l_read_all(const char *path, u8 **out, u64 *len); /* shared helper */
|
||||
/* dyn.c */
|
||||
int l_load_so(Lnk*, const char *path);
|
||||
int l_so_provides(Lso*, const char *name);
|
||||
/* l_so_provides_v: same, but also returns the export's version name
|
||||
* (NULL for unversioned globals) via *out_version on a hit. */
|
||||
int l_so_provides_v(Lso*, const char *name, const char **out_version);
|
||||
/* sym.c */
|
||||
Lsym *l_intern(Lnk*, const char *name);
|
||||
Lsym *l_lookup(Lnk*, const char *name);
|
||||
/* pass.c */
|
||||
int l_resolve(Lnk*);
|
||||
int l_relocate(Lnk*, u64 base);
|
||||
/* out.c */
|
||||
int l_emit_elf(Lnk*, FILE *out, u64 base, u64 entry);
|
||||
/* dynout.c — emit a dynamic-linked ELF executable. Called by
|
||||
* l_emit_elf when l->sos is non-empty. */
|
||||
int l_emit_dyn_elf(Lnk*, FILE *out, u64 base, u64 entry);
|
||||
|
||||
#endif
|
||||
142
cmd/w6l/main.c
Normal file
142
cmd/w6l/main.c
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* w6l — amd64 linker. Reads relocatable ELF .o files (from w6a) plus
|
||||
* .a archives, resolves, relocates, writes a static ELF executable.
|
||||
* Dynamic linking against .so files is the next increment; the -L/-l
|
||||
* flag plumbing here is its first step.
|
||||
*
|
||||
* w6l -o out [-L<dir>...] [-l<name>...] file1.o file2.o ...
|
||||
*
|
||||
* The first symbol named "_start" defined among the inputs becomes
|
||||
* the entry point. If none is found, fall back to "main".
|
||||
*/
|
||||
#include "l.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* `path` is acceptable iff it's either an archive ("!<arch>\n") or
|
||||
* an ELF file ("\x7fELF"). Distros often ship lib<name>.so as a GNU
|
||||
* ld linker script (plain text); we skip those rather than parse the
|
||||
* GROUP/INPUT directives.
|
||||
*/
|
||||
static int
|
||||
is_linkable(const char *path)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (f == NULL) return 0;
|
||||
u8 magic[8] = {0};
|
||||
size_t n = fread(magic, 1, 8, f);
|
||||
fclose(f);
|
||||
if (n >= 8 && memcmp(magic, "!<arch>\n", 8) == 0) return 1;
|
||||
if (n >= 4 && memcmp(magic, "\x7f""ELF", 4) == 0) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Resolve -l<name> to a filesystem path by walking the libdirs we
|
||||
* collected. Order: lib<name>.so → lib<name>.so.<N> globs → lib<name>.a.
|
||||
* Skip anything that isn't a real archive or ELF (e.g. ld scripts).
|
||||
*/
|
||||
static const char *
|
||||
resolve_lib(const char *name, char **libdirs, int n_libdirs)
|
||||
{
|
||||
static char buf[1024];
|
||||
for (int i = 0; i < n_libdirs; i++) {
|
||||
snprintf(buf, sizeof buf, "%s/lib%s.so", libdirs[i], name);
|
||||
if (access(buf, 0) == 0 && is_linkable(buf)) return strdup(buf);
|
||||
for (int v = 0; v <= 8; v++) {
|
||||
snprintf(buf, sizeof buf, "%s/lib%s.so.%d",
|
||||
libdirs[i], name, v);
|
||||
if (access(buf, 0) == 0 && is_linkable(buf))
|
||||
return strdup(buf);
|
||||
}
|
||||
snprintf(buf, sizeof buf, "%s/lib%s.a", libdirs[i], name);
|
||||
if (access(buf, 0) == 0 && is_linkable(buf)) return strdup(buf);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
const char *out = NULL;
|
||||
const char **inputs = calloc(argc, sizeof *inputs);
|
||||
int ninputs = 0;
|
||||
char **libdirs = calloc(argc, sizeof *libdirs);
|
||||
int n_libdirs = 0;
|
||||
const char **lflags = calloc(argc, sizeof *lflags);
|
||||
int n_lflags = 0;
|
||||
u64 base = 0x400000;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) {
|
||||
out = argv[++i];
|
||||
} else if (strncmp(argv[i], "-L", 2) == 0 && argv[i][2]) {
|
||||
libdirs[n_libdirs++] = strdup(argv[i] + 2);
|
||||
} else if (strcmp(argv[i], "-L") == 0 && i + 1 < argc) {
|
||||
libdirs[n_libdirs++] = strdup(argv[++i]);
|
||||
} else if (strncmp(argv[i], "-l", 2) == 0 && argv[i][2]) {
|
||||
lflags[n_lflags++] = argv[i] + 2;
|
||||
} else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
|
||||
lflags[n_lflags++] = argv[++i];
|
||||
} else if (argv[i][0] == '-') {
|
||||
fprintf(stderr, "w6l: unknown flag %s\n", argv[i]);
|
||||
return 2;
|
||||
} else {
|
||||
inputs[ninputs++] = argv[i];
|
||||
}
|
||||
}
|
||||
if (out == NULL || ninputs == 0) {
|
||||
fputs("usage: w6l -o exe [-L<dir>...] [-l<name>...] "
|
||||
"file1.o [file2.o...]\n", stderr);
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Append -l-resolved files to the input list, after the .o/.a the
|
||||
* caller passed positionally. They obey the same archive-pull
|
||||
* semantics as a positional .a. */
|
||||
for (int i = 0; i < n_lflags; i++) {
|
||||
const char *p = resolve_lib(lflags[i], libdirs, n_libdirs);
|
||||
if (p == NULL) {
|
||||
fprintf(stderr, "w6l: cannot find -l%s\n", lflags[i]);
|
||||
return 1;
|
||||
}
|
||||
inputs[ninputs++] = p;
|
||||
}
|
||||
|
||||
Lnk l = {0};
|
||||
/* Seed the symbol table with the entry point so archive pulls
|
||||
* include the .o that defines it. Without this, a libwwrt.a
|
||||
* containing start.o is silently skipped if no user .o
|
||||
* references _start, and the entry falls back to main — which
|
||||
* has no proper exit path. */
|
||||
(void)l_intern(&l, "_start");
|
||||
for (int i = 0; i < ninputs; i++) {
|
||||
if (l_load(&l, inputs[i]) != 0) return 1;
|
||||
}
|
||||
if (l_resolve(&l) != 0) return 1;
|
||||
if (l_relocate(&l, base + 0x1000) != 0) return 1;
|
||||
|
||||
Lsym *entry = l_lookup(&l, "_start");
|
||||
if (entry == NULL || !entry->defined) entry = l_lookup(&l, "main");
|
||||
if (entry == NULL || !entry->defined) {
|
||||
fprintf(stderr, "w6l: no _start or main symbol defined\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
FILE *f = fopen(out, "wb");
|
||||
if (f == NULL) {
|
||||
fprintf(stderr, "w6l: cannot open %s\n", out);
|
||||
return 1;
|
||||
}
|
||||
int rc = l_emit_elf(&l, f, base, base + 0x1000 + entry->val);
|
||||
fclose(f);
|
||||
if (rc == 0) {
|
||||
/* chmod +x */
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof cmd, "chmod +x %s", out);
|
||||
(void)system(cmd);
|
||||
}
|
||||
free(inputs);
|
||||
return rc;
|
||||
}
|
||||
336
cmd/w6l/obj.c
Normal file
336
cmd/w6l/obj.c
Normal file
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* obj.c — load an ELF64 relocatable object emitted by w6a, append its
|
||||
* .text bytes to the combined image, and pull its symbols and
|
||||
* relocations into the global tables (with offsets adjusted to the
|
||||
* combined section).
|
||||
*/
|
||||
#include "l.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define ET_REL 1
|
||||
#define EM_X86_64 62
|
||||
|
||||
#define SHT_PROGBITS 1
|
||||
#define SHT_SYMTAB 2
|
||||
#define SHT_STRTAB 3
|
||||
#define SHT_RELA 4
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
u8 e_ident[16];
|
||||
u16 e_type, e_machine;
|
||||
u32 e_version;
|
||||
u64 e_entry, e_phoff, e_shoff;
|
||||
u32 e_flags;
|
||||
u16 e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx;
|
||||
} Ehdr;
|
||||
|
||||
typedef struct {
|
||||
u32 sh_name, sh_type;
|
||||
u64 sh_flags, sh_addr, sh_offset, sh_size;
|
||||
u32 sh_link, sh_info;
|
||||
u64 sh_addralign, sh_entsize;
|
||||
} Shdr;
|
||||
|
||||
typedef struct {
|
||||
u32 st_name;
|
||||
u8 st_info, st_other;
|
||||
u16 st_shndx;
|
||||
u64 st_value, st_size;
|
||||
} Sym64;
|
||||
|
||||
typedef struct {
|
||||
u64 r_offset;
|
||||
u64 r_info;
|
||||
i64 r_addend;
|
||||
} Rela64;
|
||||
#pragma pack(pop)
|
||||
|
||||
#define ELF64_R_SYM(i) ((u32)((i) >> 32))
|
||||
#define ELF64_R_TYPE(i) ((u32)((i) & 0xffffffff))
|
||||
#define ELF64_ST_TYPE(i) ((i) & 0xf)
|
||||
#define ELF64_ST_BIND(i) ((i) >> 4)
|
||||
|
||||
int
|
||||
l_read_all(const char *path, u8 **out, u64 *len)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (f == NULL) return -1;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long n = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if (n < 0) { fclose(f); return -1; }
|
||||
u8 *b = malloc((size_t)n);
|
||||
if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; }
|
||||
fclose(f);
|
||||
*out = b;
|
||||
*len = (u64)n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define read_all l_read_all
|
||||
|
||||
static void
|
||||
emit_text(Lnk *l, const u8 *src, u64 n)
|
||||
{
|
||||
if (l->textlen + n > l->textcap) {
|
||||
u64 nc = l->textcap ? l->textcap * 2 : 4096;
|
||||
while (nc < l->textlen + n) nc *= 2;
|
||||
l->text = realloc(l->text, nc);
|
||||
l->textcap = nc;
|
||||
}
|
||||
memcpy(l->text + l->textlen, src, n);
|
||||
l->textlen += n;
|
||||
}
|
||||
|
||||
/* Internal: load a single ELF .o image already in memory. The caller
|
||||
* gives us the bytes (we own them) and a path tag for diagnostics.
|
||||
* If the bytes look like an archive (magic "!<arch>\n") we recurse
|
||||
* over each member instead.
|
||||
*/
|
||||
static int load_image(Lnk *l, const char *path, u8 *buf, u64 len);
|
||||
|
||||
static u64
|
||||
ar_field(const u8 *p, int n)
|
||||
{
|
||||
/* decimal field, space-padded */
|
||||
u64 v = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (p[i] >= '0' && p[i] <= '9') v = v * 10 + (p[i] - '0');
|
||||
else if (p[i] == ' ') break;
|
||||
else if (p[i] == 0) break;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/* Read an ELF .o image's globally-defined symbol names without
|
||||
* actually appending it to the link. Returns a heap-allocated
|
||||
* NULL-terminated array; caller frees the array (not the strings,
|
||||
* which point into the .o image and must remain alive).
|
||||
*/
|
||||
static char **
|
||||
elf_globals(const u8 *buf, u64 len)
|
||||
{
|
||||
if (len < sizeof(Ehdr)) return NULL;
|
||||
Ehdr *eh = (Ehdr *)buf;
|
||||
if (memcmp(eh->e_ident, "\x7f""ELF", 4) != 0) return NULL;
|
||||
Shdr *sh = (Shdr *)(buf + eh->e_shoff);
|
||||
int idx_text = -1, idx_symtab = -1;
|
||||
const char *shstr = (const char *)(buf + sh[eh->e_shstrndx].sh_offset);
|
||||
for (u16 i = 0; i < eh->e_shnum; i++) {
|
||||
if (sh[i].sh_type == SHT_PROGBITS &&
|
||||
strcmp(shstr + sh[i].sh_name, ".text") == 0)
|
||||
idx_text = i;
|
||||
else if (sh[i].sh_type == SHT_SYMTAB)
|
||||
idx_symtab = i;
|
||||
}
|
||||
if (idx_text < 0 || idx_symtab < 0) return NULL;
|
||||
int idx_strtab = sh[idx_symtab].sh_link;
|
||||
Sym64 *symtab = (Sym64 *)(buf + sh[idx_symtab].sh_offset);
|
||||
u64 nsyms = sh[idx_symtab].sh_size / sizeof(Sym64);
|
||||
const char *str = (const char *)(buf + sh[idx_strtab].sh_offset);
|
||||
|
||||
char **out = calloc(nsyms + 1, sizeof *out);
|
||||
int n = 0;
|
||||
for (u64 i = 1; i < nsyms; i++) {
|
||||
if (symtab[i].st_shndx == 0) continue;
|
||||
if ((symtab[i].st_info >> 4) != 1) continue; /* STB_GLOBAL */
|
||||
if ((int)symtab[i].st_shndx != idx_text) continue;
|
||||
out[n++] = strdup(str + symtab[i].st_name);
|
||||
}
|
||||
out[n] = NULL;
|
||||
return out;
|
||||
}
|
||||
|
||||
typedef struct ArMember ArMember;
|
||||
struct ArMember {
|
||||
u8 *data; /* heap copy; freed if never loaded */
|
||||
u64 size;
|
||||
char **defs; /* NULL-terminated list of defined globals */
|
||||
int loaded;
|
||||
ArMember *next;
|
||||
};
|
||||
|
||||
static int
|
||||
member_defines_undef(Lnk *l, ArMember *m)
|
||||
{
|
||||
if (m->defs == NULL) return 0;
|
||||
for (int i = 0; m->defs[i]; i++) {
|
||||
Lsym *s = l_lookup(l, m->defs[i]);
|
||||
if (s != NULL && !s->defined) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
load_archive(Lnk *l, const char *path, u8 *buf, u64 len)
|
||||
{
|
||||
/* Pass 1: index members. We copy each member's bytes (cheap; few
|
||||
* tens of KB per stdlib module) so the archive buffer can be
|
||||
* freed once we're done indexing. */
|
||||
ArMember *head = NULL, *tail = NULL;
|
||||
u64 pos = 8; /* past "!<arch>\n" */
|
||||
while (pos + 60 <= len) {
|
||||
const u8 *hdr = buf + pos;
|
||||
u64 size = ar_field(hdr + 48, 10);
|
||||
u64 hdr_end = pos + 60;
|
||||
if (hdr_end + size > len) break;
|
||||
if (hdr[0] != '/' && hdr[0] != 0 && hdr[0] != ' ') {
|
||||
ArMember *m = calloc(1, sizeof *m);
|
||||
m->size = size;
|
||||
m->data = malloc((size_t)size);
|
||||
memcpy(m->data, buf + hdr_end, (size_t)size);
|
||||
m->defs = elf_globals(m->data, size);
|
||||
if (head == NULL) head = m;
|
||||
else tail->next = m;
|
||||
tail = m;
|
||||
}
|
||||
pos = hdr_end + size;
|
||||
if (size & 1) pos++;
|
||||
}
|
||||
free(buf);
|
||||
|
||||
/* Pass 2: iteratively pull members that define a currently-
|
||||
* undefined symbol. Each pull may introduce new undefs, so loop. */
|
||||
int changed = 1;
|
||||
while (changed) {
|
||||
changed = 0;
|
||||
for (ArMember *m = head; m; m = m->next) {
|
||||
if (m->loaded) continue;
|
||||
if (!member_defines_undef(l, m)) continue;
|
||||
u8 *copy = malloc((size_t)m->size);
|
||||
memcpy(copy, m->data, m->size);
|
||||
if (load_image(l, path, copy, m->size) == 0) {
|
||||
m->loaded = 1;
|
||||
changed = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Free unloaded members; loaded ones had their bytes consumed
|
||||
* by load_image (which took the copy). */
|
||||
while (head) {
|
||||
ArMember *next = head->next;
|
||||
free(head->data);
|
||||
if (head->defs) {
|
||||
for (int i = 0; head->defs[i]; i++) free(head->defs[i]);
|
||||
free(head->defs);
|
||||
}
|
||||
free(head);
|
||||
head = next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
l_load(Lnk *l, const char *path)
|
||||
{
|
||||
u8 *buf;
|
||||
u64 len;
|
||||
if (read_all(path, &buf, &len) < 0) return -1;
|
||||
if (len >= 8 && memcmp(buf, "!<arch>\n", 8) == 0)
|
||||
return load_archive(l, path, buf, len);
|
||||
/* Shared object: dispatch to dyn.c, which re-reads (small loss
|
||||
* for a much cleaner separation of static vs dynamic loaders). */
|
||||
if (len >= sizeof(Ehdr)) {
|
||||
Ehdr *eh = (Ehdr *)buf;
|
||||
if (memcmp(eh->e_ident, "\x7f""ELF", 4) == 0
|
||||
&& eh->e_type == 3 /* ET_DYN */) {
|
||||
free(buf);
|
||||
return l_load_so(l, path);
|
||||
}
|
||||
}
|
||||
return load_image(l, path, buf, len);
|
||||
}
|
||||
|
||||
static int
|
||||
load_image(Lnk *l, const char *path, u8 *buf, u64 len)
|
||||
{
|
||||
if (len < sizeof(Ehdr)) { free(buf); return -1; }
|
||||
Ehdr *eh = (Ehdr *)buf;
|
||||
if (memcmp(eh->e_ident, "\x7f""ELF", 4) != 0 || eh->e_ident[4] != 2
|
||||
|| eh->e_machine != EM_X86_64 || eh->e_type != ET_REL) {
|
||||
fprintf(stderr, "w6l: %s: not an amd64 ELF64 relocatable\n", path);
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
Shdr *sh = (Shdr *)(buf + eh->e_shoff);
|
||||
if (eh->e_shstrndx >= eh->e_shnum) { free(buf); return -1; }
|
||||
const char *shstr = (const char *)(buf + sh[eh->e_shstrndx].sh_offset);
|
||||
|
||||
/* find .text, .symtab, .strtab, .rela.text */
|
||||
int idx_text = -1, idx_symtab = -1, idx_strtab = -1, idx_rela = -1;
|
||||
for (u16 i = 0; i < eh->e_shnum; i++) {
|
||||
const char *nm = shstr + sh[i].sh_name;
|
||||
if (sh[i].sh_type == SHT_PROGBITS && strcmp(nm, ".text") == 0)
|
||||
idx_text = i;
|
||||
else if (sh[i].sh_type == SHT_SYMTAB)
|
||||
idx_symtab = i;
|
||||
else if (sh[i].sh_type == SHT_RELA && strcmp(nm, ".rela.text") == 0)
|
||||
idx_rela = i;
|
||||
}
|
||||
if (idx_text < 0 || idx_symtab < 0) {
|
||||
fprintf(stderr, "w6l: %s: missing .text or .symtab\n", path);
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
idx_strtab = sh[idx_symtab].sh_link;
|
||||
|
||||
Lobj *ob = calloc(1, sizeof *ob);
|
||||
ob->path = strdup(path);
|
||||
ob->buf = buf;
|
||||
ob->len = len;
|
||||
ob->text_off = l->textlen;
|
||||
ob->text_size = sh[idx_text].sh_size;
|
||||
ob->next = l->objs;
|
||||
l->objs = ob;
|
||||
|
||||
/* append .text */
|
||||
emit_text(l, buf + sh[idx_text].sh_offset, sh[idx_text].sh_size);
|
||||
|
||||
/* per-object: load symbols */
|
||||
Sym64 *symtab = (Sym64 *)(buf + sh[idx_symtab].sh_offset);
|
||||
u64 nsyms = sh[idx_symtab].sh_size / sizeof(Sym64);
|
||||
const char *str = (const char *)(buf + sh[idx_strtab].sh_offset);
|
||||
|
||||
/* map per-object sym index → global Lsym */
|
||||
Lsym **map = calloc(nsyms, sizeof *map);
|
||||
for (u64 i = 1; i < nsyms; i++) {
|
||||
const char *nm = str + symtab[i].st_name;
|
||||
if (nm[0] == '\0') continue;
|
||||
Lsym *gs = l_intern(l, nm);
|
||||
if (symtab[i].st_shndx != 0 /* SHN_UNDEF */
|
||||
&& symtab[i].st_shndx == idx_text) {
|
||||
if (gs->defined) {
|
||||
fprintf(stderr, "w6l: %s: duplicate symbol %s\n",
|
||||
path, nm);
|
||||
l->errs++;
|
||||
} else {
|
||||
gs->defined = 1;
|
||||
gs->owner = ob;
|
||||
gs->idx_in_owner = (int)i;
|
||||
gs->val = ob->text_off + symtab[i].st_value;
|
||||
}
|
||||
}
|
||||
map[i] = gs;
|
||||
}
|
||||
|
||||
/* per-object: collect relocations */
|
||||
if (idx_rela >= 0) {
|
||||
Rela64 *rt = (Rela64 *)(buf + sh[idx_rela].sh_offset);
|
||||
u64 nrel = sh[idx_rela].sh_size / sizeof(Rela64);
|
||||
for (u64 i = 0; i < nrel; i++) {
|
||||
Lrel *r = calloc(1, sizeof *r);
|
||||
r->off = ob->text_off + rt[i].r_offset;
|
||||
r->kind = (int)ELF64_R_TYPE(rt[i].r_info);
|
||||
u32 sidx = ELF64_R_SYM(rt[i].r_info);
|
||||
r->sym = (sidx < nsyms) ? map[sidx] : NULL;
|
||||
r->addend = rt[i].r_addend;
|
||||
r->next = l->rels;
|
||||
l->rels = r;
|
||||
}
|
||||
}
|
||||
free(map);
|
||||
return 0;
|
||||
}
|
||||
94
cmd/w6l/out.c
Normal file
94
cmd/w6l/out.c
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* out.c — emit a static ELF64 executable.
|
||||
*
|
||||
* Layout (file order):
|
||||
* [0..64) ELF header
|
||||
* [64..120) program header (one PT_LOAD)
|
||||
* [120..0x1000) zero pad
|
||||
* [0x1000..) .text bytes
|
||||
*
|
||||
* The single PT_LOAD covers the whole file, R+X. No interpreter,
|
||||
* no dynamic, no .bss yet. Entry point is the address of the
|
||||
* symbol named "_start" (or whatever main supplies via -e).
|
||||
*/
|
||||
#include "l.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define ET_EXEC 2
|
||||
#define EM_X86_64 62
|
||||
#define EV_CURRENT 1
|
||||
#define ELFCLASS64 2
|
||||
#define ELFDATA2LSB 1
|
||||
|
||||
#define PT_LOAD 1
|
||||
#define PF_X 1
|
||||
#define PF_W 2
|
||||
#define PF_R 4
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
u8 e_ident[16];
|
||||
u16 e_type, e_machine;
|
||||
u32 e_version;
|
||||
u64 e_entry, e_phoff, e_shoff;
|
||||
u32 e_flags;
|
||||
u16 e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx;
|
||||
} Ehdr;
|
||||
|
||||
typedef struct {
|
||||
u32 p_type, p_flags;
|
||||
u64 p_offset, p_vaddr, p_paddr;
|
||||
u64 p_filesz, p_memsz, p_align;
|
||||
} Phdr;
|
||||
#pragma pack(pop)
|
||||
|
||||
int
|
||||
l_emit_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
{
|
||||
/* Dispatch: any loaded shared object plus any dynamic ref means
|
||||
* we owe the loader a real PT_INTERP/PT_DYNAMIC binary. */
|
||||
if (l->sos != NULL && l->dyn_n > 0)
|
||||
return l_emit_dyn_elf(l, f, base, entry);
|
||||
|
||||
const u64 text_off = 0x1000;
|
||||
const u64 text_va = base + text_off;
|
||||
const u64 filesz = text_off + l->textlen;
|
||||
|
||||
Ehdr eh = {0};
|
||||
memcpy(eh.e_ident, "\x7f""ELF", 4);
|
||||
eh.e_ident[4] = ELFCLASS64;
|
||||
eh.e_ident[5] = ELFDATA2LSB;
|
||||
eh.e_ident[6] = EV_CURRENT;
|
||||
eh.e_type = ET_EXEC;
|
||||
eh.e_machine = EM_X86_64;
|
||||
eh.e_version = EV_CURRENT;
|
||||
eh.e_entry = entry;
|
||||
eh.e_phoff = sizeof(Ehdr);
|
||||
eh.e_ehsize = sizeof(Ehdr);
|
||||
eh.e_phentsize = sizeof(Phdr);
|
||||
eh.e_phnum = 1;
|
||||
(void)text_va;
|
||||
|
||||
Phdr ph = {0};
|
||||
ph.p_type = PT_LOAD;
|
||||
ph.p_flags = PF_R | PF_X;
|
||||
ph.p_offset = 0;
|
||||
ph.p_vaddr = base;
|
||||
ph.p_paddr = base;
|
||||
ph.p_filesz = filesz;
|
||||
ph.p_memsz = filesz;
|
||||
ph.p_align = 0x1000;
|
||||
|
||||
fwrite(&eh, 1, sizeof eh, f);
|
||||
fwrite(&ph, 1, sizeof ph, f);
|
||||
|
||||
/* pad to text_off */
|
||||
long here = ftell(f);
|
||||
for (long i = here; i < (long)text_off; i++) fputc(0, f);
|
||||
|
||||
if (l->textlen) fwrite(l->text, 1, l->textlen, f);
|
||||
|
||||
return 0;
|
||||
}
|
||||
95
cmd/w6l/pass.c
Normal file
95
cmd/w6l/pass.c
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* pass.c — resolution + relocation. After all objects are loaded:
|
||||
*
|
||||
* l_resolve : check that every symbol referenced by a relocation
|
||||
* is defined somewhere. Errors get logged.
|
||||
* l_relocate: with the final virtual base address known, walk the
|
||||
* relocation list and patch the .text bytes in place.
|
||||
*
|
||||
* Supported relocation kinds: PC32 (2), PLT32 (4). Both are PC-relative
|
||||
* 32-bit displacements; for static linking PLT32 collapses to PC32.
|
||||
*/
|
||||
#include "l.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define R_X86_64_PC32 2
|
||||
#define R_X86_64_PLT32 4
|
||||
|
||||
int
|
||||
l_resolve(Lnk *l)
|
||||
{
|
||||
/* Initialise dynamic-linking fields. plt_idx and dynsym_idx
|
||||
* default to -1; l_intern sets is_dyn/dyn_lib to 0/NULL via
|
||||
* calloc, but we make the invariants explicit here for clarity. */
|
||||
for (Lsym *s = l->syms; s; s = s->next) {
|
||||
s->plt_idx = -1;
|
||||
s->dynsym_idx = -1;
|
||||
}
|
||||
|
||||
/* Promote each undefined sym that some Lso exports to "dynamic"
|
||||
* and hand it a PLT slot. Order is the iteration order over the
|
||||
* relocation list; that determines slot numbering and is stable
|
||||
* across runs (rels are pushed onto the head as objects load). */
|
||||
for (Lrel *r = l->rels; r; r = r->next) {
|
||||
if (r->sym == NULL || r->sym->defined) continue;
|
||||
if (r->sym->is_dyn) continue; /* already promoted */
|
||||
for (Lso *so = l->sos; so; so = so->next) {
|
||||
const char *ver = NULL;
|
||||
if (l_so_provides_v(so, r->sym->name, &ver)) {
|
||||
r->sym->is_dyn = 1;
|
||||
r->sym->dyn_lib = so;
|
||||
r->sym->dyn_version = ver; /* may be NULL */
|
||||
r->sym->plt_idx = l->dyn_n++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* What remains undefined truly is undefined. */
|
||||
for (Lrel *r = l->rels; r; r = r->next) {
|
||||
if (r->sym == NULL) continue;
|
||||
if (!r->sym->defined && !r->sym->is_dyn) {
|
||||
fprintf(stderr, "w6l: undefined reference to '%s'\n",
|
||||
r->sym->name);
|
||||
l->errs++;
|
||||
}
|
||||
}
|
||||
return l->errs;
|
||||
}
|
||||
|
||||
static void
|
||||
patch_u32(u8 *p, u32 v)
|
||||
{
|
||||
p[0] = (u8)(v & 0xff);
|
||||
p[1] = (u8)((v >> 8) & 0xff);
|
||||
p[2] = (u8)((v >> 16) & 0xff);
|
||||
p[3] = (u8)((v >> 24) & 0xff);
|
||||
}
|
||||
|
||||
int
|
||||
l_relocate(Lnk *l, u64 base)
|
||||
{
|
||||
for (Lrel *r = l->rels; r; r = r->next) {
|
||||
if (r->sym == NULL) continue;
|
||||
/* Dynamic syms are patched later, in l_emit_elf, once the
|
||||
* PLT's virtual address is known. */
|
||||
if (r->sym->is_dyn) continue;
|
||||
if (!r->sym->defined) continue;
|
||||
switch (r->kind) {
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_PLT32: {
|
||||
u64 site = base + r->off;
|
||||
i64 target = (i64)(base + r->sym->val);
|
||||
i64 rel = target - (i64)site + r->addend;
|
||||
patch_u32(l->text + r->off, (u32)(i32)rel);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
fprintf(stderr, "w6l: unsupported reloc kind %d\n",
|
||||
r->kind);
|
||||
l->errs++;
|
||||
}
|
||||
}
|
||||
return l->errs;
|
||||
}
|
||||
27
cmd/w6l/sym.c
Normal file
27
cmd/w6l/sym.c
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* sym.c — global symbol table for the linker. Plain singly-linked
|
||||
* list; usually a few hundred entries, hashing isn't worth it yet.
|
||||
*/
|
||||
#include "l.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
Lsym *
|
||||
l_intern(Lnk *l, const char *name)
|
||||
{
|
||||
for (Lsym *s = l->syms; s; s = s->next)
|
||||
if (strcmp(s->name, name) == 0) return s;
|
||||
Lsym *s = calloc(1, sizeof *s);
|
||||
s->name = strdup(name);
|
||||
s->next = l->syms;
|
||||
l->syms = s;
|
||||
return s;
|
||||
}
|
||||
|
||||
Lsym *
|
||||
l_lookup(Lnk *l, const char *name)
|
||||
{
|
||||
for (Lsym *s = l->syms; s; s = s->next)
|
||||
if (strcmp(s->name, name) == 0) return s;
|
||||
return NULL;
|
||||
}
|
||||
Reference in New Issue
Block a user