toolchain: banner purge + WHY-only comment sweep (rule 8)

selfhost/, cmd/, internal/ join the tree-wide sweep: every section
banner dies (91 selfhost + the cmd C-style dividers -> 0); narration
and stale contracts deleted (pre-#22 bundler notes, retired
single-PT_LOAD and no-archive claims, superseded ABI tables); every
ref/harec/qbe cite, task cite, encoding/ELF contract, and rule-10
twin pointer kept; lost lifetime/rationale lines restored where the
sweep over-cut (elf_globals ownership, kwtab linear-scan). Comment-
only proven: all five wwstage tool binaries byte-identical across
the sweep; test-commit, test-byteid (161+1399, 0 pinned-divergent),
and test-bootstrap (fixed point + 991-995 byte-id) all exit 0.
The read-through banked 66 latent-bug leads (checkpoint).
This commit is contained in:
2026-08-08 23:14:03 +09:00
parent 83f5956df2
commit 62b9d20383
60 changed files with 232 additions and 1045 deletions

View File

@@ -1,11 +1,6 @@
/*
* 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.
* We do not pull bytes from the .so; the dynamic loader maps it at
* runtime.
*/
#include "l.h"
#include <stdio.h>

View File

@@ -1,11 +1,7 @@
/*
* 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
* startup (no lazy binding, no PLT0 trampoline). SysV .hash, not
* .gnu.hash. Non-PIE, fixed base.
*
* File layout:
@@ -31,7 +27,6 @@
#include <stdlib.h>
#include <string.h>
/* ELF constants */
#define ET_EXEC 2
#define EM_X86_64 62
#define EV_CURRENT 1
@@ -127,7 +122,6 @@ elf_hash(const char *name)
return h;
}
/* Patch a 4-byte little-endian field in `buf` at offset `off`. */
static void
poke32(u8 *buf, u64 off, u32 v)
{
@@ -151,8 +145,6 @@ 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
@@ -189,7 +181,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
}
}
/* Build .dynstr in a growable buffer. */
u8 *dynstr = NULL;
u64 dynstr_cap = 0, dynstr_len = 0;
#define DSTR_PUT(s) do { \
@@ -216,9 +207,7 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
DSTR_PUT(dynsyms[i]->name);
}
/* ---- Versioning: group dyn syms by (lib, version) ----
*
* For every sym whose dyn_version is non-NULL, there's a
/* 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.
@@ -263,13 +252,11 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
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;
@@ -301,8 +288,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
}
}
/* ---- 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);
@@ -343,8 +328,7 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
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
/* 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. */
@@ -409,9 +393,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
const u64 data_file_len = l->datalen - bsslen;
const u64 file_data_end = data_off + data_file_len;
/* ---- 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];
@@ -438,7 +419,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
}
/* 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;
@@ -522,7 +502,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
}
/* [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;
@@ -559,9 +538,9 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
}
}
/* ---- 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. */
/* Patch .text relocations targeting dynamic syms: the site is
* the existing PC32/PLT32 displacement field, the target 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) {
@@ -579,8 +558,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
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;
@@ -625,7 +602,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
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;
@@ -635,7 +611,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
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;
@@ -648,7 +623,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
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); \

View File

@@ -1,9 +1,3 @@
/*
* 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

View File

@@ -1,9 +1,4 @@
/*
* 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
@@ -134,7 +129,6 @@ main(int argc, char **argv)
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);

View File

@@ -1,9 +1,3 @@
/*
* 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>
@@ -97,11 +91,8 @@ emit_data(Lnk *l, const u8 *src, u64 n)
l->datalen += 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.
*/
/* The caller gives us the bytes (we own them) and a path tag for
* diagnostics. */
static int load_image(Lnk *l, const char *path, u8 *buf, u64 len);
static u64
@@ -119,9 +110,8 @@ ar_field(const u8 *p, int n)
/* 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).
*/
* NULL-terminated array; caller frees the array, not the strings,
* which point into the .o image and need it kept alive. */
static char **
elf_globals(const u8 *buf, u64 len)
{
@@ -299,8 +289,6 @@ load_image(Lnk *l, const char *path, u8 *buf, u64 len)
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, .data (optional), .symtab, .strtab, .rela.text,
* .rela.data (optional) */
int idx_text = -1, idx_data = -1, idx_symtab = -1, idx_strtab = -1;
int idx_rela = -1, idx_relad = -1;
for (u16 i = 0; i < eh->e_shnum; i++) {
@@ -334,12 +322,10 @@ load_image(Lnk *l, const char *path, u8 *buf, u64 len)
ob->next = l->objs;
l->objs = ob;
/* append .text and (if present) .data */
emit_text(l, buf + sh[idx_text].sh_offset, sh[idx_text].sh_size);
if (idx_data >= 0 && sh[idx_data].sh_size > 0)
emit_data(l, buf + sh[idx_data].sh_offset, sh[idx_data].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);
@@ -373,7 +359,6 @@ load_image(Lnk *l, const char *path, u8 *buf, u64 len)
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);
@@ -389,8 +374,7 @@ load_image(Lnk *l, const char *path, u8 *buf, u64 len)
l->rels = r;
}
}
/* per-object: collect data relocations from .rela.data. The
* .data section in the .o starts at a per-object 0; we shift
/* The .data section in the .o starts at a per-object 0; we shift
* by ob->data_off so r->off indexes the combined .data buffer. */
if (idx_relad >= 0) {
Rela64 *rt = (Rela64 *)(buf + sh[idx_relad].sh_offset);

View File

@@ -1,6 +1,4 @@
/*
* out.c — emit a static ELF64 executable.
*
* Layout (file order) without .data:
* [0..64) ELF header
* [64..120) one program header (PT_LOAD R+X)
@@ -13,9 +11,6 @@
* [176..0x1000) zero pad
* [0x1000..) .text bytes
* [data_off..) .data bytes (file offset and vaddr page-aligned)
*
* 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>
@@ -102,9 +97,6 @@ l_emit_elf(Lnk *l, FILE *f, u64 base, u64 entry)
eh.e_phentsize = sizeof(Phdr);
eh.e_phnum = has_data ? 2 : 1;
/* R+X load covering [0, rx_end). When .data is present we still
* round up to a page in memsz so the loader doesn't try to give
* the same page both R+X and R+W permissions. */
Phdr phx = {0};
phx.p_type = PT_LOAD;
phx.p_flags = PF_R | PF_X;
@@ -131,14 +123,12 @@ l_emit_elf(Lnk *l, FILE *f, u64 base, u64 entry)
fwrite(&phx, 1, sizeof phx, f);
if (has_data) fwrite(&phw, 1, sizeof phw, 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);
if (has_data && data_file_len > 0) {
/* pad to data_off */
here = ftell(f);
for (long i = here; i < (long)data_off; i++) fputc(0, f);
fwrite(l->data, 1, data_file_len, f);

View File

@@ -1,14 +1,3 @@
/*
* 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>
@@ -34,7 +23,7 @@ l_resolve(Lnk *l)
* 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 */
if (r->sym->is_dyn) continue;
for (Lso *so = l->sos; so; so = so->next) {
const char *ver = NULL;
if (l_so_provides_v(so, r->sym->name, &ver)) {
@@ -47,7 +36,6 @@ l_resolve(Lnk *l)
}
}
/* 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) {
@@ -90,7 +78,8 @@ l_relocate(Lnk *l, u64 text_va, u64 data_va)
switch (r->kind) {
case R_X86_64_PC32:
case R_X86_64_PLT32: {
/* PC-relative 32-bit displacement; lands in .text. */
/* PC-relative 32-bit displacement; lands in .text.
* For static linking PLT32 collapses to PC32. */
u64 site = text_va + r->off;
i64 rel = (i64)sym_va - (i64)site + r->addend;
patch_u32(l->text + r->off, (u32)(i32)rel);

View File

@@ -1,6 +1,6 @@
/*
* sym.c — global symbol table for the linker. Plain singly-linked
* list; usually a few hundred entries, hashing isn't worth it yet.
* Plain singly-linked list; usually a few hundred entries, hashing
* isn't worth it yet.
*/
#include "l.h"
#include <stdlib.h>