Files
ww/cmd/w6l/obj.c
Hojun-Cho d998425391 w6a+w6l: DATAR directive for absolute-address relocs in .data
Unblock literal initialisers for str/slice/struct globals by wiring
an R_X86_64_64 relocation kind through both assembler and static
linker.

w6a:
  - new A_DATAR directive, syntax `DATAR slot+off(SB),target(SB)`,
    records an R_X86_64_64 reloc at slot+off in .data pointing at
    target. The slot must be pre-defined by a prior DATAW;
  - parse_operand learned the `name+disp(SB)` shape so the slot's
    byte offset can be addressed explicitly;
  - Areloc carries a `section` flag (0=.text / 1=.data) and obj.c
    splits the reloc list into .rela.text and .rela.data, emitting
    the latter conditionally with sh_info pointing at .data.

w6l:
  - Lrel grows the same `section` flag; obj.c loads `.rela.data`
    sections into the global reloc list with offsets shifted by
    each input's data_off;
  - pass.c handles R_X86_64_64: target VA is data_va+sym.val for
    in_data symbols (else text_va+sym.val), addend is added, and
    the 8-byte slot is patched in l->data (or l->text).

Inputs without DATAR are unaffected — bootstrap, 991 (selfhost .o
diff) and 992 (selfhost exe diff) keep their byte-identical
output. 520_datar covers the new path: asm a DATAW+DATAR pair,
verify .rela.data has exactly one R_X86_64_64 entry, link, run,
confirm the relocated pointer feeds a 5-byte write that prints
"hello".

Selfhost mirror + w6c emission for str/slice/struct literal init
land in follow-ups.
2026-05-12 12:45:11 +09:00

392 lines
11 KiB
C

/*
* 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>
#define ET_REL 1
#define EM_X86_64 62
#define SHT_PROGBITS 1
#define SHT_SYMTAB 2
#define SHT_STRTAB 3
#define SHT_RELA 4
#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 sh_name, sh_type;
u64 sh_flags, sh_addr, sh_offset, sh_size;
u32 sh_link, sh_info;
u64 sh_addralign, sh_entsize;
} Shdr;
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;
#pragma pack(pop)
#define ELF64_R_SYM(i) ((u32)((i) >> 32))
#define ELF64_R_TYPE(i) ((u32)((i) & 0xffffffff))
#define ELF64_ST_TYPE(i) ((i) & 0xf)
#define ELF64_ST_BIND(i) ((i) >> 4)
int
l_read_all(const char *path, u8 **out, u64 *len)
{
FILE *f = fopen(path, "rb");
if (f == NULL) return -1;
fseek(f, 0, SEEK_END);
long n = ftell(f);
fseek(f, 0, SEEK_SET);
if (n < 0) { fclose(f); return -1; }
u8 *b = malloc((size_t)n);
if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; }
fclose(f);
*out = b;
*len = (u64)n;
return 0;
}
#define read_all l_read_all
static void
emit_text(Lnk *l, const u8 *src, u64 n)
{
if (l->textlen + n > l->textcap) {
u64 nc = l->textcap ? l->textcap * 2 : 4096;
while (nc < l->textlen + n) nc *= 2;
l->text = realloc(l->text, nc);
l->textcap = nc;
}
memcpy(l->text + l->textlen, src, n);
l->textlen += n;
}
static void
emit_data(Lnk *l, const u8 *src, u64 n)
{
if (l->datalen + n > l->datacap) {
u64 nc = l->datacap ? l->datacap * 2 : 256;
while (nc < l->datalen + n) nc *= 2;
l->data = realloc(l->data, nc);
l->datacap = nc;
}
memcpy(l->data + l->datalen, src, 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.
*/
static int load_image(Lnk *l, const char *path, u8 *buf, u64 len);
static u64
ar_field(const u8 *p, int n)
{
/* decimal field, space-padded */
u64 v = 0;
for (int i = 0; i < n; i++) {
if (p[i] >= '0' && p[i] <= '9') v = v * 10 + (p[i] - '0');
else if (p[i] == ' ') break;
else if (p[i] == 0) break;
}
return v;
}
/* 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).
*/
static char **
elf_globals(const u8 *buf, u64 len)
{
if (len < sizeof(Ehdr)) return NULL;
Ehdr *eh = (Ehdr *)buf;
if (memcmp(eh->e_ident, "\x7f""ELF", 4) != 0) return NULL;
Shdr *sh = (Shdr *)(buf + eh->e_shoff);
int idx_text = -1, idx_data = -1, idx_symtab = -1;
const char *shstr = (const char *)(buf + sh[eh->e_shstrndx].sh_offset);
for (u16 i = 0; i < eh->e_shnum; i++) {
if (sh[i].sh_type == SHT_PROGBITS &&
strcmp(shstr + sh[i].sh_name, ".text") == 0)
idx_text = i;
else if (sh[i].sh_type == SHT_PROGBITS &&
strcmp(shstr + sh[i].sh_name, ".data") == 0)
idx_data = i;
else if (sh[i].sh_type == SHT_SYMTAB)
idx_symtab = i;
}
if (idx_text < 0 || idx_symtab < 0) return NULL;
int idx_strtab = sh[idx_symtab].sh_link;
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);
/* Include both .text and .data globals so archive members that
* define a data global get pulled in when something references it. */
char **out = calloc(nsyms + 1, sizeof *out);
int n = 0;
for (u64 i = 1; i < nsyms; i++) {
if (symtab[i].st_shndx == 0) continue;
if ((symtab[i].st_info >> 4) != 1) continue; /* STB_GLOBAL */
int sx = (int)symtab[i].st_shndx;
if (sx != idx_text && sx != idx_data) continue;
out[n++] = strdup(str + symtab[i].st_name);
}
out[n] = NULL;
return out;
}
typedef struct ArMember ArMember;
struct ArMember {
u8 *data; /* heap copy; freed if never loaded */
u64 size;
char **defs; /* NULL-terminated list of defined globals */
int loaded;
ArMember *next;
};
static int
member_defines_undef(Lnk *l, ArMember *m)
{
if (m->defs == NULL) return 0;
for (int i = 0; m->defs[i]; i++) {
Lsym *s = l_lookup(l, m->defs[i]);
if (s != NULL && !s->defined) return 1;
}
return 0;
}
static int
load_archive(Lnk *l, const char *path, u8 *buf, u64 len)
{
/* Pass 1: index members. We copy each member's bytes (cheap; few
* tens of KB per stdlib module) so the archive buffer can be
* freed once we're done indexing. */
ArMember *head = NULL, *tail = NULL;
u64 pos = 8; /* past "!<arch>\n" */
while (pos + 60 <= len) {
const u8 *hdr = buf + pos;
u64 size = ar_field(hdr + 48, 10);
u64 hdr_end = pos + 60;
if (hdr_end + size > len) break;
if (hdr[0] != '/' && hdr[0] != 0 && hdr[0] != ' ') {
ArMember *m = calloc(1, sizeof *m);
m->size = size;
m->data = malloc((size_t)size);
memcpy(m->data, buf + hdr_end, (size_t)size);
m->defs = elf_globals(m->data, size);
if (head == NULL) head = m;
else tail->next = m;
tail = m;
}
pos = hdr_end + size;
if (size & 1) pos++;
}
free(buf);
/* Pass 2: iteratively pull members that define a currently-
* undefined symbol. Each pull may introduce new undefs, so loop. */
int changed = 1;
while (changed) {
changed = 0;
for (ArMember *m = head; m; m = m->next) {
if (m->loaded) continue;
if (!member_defines_undef(l, m)) continue;
u8 *copy = malloc((size_t)m->size);
memcpy(copy, m->data, m->size);
if (load_image(l, path, copy, m->size) == 0) {
m->loaded = 1;
changed = 1;
}
}
}
/* Free unloaded members; loaded ones had their bytes consumed
* by load_image (which took the copy). */
while (head) {
ArMember *next = head->next;
free(head->data);
if (head->defs) {
for (int i = 0; head->defs[i]; i++) free(head->defs[i]);
free(head->defs);
}
free(head);
head = next;
}
return 0;
}
int
l_load(Lnk *l, const char *path)
{
u8 *buf;
u64 len;
if (read_all(path, &buf, &len) < 0) return -1;
if (len >= 8 && memcmp(buf, "!<arch>\n", 8) == 0)
return load_archive(l, path, buf, len);
/* Shared object: dispatch to dyn.c, which re-reads (small loss
* for a much cleaner separation of static vs dynamic loaders). */
if (len >= sizeof(Ehdr)) {
Ehdr *eh = (Ehdr *)buf;
if (memcmp(eh->e_ident, "\x7f""ELF", 4) == 0
&& eh->e_type == 3 /* ET_DYN */) {
free(buf);
return l_load_so(l, path);
}
}
return load_image(l, path, buf, len);
}
static int
load_image(Lnk *l, const char *path, u8 *buf, u64 len)
{
if (len < sizeof(Ehdr)) { free(buf); return -1; }
Ehdr *eh = (Ehdr *)buf;
if (memcmp(eh->e_ident, "\x7f""ELF", 4) != 0 || eh->e_ident[4] != 2
|| eh->e_machine != EM_X86_64 || eh->e_type != ET_REL) {
fprintf(stderr, "w6l: %s: not an amd64 ELF64 relocatable\n", path);
free(buf);
return -1;
}
Shdr *sh = (Shdr *)(buf + eh->e_shoff);
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++) {
const char *nm = shstr + sh[i].sh_name;
if (sh[i].sh_type == SHT_PROGBITS && strcmp(nm, ".text") == 0)
idx_text = i;
else if (sh[i].sh_type == SHT_PROGBITS && strcmp(nm, ".data") == 0)
idx_data = i;
else if (sh[i].sh_type == SHT_SYMTAB)
idx_symtab = i;
else if (sh[i].sh_type == SHT_RELA && strcmp(nm, ".rela.text") == 0)
idx_rela = i;
else if (sh[i].sh_type == SHT_RELA && strcmp(nm, ".rela.data") == 0)
idx_relad = i;
}
if (idx_text < 0 || idx_symtab < 0) {
fprintf(stderr, "w6l: %s: missing .text or .symtab\n", path);
free(buf);
return -1;
}
idx_strtab = sh[idx_symtab].sh_link;
Lobj *ob = calloc(1, sizeof *ob);
ob->path = strdup(path);
ob->buf = buf;
ob->len = len;
ob->text_off = l->textlen;
ob->text_size = sh[idx_text].sh_size;
ob->data_off = l->datalen;
ob->data_size = (idx_data >= 0) ? sh[idx_data].sh_size : 0;
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);
/* map per-object sym index → global Lsym */
Lsym **map = calloc(nsyms, sizeof *map);
for (u64 i = 1; i < nsyms; i++) {
const char *nm = str + symtab[i].st_name;
if (nm[0] == '\0') continue;
Lsym *gs = l_intern(l, nm);
int sx = (int)symtab[i].st_shndx;
int in_text = (symtab[i].st_shndx != 0 && sx == idx_text);
int in_data = (idx_data >= 0 && sx == idx_data);
if (in_text || in_data) {
if (gs->defined) {
fprintf(stderr, "w6l: %s: duplicate symbol %s\n",
path, nm);
l->errs++;
} else {
gs->defined = 1;
gs->owner = ob;
gs->idx_in_owner = (int)i;
if (in_data) {
gs->in_data = 1;
gs->val = ob->data_off + symtab[i].st_value;
} else {
gs->val = ob->text_off + symtab[i].st_value;
}
}
}
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);
for (u64 i = 0; i < nrel; i++) {
Lrel *r = calloc(1, sizeof *r);
r->off = ob->text_off + rt[i].r_offset;
r->section = 0;
r->kind = (int)ELF64_R_TYPE(rt[i].r_info);
u32 sidx = ELF64_R_SYM(rt[i].r_info);
r->sym = (sidx < nsyms) ? map[sidx] : NULL;
r->addend = rt[i].r_addend;
r->next = l->rels;
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
* 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);
u64 nrel = sh[idx_relad].sh_size / sizeof(Rela64);
for (u64 i = 0; i < nrel; i++) {
Lrel *r = calloc(1, sizeof *r);
r->off = ob->data_off + rt[i].r_offset;
r->section = 1;
r->kind = (int)ELF64_R_TYPE(rt[i].r_info);
u32 sidx = ELF64_R_SYM(rt[i].r_info);
r->sym = (sidx < nsyms) ? map[sidx] : NULL;
r->addend = rt[i].r_addend;
r->next = l->rels;
l->rels = r;
}
}
free(map);
return 0;
}