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).
105 lines
2.8 KiB
C
105 lines
2.8 KiB
C
/*
|
|
* l.h — 6l-private header. Loads relocatable ELF64 .o files (the
|
|
* format produced by 6a) 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
|