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).
656 lines
21 KiB
C
656 lines
21 KiB
C
/*
|
|
* 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). 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>
|
|
|
|
#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;
|
|
}
|
|
|
|
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)
|
|
{
|
|
/* Writable globals share the dyn-path R+W segment with .got.plt
|
|
* and .dynamic. .data is placed after .dynamic; the segment's
|
|
* filesz/memsz are extended to cover all three. Relocation
|
|
* targeting .data uses data_va computed inside this function
|
|
* (l_relocate now runs from here, not main, so the dyn layout's
|
|
* data VA is the one that lands in patched offsets). */
|
|
|
|
const int N = l->dyn_n;
|
|
|
|
/* 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, "w6l: 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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/* 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++;
|
|
}
|
|
|
|
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++;
|
|
|
|
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, "w6l: dynout: unmatched version %s for %s\n",
|
|
vname, dynsyms[j]->name);
|
|
versym_for[j] = VER_NDX_GLOBAL;
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
/* 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.
|
|
* .data sits after .dynamic so the whole R+W run is one segment. */
|
|
const u64 gotplt_off = (rx_end + page - 1) & ~(page - 1);
|
|
const u64 dynamic_off = gotplt_off + gotplt_sz;
|
|
const u64 data_off = dynamic_off + dynamic_sz;
|
|
const u64 file_end = data_off + l->datalen;
|
|
|
|
/* 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;
|
|
const u64 data_va = base + data_off;
|
|
(void)dynstr_va; (void)hash_va; (void)plt_va;
|
|
|
|
/* Now that the dyn layout pins text_va/data_va, apply
|
|
* relocations. main.c defers this; the static path runs it from
|
|
* out.c with its own VAs. */
|
|
if (l_relocate(l, text_va, data_va) != 0) return 1;
|
|
|
|
/* BSS optimisation — same trailing-zero scan as out.c. */
|
|
u64 bsslen = 0;
|
|
if (l->datalen > 0) {
|
|
while (bsslen < l->datalen
|
|
&& l->data[l->datalen - 1 - bsslen] == 0)
|
|
bsslen++;
|
|
}
|
|
const u64 data_file_len = l->datalen - bsslen;
|
|
const u64 file_data_end = data_off + data_file_len;
|
|
|
|
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. */
|
|
|
|
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. */
|
|
|
|
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, "w6l: dynamic entry count mismatch\n");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* 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) {
|
|
fprintf(stderr, "w6l: 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);
|
|
}
|
|
|
|
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;
|
|
/* #63: main computed `entry` as base + 0x1000 + val assuming .text sits
|
|
* at file offset 0x1000, but text_off above is recomputed and overflows
|
|
* 0x1000 once the dynamic headers exceed the first page (e.g. ~100 dyn
|
|
* syms). Rebase e_entry onto the actual text_off so it points at the
|
|
* real _start instead of into the headers. e_entry is a virtual address;
|
|
* PT_LOAD #1 maps file offset 0 at `base`, so the entry VA tracks
|
|
* text_off. Both stages fixed together (#263). */
|
|
eh.e_entry = entry - 0x1000 + text_off;
|
|
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, .dynamic, and .data.
|
|
* filesz drops the trailing-zero suffix (BSS); memsz keeps the
|
|
* full extent so the loader zero-fills the gap. */
|
|
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_data_end - gotplt_off;
|
|
ph[1].p_memsz = file_end - gotplt_off;
|
|
ph[1].p_align = page;
|
|
|
|
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;
|
|
|
|
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);
|
|
|
|
#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);
|
|
if (data_file_len > 0) {
|
|
PAD_TO(data_off); fwrite(l->data, 1, data_file_len, 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;
|
|
}
|