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:
2026-05-11 09:42:27 +09:00
parent 635818eb13
commit ecf0a84127
10 changed files with 1250 additions and 30 deletions

View File

@@ -156,7 +156,8 @@ expand(FILE *out, const char *path, struct ImportSet *visited,
}
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 *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(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) {
fprintf(stderr, "ww: 6l failed\n");
return 1;
@@ -256,21 +263,26 @@ static int
do_build(int argc, char **argv)
{
const char *src = NULL;
char libs[2048] = {0};
char incs[2048] = {0};
char libs[2048] = {0}; /* -l<name> entries, space-separated */
char libdirs[2048] = {0}; /* -L<dir> entries, space-separated */
char incs[2048] = {0};
for (int i = 0; i < argc; i++) {
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);
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) {
size_t n = strlen(incs);
snprintf(incs + n, sizeof incs - n,
@@ -290,10 +302,7 @@ do_build(int argc, char **argv)
snprintf(out, sizeof out, "%s", base);
char *dot = strrchr(out, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
(void)libs; /* libs string is gathered; build_one currently
* always links libwwrt.a; -l support pending more
* glue between driver and 6l invocation. */
return build_one(src, out, incs);
return build_one(src, out, incs, libs, libdirs);
}
static int
@@ -302,7 +311,7 @@ do_run(int argc, char **argv)
if (argc < 1) { fputs("ww run: missing source\n", stderr); return 2; }
char tmp[1024];
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);
unlink(tmp);
return rc;