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:
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");
}