w6l+selfhost: dynamic-link + .data — shared R+W segment
.data now lives at the end of the dyn-path R+W PT_LOAD, just after .dynamic. The single segment covers .got.plt + .dynamic + .data; its filesz drops trailing zeros (BSS) while memsz spans the full extent. Relocation moves from main.c into each emit function so the static and dynamic paths use their own data_va — text→data refs land on the right VA regardless of path. Removes the early-error in dynout.c that previously refused any .data with -l/-L. Tests: 810_dyn gains two new dyn+.data fixtures (mutable read+write of an i32, plus a zero-init i64 verifying the BSS scan still produces a valid p_filesz<p_memsz under the shared segment).
This commit is contained in:
@@ -142,17 +142,12 @@ poke32(u8 *buf, u64 off, u32 v)
|
||||
int
|
||||
l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
{
|
||||
/* Writable globals on the dynamic-link path need their own R+W
|
||||
* segment, and the existing R+W segment (gotplt + dynamic) has a
|
||||
* fixed layout that l_relocate's data_va guess wouldn't match.
|
||||
* Rather than ship a silently-miscompiled binary, refuse and
|
||||
* leave the feature for a follow-up. */
|
||||
if (l->datalen > 0) {
|
||||
fprintf(stderr, "w6l: top-level mutable globals (.data) are "
|
||||
"not yet supported with -l/-L; link without shared "
|
||||
"libraries to use them.\n");
|
||||
return 1;
|
||||
}
|
||||
/* 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;
|
||||
|
||||
@@ -375,10 +370,12 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
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. */
|
||||
* 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 file_end = dynamic_off + dynamic_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
|
||||
@@ -394,8 +391,24 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
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;
|
||||
|
||||
/* ---- Pass 4: build each section into a buffer ---- */
|
||||
|
||||
/* .dynsym */
|
||||
@@ -593,13 +606,15 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
ph[0].p_memsz = rx_end;
|
||||
ph[0].p_align = page;
|
||||
|
||||
/* PT_LOAD #2 — R+W covering .got.plt and .dynamic. */
|
||||
/* 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_end - gotplt_off;
|
||||
ph[1].p_filesz = file_data_end - gotplt_off;
|
||||
ph[1].p_memsz = file_end - gotplt_off;
|
||||
ph[1].p_align = page;
|
||||
|
||||
@@ -645,6 +660,9 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
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);
|
||||
|
||||
@@ -115,20 +115,9 @@ main(int argc, char **argv)
|
||||
if (l_load(&l, inputs[i]) != 0) return 1;
|
||||
}
|
||||
if (l_resolve(&l) != 0) return 1;
|
||||
/* Layout for relocation purposes. The static path (out.c) uses
|
||||
* text_va = base + 0x1000 and places .data at the next page after
|
||||
* .text — these must agree with the layout in out.c. PC-relative
|
||||
* displacements between .text symbols cancel the absolute VAs, so
|
||||
* a dyn-path link whose data_va is "wrong" still relocates text→
|
||||
* text correctly; text→data is currently rejected in dynout.c. */
|
||||
u64 text_va = base + 0x1000;
|
||||
u64 data_va = 0;
|
||||
if (l.datalen > 0) {
|
||||
u64 page = 0x1000;
|
||||
u64 data_off = (0x1000 + l.textlen + page - 1) & ~(page - 1);
|
||||
data_va = base + data_off;
|
||||
}
|
||||
if (l_relocate(&l, text_va, data_va) != 0) return 1;
|
||||
/* Relocation is deferred to the emit functions — each path knows
|
||||
* its own layout (text_va, data_va), and the static and dynamic
|
||||
* paths place .data at different VAs. */
|
||||
|
||||
Lsym *entry = l_lookup(&l, "_start");
|
||||
if (entry == NULL || !entry->defined) entry = l_lookup(&l, "main");
|
||||
|
||||
@@ -63,6 +63,16 @@ l_emit_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
const u64 rx_end = text_off + l->textlen;
|
||||
const int has_data = (l->datalen > 0);
|
||||
|
||||
/* data goes at the next page boundary so the loader can grant a
|
||||
* fresh page of R+W permissions without overlapping the R+X mapping. */
|
||||
const u64 data_off = has_data ? ((rx_end + page - 1) & ~(page - 1)) : 0;
|
||||
const u64 data_va = has_data ? (base + data_off) : 0;
|
||||
|
||||
/* Apply relocations now that the layout's text_va/data_va are
|
||||
* known. Deferred from main.c so the dynamic-link path can use
|
||||
* its own data_va. */
|
||||
if (l_relocate(l, base + text_off, data_va) != 0) return 1;
|
||||
|
||||
/* BSS optimisation: trailing zero bytes in .data can be left out
|
||||
* of the file. The loader zero-fills the gap between p_filesz and
|
||||
* p_memsz, so this shrinks the binary without changing semantics.
|
||||
@@ -75,11 +85,6 @@ l_emit_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
bsslen++;
|
||||
}
|
||||
const u64 data_file_len = l->datalen - bsslen;
|
||||
|
||||
/* data goes at the next page boundary so the loader can grant a
|
||||
* fresh page of R+W permissions without overlapping the R+X mapping. */
|
||||
const u64 data_off = has_data ? ((rx_end + page - 1) & ~(page - 1)) : 0;
|
||||
const u64 data_va = has_data ? (base + data_off) : 0;
|
||||
const u64 file_end = has_data ? (data_off + data_file_len) : rx_end;
|
||||
(void)data_va;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user