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:
2026-05-12 13:50:09 +09:00
parent 2480f4c272
commit a9b804935c
8 changed files with 150 additions and 82 deletions

View File

@@ -1759,14 +1759,10 @@ fn streqd(a: str, b: str) bool = {
// ---- main entry --------------------------------------------------------
export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
// Writable globals on the dynamic-link path need their own R+W
// segment that the existing gotplt + .dynamic layout doesn't
// account for. Refuse rather than silently miscompile; folding
// .data into the existing R+W block is a follow-up.
if (l.datalen > 0u64) {
os.write(2, "w6l: top-level mutable globals (.data) not yet supported with -l/-L\n".ptr, 67u64);
return 1;
};
// .data shares the R+W PT_LOAD with .got.plt and .dynamic.
// Placed after .dynamic so the segment is one contiguous run;
// relocate runs from here so the dyn layout's datava lands in
// patched offsets.
let a: *arena = l.a;
let n: i32 = l.dynn;
let nu: u64 = n: u64;
@@ -2062,7 +2058,8 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
let gotpltoff: u64 = alignup(rxend, PAGE);
let dynamicoff: u64 = gotpltoff + gotpltsz;
let fileend: u64 = dynamicoff + dynamicsz;
let dataoff: u64 = dynamicoff + dynamicsz;
let fileend: u64 = dataoff + l.datalen;
let interpva: u64 = base + interpoff;
let dynstrva: u64 = base + dynstroff;
@@ -2075,6 +2072,23 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
let pltva: u64 = base + pltoff;
let gotpltva: u64 = base + gotpltoff;
let dynamicva: u64 = base + dynamicoff;
let datava: u64 = base + dataoff;
// Apply relocations now that the dyn layout's textva/datava are
// pinned. main.ww defers this so each path uses its own VAs.
if (relocate(l, textva, datava) != 0) { return 1; };
// BSS optimisation — same trailing-zero scan as out.ww.
let bsslen: u64 = 0u64;
if (l.datalen > 0u64) {
for (bsslen < l.datalen) {
let b: u8 = l.data[l.datalen - 1u64 - bsslen];
if (b != 0u8) { break; };
bsslen += 1u64;
};
};
let datafilelen: u64 = l.datalen - bsslen;
let filedataend: u64 = dataoff + datafilelen;
// ---- build .dynsym ----
let dynsymbuf: *u8 = amalloc(a, dynsymsz): *u8;
@@ -2282,7 +2296,9 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
dwr64(filebuf, p1 + 8u64, gotpltoff);
dwr64(filebuf, p1 + 16u64, gotpltva);
dwr64(filebuf, p1 + 24u64, gotpltva);
dwr64(filebuf, p1 + 32u64, fileend - gotpltoff);
// filesz trims the .data trailing zeros (BSS); memsz covers
// .got.plt + .dynamic + the full .data so the loader zero-fills.
dwr64(filebuf, p1 + 32u64, filedataend - gotpltoff);
dwr64(filebuf, p1 + 40u64, fileend - gotpltoff);
dwr64(filebuf, p1 + 48u64, PAGE);
@@ -2323,10 +2339,13 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
dbcopy(filebuf, pltoff, pltbuf, pltsz);
dbcopy(filebuf, gotpltoff, gotpltbuf, gotpltsz);
dbcopy(filebuf, dynamicoff, dynamicbuf, dynamicsz);
if (datafilelen > 0u64) {
dbcopy(filebuf, dataoff, l.data, datafilelen);
};
let wr: (i64 | os.oserror) = os.writeall(fd, filebuf, fileend);
let wr: (i64 | os.oserror) = os.writeall(fd, filebuf, filedataend);
match (wr) {
case let v: i64 => { if (v != fileend: i64) { return 1; }; };
case let v: i64 => { if (v != filedataend: i64) { return 1; }; };
case let e: os.oserror => return 1;
};
return 0;
@@ -2394,10 +2413,17 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
// .data lands at the next page boundary so the loader can give
// it fresh R+W permissions without overlapping the R+X mapping.
let dataoff: u64 = 0u64;
let datava: u64 = 0u64;
if (hasdata) {
dataoff = (rxend + PAGE_SZ - 1u64) & ~(PAGE_SZ - 1u64);
datava = base + dataoff;
};
// Apply relocations now that the layout's textva/datava are
// known. Deferred from main.ww so the dyn path uses its own
// datava.
if (relocate(l, base + TEXT_OFF, datava) != 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. Scan after l_relocate has applied any
@@ -2804,18 +2830,9 @@ export fn main(argc: i32, argv: **u8) i32 = {
};
if (resolve(l) != 0) { return 1; };
// Static layout for relocation purposes. .data lands at the
// next page after .text; the dyn path overrides if its layout
// is different (text→text PC32 cancels the absolute VA so
// only the data delta matters here).
let textva: u64 = BASE + CODE_VA_OFF;
let datava: u64 = 0u64;
if (l.datalen > 0u64) {
let page: u64 = 4096u64;
let dataoff: u64 = (CODE_VA_OFF + l.textlen + page - 1u64) & ~(page - 1u64);
datava = BASE + dataoff;
};
if (relocate(l, textva, datava) != 0) { return 1; };
// Relocation is deferred to the emit functions — each path
// knows its own layout (textva, datava); the static and dyn
// paths place .data at different VAs.
let entrysym: *lsym = lookup(l, "_start");
if (entrysym == nil) { entrysym = lookup(l, "main"); }