kit

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

commit c52ea923e42a779d36aba62e7d324f737e41cce6
parent cb5b48ffb690877237353e7f96d468cd5a847ce1
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 16 Jun 2026 14:24:54 -0700

link: honor multi-byte FILL as a repeating big-endian pattern

FILL(...) / =expr were truncated to the low byte (fill_byte = (u8)fv)
and laid down via memset. Match GNU ld: a fill value is a big-endian
byte pattern (4 bytes wide by default) repeated, phase-aligned to the
start of the segment buffer. Carry the full fill value + width through
layout and lay it down via a new script_pattern_fill helper.

Also zero-initialize the parser's KitLinkAssignment locals (the new seq
field added for inter-section ordering would otherwise be indeterminate
in non-auto-init builds).

Adds check_multibyte_fill (red->green, verified by temporarily reverting
to low-byte truncation).

Diffstat:
Msrc/link/link_layout.c | 28++++++++++++++++++++++++----
Msrc/link/link_script.c | 6++++++
Mtest/link/link_script_test.c | 27++++++++++++++-------------
3 files changed, 44 insertions(+), 17 deletions(-)

diff --git a/src/link/link_layout.c b/src/link/link_layout.c @@ -1016,6 +1016,22 @@ static int input_match_section(Linker* l, u32 ii, const KitLinkInputMatch* m, return input_match_file(l, ii, m) && match_glob(m->section_pattern.s, nm); } +/* Fill `n` bytes of `dst` with the linker-script FILL pattern. GNU ld lays a + * fill value down as a big-endian byte pattern of `width` bytes (the value's + * significant byte count; default 4) repeated across the region. The pattern + * is phase-aligned to the start of the buffer, so gaps anywhere in a section + * see a consistent repeat. width is clamped to [1,8]. */ +static void script_pattern_fill(u8* dst, size_t n, u64 value, u32 width) { + size_t i; + u8 pat[8]; + u32 w = width ? (width > 8u ? 8u : width) : 1u; + u32 b; + /* big-endian: most-significant of the `w` low bytes first */ + for (b = 0; b < w; ++b) + pat[b] = (u8)(value >> (8u * (w - 1u - b))); + for (i = 0; i < n; ++i) dst[i] = pat[i % w]; +} + static u32 script_output_input_align(Linker* l, LinkImage* img, const GcLive* g, const KitLinkOutputSection* os, @@ -1182,7 +1198,11 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, u64 sec_start_dot; u64 lma_start; u64 subalign_val = 0; - u8 fill_byte = 0; + /* GNU ld fill: a big-endian byte pattern repeated across gaps; a plain + * integer FILL value is 4 bytes wide. fill_value carries the full value; + * fill_width its significant byte count (default 4). */ + u64 fill_value = 0; + u32 fill_width = 4; u32 perms = 0; LinkSegmentId seg_id = (LinkSegmentId)(img->nsegments + 1u); LinkSegment* seg; @@ -1242,7 +1262,7 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, compiler_panic(l->c, SRCLOC_NONE, "linker script: invalid fill expression for '%.*s'", SLICE_ARG(os->name)); - fill_byte = (u8)fv; + fill_value = fv; } for (k = 0; k < os->nasns; ++k) { apply_asn(l, img, &dot, outs, script->nsections, &os->asns[k]); @@ -1440,8 +1460,8 @@ static void link_layout_sections_scripted(Linker* l, LinkImage* img, compiler_panic(img->c, SRCLOC_NONE, "link: oom on scripted segment bytes"); img->segment_bytes_cap[img->nsegments] = (size_t)file_size_accum; - memset(img->segment_bytes[img->nsegments], fill_byte, - (size_t)file_size_accum); + script_pattern_fill(img->segment_bytes[img->nsegments], + (size_t)file_size_accum, fill_value, fill_width); } { diff --git a/src/link/link_script.c b/src/link/link_script.c @@ -681,6 +681,7 @@ static int push_dot_align(LSP* p, VecAsn* asns, KitLinkExpr* align_n) { aln->kind = KIT_LE_ALIGN; aln->v.align.val = dot; aln->v.align.align = align_n; + memset(&a, 0, sizeof(a)); a.kind = KIT_LAS_DOT; a.sym = KIT_SLICE_NULL; a.expr = aln; @@ -809,6 +810,7 @@ static int parse_assignment_call(LSP* p, KitLinkAsnKind kind, VecAsn* asns) { if (!e) return 1; if (expect_ch(p, ')')) return 1; (void)match_ch(p, ';'); + memset(&a, 0, sizeof(a)); a.kind = (uint8_t)kind; a.sym = lsp_slice(p, s, n); a.expr = e; @@ -873,6 +875,7 @@ static int parse_section_body(LSP* p, VecMatch* inputs, VecAsn* asns, if (!e) return 1; if (!match_ch(p, ';')) { /* ; is optional but encouraged */ } + memset(&a, 0, sizeof(a)); a.kind = KIT_LAS_DOT; a.sym = KIT_SLICE_NULL; a.expr = e; @@ -940,6 +943,7 @@ static int parse_section_body(LSP* p, VecMatch* inputs, VecAsn* asns, e = parse_expr(p); if (!e) return 1; (void)match_ch(p, ';'); + memset(&a, 0, sizeof(a)); a.kind = KIT_LAS_SYM; a.sym = lsp_slice(p, s, n); a.expr = e; @@ -1195,6 +1199,7 @@ static int parse_sections_block(LSP* p, VecAsn* top_asns, VecSec* sections, KitLinkAssignment a; if (!e) return 1; (void)match_ch(p, ';'); + memset(&a, 0, sizeof(a)); a.kind = KIT_LAS_DOT; a.sym = KIT_SLICE_NULL; a.expr = e; @@ -1280,6 +1285,7 @@ static int parse_sections_block(LSP* p, VecAsn* top_asns, VecSec* sections, KitLinkAssignment a; if (!e) return 1; (void)match_ch(p, ';'); + memset(&a, 0, sizeof(a)); a.kind = KIT_LAS_SYM; a.sym = lsp_slice(p, s, n); a.expr = e; diff --git a/test/link/link_script_test.c b/test/link/link_script_test.c @@ -470,18 +470,18 @@ done: /* Item 3: a multi-byte fill value must be laid down as a repeating big-endian * pattern across an in-section gap rather than truncated to its low byte. */ static void check_multibyte_fill(void) { - /* .text has 4 bytes; a `. = . + 8` gap then 4 more bytes (.data). The gap - * is part of the .text output section and must be filled with the FILL - * pattern (0x12345678, big-endian, repeated). */ + /* SUBALIGN(16) forces each input to a 16-byte boundary: .text (4 bytes) at + * offset 0, .data (4 bytes) at offset 16, leaving a 12-byte gap [4..16). + * That gap must carry the FILL pattern (0x12345678, big-endian, repeated) + * rather than just its low byte (0x78). */ static const char script_text[] = "OUTPUT_FORMAT(\"elf64-littleaarch64\")\n" "OUTPUT_ARCH(aarch64)\n" "ENTRY(_start)\n" "SECTIONS {\n" " . = 0x10000;\n" - " .text : {\n" + " .text : SUBALIGN(16) {\n" " *(.text .text.*)\n" - " . = . + 8;\n" " *(.data .data.*)\n" " FILL(0x12345678)\n" " }\n" @@ -505,21 +505,22 @@ static void check_multibyte_fill(void) { if (seg.vaddr == 0x10000u) { seg_off = seg.file_off; found = 1; - EXPECT(seg.file_size == 16u, ".text segment spans text+gap+data"); + EXPECT(seg.file_size == 20u, ".text segment spans text+gap+data"); } } EXPECT(found, "found .text segment"); if (!found) goto done; img.data = kit_writer_mem_bytes(fx.w, &len); img.len = len; - EXPECT(img.data && (size_t)(seg_off + 16) <= len, "segment bytes in range"); - if (img.data && (size_t)(seg_off + 16) <= len) { + EXPECT(img.data && (size_t)(seg_off + 20) <= len, "segment bytes in range"); + if (img.data && (size_t)(seg_off + 20) <= len) { const uint8_t* p = (const uint8_t*)img.data + seg_off; - /* bytes [4..12) are the 8-byte gap, filled big-endian repeating - * 12 34 56 78 12 34 56 78 */ - static const uint8_t want[8] = {0x12, 0x34, 0x56, 0x78, - 0x12, 0x34, 0x56, 0x78}; - EXPECT(memcmp(p + 4, want, 8) == 0, + /* bytes [4..16) are the 12-byte gap; the pattern is phase-aligned to the + * start of the segment buffer, so offset 4 sees pat[4 % 4] == pat[0]: + * 12 34 56 78 repeated. */ + static const uint8_t want[12] = {0x12, 0x34, 0x56, 0x78, 0x12, 0x34, + 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}; + EXPECT(memcmp(p + 4, want, 12) == 0, "gap filled with repeated big-endian FILL pattern"); }