kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit adf6261f0dc335fc1fb5d38afb5d9b5a4cf066ec
parent 8dbe9e8841b691663cce45b6a526d2a9906109eb
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 16 Jun 2026 15:25:23 -0700

link: --section-start/-Tdata/-Tbss land at the EXACT requested vaddr (Bug 6)

link_apply_section_starts set sec->vaddr = ss->addr - img_base pre-shift, but
the ELF emitter then adds headers_load (the ehdr+phdrs page) in
shift_image_addresses and img_base in the final p_vaddr, so the section landed
at ss->addr + headers_load (e.g. --section-start=.text=0x500000 -> 0x504000).
The below-base ternary (want = addr>=base ? addr-base : addr) also re-added
the base for addresses under it.

Approach A (defer into the emitter): link_apply_section_starts now only
validates + warns at layout time; the ELF emitter's new apply_section_starts
runs AFTER shift_image_addresses and BEFORE apply_all_relocs, computing
delta = ss->addr - (img_base + post_shift_vaddr) and shifting the matched
section's whole segment (segment + its sections + the symbols and reloc sites
resolved into them) uniformly. The delta absorbs headers_load AND img_base with
no special-casing, including below-base requests, so the FINAL p_vaddr / entry
/ symbol addresses equal ss->addr exactly. -Ttext is unchanged (it sets the
ET_EXEC image base; the entry stays at base + headers page).

Removed the now-unused link_static_image_base helper; corrected the link.h
contract comment and the buildcmds section-start comment/assertions.

Tests: fx2-ss-* assert exact .text/.data vaddr + _start symbol + entry, a
below-base pin, and the ld path; the pre-existing be-section-start asserts are
tightened from a 0x50 prefix to the exact 0x500000.

Diffstat:
Minclude/kit/link.h | 13++++++++-----
Msrc/link/link_layout.c | 49+++++++++----------------------------------------
Msrc/obj/elf/link.c | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/buildcmds/run.sh | 10++++++----
4 files changed, 95 insertions(+), 49 deletions(-)

diff --git a/include/kit/link.h b/include/kit/link.h @@ -266,11 +266,14 @@ typedef struct KitLinkDefsym { } KitLinkDefsym; /* `--section-start=.name=ADDR` (and the `-Tdata`/`-Tbss` shorthands, which - * the driver folds in as `.data` / `.bss`): force an output section's runtime - * address. Applied as a post-layout vaddr override on the matching section and - * its containing segment. Honored on the default (non-scripted) static layout; - * a linker script pins addresses itself, so these are ignored when a script is - * in force. */ + * the driver folds in as `.data` / `.bss`): force an output section's *exact* + * final runtime address (GNU-ld semantics). Applied by the format emitter after + * the headers load-page and image base are known, by shifting the matched + * section's whole segment so the section lands precisely at ADDR (intra-segment + * offsets and symbol addresses move with it). Honored on the default + * (non-scripted) static layout; a linker script pins addresses itself, so these + * are ignored when a script is in force. (Distinct from `-Ttext`, which sets the + * ET_EXEC image base — the entry then sits at base + the ehdr+phdrs page.) */ typedef struct KitLinkSectionStart { KitSlice name; uint64_t addr; diff --git a/src/link/link_layout.c b/src/link/link_layout.c @@ -2038,28 +2038,17 @@ static void link_apply_defsyms(Linker* l, LinkImage* img) { } } -/* The static ET_EXEC runtime base the ELF emitter will add to image-relative - * vaddrs. Mirrors src/obj/elf/link.c's img_base rule so --section-start can - * interpret its argument as an absolute runtime address (it subtracts this - * base to recover the image-relative vaddr the layout / emit pass uses). PIE - * and scripted images keep base 0. */ -#define LINK_IMAGE_BASE_STATIC 0x400000ULL -static u64 link_static_image_base(const LinkImage* img) { - if (img->pie || img->scripted) return 0ULL; - if (img->text_base_set) return img->text_base; - return LINK_IMAGE_BASE_STATIC; -} - -/* --section-start / -Tdata / -Tbss: post-layout vaddr override for a named - * output section and its containing segment. The requested address is an - * absolute runtime address (GNU-ld semantics); it is converted to the - * image-relative vaddr space layout/emit use by subtracting the static image - * base. Ignored under a script (which pins addresses itself). Best-effort - * under the bucket layout: it shifts the matched section's segment so the - * section lands at the requested address, keeping intra-segment offsets. */ +/* --section-start / -Tdata / -Tbss: force a named output section's *final* + * runtime address to exactly the requested value (GNU-ld semantics). The actual + * placement is deferred to the format emitter (ELF: link_apply_section_starts + * in src/obj/elf/link.c, applied AFTER shift_image_addresses so the headers + * load-page and the runtime image base both fall out of the delta) — at layout + * time we only validate, since the image-relative vaddr here is not yet in the + * final coordinate system. Ignored under a script (which pins addresses + * itself). This pass emits the diagnostics (it has Linker / link_warn); the + * matched section's segment is shifted by the emitter to land it exactly. */ static void link_apply_section_starts(Linker* l, LinkImage* img) { u32 i, j; - u64 base; if (l->script) { if (l->nsection_starts) link_warn(l, @@ -2067,11 +2056,8 @@ static void link_apply_section_starts(Linker* l, LinkImage* img) { "script"); return; } - base = link_static_image_base(img); for (i = 0; i < l->nsection_starts; ++i) { const KitLinkSectionStart* ss = &l->section_starts[i]; - /* The requested absolute address, expressed in image-relative space. */ - u64 want = ss->addr >= base ? ss->addr - base : ss->addr; int matched = 0; if (!ss->name.s || ss->name.len == 0) continue; for (j = 0; j < img->nsections; ++j) { @@ -2081,23 +2067,6 @@ static void link_apply_section_starts(Linker* l, LinkImage* img) { memcmp(nm.s, ss->name.s, nm.len) != 0) continue; matched = 1; - if (sec->segment_id != LINK_SEG_NONE && - sec->segment_id <= img->nsegments) { - LinkSegment* seg = &img->segments[sec->segment_id - 1]; - u64 delta = want - sec->vaddr; - u32 k; - /* Shift the whole segment so this section lands at the requested - * address; other sections in the segment keep their relative - * placement. */ - seg->vaddr += delta; - seg->paddr += delta; - for (k = 0; k < img->nsections; ++k) { - if (img->sections[k].segment_id == seg->id) - img->sections[k].vaddr += delta; - } - } else { - sec->vaddr = want; - } } if (!matched) link_warn(l, "link: --section-start: no output section named '%.*s'", diff --git a/src/obj/elf/link.c b/src/obj/elf/link.c @@ -343,6 +343,74 @@ static void shift_image_addresses(LinkImage* img, u64 delta) { } } +/* --section-start / -Tdata / -Tbss: force the FINAL runtime address of a named + * output section to exactly the requested value (GNU-ld semantics). Applied + * here, AFTER shift_image_addresses, so the requested address is compared + * against the section's final p_vaddr (img_base + post-shift vaddr); the delta + * absorbs both the headers load-page (headers_load) and the runtime image base + * (img_base) with no special-casing, including below-base requests. The + * matched section's whole segment is shifted uniformly so intra-segment offsets + * and symbol addresses stay consistent, mirroring shift_image_addresses. Must + * run before apply_all_relocs so reloc sites see the pinned addresses. Scripted + * images pin addresses via the script and carry no pins here. */ +static void apply_section_starts(LinkImage* img, u64 img_base) { + Linker* l = img->linker; + u32 i, j; + if (!l || img->scripted) return; + for (i = 0; i < l->nsection_starts; ++i) { + const KitLinkSectionStart* ss = &l->section_starts[i]; + LinkSegmentId target_seg = LINK_SEG_NONE; + i64 delta = 0; + if (!ss->name.s || ss->name.len == 0) continue; + /* Find the matched output section and compute the shift that lands its + * final address on ss->addr. (A name-less warning was already issued at + * layout time by link_apply_section_starts.) */ + for (j = 0; j < img->nsections; ++j) { + LinkSection* sec = &img->sections[j]; + Slice nm = sec->name ? pool_slice(img->c->global, sec->name) : SLICE_NULL; + if (!nm.s || nm.len != ss->name.len || + memcmp(nm.s, ss->name.s, nm.len) != 0) + continue; + if (sec->file_only) continue; + if (sec->segment_id != LINK_SEG_NONE && + sec->segment_id <= img->nsegments) { + target_seg = sec->segment_id; + delta = (i64)ss->addr - (i64)(img_base + sec->vaddr); + } else { + /* No containing segment (rare): pin the section's vaddr directly so + * img_base + vaddr == ss->addr. */ + sec->vaddr = ss->addr - img_base; + } + break; + } + if (target_seg == LINK_SEG_NONE || delta == 0) continue; + /* Shift the whole segment (segment + its sections + the symbols and reloc + * sites resolved into them) so the pinned section lands exactly, keeping + * every relative placement within the segment. */ + img->segments[target_seg - 1].vaddr += (u64)delta; + img->segments[target_seg - 1].paddr += (u64)delta; + for (j = 0; j < img->nsections; ++j) + if (img->sections[j].segment_id == target_seg) + img->sections[j].vaddr += (u64)delta; + for (j = 0; j < LinkSyms_count(&img->syms); ++j) { + LinkSymbol* s = LinkSyms_at(&img->syms, j); + if (s->kind == SK_ABS || !s->defined) continue; + if (s->section_id == LINK_SEC_NONE || s->section_id > img->nsections) + continue; + if (img->sections[s->section_id - 1].segment_id == target_seg) + s->vaddr += (u64)delta; + } + for (j = 0; j < LinkRelocs_count(&img->relocs); ++j) { + LinkRelocApply* r = LinkRelocs_at(&img->relocs, j); + if (r->link_section_id == LINK_SEC_NONE || + r->link_section_id > img->nsections) + continue; + if (img->sections[r->link_section_id - 1].segment_id == target_seg) + r->write_vaddr += (u64)delta; + } + } +} + /* AArch64 ELF ABI: the per-thread TLS block starts at TP + 16 bytes * (the TCB sits ahead of the TLS image). RISC-V psABI normally points * tp at the start of the TLS image; the kit harness's start.c @@ -1003,6 +1071,10 @@ void link_emit_elf(LinkImage* img, Writer* w) { shift_image_file_offsets(img, headers_load); else shift_image_addresses(img, headers_load); + /* --section-start / -Tdata / -Tbss: pin chosen sections to their exact final + * runtime addresses now that vaddrs are post-shift (and before relocs see + * them). No-op for scripted images. */ + apply_section_starts(img, img_base); apply_all_relocs(img, img_base); /* ---- write .dynamic body + re-serialize .rela.dyn (PIE only) ---- diff --git a/test/buildcmds/run.sh b/test/buildcmds/run.sh @@ -350,13 +350,15 @@ run_fail be-defsym-bad "$KIT" build-exe -target x86_64-linux -nostdlib -static \ contains be-defsym-bad-diag "$work/be-defsym-bad.err" "NAME=EXPR" # ---- --section-start / -Tdata / -Tbss: per-section address overrides ------- -# These are accepted (best-effort placement; -Ttext / a linker script give -# exact addresses). Assert acceptance + determinism rather than an exact vaddr. +# The requested absolute address is the section's EXACT final runtime address +# (GNU-ld semantics). (-Ttext is the ET_EXEC header-page model — img_base plus +# the ehdr+phdrs page — so it is intentionally NOT exact; see the FX2 block at +# the end for the dedicated exact-vaddr coverage.) run_ok be-section-start "$KIT" build-exe -target x86_64-linux -nostdlib \ -static -no-pie --section-start=.text=0x500000 -Tdata 0x600000 \ --map secstart.map kstart.c -o secstart.elf -contains be-section-start-text secstart.map "vaddr=0x50" -contains be-section-start-data secstart.map "vaddr=0x60" +contains be-section-start-text secstart.map "vaddr=0x500000" +contains be-section-start-data secstart.map "vaddr=0x600000" run_ok ld-tbss "$KIT" ld -nostdlib -static -no-pie -Tdata 0x600000 \ kstart.o -o tbss.elf run_ok ld-section-start "$KIT" ld -nostdlib -static -no-pie \