kit

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

link_script_test.c (38459B)


      1 /* Public linker-script parser coverage for the kernel-script subset. */
      2 
      3 #include <kit/core.h>
      4 #include <kit/link.h>
      5 #include <kit/object.h>
      6 #include <string.h>
      7 
      8 #include "lib/kit_unit.h"
      9 
     10 static KitUnit g_u;
     11 #define EXPECT(c, ...) CU_EXPECT(&g_u, c, __VA_ARGS__)
     12 
     13 static void check_kernel_script_subset(void) {
     14   static const char script_text[] =
     15       "OUTPUT_FORMAT(\"elf64-littleaarch64\")\n"
     16       "OUTPUT_ARCH(aarch64)\n"
     17       "ENTRY(_start)\n"
     18       "EXTERN(retained)\n"
     19       "MEMORY {\n"
     20       "  ROM (rx)  : ORIGIN = 0x10000, LENGTH = 64K\n"
     21       "  RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 128K\n"
     22       "}\n"
     23       "PHDRS {\n"
     24       "  text PT_LOAD FILEHDR PHDRS FLAGS(5);\n"
     25       "  data PT_LOAD FLAGS(6);\n"
     26       "}\n"
     27       "PROVIDE_HIDDEN(__image_base = ORIGIN(ROM));\n"
     28       "SECTIONS {\n"
     29       "  . = ORIGIN(ROM) + SIZEOF_HEADERS;\n"
     30       "  .text : ALIGN(16) AT(ORIGIN(ROM)) {\n"
     31       "    KEEP(*(.text.start))\n"
     32       "    *(.text .text.*)\n"
     33       "    PROVIDE(__etext = .);\n"
     34       "  } > ROM :text =0x90\n"
     35       "  .data ORIGIN(RAM) : {\n"
     36       "    *(.rodata .rodata.*)\n"
     37       "    *(EXCLUDE_FILE (*crtend.o) .data .data.*)\n"
     38       "  } > RAM AT> ROM :data\n"
     39       "  .bss (NOLOAD) : {\n"
     40       "    __bss_start = .;\n"
     41       "    *(.bss .bss.* COMMON)\n"
     42       "    . = ALIGN(., 16);\n"
     43       "  } > RAM\n"
     44       "  ASSERT(LENGTH(ROM) - SIZEOF(.text), \"text too large\");\n"
     45       "}\n";
     46 
     47   KitLinkScript* s = NULL;
     48   KitSlice text = {.s = script_text, .len = sizeof(script_text) - 1u};
     49   EXPECT(kit_link_script_parse(&g_u.ctx, text, &s) == KIT_OK,
     50          "kernel linker script parses");
     51   EXPECT(s != NULL, "parser returns script");
     52   if (!s) return;
     53 
     54   EXPECT(kit_slice_eq_cstr(s->entry, "_start"), "ENTRY recorded");
     55   EXPECT(kit_slice_eq_cstr(s->output_format, "elf64-littleaarch64"),
     56          "OUTPUT_FORMAT recorded");
     57   EXPECT(kit_slice_eq_cstr(s->output_arch, "aarch64"),
     58          "OUTPUT_ARCH recorded");
     59   EXPECT(s->nexterns == 1 &&
     60              kit_slice_eq_cstr(s->externs[0], "retained"),
     61          "EXTERN recorded");
     62 
     63   EXPECT(s->nregions == 2, "MEMORY region count");
     64   EXPECT(kit_slice_eq_cstr(s->regions[0].name, "ROM"), "ROM region name");
     65   EXPECT(s->regions[0].origin == 0x10000u, "ROM origin");
     66   EXPECT(s->regions[0].length == 64u * 1024u, "ROM length suffix");
     67   EXPECT((s->regions[0].flags & (KIT_LRF_R | KIT_LRF_X)) ==
     68              (KIT_LRF_R | KIT_LRF_X),
     69          "ROM flags");
     70   EXPECT(kit_slice_eq_cstr(s->regions[1].name, "RAM"), "RAM region name");
     71 
     72   EXPECT(s->nphdrs == 2, "PHDRS count");
     73   EXPECT(kit_slice_eq_cstr(s->phdrs[0].name, "text"), "text PHDR name");
     74   EXPECT(s->phdrs[0].filehdr && s->phdrs[0].phdrs,
     75          "text PHDR header attrs");
     76   EXPECT(s->phdrs[0].flags &&
     77              s->phdrs[0].flags->kind == KIT_LE_INT &&
     78              s->phdrs[0].flags->v.int_val == 5,
     79          "text PHDR FLAGS");
     80 
     81   EXPECT(s->ntop_asns == 2, "top-level dot and PROVIDE_HIDDEN assignments");
     82   const KitLinkAssignment* provide = NULL;
     83   for (uint32_t i = 0; i < s->ntop_asns; ++i)
     84     if (s->top_asns[i].kind == KIT_LAS_PROVIDE_HIDDEN)
     85       provide = &s->top_asns[i];
     86   EXPECT(provide != NULL, "PROVIDE_HIDDEN assignment kind");
     87   EXPECT(provide && provide->expr &&
     88              provide->expr->kind == KIT_LE_REGION_ORIGIN,
     89          "PROVIDE_HIDDEN ORIGIN expression");
     90 
     91   EXPECT(s->nsections == 3, "output section count");
     92   const KitLinkOutputSection* text_sec = &s->sections[0];
     93   const KitLinkOutputSection* data_sec = &s->sections[1];
     94   const KitLinkOutputSection* bss_sec = &s->sections[2];
     95   EXPECT(kit_slice_eq_cstr(text_sec->name, ".text"), ".text section");
     96   EXPECT(kit_slice_eq_cstr(text_sec->region, "ROM"), ".text region");
     97   EXPECT(text_sec->lma && text_sec->lma->kind == KIT_LE_REGION_ORIGIN,
     98          ".text AT(ORIGIN)");
     99   EXPECT(text_sec->nphdrs == 1 &&
    100              kit_slice_eq_cstr(text_sec->phdrs[0], "text"),
    101          ".text PHDR reference");
    102   EXPECT(text_sec->fill && text_sec->fill->kind == KIT_LE_INT &&
    103              text_sec->fill->v.int_val == 0x90,
    104          ".text fill");
    105   EXPECT(text_sec->ninputs >= 1 && text_sec->inputs[0].keep,
    106          "KEEP input matcher");
    107 
    108   EXPECT(data_sec->vma && data_sec->vma->kind == KIT_LE_REGION_ORIGIN,
    109          ".data VMA expression");
    110   EXPECT(kit_slice_eq_cstr(data_sec->region, "RAM"), ".data VMA region");
    111   EXPECT(kit_slice_eq_cstr(data_sec->load_region, "ROM"),
    112          ".data load region");
    113   EXPECT(data_sec->nphdrs == 1 &&
    114              kit_slice_eq_cstr(data_sec->phdrs[0], "data"),
    115          ".data PHDR reference");
    116   EXPECT(data_sec->ninputs >= 4 &&
    117              data_sec->inputs[2].nexclude_file_patterns == 1 &&
    118              kit_slice_eq_cstr(data_sec->inputs[2].exclude_file_patterns[0],
    119                                "*crtend.o"),
    120          "EXCLUDE_FILE matcher");
    121 
    122   EXPECT(bss_sec->noload, ".bss NOLOAD");
    123   EXPECT(kit_slice_eq_cstr(bss_sec->region, "RAM"), ".bss region");
    124   EXPECT(bss_sec->nasns == 2, ".bss assignments");
    125   EXPECT(s->nasserts == 1 &&
    126              kit_slice_eq_cstr(s->asserts[0].message, "text too large"),
    127          "ASSERT recorded");
    128 
    129   kit_link_script_free(&g_u.ctx, s);
    130 }
    131 
    132 static KitObjBuilder* build_layout_input(KitCompiler* c) {
    133   static const uint8_t text_bytes[4] = {0xc0, 0x03, 0x5f, 0xd6};
    134   static const uint8_t data_bytes[4] = {1, 2, 3, 4};
    135   KitObjBuilder* ob = NULL;
    136   KitObjSection text = KIT_SECTION_NONE;
    137   KitObjSection data = KIT_SECTION_NONE;
    138   KitObjSymbol sym = KIT_OBJ_SYMBOL_NONE;
    139   KitObjSectionDesc text_desc;
    140   KitObjSectionDesc data_desc;
    141   KitObjSymbolDesc sym_desc;
    142 
    143   if (kit_obj_builder_new(c, &ob) != KIT_OK || !ob) {
    144     EXPECT(0, "obj builder allocation");
    145     return NULL;
    146   }
    147 
    148   memset(&text_desc, 0, sizeof(text_desc));
    149   text_desc.name = kit_sym_intern(c, KIT_SLICE_LIT(".text"));
    150   text_desc.kind = KIT_SEC_TEXT;
    151   text_desc.flags = KIT_SF_ALLOC | KIT_SF_EXEC;
    152   text_desc.align = 4;
    153   if (kit_obj_builder_section(ob, &text_desc, &text) != KIT_OK) {
    154     EXPECT(0, "create .text");
    155     goto fail;
    156   }
    157   if (kit_obj_builder_write(ob, text, text_bytes, sizeof(text_bytes)) !=
    158       KIT_OK) {
    159     EXPECT(0, "write .text");
    160     goto fail;
    161   }
    162 
    163   memset(&data_desc, 0, sizeof(data_desc));
    164   data_desc.name = kit_sym_intern(c, KIT_SLICE_LIT(".data"));
    165   data_desc.kind = KIT_SEC_DATA;
    166   data_desc.flags = KIT_SF_ALLOC | KIT_SF_WRITE;
    167   data_desc.align = 4;
    168   if (kit_obj_builder_section(ob, &data_desc, &data) != KIT_OK) {
    169     EXPECT(0, "create .data");
    170     goto fail;
    171   }
    172   if (kit_obj_builder_write(ob, data, data_bytes, sizeof(data_bytes)) !=
    173       KIT_OK) {
    174     EXPECT(0, "write .data");
    175     goto fail;
    176   }
    177 
    178   memset(&sym_desc, 0, sizeof(sym_desc));
    179   sym_desc.name = kit_sym_intern(c, KIT_SLICE_LIT("_start"));
    180   sym_desc.bind = KIT_SB_GLOBAL;
    181   sym_desc.kind = KIT_SK_FUNC;
    182   sym_desc.section = text;
    183   sym_desc.value = 0;
    184   sym_desc.size = sizeof(text_bytes);
    185   if (kit_obj_builder_symbol(ob, &sym_desc, &sym) != KIT_OK) {
    186     EXPECT(0, "define _start");
    187     goto fail;
    188   }
    189 
    190   sym = KIT_OBJ_SYMBOL_NONE;
    191   memset(&sym_desc, 0, sizeof(sym_desc));
    192   sym_desc.name = kit_sym_intern(c, KIT_SLICE_LIT("global_data"));
    193   sym_desc.bind = KIT_SB_GLOBAL;
    194   sym_desc.kind = KIT_SK_OBJ;
    195   sym_desc.section = data;
    196   sym_desc.value = 0;
    197   sym_desc.size = sizeof(data_bytes);
    198   if (kit_obj_builder_symbol(ob, &sym_desc, &sym) != KIT_OK) {
    199     EXPECT(0, "define global_data");
    200     goto fail;
    201   }
    202 
    203   if (kit_obj_builder_finalize(ob) != KIT_OK) {
    204     EXPECT(0, "finalize input object");
    205     goto fail;
    206   }
    207   return ob;
    208 
    209 fail:
    210   kit_obj_builder_free(ob);
    211   return NULL;
    212 }
    213 
    214 /* Parse `script_text`, link `build_layout_input()` against it, and open the
    215  * emitted image. On success *out_f / *out_w / *out_c / *out_s / *out_ob own
    216  * resources the caller must release (use emit_cleanup). Returns 1 on success,
    217  * 0 if any stage failed (an EXPECT already fired). */
    218 typedef struct EmitFixture {
    219   KitCompiler* c;
    220   KitObjBuilder* ob;
    221   KitLinkScript* s;
    222   KitLinkSession* sess;
    223   KitWriter* w;
    224   KitObjFile* f;
    225 } EmitFixture;
    226 
    227 static void emit_cleanup(EmitFixture* fx) {
    228   if (fx->f) kit_obj_free(fx->f);
    229   if (fx->w) kit_writer_close(fx->w);
    230   if (fx->sess) kit_link_session_free(fx->sess);
    231   if (fx->ob) kit_obj_builder_free(fx->ob);
    232   if (fx->s) kit_link_script_free(&g_u.ctx, fx->s);
    233   if (fx->c) kit_compiler_free(fx->c);
    234   memset(fx, 0, sizeof(*fx));
    235 }
    236 
    237 static int emit_layout(const char* script_text, size_t script_len,
    238                        EmitFixture* fx) {
    239   KitTargetSpec target =
    240       kit_unit_target(KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF);
    241   KitLinkSessionOptions opts;
    242   KitSlice script;
    243   KitSlice bytes;
    244   size_t len = 0;
    245 
    246   memset(fx, 0, sizeof(*fx));
    247   script.s = script_text;
    248   script.len = script_len;
    249   EXPECT(kit_link_script_parse(&g_u.ctx, script, &fx->s) == KIT_OK && fx->s,
    250          "layout script parses");
    251   if (!fx->s) return 0;
    252   EXPECT(kit_unit_compiler_new(&g_u, target, &fx->c) == KIT_OK && fx->c,
    253          "compiler allocation");
    254   if (!fx->c) return 0;
    255   fx->ob = build_layout_input(fx->c);
    256   if (!fx->ob) return 0;
    257   memset(&opts, 0, sizeof(opts));
    258   opts.output_kind = KIT_LINK_OUTPUT_EXE;
    259   opts.linker_script = fx->s;
    260   EXPECT(kit_link_session_new(fx->c, &opts, &fx->sess) == KIT_OK && fx->sess,
    261          "link session allocation");
    262   if (!fx->sess) return 0;
    263   EXPECT(kit_link_session_add_obj(fx->sess, fx->ob) == KIT_OK,
    264          "add input object");
    265   EXPECT(kit_writer_mem(&g_u.heap, &fx->w) == KIT_OK && fx->w, "memory writer");
    266   if (!fx->w) return 0;
    267   EXPECT(kit_link_session_emit(fx->sess, fx->w) == KIT_OK,
    268          "emit scripted image");
    269   bytes.data = kit_writer_mem_bytes(fx->w, &len);
    270   bytes.len = len;
    271   EXPECT(bytes.data && bytes.len > 0, "scripted image bytes");
    272   if (!bytes.data || bytes.len == 0) return 0;
    273   EXPECT(kit_obj_open(&g_u.ctx, KIT_SLICE_LIT("<scripted-layout>"), &bytes,
    274                       &fx->f) == KIT_OK &&
    275              fx->f,
    276          "open scripted image");
    277   return fx->f != NULL;
    278 }
    279 
    280 static void check_scripted_layout_vma_lma(void) {
    281   static const char script_text[] =
    282       "OUTPUT_FORMAT(\"elf64-littleaarch64\")\n"
    283       "OUTPUT_ARCH(aarch64)\n"
    284       "ENTRY(_start)\n"
    285       "MEMORY {\n"
    286       "  ROM (rx)  : ORIGIN = 0x10000, LENGTH = 64K\n"
    287       "  RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 64K\n"
    288       "}\n"
    289       "PHDRS {\n"
    290       "  text PT_LOAD FLAGS(5);\n"
    291       "  data PT_LOAD FLAGS(6);\n"
    292       "}\n"
    293       "SECTIONS {\n"
    294       "  .text : { KEEP(*(.text .text.*)) } > ROM :text\n"
    295       "  .data ORIGIN(RAM) : { *(.data .data.*) } > RAM AT> ROM :data\n"
    296       "}\n";
    297   KitTargetSpec target =
    298       kit_unit_target(KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF);
    299   KitCompiler* c = NULL;
    300   KitObjBuilder* ob = NULL;
    301   KitLinkScript* s = NULL;
    302   KitLinkSession* sess = NULL;
    303   KitWriter* w = NULL;
    304   KitObjFile* f = NULL;
    305   KitObjSegIter* it = NULL;
    306   KitSlice script;
    307   KitSlice bytes;
    308   KitLinkSessionOptions opts;
    309   size_t len = 0;
    310   uint32_t nload = 0;
    311   int saw_text = 0;
    312   int saw_data = 0;
    313 
    314   script.s = script_text;
    315   script.len = sizeof(script_text) - 1u;
    316   EXPECT(kit_link_script_parse(&g_u.ctx, script, &s) == KIT_OK && s,
    317          "layout script parses");
    318   if (!s) goto done;
    319 
    320   EXPECT(kit_unit_compiler_new(&g_u, target, &c) == KIT_OK && c,
    321          "compiler allocation");
    322   if (!c) goto done;
    323 
    324   ob = build_layout_input(c);
    325   if (!ob) goto done;
    326 
    327   memset(&opts, 0, sizeof(opts));
    328   opts.output_kind = KIT_LINK_OUTPUT_EXE;
    329   opts.linker_script = s;
    330   EXPECT(kit_link_session_new(c, &opts, &sess) == KIT_OK && sess,
    331          "link session allocation");
    332   if (!sess) goto done;
    333   EXPECT(kit_link_session_add_obj(sess, ob) == KIT_OK, "add input object");
    334   EXPECT(kit_writer_mem(&g_u.heap, &w) == KIT_OK && w, "memory writer");
    335   if (!w) goto done;
    336   EXPECT(kit_link_session_emit(sess, w) == KIT_OK, "emit scripted image");
    337   bytes.data = kit_writer_mem_bytes(w, &len);
    338   bytes.len = len;
    339   EXPECT(bytes.data && bytes.len > 0, "scripted image bytes");
    340   if (!bytes.data || bytes.len == 0) goto done;
    341   EXPECT(kit_obj_open(&g_u.ctx, KIT_SLICE_LIT("<scripted-layout>"), &bytes,
    342                       &f) == KIT_OK &&
    343              f,
    344          "open scripted image");
    345   if (!f) goto done;
    346   EXPECT(kit_obj_kind(f) == KIT_OBJ_KIND_EXEC, "scripted image is executable");
    347   EXPECT(kit_obj_segiter_new(f, &it) == KIT_OK && it, "segment iterator");
    348   if (!it) goto done;
    349   for (;;) {
    350     KitObjSegInfo seg;
    351     KitIterResult r = kit_obj_segiter_next(it, &seg);
    352     if (r == KIT_ITER_END) break;
    353     EXPECT(r == KIT_ITER_ITEM, "segment iteration");
    354     if (r != KIT_ITER_ITEM) break;
    355     if (!kit_slice_eq_cstr(seg.name, "LOAD")) continue;
    356     ++nload;
    357     if (seg.vaddr == 0x10000u) {
    358       saw_text = 1;
    359       EXPECT(seg.paddr == 0x10000u, ".text LMA follows ROM origin");
    360       EXPECT(seg.file_size == 4u && seg.vsize == 4u, ".text segment size");
    361       EXPECT(seg.perms == (KIT_SEG_R | KIT_SEG_X), ".text PHDR flags");
    362     } else if (seg.vaddr == 0x80000000u) {
    363       saw_data = 1;
    364       EXPECT(seg.paddr == 0x10004u, ".data AT> ROM follows .text bytes");
    365       EXPECT(seg.file_size == 4u && seg.vsize == 4u, ".data segment size");
    366       EXPECT(seg.perms == (KIT_SEG_R | KIT_SEG_W), ".data PHDR flags");
    367     }
    368   }
    369   EXPECT(nload == 2, "scripted image LOAD segment count");
    370   EXPECT(saw_text, "scripted image text LOAD");
    371   EXPECT(saw_data, "scripted image data LOAD");
    372 
    373 done:
    374   if (it) kit_obj_segiter_free(it);
    375   if (f) kit_obj_free(f);
    376   if (w) kit_writer_close(w);
    377   if (sess) kit_link_session_free(sess);
    378   if (ob) kit_obj_builder_free(ob);
    379   if (s) kit_link_script_free(&g_u.ctx, s);
    380   if (c) kit_compiler_free(c);
    381 }
    382 
    383 /* Item 1: a PROGBITS-content output section marked (NOLOAD) must contribute
    384  * zero file bytes and present a NOBITS-style segment (file_size 0, vsize > 0). */
    385 static void check_noload_progbits(void) {
    386   static const char script_text[] =
    387       "OUTPUT_FORMAT(\"elf64-littleaarch64\")\n"
    388       "OUTPUT_ARCH(aarch64)\n"
    389       "ENTRY(_start)\n"
    390       "SECTIONS {\n"
    391       "  . = 0x10000;\n"
    392       "  .text : { *(.text .text.*) }\n"
    393       "  .shadow (NOLOAD) : { *(.data .data.*) }\n"
    394       "}\n";
    395   EmitFixture fx;
    396   KitObjSegIter* it = NULL;
    397   int saw_shadow = 0;
    398   uint64_t text_end = 0;
    399 
    400   if (!emit_layout(script_text, sizeof(script_text) - 1u, &fx)) goto done;
    401   EXPECT(kit_obj_segiter_new(fx.f, &it) == KIT_OK && it, "segment iterator");
    402   if (!it) goto done;
    403   for (;;) {
    404     KitObjSegInfo seg;
    405     KitIterResult r = kit_obj_segiter_next(it, &seg);
    406     if (r == KIT_ITER_END) break;
    407     if (r != KIT_ITER_ITEM) break;
    408     if (!kit_slice_eq_cstr(seg.name, "LOAD")) continue;
    409     if (seg.file_size == 4u && seg.perms == (KIT_SEG_R | KIT_SEG_X))
    410       text_end = seg.vaddr + seg.vsize;
    411     /* the shadow segment carries the .data bytes (4) as pure bss */
    412     if (seg.vsize == 4u && seg.file_size == 0u &&
    413         (seg.perms & KIT_SEG_X) == 0) {
    414       saw_shadow = 1;
    415       EXPECT(seg.file_size == 0u, "NOLOAD section contributes no file bytes");
    416       EXPECT(seg.vsize == 4u, "NOLOAD section keeps memory size");
    417     }
    418   }
    419   EXPECT(text_end != 0u, "saw .text LOAD before shadow");
    420   EXPECT(saw_shadow, "saw NOLOAD shadow segment with zero file size");
    421 
    422 done:
    423   if (it) kit_obj_segiter_free(it);
    424   emit_cleanup(&fx);
    425 }
    426 
    427 /* Item 2: an inter-section `. = ALIGN(0x1000)` written between two output
    428  * sections must advance the location counter at its textual position so the
    429  * following section lands at the aligned address. */
    430 static void check_intersection_dot_align(void) {
    431   static const char script_text[] =
    432       "OUTPUT_FORMAT(\"elf64-littleaarch64\")\n"
    433       "OUTPUT_ARCH(aarch64)\n"
    434       "ENTRY(_start)\n"
    435       "SECTIONS {\n"
    436       "  . = 0x10000;\n"
    437       "  .text : { *(.text .text.*) }\n"
    438       "  . = ALIGN(0x1000);\n"
    439       "  .data : { *(.data .data.*) }\n"
    440       "}\n";
    441   EmitFixture fx;
    442   KitObjSegIter* it = NULL;
    443   int saw_text = 0;
    444   int saw_data = 0;
    445 
    446   if (!emit_layout(script_text, sizeof(script_text) - 1u, &fx)) goto done;
    447   EXPECT(kit_obj_segiter_new(fx.f, &it) == KIT_OK && it, "segment iterator");
    448   if (!it) goto done;
    449   for (;;) {
    450     KitObjSegInfo seg;
    451     KitIterResult r = kit_obj_segiter_next(it, &seg);
    452     if (r == KIT_ITER_END) break;
    453     if (r != KIT_ITER_ITEM) break;
    454     if (!kit_slice_eq_cstr(seg.name, "LOAD")) continue;
    455     if (seg.vaddr == 0x10000u) saw_text = 1;
    456     if (seg.perms & KIT_SEG_W) {
    457       saw_data = 1;
    458       EXPECT(seg.vaddr == 0x11000u,
    459              ".data lands at . = ALIGN(0x1000) after .text");
    460     }
    461   }
    462   EXPECT(saw_text, "saw .text at 0x10000");
    463   EXPECT(saw_data, "saw .data aligned to 0x1000");
    464 
    465 done:
    466   if (it) kit_obj_segiter_free(it);
    467   emit_cleanup(&fx);
    468 }
    469 
    470 /* Item 3: a multi-byte fill value must be laid down as a repeating big-endian
    471  * pattern across an in-section gap rather than truncated to its low byte. */
    472 static void check_multibyte_fill(void) {
    473   /* SUBALIGN(16) forces each input to a 16-byte boundary: .text (4 bytes) at
    474    * offset 0, .data (4 bytes) at offset 16, leaving a 12-byte gap [4..16).
    475    * That gap must carry the FILL pattern (0x12345678, big-endian, repeated)
    476    * rather than just its low byte (0x78). */
    477   static const char script_text[] =
    478       "OUTPUT_FORMAT(\"elf64-littleaarch64\")\n"
    479       "OUTPUT_ARCH(aarch64)\n"
    480       "ENTRY(_start)\n"
    481       "SECTIONS {\n"
    482       "  . = 0x10000;\n"
    483       "  .text : SUBALIGN(16) {\n"
    484       "    *(.text .text.*)\n"
    485       "    *(.data .data.*)\n"
    486       "    FILL(0x12345678)\n"
    487       "  }\n"
    488       "}\n";
    489   EmitFixture fx;
    490   KitObjSegIter* it = NULL;
    491   KitSlice img;
    492   size_t len = 0;
    493   uint64_t seg_off = 0;
    494   int found = 0;
    495 
    496   if (!emit_layout(script_text, sizeof(script_text) - 1u, &fx)) goto done;
    497   EXPECT(kit_obj_segiter_new(fx.f, &it) == KIT_OK && it, "segment iterator");
    498   if (!it) goto done;
    499   for (;;) {
    500     KitObjSegInfo seg;
    501     KitIterResult r = kit_obj_segiter_next(it, &seg);
    502     if (r == KIT_ITER_END) break;
    503     if (r != KIT_ITER_ITEM) break;
    504     if (!kit_slice_eq_cstr(seg.name, "LOAD")) continue;
    505     if (seg.vaddr == 0x10000u) {
    506       seg_off = seg.file_off;
    507       found = 1;
    508       EXPECT(seg.file_size == 20u, ".text segment spans text+gap+data");
    509     }
    510   }
    511   EXPECT(found, "found .text segment");
    512   if (!found) goto done;
    513   img.data = kit_writer_mem_bytes(fx.w, &len);
    514   img.len = len;
    515   EXPECT(img.data && (size_t)(seg_off + 20) <= len, "segment bytes in range");
    516   if (img.data && (size_t)(seg_off + 20) <= len) {
    517     const uint8_t* p = (const uint8_t*)img.data + seg_off;
    518     /* bytes [4..16) are the 12-byte gap; the pattern is phase-aligned to the
    519      * start of the segment buffer, so offset 4 sees pat[4 % 4] == pat[0]:
    520      * 12 34 56 78 repeated. */
    521     static const uint8_t want[12] = {0x12, 0x34, 0x56, 0x78, 0x12, 0x34,
    522                                      0x56, 0x78, 0x12, 0x34, 0x56, 0x78};
    523     EXPECT(memcmp(p + 4, want, 12) == 0,
    524            "gap filled with repeated big-endian FILL pattern");
    525   }
    526 
    527 done:
    528   if (it) kit_obj_segiter_free(it);
    529   emit_cleanup(&fx);
    530 }
    531 
    532 /* Item 4: (a) two output sections naming the same :phdr coalesce into a single
    533  * PT_LOAD; (b) a section listing several phdrs appears under each named
    534  * segment. */
    535 static void check_phdr_coalesce_and_multi(void) {
    536   static const char script_text[] =
    537       "OUTPUT_FORMAT(\"elf64-littleaarch64\")\n"
    538       "OUTPUT_ARCH(aarch64)\n"
    539       "ENTRY(_start)\n"
    540       "PHDRS {\n"
    541       "  text PT_LOAD FLAGS(5);\n"
    542       "  data PT_LOAD FLAGS(6);\n"
    543       "}\n"
    544       "SECTIONS {\n"
    545       "  . = 0x10000;\n"
    546       "  .text : { *(.text .text.*) } :text\n"
    547       "  .also : { *(.data .data.*) } :text :data\n"
    548       "}\n";
    549   EmitFixture fx;
    550   KitObjSegIter* it = NULL;
    551   uint32_t nload = 0;
    552   uint32_t n_rx = 0; /* PT_LOAD with R|X (the coalesced text phdr) */
    553   uint32_t n_rw = 0; /* PT_LOAD with R|W (the data phdr) */
    554 
    555   if (!emit_layout(script_text, sizeof(script_text) - 1u, &fx)) goto done;
    556   EXPECT(kit_obj_segiter_new(fx.f, &it) == KIT_OK && it, "segment iterator");
    557   if (!it) goto done;
    558   for (;;) {
    559     KitObjSegInfo seg;
    560     KitIterResult r = kit_obj_segiter_next(it, &seg);
    561     if (r == KIT_ITER_END) break;
    562     if (r != KIT_ITER_ITEM) break;
    563     if (!kit_slice_eq_cstr(seg.name, "LOAD")) continue;
    564     ++nload;
    565     if (seg.perms == (KIT_SEG_R | KIT_SEG_X)) ++n_rx;
    566     if (seg.perms == (KIT_SEG_R | KIT_SEG_W)) ++n_rw;
    567   }
    568   /* .text and .also both name :text -> one R|X PT_LOAD; .also also names
    569    * :data -> a second PT_LOAD with the data flags. So exactly 2 PT_LOAD. */
    570   EXPECT(nload == 2, "two sections sharing :text coalesce; :data adds one");
    571   EXPECT(n_rx == 1, "single coalesced R|X text segment");
    572   EXPECT(n_rw == 1, ".also also appears under the :data segment");
    573 
    574 done:
    575   if (it) kit_obj_segiter_free(it);
    576   emit_cleanup(&fx);
    577 }
    578 
    579 /* Build an input object whose .data output-bound content is followed by a
    580  * NOBITS .bss tail, so that an output section collecting both gets a PROGBITS
    581  * head + NOBITS tail (mem_size > file_size). Defines _start (.text),
    582  * data_sym (.data), and bss_sym (.bss) so a linked image exposes their vaddrs. */
    583 static KitObjBuilder* build_bss_tail_input(KitCompiler* c) {
    584   static const uint8_t text_bytes[4] = {0xc0, 0x03, 0x5f, 0xd6};
    585   static const uint8_t data_bytes[4] = {1, 2, 3, 4};
    586   KitObjBuilder* ob = NULL;
    587   KitObjSection text = KIT_SECTION_NONE;
    588   KitObjSection data = KIT_SECTION_NONE;
    589   KitObjSection bss = KIT_SECTION_NONE;
    590   KitObjSymbol sym = KIT_OBJ_SYMBOL_NONE;
    591   KitObjSectionDesc desc;
    592   KitObjSymbolDesc sym_desc;
    593 
    594   if (kit_obj_builder_new(c, &ob) != KIT_OK || !ob) {
    595     EXPECT(0, "obj builder allocation");
    596     return NULL;
    597   }
    598 
    599   memset(&desc, 0, sizeof(desc));
    600   desc.name = kit_sym_intern(c, KIT_SLICE_LIT(".text"));
    601   desc.kind = KIT_SEC_TEXT;
    602   desc.flags = KIT_SF_ALLOC | KIT_SF_EXEC;
    603   desc.align = 4;
    604   if (kit_obj_builder_section(ob, &desc, &text) != KIT_OK ||
    605       kit_obj_builder_write(ob, text, text_bytes, sizeof(text_bytes)) !=
    606           KIT_OK) {
    607     EXPECT(0, "create+write .text");
    608     goto fail;
    609   }
    610 
    611   memset(&desc, 0, sizeof(desc));
    612   desc.name = kit_sym_intern(c, KIT_SLICE_LIT(".data"));
    613   desc.kind = KIT_SEC_DATA;
    614   desc.flags = KIT_SF_ALLOC | KIT_SF_WRITE;
    615   desc.align = 4;
    616   if (kit_obj_builder_section(ob, &desc, &data) != KIT_OK ||
    617       kit_obj_builder_write(ob, data, data_bytes, sizeof(data_bytes)) !=
    618           KIT_OK) {
    619     EXPECT(0, "create+write .data");
    620     goto fail;
    621   }
    622 
    623   memset(&desc, 0, sizeof(desc));
    624   desc.name = kit_sym_intern(c, KIT_SLICE_LIT(".bss"));
    625   desc.kind = KIT_SEC_BSS;
    626   desc.flags = KIT_SF_ALLOC | KIT_SF_WRITE;
    627   desc.align = 8;
    628   if (kit_obj_builder_section(ob, &desc, &bss) != KIT_OK ||
    629       kit_obj_builder_reserve_bss(ob, bss, 0x40u, 8u) != KIT_OK) {
    630     EXPECT(0, "create+reserve .bss");
    631     goto fail;
    632   }
    633 
    634   memset(&sym_desc, 0, sizeof(sym_desc));
    635   sym_desc.name = kit_sym_intern(c, KIT_SLICE_LIT("_start"));
    636   sym_desc.bind = KIT_SB_GLOBAL;
    637   sym_desc.kind = KIT_SK_FUNC;
    638   sym_desc.section = text;
    639   sym_desc.size = sizeof(text_bytes);
    640   if (kit_obj_builder_symbol(ob, &sym_desc, &sym) != KIT_OK) {
    641     EXPECT(0, "define _start");
    642     goto fail;
    643   }
    644 
    645   sym = KIT_OBJ_SYMBOL_NONE;
    646   memset(&sym_desc, 0, sizeof(sym_desc));
    647   sym_desc.name = kit_sym_intern(c, KIT_SLICE_LIT("data_sym"));
    648   sym_desc.bind = KIT_SB_GLOBAL;
    649   sym_desc.kind = KIT_SK_OBJ;
    650   sym_desc.section = data;
    651   sym_desc.size = sizeof(data_bytes);
    652   if (kit_obj_builder_symbol(ob, &sym_desc, &sym) != KIT_OK) {
    653     EXPECT(0, "define data_sym");
    654     goto fail;
    655   }
    656 
    657   sym = KIT_OBJ_SYMBOL_NONE;
    658   memset(&sym_desc, 0, sizeof(sym_desc));
    659   sym_desc.name = kit_sym_intern(c, KIT_SLICE_LIT("bss_sym"));
    660   sym_desc.bind = KIT_SB_GLOBAL;
    661   sym_desc.kind = KIT_SK_OBJ;
    662   sym_desc.section = bss;
    663   sym_desc.size = 0x40u;
    664   if (kit_obj_builder_symbol(ob, &sym_desc, &sym) != KIT_OK) {
    665     EXPECT(0, "define bss_sym");
    666     goto fail;
    667   }
    668 
    669   if (kit_obj_builder_finalize(ob) != KIT_OK) {
    670     EXPECT(0, "finalize bss-tail input");
    671     goto fail;
    672   }
    673   return ob;
    674 
    675 fail:
    676   kit_obj_builder_free(ob);
    677   return NULL;
    678 }
    679 
    680 /* Like emit_layout, but uses build_bss_tail_input as the link input. */
    681 static int emit_layout_with(const char* script_text, size_t script_len,
    682                             KitObjBuilder* (*build)(KitCompiler*),
    683                             EmitFixture* fx) {
    684   KitTargetSpec target =
    685       kit_unit_target(KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF);
    686   KitLinkSessionOptions opts;
    687   KitSlice script;
    688   KitSlice bytes;
    689   size_t len = 0;
    690 
    691   memset(fx, 0, sizeof(*fx));
    692   script.s = script_text;
    693   script.len = script_len;
    694   EXPECT(kit_link_script_parse(&g_u.ctx, script, &fx->s) == KIT_OK && fx->s,
    695          "layout script parses");
    696   if (!fx->s) return 0;
    697   EXPECT(kit_unit_compiler_new(&g_u, target, &fx->c) == KIT_OK && fx->c,
    698          "compiler allocation");
    699   if (!fx->c) return 0;
    700   fx->ob = build(fx->c);
    701   if (!fx->ob) return 0;
    702   memset(&opts, 0, sizeof(opts));
    703   opts.output_kind = KIT_LINK_OUTPUT_EXE;
    704   opts.linker_script = fx->s;
    705   EXPECT(kit_link_session_new(fx->c, &opts, &fx->sess) == KIT_OK && fx->sess,
    706          "link session allocation");
    707   if (!fx->sess) return 0;
    708   EXPECT(kit_link_session_add_obj(fx->sess, fx->ob) == KIT_OK,
    709          "add input object");
    710   EXPECT(kit_writer_mem(&g_u.heap, &fx->w) == KIT_OK && fx->w, "memory writer");
    711   if (!fx->w) return 0;
    712   EXPECT(kit_link_session_emit(fx->sess, fx->w) == KIT_OK,
    713          "emit scripted image");
    714   bytes.data = kit_writer_mem_bytes(fx->w, &len);
    715   bytes.len = len;
    716   EXPECT(bytes.data && bytes.len > 0, "scripted image bytes");
    717   if (!bytes.data || bytes.len == 0) return 0;
    718   EXPECT(kit_obj_open(&g_u.ctx, KIT_SLICE_LIT("<scripted-layout>"), &bytes,
    719                       &fx->f) == KIT_OK &&
    720              fx->f,
    721          "open scripted image");
    722   return fx->f != NULL;
    723 }
    724 
    725 /* Bug 1: an output section with a PROGBITS head + NOBITS (bss) tail must NOT
    726  * coalesce a following same-:phdr, vaddr-contiguous output section into the
    727  * same PT_LOAD by packing its file bytes file-contiguously — that would skew
    728  * the follower's loaded bytes down by the bss-tail size relative to its vaddr.
    729  *
    730  * .first collects .data (4 PROGBITS bytes) + .bss (0x40 NOBITS bytes) so its
    731  * mem_size (0x44) > file_size (4). .second collects .text and shares :ld and is
    732  * vaddr-contiguous. We assert no vaddr<->file-offset skew: the segment carrying
    733  * .second's bytes must place them so (file_off - seg_file_off) matches
    734  * (vaddr - seg_vaddr). The safe fix splits into two PT_LOADs. */
    735 static void check_bss_tail_no_coalesce_skew(void) {
    736   static const char script_text[] =
    737       "OUTPUT_FORMAT(\"elf64-littleaarch64\")\n"
    738       "OUTPUT_ARCH(aarch64)\n"
    739       "ENTRY(_start)\n"
    740       "PHDRS {\n"
    741       "  ld PT_LOAD FLAGS(6);\n"
    742       "}\n"
    743       "SECTIONS {\n"
    744       "  . = 0x10000;\n"
    745       "  .first : { *(.data .data.*) *(.bss .bss.*) } :ld\n"
    746       "  .second : { *(.text .text.*) } :ld\n"
    747       "}\n";
    748   EmitFixture fx;
    749   KitObjSegIter* it = NULL;
    750   KitObjSymInfo text_sym;
    751   uint64_t text_vaddr = 0;
    752   int saw_text_load = 0;
    753   int got_text_sym = 0;
    754 
    755   if (!emit_layout_with(script_text, sizeof(script_text) - 1u,
    756                         build_bss_tail_input, &fx))
    757     goto done;
    758 
    759   got_text_sym = kit_obj_symbol_by_name(fx.f, KIT_SLICE_LIT("_start"),
    760                                         &text_sym) == KIT_OK;
    761   EXPECT(got_text_sym, "_start resolves in linked image");
    762   if (!got_text_sym) goto done;
    763   text_vaddr = text_sym.value;
    764 
    765   EXPECT(kit_obj_segiter_new(fx.f, &it) == KIT_OK && it, "segment iterator");
    766   if (!it) goto done;
    767   for (;;) {
    768     KitObjSegInfo seg;
    769     KitIterResult r = kit_obj_segiter_next(it, &seg);
    770     if (r == KIT_ITER_END) break;
    771     if (r != KIT_ITER_ITEM) break;
    772     if (!kit_slice_eq_cstr(seg.name, "LOAD")) continue;
    773     /* Find the LOAD segment that contains _start's vaddr. */
    774     if (text_vaddr >= seg.vaddr && text_vaddr < seg.vaddr + seg.vsize) {
    775       uint64_t voff = text_vaddr - seg.vaddr;
    776       saw_text_load = 1;
    777       /* The byte for _start must be file-resident at the matching offset:
    778        * its file delta must equal its vaddr delta (no NOBITS-tail skew). */
    779       EXPECT(voff < seg.file_size,
    780              "_start's vaddr maps to a file-resident byte (no bss-tail skew)");
    781       /* And the segment's own geometry must be self-consistent: a PROGBITS
    782        * follower coalesced after a bss tail would have file_size < the offset
    783        * at which its bytes sit. The split-PT_LOAD fix keeps file_size==vsize
    784        * for the .text-only segment. */
    785       EXPECT(seg.file_size >= voff + 4u,
    786              "_start's 4 bytes lie within the segment file image");
    787     }
    788   }
    789   EXPECT(saw_text_load, "found the LOAD segment carrying _start");
    790 
    791 done:
    792   if (it) kit_obj_segiter_free(it);
    793   emit_cleanup(&fx);
    794 }
    795 
    796 /* Bug 2: a FLAGS-less PHDRS PT_LOAD that coalesces an RX section and an RW
    797  * section must emit p_flags = R|W|X (the union of mapped sections' perms),
    798  * matching GNU ld. Without the perms-union, the single PT_LOAD would carry only
    799  * the FIRST section's perms. Two order variants pin this down. */
    800 static void check_coalesce_perms_union(void) {
    801   static const char script_rx_first[] =
    802       "OUTPUT_FORMAT(\"elf64-littleaarch64\")\n"
    803       "OUTPUT_ARCH(aarch64)\n"
    804       "ENTRY(_start)\n"
    805       "PHDRS {\n"
    806       "  ld PT_LOAD;\n"
    807       "}\n"
    808       "SECTIONS {\n"
    809       "  . = 0x10000;\n"
    810       "  .text : { *(.text .text.*) } :ld\n"
    811       "  .data : { *(.data .data.*) } :ld\n"
    812       "}\n";
    813   static const char script_rw_first[] =
    814       "OUTPUT_FORMAT(\"elf64-littleaarch64\")\n"
    815       "OUTPUT_ARCH(aarch64)\n"
    816       "ENTRY(_start)\n"
    817       "PHDRS {\n"
    818       "  ld PT_LOAD;\n"
    819       "}\n"
    820       "SECTIONS {\n"
    821       "  . = 0x10000;\n"
    822       "  .data : { *(.data .data.*) } :ld\n"
    823       "  .text : { *(.text .text.*) } :ld\n"
    824       "}\n";
    825   const char* variants[2];
    826   size_t lens[2];
    827   unsigned v;
    828   variants[0] = script_rx_first;
    829   lens[0] = sizeof(script_rx_first) - 1u;
    830   variants[1] = script_rw_first;
    831   lens[1] = sizeof(script_rw_first) - 1u;
    832 
    833   for (v = 0; v < 2; ++v) {
    834     EmitFixture fx;
    835     KitObjSegIter* it = NULL;
    836     uint32_t nload = 0;
    837     int saw_rwx = 0;
    838 
    839     if (!emit_layout(variants[v], lens[v], &fx)) {
    840       emit_cleanup(&fx);
    841       continue;
    842     }
    843     EXPECT(kit_obj_segiter_new(fx.f, &it) == KIT_OK && it, "segment iterator");
    844     if (!it) {
    845       emit_cleanup(&fx);
    846       continue;
    847     }
    848     for (;;) {
    849       KitObjSegInfo seg;
    850       KitIterResult r = kit_obj_segiter_next(it, &seg);
    851       if (r == KIT_ITER_END) break;
    852       if (r != KIT_ITER_ITEM) break;
    853       if (!kit_slice_eq_cstr(seg.name, "LOAD")) continue;
    854       ++nload;
    855       if (seg.perms == (KIT_SEG_R | KIT_SEG_W | KIT_SEG_X)) ++saw_rwx;
    856     }
    857     EXPECT(nload == 1,
    858            v == 0 ? "RX-then-RW coalesces into one PT_LOAD"
    859                   : "RW-then-RX coalesces into one PT_LOAD");
    860     EXPECT(saw_rwx == 1,
    861            v == 0 ? "RX-first coalesced PT_LOAD has R|W|X perms (union)"
    862                   : "RW-first coalesced PT_LOAD has R|W|X perms (union)");
    863     if (it) kit_obj_segiter_free(it);
    864     emit_cleanup(&fx);
    865   }
    866 }
    867 
    868 /* Item 5: deeply nested parentheses must fail cleanly (diagnostic) rather than
    869  * overflow the parser/eval stack. */
    870 static void check_recursion_depth_guard(void) {
    871   /* 512 nested parens around an int — well past the 256 limit. */
    872   char buf[2048];
    873   size_t pos = 0;
    874   unsigned i;
    875   KitLinkScript* s = NULL;
    876   KitSlice text;
    877   static const char prefix[] = "SECTIONS {\n  . = ";
    878   static const char suffix[] = ";\n}\n";
    879   memcpy(buf + pos, prefix, sizeof(prefix) - 1);
    880   pos += sizeof(prefix) - 1;
    881   for (i = 0; i < 512; ++i) buf[pos++] = '(';
    882   buf[pos++] = '0';
    883   for (i = 0; i < 512; ++i) buf[pos++] = ')';
    884   memcpy(buf + pos, suffix, sizeof(suffix) - 1);
    885   pos += sizeof(suffix) - 1;
    886   text.s = buf;
    887   text.len = pos;
    888   /* must return an error, not crash, and not produce a script */
    889   EXPECT(kit_link_script_parse(&g_u.ctx, text, &s) != KIT_OK,
    890          "deeply nested parens rejected, no crash");
    891   if (s) kit_link_script_free(&g_u.ctx, s);
    892 }
    893 
    894 /* Build an input with two writable data sections (.data = 4 bytes, .data.more
    895  * = 8 bytes) plus a _start in .text, so a section body can glob each
    896  * separately and place interior `sym = .` markers between them. */
    897 static KitObjBuilder* build_two_data_input(KitCompiler* c) {
    898   static const uint8_t text_bytes[4] = {0xc0, 0x03, 0x5f, 0xd6};
    899   static const uint8_t data_bytes[4] = {1, 2, 3, 4};
    900   static const uint8_t data2_bytes[8] = {5, 6, 7, 8, 9, 10, 11, 12};
    901   KitObjBuilder* ob = NULL;
    902   KitObjSection text = KIT_SECTION_NONE;
    903   KitObjSection data = KIT_SECTION_NONE;
    904   KitObjSection data2 = KIT_SECTION_NONE;
    905   KitObjSymbol sym = KIT_OBJ_SYMBOL_NONE;
    906   KitObjSectionDesc d;
    907   KitObjSymbolDesc sd;
    908 
    909   if (kit_obj_builder_new(c, &ob) != KIT_OK || !ob) {
    910     EXPECT(0, "obj builder allocation");
    911     return NULL;
    912   }
    913   memset(&d, 0, sizeof(d));
    914   d.name = kit_sym_intern(c, KIT_SLICE_LIT(".text"));
    915   d.kind = KIT_SEC_TEXT;
    916   d.flags = KIT_SF_ALLOC | KIT_SF_EXEC;
    917   d.align = 4;
    918   if (kit_obj_builder_section(ob, &d, &text) != KIT_OK ||
    919       kit_obj_builder_write(ob, text, text_bytes, sizeof(text_bytes)) != KIT_OK)
    920     goto fail;
    921 
    922   memset(&d, 0, sizeof(d));
    923   d.name = kit_sym_intern(c, KIT_SLICE_LIT(".data"));
    924   d.kind = KIT_SEC_DATA;
    925   d.flags = KIT_SF_ALLOC | KIT_SF_WRITE;
    926   d.align = 4;
    927   if (kit_obj_builder_section(ob, &d, &data) != KIT_OK ||
    928       kit_obj_builder_write(ob, data, data_bytes, sizeof(data_bytes)) != KIT_OK)
    929     goto fail;
    930 
    931   memset(&d, 0, sizeof(d));
    932   d.name = kit_sym_intern(c, KIT_SLICE_LIT(".data.more"));
    933   d.kind = KIT_SEC_DATA;
    934   d.flags = KIT_SF_ALLOC | KIT_SF_WRITE;
    935   d.align = 4;
    936   if (kit_obj_builder_section(ob, &d, &data2) != KIT_OK ||
    937       kit_obj_builder_write(ob, data2, data2_bytes, sizeof(data2_bytes)) !=
    938           KIT_OK)
    939     goto fail;
    940 
    941   memset(&sd, 0, sizeof(sd));
    942   sd.name = kit_sym_intern(c, KIT_SLICE_LIT("_start"));
    943   sd.bind = KIT_SB_GLOBAL;
    944   sd.kind = KIT_SK_FUNC;
    945   sd.section = text;
    946   sd.value = 0;
    947   sd.size = sizeof(text_bytes);
    948   if (kit_obj_builder_symbol(ob, &sd, &sym) != KIT_OK) goto fail;
    949 
    950   if (kit_obj_builder_finalize(ob) != KIT_OK) goto fail;
    951   return ob;
    952 fail:
    953   EXPECT(0, "build two-data input");
    954   kit_obj_builder_free(ob);
    955   return NULL;
    956 }
    957 
    958 /* Bug fix: section-body commands execute in source order, so a `sym = .` after
    959  * an input-section glob captures the location counter ADVANCED past the glob's
    960  * content, and `. = ALIGN(N)` after the last glob extends the section. Before
    961  * the fix every body assignment was evaluated against the section-start dot, so
    962  * _sdata == _mid == _edata. This drives the exact bare-metal `.data` copy-loop
    963  * idiom (_sdata/_edata bracketing initialized data) plus an interior midpoint
    964  * marker between two globs. */
    965 static void check_body_symbols_after_glob(void) {
    966   static const char script_text[] =
    967       "OUTPUT_FORMAT(\"elf64-littleaarch64\")\n"
    968       "OUTPUT_ARCH(aarch64)\n"
    969       "ENTRY(_start)\n"
    970       "MEMORY {\n"
    971       "  FLASH (rx)  : ORIGIN = 0x10000, LENGTH = 64K\n"
    972       "  SRAM  (rwx) : ORIGIN = 0x20000000, LENGTH = 64K\n"
    973       "}\n"
    974       "SECTIONS {\n"
    975       "  .text : { *(.text .text.*) } > FLASH\n"
    976       "  .data : ALIGN(4) {\n"
    977       "    _sdata = .;\n"
    978       "    *(.data)\n"
    979       "    _mid = .;\n"
    980       "    *(.data.more)\n"
    981       "    . = ALIGN(16);\n"
    982       "    _edata = .;\n"
    983       "  } > SRAM AT> FLASH\n"
    984       "}\n";
    985   KitTargetSpec target =
    986       kit_unit_target(KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF);
    987   KitCompiler* c = NULL;
    988   KitObjBuilder* ob = NULL;
    989   KitLinkScript* s = NULL;
    990   KitLinkSession* sess = NULL;
    991   KitWriter* w = NULL;
    992   KitObjFile* f = NULL;
    993   KitSlice script;
    994   KitSlice bytes;
    995   KitLinkSessionOptions opts;
    996   KitObjSymInfo sdata, mid, edata;
    997   size_t len = 0;
    998 
    999   script.s = script_text;
   1000   script.len = sizeof(script_text) - 1u;
   1001   EXPECT(kit_link_script_parse(&g_u.ctx, script, &s) == KIT_OK && s,
   1002          "body-symbol script parses");
   1003   if (!s) goto done;
   1004   EXPECT(kit_unit_compiler_new(&g_u, target, &c) == KIT_OK && c,
   1005          "compiler allocation");
   1006   if (!c) goto done;
   1007   ob = build_two_data_input(c);
   1008   if (!ob) goto done;
   1009   memset(&opts, 0, sizeof(opts));
   1010   opts.output_kind = KIT_LINK_OUTPUT_EXE;
   1011   opts.linker_script = s;
   1012   EXPECT(kit_link_session_new(c, &opts, &sess) == KIT_OK && sess,
   1013          "link session allocation");
   1014   if (!sess) goto done;
   1015   EXPECT(kit_link_session_add_obj(sess, ob) == KIT_OK, "add input object");
   1016   EXPECT(kit_writer_mem(&g_u.heap, &w) == KIT_OK && w, "memory writer");
   1017   if (!w) goto done;
   1018   EXPECT(kit_link_session_emit(sess, w) == KIT_OK, "emit scripted image");
   1019   bytes.data = kit_writer_mem_bytes(w, &len);
   1020   bytes.len = len;
   1021   if (!bytes.data || bytes.len == 0) goto done;
   1022   EXPECT(kit_obj_open(&g_u.ctx, KIT_SLICE_LIT("<body-symbols>"), &bytes, &f) ==
   1023                  KIT_OK &&
   1024              f,
   1025          "open scripted image");
   1026   if (!f) goto done;
   1027 
   1028   EXPECT(kit_obj_symbol_by_name(f, KIT_SLICE_LIT("_sdata"), &sdata) == KIT_OK,
   1029          "_sdata defined");
   1030   EXPECT(kit_obj_symbol_by_name(f, KIT_SLICE_LIT("_mid"), &mid) == KIT_OK,
   1031          "_mid defined");
   1032   EXPECT(kit_obj_symbol_by_name(f, KIT_SLICE_LIT("_edata"), &edata) == KIT_OK,
   1033          "_edata defined");
   1034 
   1035   /* .data is placed at SRAM origin (4-aligned). The leading marker captures the
   1036    * base; `.data` (4 bytes) advances to base+4; `.data.more` (8 bytes,
   1037    * already 4-aligned) ends at base+12; `. = ALIGN(16)` rounds to base+16. */
   1038   EXPECT(sdata.value == 0x20000000u, "_sdata == .data start (section base)");
   1039   EXPECT(mid.value == 0x20000004u, "_mid == base + sizeof(.data) after glob");
   1040   EXPECT(edata.value == 0x20000010u,
   1041          "_edata == ALIGN(16) of base + .data + .data.more");
   1042   EXPECT(sdata.value != edata.value,
   1043          "interior symbols around globs are not collapsed to the section start");
   1044 
   1045 done:
   1046   if (f) kit_obj_free(f);
   1047   if (w) kit_writer_close(w);
   1048   if (sess) kit_link_session_free(sess);
   1049   if (ob) kit_obj_builder_free(ob);
   1050   if (s) kit_link_script_free(&g_u.ctx, s);
   1051   if (c) kit_compiler_free(c);
   1052 }
   1053 
   1054 int main(void) {
   1055   kit_unit_init(&g_u);
   1056   check_kernel_script_subset();
   1057   check_scripted_layout_vma_lma();
   1058   check_noload_progbits();
   1059   check_intersection_dot_align();
   1060   check_body_symbols_after_glob();
   1061   check_multibyte_fill();
   1062   check_phdr_coalesce_and_multi();
   1063   check_bss_tail_no_coalesce_skew();
   1064   check_coalesce_perms_union();
   1065   check_recursion_depth_guard();
   1066   kit_unit_summary(&g_u, "link_script_test");
   1067   return kit_unit_status(&g_u);
   1068 }