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;
|
||||
|
||||
|
||||
@@ -163,14 +163,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;
|
||||
@@ -466,7 +462,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;
|
||||
@@ -479,6 +476,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;
|
||||
@@ -686,7 +700,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);
|
||||
|
||||
@@ -727,10 +743,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;
|
||||
|
||||
@@ -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"); }
|
||||
|
||||
@@ -298,18 +298,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"); }
|
||||
|
||||
@@ -59,10 +59,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
|
||||
|
||||
@@ -76,6 +76,28 @@ static const struct row rows[] = {
|
||||
" return 0;\n"
|
||||
"};", 5 },
|
||||
|
||||
/* 5. Writable global on the dyn-link path. .data now lives in the
|
||||
* shared R+W segment with .got.plt and .dynamic; read and write
|
||||
* exercise the new layout's data_va. */
|
||||
{ "@symbol(\"_exit\") fn libc_exit(c: i32) void;\n"
|
||||
"let counter: i32 = 0;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" counter = 11;\n"
|
||||
" libc_exit(counter);\n"
|
||||
" return 0;\n"
|
||||
"};", 11 },
|
||||
|
||||
/* 6. Zero-init slot under dyn-link: the BSS trailing-zero scan
|
||||
* should still produce a valid p_filesz<p_memsz layout when the
|
||||
* R+W segment is the shared gotplt+dynamic+.data one. */
|
||||
{ "@symbol(\"_exit\") fn libc_exit(c: i32) void;\n"
|
||||
"let z: i64;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" z = 9i64;\n"
|
||||
" libc_exit(z: i32);\n"
|
||||
" return 0;\n"
|
||||
"};", 9 },
|
||||
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user