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:
@@ -19,9 +19,37 @@
|
||||
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) {
|
||||
if (!r->sym->defined && !r->sym->is_dyn) {
|
||||
fprintf(stderr, "6l: undefined reference to '%s'\n",
|
||||
r->sym->name);
|
||||
l->errs++;
|
||||
@@ -43,7 +71,11 @@ int
|
||||
l_relocate(Lnk *l, u64 base)
|
||||
{
|
||||
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) {
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_PLT32: {
|
||||
|
||||
Reference in New Issue
Block a user