6l: dynamic linking with symbol versioning
Teach the linker to consume ET_DYN shared objects and emit a dynamically-linked ELF executable. Snake et al. can now link against libncurses + libc through the system dynamic loader. Pipeline additions: - dyn.c: read ET_DYN, parse .dynsym + DT_SONAME, walk .gnu.version_d / .gnu.version to learn each export's default version (skip hidden entries). - pass.c: when an undefined sym is provided by some Lso, promote it to dynamic, assign a PLT slot, record the matched version on the Lsym. - dynout.c: emit PT_INTERP + PT_DYNAMIC, .dynsym/.dynstr/.hash, .plt + .got.plt + .rela.plt, .gnu.version + .gnu.version_r, and the full DT_* set with DT_BIND_NOW. Patch PC32/PLT32 references against dyn syms to point at their PLT stubs. - main.c: -L<dir> and -l<name> flag parsing; resolve <name> via .so / .so.<N> / .a in libdir order, skipping GNU ld linker scripts (libc.so on most distros). - ww driver: collect -l/-L (joined and split forms) and pass through to 6l. Design choices: - DT_BIND_NOW so the loader resolves all PLT slots at startup; no PLT0 lazy resolver stub. - SysV .hash, not .gnu.hash. One bucket; loader scans the chain. Slow at scale, fine for snake-class binaries. - Non-PIE at fixed 0x400000. - No section headers — loader uses program headers, but readelf -V/-S won't display anything. Symbol versioning is the only correctness item beyond the basic PLT/GOT machinery: glibc symbols default to versions later than GLIBC_2.2.5 (e.g. clock_gettime → GLIBC_2.17 for the vDSO impl), and the loader rejects unversioned references to those without a matching Vernaux entry. test/wwc/810_dyn covers four cases: bare libc dyn call, multi-PLT, clock_gettime versioning, and fn-pointer to FFI binding (which exercises the codegen fixes from the parent commit alongside the new linker path).
This commit is contained in:
11
Makefile
11
Makefile
@@ -34,7 +34,8 @@ C6_OBJ = $(C6_SRC:cmd/6c/%.c=$(OBJ)/6c/%.o)
|
|||||||
A6_SRC = cmd/6a/main.c cmd/6a/lex.c cmd/6a/parse.c cmd/6a/asm.c cmd/6a/obj.c
|
A6_SRC = cmd/6a/main.c cmd/6a/lex.c cmd/6a/parse.c cmd/6a/asm.c cmd/6a/obj.c
|
||||||
A6_OBJ = $(A6_SRC:cmd/6a/%.c=$(OBJ)/6a/%.o)
|
A6_OBJ = $(A6_SRC:cmd/6a/%.c=$(OBJ)/6a/%.o)
|
||||||
|
|
||||||
L6_SRC = cmd/6l/main.c cmd/6l/obj.c cmd/6l/sym.c cmd/6l/pass.c cmd/6l/out.c
|
L6_SRC = cmd/6l/main.c cmd/6l/obj.c cmd/6l/sym.c cmd/6l/pass.c cmd/6l/out.c \
|
||||||
|
cmd/6l/dyn.c cmd/6l/dynout.c
|
||||||
L6_OBJ = $(L6_SRC:cmd/6l/%.c=$(OBJ)/6l/%.o)
|
L6_OBJ = $(L6_SRC:cmd/6l/%.c=$(OBJ)/6l/%.o)
|
||||||
|
|
||||||
RT_S = rt/start.s rt/syscall.s rt/alloc.s rt/streq.s rt/abort.s
|
RT_S = rt/start.s rt/syscall.s rt/alloc.s rt/streq.s rt/abort.s
|
||||||
@@ -170,8 +171,8 @@ $(BIN) $(LIB) $(OBJ)/wwc $(OBJ)/ww $(OBJ)/wwdump $(OBJ)/6c $(OBJ)/6a $(OBJ)/6l $
|
|||||||
# Each phase adds a $(BIN)/test_<name> target; the runner walks them.
|
# Each phase adds a $(BIN)/test_<name> target; the runner walks them.
|
||||||
TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||||
$(BIN)/test_6c $(BIN)/test_6a $(BIN)/test_6l $(BIN)/test_arch \
|
$(BIN)/test_6c $(BIN)/test_6a $(BIN)/test_6l $(BIN)/test_arch \
|
||||||
$(BIN)/test_e2e $(BIN)/test_ffi $(BIN)/test_stdlib $(BIN)/test_selfhost \
|
$(BIN)/test_e2e $(BIN)/test_ffi $(BIN)/test_dyn $(BIN)/test_stdlib \
|
||||||
$(BIN)/test_6a_ww $(BIN)/test_6l_ww $(BIN)/test_ww_ww
|
$(BIN)/test_selfhost $(BIN)/test_6a_ww $(BIN)/test_6l_ww $(BIN)/test_ww_ww
|
||||||
|
|
||||||
$(BIN)/test_smoke: test/wwc/000_smoke.c $(LIB)/libwwc.a | $(BIN)
|
$(BIN)/test_smoke: test/wwc/000_smoke.c $(LIB)/libwwc.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) $(INCS) -o $@ $< -L$(LIB) -lwwc
|
$(CC) $(CFLAGS) $(INCS) -o $@ $< -L$(LIB) -lwwc
|
||||||
@@ -205,6 +206,10 @@ $(BIN)/test_e2e: test/wwc/700_e2e.c $(BIN)/ww $(BIN)/6c $(BIN)/6a $(BIN)/6l \
|
|||||||
$(BIN)/test_ffi: test/wwc/800_ffi.c $(BIN)/6c | $(BIN)
|
$(BIN)/test_ffi: test/wwc/800_ffi.c $(BIN)/6c | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
$(BIN)/test_dyn: test/wwc/810_dyn.c $(BIN)/ww $(BIN)/6c $(BIN)/6a $(BIN)/6l \
|
||||||
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
$(BIN)/test_stdlib: test/wwc/900_stdlib.c $(BIN)/6c | $(BIN)
|
$(BIN)/test_stdlib: test/wwc/900_stdlib.c $(BIN)/6c | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
|||||||
262
cmd/6l/dyn.c
Normal file
262
cmd/6l/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, "6l: %s: cannot read\n", path);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (len < sizeof(Ehdr)) {
|
||||||
|
fprintf(stderr, "6l: %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, "6l: %s: not an amd64 ET_DYN\n", path);
|
||||||
|
free(buf);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (eh->e_shoff == 0 || eh->e_shnum == 0) {
|
||||||
|
fprintf(stderr, "6l: %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, "6l: %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/6l/dynout.c
Normal file
644
cmd/6l/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, "6l: 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, "6l: 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, "6l: 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, "6l: 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;
|
||||||
|
}
|
||||||
30
cmd/6l/l.h
30
cmd/6l/l.h
@@ -23,6 +23,7 @@ typedef uint64_t u64;
|
|||||||
typedef struct Lsym Lsym;
|
typedef struct Lsym Lsym;
|
||||||
typedef struct Lrel Lrel;
|
typedef struct Lrel Lrel;
|
||||||
typedef struct Lobj Lobj;
|
typedef struct Lobj Lobj;
|
||||||
|
typedef struct Lso Lso;
|
||||||
typedef struct Lnk Lnk;
|
typedef struct Lnk Lnk;
|
||||||
|
|
||||||
struct Lsym {
|
struct Lsym {
|
||||||
@@ -31,6 +32,14 @@ struct Lsym {
|
|||||||
int defined; /* 1 if a Lobj defines this symbol */
|
int defined; /* 1 if a Lobj defines this symbol */
|
||||||
Lobj *owner;
|
Lobj *owner;
|
||||||
int idx_in_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;
|
Lsym *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -51,17 +60,35 @@ struct Lobj {
|
|||||||
Lobj *next;
|
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 {
|
struct Lnk {
|
||||||
Lobj *objs;
|
Lobj *objs;
|
||||||
|
Lso *sos;
|
||||||
Lsym *syms;
|
Lsym *syms;
|
||||||
Lrel *rels;
|
Lrel *rels;
|
||||||
u8 *text; /* combined .text */
|
u8 *text; /* combined .text */
|
||||||
u64 textcap, textlen;
|
u64 textcap, textlen;
|
||||||
int errs;
|
int errs;
|
||||||
|
int dyn_n; /* number of symbols routed through PLT */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* obj.c */
|
/* obj.c */
|
||||||
int l_load(Lnk*, const char *path);
|
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 */
|
/* sym.c */
|
||||||
Lsym *l_intern(Lnk*, const char *name);
|
Lsym *l_intern(Lnk*, const char *name);
|
||||||
Lsym *l_lookup(Lnk*, const char *name);
|
Lsym *l_lookup(Lnk*, const char *name);
|
||||||
@@ -70,5 +97,8 @@ int l_resolve(Lnk*);
|
|||||||
int l_relocate(Lnk*, u64 base);
|
int l_relocate(Lnk*, u64 base);
|
||||||
/* out.c */
|
/* out.c */
|
||||||
int l_emit_elf(Lnk*, FILE *out, u64 base, u64 entry);
|
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
|
#endif
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
/*
|
/*
|
||||||
* 6l — amd64 static linker. Reads relocatable ELF .o files, resolves,
|
* 6l — amd64 linker. Reads relocatable ELF .o files (from 6a) plus
|
||||||
* relocates, writes a static ELF executable.
|
* .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.
|
||||||
*
|
*
|
||||||
* 6l -o out file1.o file2.o ...
|
* 6l -o out [-L<dir>...] [-l<name>...] file1.o file2.o ...
|
||||||
*
|
*
|
||||||
* The first symbol named "_start" defined among the inputs becomes
|
* The first symbol named "_start" defined among the inputs becomes
|
||||||
* the entry point. If none is found, fall back to "main".
|
* the entry point. If none is found, fall back to "main".
|
||||||
@@ -11,6 +13,48 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.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
|
int
|
||||||
main(int argc, char **argv)
|
main(int argc, char **argv)
|
||||||
@@ -18,11 +62,23 @@ main(int argc, char **argv)
|
|||||||
const char *out = NULL;
|
const char *out = NULL;
|
||||||
const char **inputs = calloc(argc, sizeof *inputs);
|
const char **inputs = calloc(argc, sizeof *inputs);
|
||||||
int ninputs = 0;
|
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;
|
u64 base = 0x400000;
|
||||||
|
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) {
|
if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) {
|
||||||
out = argv[++i];
|
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] == '-') {
|
} else if (argv[i][0] == '-') {
|
||||||
fprintf(stderr, "6l: unknown flag %s\n", argv[i]);
|
fprintf(stderr, "6l: unknown flag %s\n", argv[i]);
|
||||||
return 2;
|
return 2;
|
||||||
@@ -31,10 +87,23 @@ main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (out == NULL || ninputs == 0) {
|
if (out == NULL || ninputs == 0) {
|
||||||
fputs("usage: 6l -o exe file1.o [file2.o...]\n", stderr);
|
fputs("usage: 6l -o exe [-L<dir>...] [-l<name>...] "
|
||||||
|
"file1.o [file2.o...]\n", stderr);
|
||||||
return 2;
|
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, "6l: cannot find -l%s\n", lflags[i]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
inputs[ninputs++] = p;
|
||||||
|
}
|
||||||
|
|
||||||
Lnk l = {0};
|
Lnk l = {0};
|
||||||
/* Seed the symbol table with the entry point so archive pulls
|
/* Seed the symbol table with the entry point so archive pulls
|
||||||
* include the .o that defines it. Without this, a libwwrt.a
|
* include the .o that defines it. Without this, a libwwrt.a
|
||||||
|
|||||||
16
cmd/6l/obj.c
16
cmd/6l/obj.c
@@ -52,8 +52,8 @@ typedef struct {
|
|||||||
#define ELF64_ST_TYPE(i) ((i) & 0xf)
|
#define ELF64_ST_TYPE(i) ((i) & 0xf)
|
||||||
#define ELF64_ST_BIND(i) ((i) >> 4)
|
#define ELF64_ST_BIND(i) ((i) >> 4)
|
||||||
|
|
||||||
static int
|
int
|
||||||
read_all(const char *path, u8 **out, u64 *len)
|
l_read_all(const char *path, u8 **out, u64 *len)
|
||||||
{
|
{
|
||||||
FILE *f = fopen(path, "rb");
|
FILE *f = fopen(path, "rb");
|
||||||
if (f == NULL) return -1;
|
if (f == NULL) return -1;
|
||||||
@@ -69,6 +69,8 @@ read_all(const char *path, u8 **out, u64 *len)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define read_all l_read_all
|
||||||
|
|
||||||
static void
|
static void
|
||||||
emit_text(Lnk *l, const u8 *src, u64 n)
|
emit_text(Lnk *l, const u8 *src, u64 n)
|
||||||
{
|
{
|
||||||
@@ -229,6 +231,16 @@ l_load(Lnk *l, const char *path)
|
|||||||
if (read_all(path, &buf, &len) < 0) return -1;
|
if (read_all(path, &buf, &len) < 0) return -1;
|
||||||
if (len >= 8 && memcmp(buf, "!<arch>\n", 8) == 0)
|
if (len >= 8 && memcmp(buf, "!<arch>\n", 8) == 0)
|
||||||
return load_archive(l, path, buf, len);
|
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);
|
return load_image(l, path, buf, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ typedef struct {
|
|||||||
int
|
int
|
||||||
l_emit_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
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_off = 0x1000;
|
||||||
const u64 text_va = base + text_off;
|
const u64 text_va = base + text_off;
|
||||||
const u64 filesz = text_off + l->textlen;
|
const u64 filesz = text_off + l->textlen;
|
||||||
|
|||||||
@@ -19,9 +19,37 @@
|
|||||||
int
|
int
|
||||||
l_resolve(Lnk *l)
|
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) {
|
for (Lrel *r = l->rels; r; r = r->next) {
|
||||||
if (r->sym == NULL) continue;
|
if (r->sym == NULL) continue;
|
||||||
if (!r->sym->defined) {
|
if (!r->sym->defined && !r->sym->is_dyn) {
|
||||||
fprintf(stderr, "6l: undefined reference to '%s'\n",
|
fprintf(stderr, "6l: undefined reference to '%s'\n",
|
||||||
r->sym->name);
|
r->sym->name);
|
||||||
l->errs++;
|
l->errs++;
|
||||||
@@ -43,7 +71,11 @@ int
|
|||||||
l_relocate(Lnk *l, u64 base)
|
l_relocate(Lnk *l, u64 base)
|
||||||
{
|
{
|
||||||
for (Lrel *r = l->rels; r; r = r->next) {
|
for (Lrel *r = l->rels; r; r = r->next) {
|
||||||
if (r->sym == NULL || !r->sym->defined) continue;
|
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) {
|
switch (r->kind) {
|
||||||
case R_X86_64_PC32:
|
case R_X86_64_PC32:
|
||||||
case R_X86_64_PLT32: {
|
case R_X86_64_PLT32: {
|
||||||
|
|||||||
@@ -156,7 +156,8 @@ expand(FILE *out, const char *path, struct ImportSet *visited,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
build_one(const char *src, const char *out, const char *extra_includes)
|
build_one(const char *src, const char *out, const char *extra_includes,
|
||||||
|
const char *extra_libs, const char *extra_libdirs)
|
||||||
{
|
{
|
||||||
const char *c6 = toolpath("WW_6C", "6c");
|
const char *c6 = toolpath("WW_6C", "6c");
|
||||||
const char *a6 = toolpath("WW_6A", "6a");
|
const char *a6 = toolpath("WW_6A", "6a");
|
||||||
@@ -237,7 +238,13 @@ build_one(const char *src, const char *out, const char *extra_includes)
|
|||||||
snprintf(a2, sizeof a2, "%s/../obj/rt/syscall.o", self_dir);
|
snprintf(a2, sizeof a2, "%s/../obj/rt/syscall.o", self_dir);
|
||||||
snprintf(rtargs, sizeof rtargs, "%s %s", a1, a2);
|
snprintf(rtargs, sizeof rtargs, "%s %s", a1, a2);
|
||||||
}
|
}
|
||||||
snprintf(cmd, sizeof cmd, "%s -o %s %s %s", l6, out, obj, rtargs);
|
/* -L<dir> goes before -l<name> so 6l can resolve the latter. */
|
||||||
|
const char *libargs = (extra_libs && extra_libs[0]) ? extra_libs : "";
|
||||||
|
const char *libdirset = (extra_libdirs && extra_libdirs[0]) ? extra_libdirs : "";
|
||||||
|
snprintf(cmd, sizeof cmd, "%s -o %s %s %s%s%s%s%s",
|
||||||
|
l6, out, obj, rtargs,
|
||||||
|
libdirset[0] ? " " : "", libdirset,
|
||||||
|
libargs[0] ? " " : "", libargs);
|
||||||
if (run(cmd) != 0) {
|
if (run(cmd) != 0) {
|
||||||
fprintf(stderr, "ww: 6l failed\n");
|
fprintf(stderr, "ww: 6l failed\n");
|
||||||
return 1;
|
return 1;
|
||||||
@@ -256,21 +263,26 @@ static int
|
|||||||
do_build(int argc, char **argv)
|
do_build(int argc, char **argv)
|
||||||
{
|
{
|
||||||
const char *src = NULL;
|
const char *src = NULL;
|
||||||
char libs[2048] = {0};
|
char libs[2048] = {0}; /* -l<name> entries, space-separated */
|
||||||
|
char libdirs[2048] = {0}; /* -L<dir> entries, space-separated */
|
||||||
char incs[2048] = {0};
|
char incs[2048] = {0};
|
||||||
for (int i = 0; i < argc; i++) {
|
for (int i = 0; i < argc; i++) {
|
||||||
if (strncmp(argv[i], "-l", 2) == 0 && argv[i][2]) {
|
if (strncmp(argv[i], "-l", 2) == 0 && argv[i][2]) {
|
||||||
char libpath[512];
|
|
||||||
const char *libdir = getenv("WW_LIB");
|
|
||||||
if (libdir == NULL) {
|
|
||||||
static char def[1024];
|
|
||||||
snprintf(def, sizeof def, "%s/../lib", self_dir);
|
|
||||||
libdir = def;
|
|
||||||
}
|
|
||||||
snprintf(libpath, sizeof libpath, "%s/lib%s.a",
|
|
||||||
libdir, argv[i] + 2);
|
|
||||||
size_t n = strlen(libs);
|
size_t n = strlen(libs);
|
||||||
snprintf(libs + n, sizeof libs - n, " %s", libpath);
|
snprintf(libs + n, sizeof libs - n,
|
||||||
|
"%s%s", n ? " " : "", argv[i]);
|
||||||
|
} else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
|
||||||
|
size_t n = strlen(libs);
|
||||||
|
snprintf(libs + n, sizeof libs - n,
|
||||||
|
"%s-l%s", n ? " " : "", argv[++i]);
|
||||||
|
} else if (strcmp(argv[i], "-L") == 0 && i + 1 < argc) {
|
||||||
|
size_t n = strlen(libdirs);
|
||||||
|
snprintf(libdirs + n, sizeof libdirs - n,
|
||||||
|
"%s-L%s", n ? " " : "", argv[++i]);
|
||||||
|
} else if (strncmp(argv[i], "-L", 2) == 0 && argv[i][2]) {
|
||||||
|
size_t n = strlen(libdirs);
|
||||||
|
snprintf(libdirs + n, sizeof libdirs - n,
|
||||||
|
"%s%s", n ? " " : "", argv[i]);
|
||||||
} else if (strcmp(argv[i], "-I") == 0 && i + 1 < argc) {
|
} else if (strcmp(argv[i], "-I") == 0 && i + 1 < argc) {
|
||||||
size_t n = strlen(incs);
|
size_t n = strlen(incs);
|
||||||
snprintf(incs + n, sizeof incs - n,
|
snprintf(incs + n, sizeof incs - n,
|
||||||
@@ -290,10 +302,7 @@ do_build(int argc, char **argv)
|
|||||||
snprintf(out, sizeof out, "%s", base);
|
snprintf(out, sizeof out, "%s", base);
|
||||||
char *dot = strrchr(out, '.');
|
char *dot = strrchr(out, '.');
|
||||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||||
(void)libs; /* libs string is gathered; build_one currently
|
return build_one(src, out, incs, libs, libdirs);
|
||||||
* always links libwwrt.a; -l support pending more
|
|
||||||
* glue between driver and 6l invocation. */
|
|
||||||
return build_one(src, out, incs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -302,7 +311,7 @@ do_run(int argc, char **argv)
|
|||||||
if (argc < 1) { fputs("ww run: missing source\n", stderr); return 2; }
|
if (argc < 1) { fputs("ww run: missing source\n", stderr); return 2; }
|
||||||
char tmp[1024];
|
char tmp[1024];
|
||||||
snprintf(tmp, sizeof tmp, "/tmp/ww_run_%d", getpid());
|
snprintf(tmp, sizeof tmp, "/tmp/ww_run_%d", getpid());
|
||||||
if (build_one(argv[0], tmp, "") != 0) return 1;
|
if (build_one(argv[0], tmp, "", "", "") != 0) return 1;
|
||||||
int rc = run(tmp);
|
int rc = run(tmp);
|
||||||
unlink(tmp);
|
unlink(tmp);
|
||||||
return rc;
|
return rc;
|
||||||
|
|||||||
152
test/wwc/810_dyn.c
Normal file
152
test/wwc/810_dyn.c
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* 810_dyn — end-to-end test of 6l's dynamic linker.
|
||||||
|
*
|
||||||
|
* Drives `ww build -L /usr/lib -l c` over a small ww program that
|
||||||
|
* binds libc symbols via @symbol, then runs the produced binary and
|
||||||
|
* checks the exit status. Verifies:
|
||||||
|
*
|
||||||
|
* - the .so loader (dyn.c) reads ET_DYN and extracts exports
|
||||||
|
* - PLT/GOT generation and the JUMP_SLOT relocation
|
||||||
|
* - PT_INTERP/PT_DYNAMIC emission
|
||||||
|
* - symbol versioning (.gnu.version + .gnu.version_r) — clock_gettime
|
||||||
|
* has both GLIBC_2.2.5 (compat) and GLIBC_2.17 (default) on glibc;
|
||||||
|
* the loader requires the right .gnu.version_r entry to bind
|
||||||
|
* the default vDSO-aware impl.
|
||||||
|
*
|
||||||
|
* Skipped (passing trivially) on systems without /usr/lib/libc.so.6.
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
static int
|
||||||
|
runwait(const char *cmd)
|
||||||
|
{
|
||||||
|
int rc = system(cmd);
|
||||||
|
if (rc == -1) return -1;
|
||||||
|
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct row {
|
||||||
|
const char *src;
|
||||||
|
int want_exit;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct row rows[] = {
|
||||||
|
/* 1. _exit via dynamically-linked libc. Exit code is the plumbing
|
||||||
|
* of choice — proves the PLT entry calls into libc.so.6. */
|
||||||
|
{ "@symbol(\"_exit\") fn libc_exit(c: i32) void;\n"
|
||||||
|
"export fn main() i32 = { libc_exit(42); return 0; };", 42 },
|
||||||
|
|
||||||
|
/* 2. Multiple dyn syms in one binary: write + _exit. */
|
||||||
|
{ "@symbol(\"write\") fn libc_write(fd: i32, b: *u8, n: u64) i64;\n"
|
||||||
|
"@symbol(\"_exit\") fn libc_exit(c: i32) void;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" libc_write(1, \"x\".ptr, 1u64);\n"
|
||||||
|
" libc_exit(7);\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};", 7 },
|
||||||
|
|
||||||
|
/* 3. Symbol versioning: clock_gettime. Without DT_VERSYM/VERNEED
|
||||||
|
* pointing at a Vernaux entry for GLIBC_2.17, the loader either
|
||||||
|
* picks the wrong impl or fails outright on modern glibc. */
|
||||||
|
{ "@symbol(\"clock_gettime\") fn clock_gettime(c: i32, ts: *u8) i32;\n"
|
||||||
|
"@symbol(\"_exit\") fn libc_exit(c: i32) void;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let ts: [16]u8;\n"
|
||||||
|
" let r: i32 = clock_gettime(1, ts.ptr);\n" /* CLOCK_MONOTONIC */
|
||||||
|
" libc_exit(r);\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};", 0 },
|
||||||
|
|
||||||
|
/* 4. Take the address of an FFI binding and call it indirectly.
|
||||||
|
* Codegen must apply @symbol resolution at the LEAQ site (so the
|
||||||
|
* stub address is `write`, not the ww-side ident `libc_write`),
|
||||||
|
* and the local-variable call must be CALL *AX, not CALL fp(SB). */
|
||||||
|
{ "@symbol(\"write\") fn libc_write(fd: i32, b: *u8, n: u64) i64;\n"
|
||||||
|
"@symbol(\"_exit\") fn libc_exit(c: i32) void;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let fp: fn(fd: i32, b: *u8, n: u64) i64 = libc_write;\n"
|
||||||
|
" fp(1, \"X\".ptr, 1u64);\n"
|
||||||
|
" libc_exit(5);\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};", 5 },
|
||||||
|
|
||||||
|
{ NULL, 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
main(void)
|
||||||
|
{
|
||||||
|
if (access("/usr/lib/libc.so.6", 0) != 0
|
||||||
|
&& access("/lib/x86_64-linux-gnu/libc.so.6", 0) != 0
|
||||||
|
&& access("/lib64/libc.so.6", 0) != 0) {
|
||||||
|
puts("dyn: no libc.so.6 on this system — skipping");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *bin = getenv("BIN");
|
||||||
|
if (!bin) bin = "out/bin";
|
||||||
|
char absbin[1024];
|
||||||
|
if (bin[0] != '/') {
|
||||||
|
char cwd[1024];
|
||||||
|
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||||
|
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||||
|
bin = absbin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Probe each common libc dir to find the right -L. */
|
||||||
|
const char *libdir = NULL;
|
||||||
|
if (access("/usr/lib/libc.so.6", 0) == 0) libdir = "/usr/lib";
|
||||||
|
else if (access("/lib/x86_64-linux-gnu/libc.so.6", 0) == 0) libdir = "/lib/x86_64-linux-gnu";
|
||||||
|
else if (access("/lib64/libc.so.6", 0) == 0) libdir = "/lib64";
|
||||||
|
|
||||||
|
int n = 0, fail = 0;
|
||||||
|
for (int i = 0; rows[i].src; i++, n++) {
|
||||||
|
char src[80], exe[80], tmpdir[80];
|
||||||
|
snprintf(src, sizeof src, "/tmp/wwdyn_%d_%d.ww", getpid(), i);
|
||||||
|
snprintf(exe, sizeof exe, "/tmp/wwdyn_%d_%d", getpid(), i);
|
||||||
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwdyn_%d_d_%d", getpid(), i);
|
||||||
|
mkdir(tmpdir, 0755);
|
||||||
|
|
||||||
|
FILE *f = fopen(src, "wb");
|
||||||
|
fputs(rows[i].src, f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
char cmd[1024];
|
||||||
|
snprintf(cmd, sizeof cmd,
|
||||||
|
"cd %s && %s/ww build %s -L %s -l c",
|
||||||
|
tmpdir, bin, src, libdir);
|
||||||
|
if (runwait(cmd) != 0) {
|
||||||
|
fprintf(stderr, "dyn row %d: build failed\n", i);
|
||||||
|
fail++;
|
||||||
|
unlink(src); rmdir(tmpdir);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char outbin[160];
|
||||||
|
const char *base = strrchr(src, '/');
|
||||||
|
base = base ? base + 1 : src;
|
||||||
|
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||||
|
char *dot = strrchr(outbin, '.');
|
||||||
|
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||||
|
|
||||||
|
int got = runwait(outbin);
|
||||||
|
if (got != rows[i].want_exit) {
|
||||||
|
fprintf(stderr, "dyn row %d: exit %d, want %d\n",
|
||||||
|
i, got, rows[i].want_exit);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||||
|
}
|
||||||
|
if (fail) {
|
||||||
|
fprintf(stderr, "%d/%d dyn tests failed\n", fail, n);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("dyn: %d/%d ok\n", n, n);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user