kit

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

test/link — Linker and JIT test corpus

Unified behavioral test suite for kit's linker (ELF executable emission) and JIT (in-process execution). Each case is a directory of C source files with a shared test_main() convention, run through three independent paths.

Paths

Path What it tests Requires
R roundtrip elf_read + elf_emit fidelity; structural diff via llvm-readelf clang cross, kit-roundtrip, llvm-readelf, python3
E exec kit_link_exe layout + reloc application; runs via qemu/podman clang cross, link-exe-runner, qemu or podman
J JIT kit_link_jit + kit_jit_*; runs natively on aarch64 host clang cross, jit-runner (aarch64 only)

Convention

Every case exposes:

int test_main(void);          // primary test; 0 = pass
int test_post_fini(void);     // optional post-dtor check (weak default: 0)

The _start stub (harness/start.c) runs init_array ctors, calls test_main, runs fini_array dtors, calls test_post_fini, and exits. jit_runner mirrors this with kit_jit_run_dtors + test_post_fini lookup. expected (default 0) is the exit code / return value the harness expects from the combined sequence.

Case markers

File Meaning
expected expected return value (default 0)
jit_only skip R and E; run J only
use_resolver pass --use-resolver to jit_runner
linker_flags one flag per line passed to link-exe-runner and jit-runner
cflags extra clang -c flags appended to every TU compile in the case
gc_absent one symbol per line that must be absent post-link (e.g. dropped by --gc-sections)
gc_present one symbol per line that must remain present post-link
archive_b package b.o as b.a; content demand (normal) or whole (--whole-archive)
linker_script basename of an .lds file in the case dir; passed to both runners via --linker-script
kernel_image empty marker; case is a freestanding kernel image. Skips R and J; on E, runs the linked exe under qemu-system-aarch64 -kernel with semihosting

Negative tests live in test/link/bad/<name>/ instead of cases/. Each bad-case directory contains source files (compile cleanly) plus an expect file: a substring that must appear in the runner's stderr. Pass = runner exits non-zero with no signal AND stderr contains expect. The bad/ location is the marker — no link_fail flag.

Case index

Group A — single-TU, reloc coverage

# Name Reloc(s) C construct
01 exit_value CALL26 intra-TU static helper call
02 rodata_u8 LDST8 const char load
03 rodata_u16 LDST16 const uint16_t load
04 rodata_u32 LDST32 const uint32_t load
05 rodata_u64 LDST64 const uint64_t load
06 rodata_u128 LDST128 const __uint128_t load
07 data_rw ADD_ABS_LO12_NC + LDST64 store address-of + write + read
08 bss_zero NOBITS uninitialized static reads 0
09 data_fnptr ABS64 function pointer in .rodata

Cases 02–09 all pair ADR_PREL_PG_HI21 with their primary LDST reloc.

Group B — multi-TU, symbol resolution

# Name Exercises
10 call_cross_tu CALL26 cross-TU; STB_GLOBAL
11 data_cross_tu LDST64 cross-TU
12 ptr_cross_tu ABS64 cross-TU function pointer
13 hidden_call CALL26 + STV_HIDDEN
14 weak_present STB_WEAK defined; weak variable read
15 weak_override strong definition beats weak
16 weak_undef STB_WEAK + SHN_UNDEF; NULL guard
17 common_coalesce SHN_COMMON; tentative defs merge
18 static_local STB_LOCAL; static helper not exported
19 three_tu CALL26 chain across three inputs

Group C — constructors and destructors

# Name Exercises
20 init_array SHT_INIT_ARRAY ctor sets flag before test_main
21 fini_array SHT_FINI_ARRAY dtor; checked via test_post_fini
22 init_fini_both ctor + dtor in same TU
23 init_order priority-ordered ctors across two TUs (101 before 102)

Group D — COMDAT, GC, archives

# Name Exercises
24 comdat_dedup STB_WEAK dedup; multiply-defined weak fn → one copy
25a gc_basic --gc-sections + -ffunction-sections; unreachable fn dropped
25b no_gc_baseline same source as 25a, no --gc-sections; unreachable fn survives
25c gc_data -fdata-sections; unreferenced global data dropped
25d gc_chain transitive closure: live A→B→C kept; orphan triple dropped
25e gc_keep_init_array constructor with no static caller survives via .init_array root
25f gc_retain SHF_GNU_RETAIN (__attribute__((retain))) survives GC
25g gc_keeps_data_via_reloc live fn references global; data section kept by reloc edge
25h gc_start_stop __start_X/__stop_X retain section X and resolve to its span
26 archive_demand b.o in b.a; demand-loaded to satisfy reference
27 archive_whole b.a with --whole-archive; unreferenced ctor runs

Group E — JIT-specific

# Name Exercises
28 extern_resolver KitExternResolver provides address for undefined symbol
29 jit_lookup_miss kit_jit_lookup returns NULL for unknown name
32 ifunc STT_GNU_IFUNC trampoline: iplt stub + igot slot, resolver invoked at JIT load

Group F — TLS

# Name Exercises
31 tls_local_exec _Thread_local w/ initializer; R_AARCH64_TLSLE_ADD_TPREL_{HI12,LO12_NC} apply + PT_TLS layout

Group G — linker scripts

# Name Exercises
35 linker_script_kernel ENTRY, SECTIONS { . = 0x40080000; .text/.rodata/.data/.bss with ALIGN; __bss_start, _end; /DISCARD/of.note.*, .comment, .eh_frame. Linked image boots under qemu-system-aarch64 -kernel` and exits via ARM semihosting.

Group H — Mach-O atoms

# Name Exercises
40 macho_gc_dead_atom_reloc GC skips relocations from dead Mach-O subsections-via-symbols atoms
41 macho_many_atom_relocs Many Mach-O function atoms and call relocations under --gc-sections

bad/ — negative tests

# Name Exercises
30 undef_strong unresolved strong symbol → linker error, clean non-zero exit

Reloc coverage matrix

Reloc Covered by
R_AARCH64_CALL26 intra-TU 01
R_AARCH64_CALL26 cross-TU 10, 12–15, 18, 19
R_AARCH64_ADR_PREL_PG_HI21 02–09, 11, 17 (paired with each LDST)
R_AARCH64_ADD_ABS_LO12_NC 07, 12
R_AARCH64_LDST8_ABS_LO12_NC 02
R_AARCH64_LDST16_ABS_LO12_NC 03
R_AARCH64_LDST32_ABS_LO12_NC 04
R_AARCH64_LDST64_ABS_LO12_NC 05, 07, 11, 17
R_AARCH64_LDST128_ABS_LO12_NC 06
R_ABS64 09, 12
R_AARCH64_PREL32 (from .eh_frame) implicit in 01–19 (Path R)

Skipped (not reliably generated from C on AArch64):

Impl requirements

Features required by the corpus but not yet implemented (tests will fail until the impl lands):

  1. kit_jit_from_image must execute init_array ctors after mprotect (needed by cases 20–23, 27).
  2. kit_jit_run_dtors must walk fini_array sections in reverse (needed by cases 21–22).
  3. kit-ld / link_emit_image_writer must define linker-synthesized boundary symbols __init_array_start/end, __fini_array_start/end (needed by start.c, cases 20–23, 27).
  4. kit-ld must sort .init_array.NNN sections by priority suffix (needed by case 23).
  5. kit_link_exe / kit_link_jit must support KitLinkOptions.gc_sections (needed by case 25).
  6. Archive reader (link_add_archive_bytes) must support demand-loading and whole_archive (needed by cases 26–27).
  7. kit_link_jit must return non-zero on unresolved strong symbols without extern_resolver (needed by case 30).
  8. link_reloc_apply must handle R_AARCH64_TLSLE_ADD_TPREL_HI12 and R_AARCH64_TLSLE_ADD_TPREL_LO12_NC; kit_link_exe must emit a PT_TLS segment for .tdata/.tbss; harness _start must set up TPIDR_EL0 (needed by case 31).