kit

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

commit 7a1ff3b3618cd6590d0c4a7e63700f96c21b6975
parent e073a97068ef9d63659cb52cb182be91f3b46c51
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 17 Jun 2026 08:59:18 -0700

test: cover DSO libc dependency resolution

Diffstat:
Mmk/test.mk | 13++++++-------
Atest/link/elf-dso-aa64/lib_foo_libc.c | 32++++++++++++++++++++++++++++++++
Mtest/link/elf-dso-aa64/run.sh | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 135 insertions(+), 9 deletions(-)

diff --git a/mk/test.mk b/mk/test.mk @@ -905,13 +905,12 @@ test-link-x64: lib rt-x86_64-linux $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUN @KIT_TEST_ARCH=x64 KIT_TEST_PATHS=RE bash test/link/run.sh # test-link-dso-aa64: ELF shared-library (DSO) harness targeting aarch64-linux. -# Lane A (all-clang) uses clang + lld to produce libfoo.so and a freestanding -# consumer, runs readelf structural checks, and executes the consumer inside -# podman containers (alpine/musl and debian/glibc). No kit linker or codegen -# is involved at lane A; lanes B (kit ld -shared) and C (all-kit) follow as the -# kit DSO implementation lands. Opt-in: requires clang aarch64 cross + ld.lld + -# readelf + provisioned podman images (make test-images) for runtime checks. -test-link-dso-aa64: +# Uses kit cc/ld to produce DSOs and freestanding consumers, runs readelf +# structural checks, and executes consumers inside podman containers when +# provisioned. The make target provisions build/musl-sysroot so the libc +# dependency lane is active; direct script runs skip that lane if it is absent. +# Opt-in: requires readelf; runtime checks need provisioned images. +test-link-dso-aa64: bin build/musl-sysroot/PROVENANCE bash test/link/elf-dso-aa64/run.sh test-macho: lib $(TEST_RT_DEP) $(ROUNDTRIP_BIN_MACHO) $(LINK_EXE_RUNNER) $(JIT_RUNNER) diff --git a/test/link/elf-dso-aa64/lib_foo_libc.c b/test/link/elf-dso-aa64/lib_foo_libc.c @@ -0,0 +1,32 @@ +/* test/link/elf-dso-aa64/lib_foo_libc.c — DSO fixture with a libc dependency. + * + * This is intentionally headerless so the compile step does not need a sysroot: + * the link step supplies libc.so as a DSO input, and the produced shared object + * must carry libc's DT_NEEDED SONAME plus UND dynsym imports for malloc/free. + * The allocation is deliberately large enough to go through libc's syscall- + * backed allocator path on musl. + */ + +extern void* malloc(__SIZE_TYPE__ size); +extern void free(void* p); +extern int bar_external(int x); + +int foo_counter = 42; + +int foo_add(int a, int b) +{ + char* p = (char*)malloc(256u * 1024u); + int ok; + if (!p) + return -1; + p[0] = 'k'; + p[256u * 1024u - 1u] = 't'; + ok = (p[0] == 'k' && p[256u * 1024u - 1u] == 't'); + free(p); + return a + b + bar_external(0) + (ok ? 0 : -1); +} + +int foo_get_counter(void) +{ + return foo_counter; +} diff --git a/test/link/elf-dso-aa64/run.sh b/test/link/elf-dso-aa64/run.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash # test/link/elf-dso-aa64/run.sh — ELF shared-library harness, aarch64 target. # -# Single kit lane: kit cc compiles PIC objects, kit ld links libfoo.so, kit cc -# links the consumers, then readelf and podman verify the result. +# Kit lanes: kit cc compiles PIC objects, kit ld links DSOs, kit cc links the +# consumers, then readelf and podman verify the result. Lane B links a produced +# DSO against libc.so so the loader must resolve a dependency of a dependency. # # Prerequisites (checked at runtime; missing prereqs produce SKIP, not FAIL): # build/kit — built kit binary @@ -107,6 +108,12 @@ exec_dso_podman() { # Runtime checks (require podman + provisioned image; SKIP if unavailable): # A/run-musl consumer-musl exits 0 inside alpine (musl) container # A/run-glibc consumer-glibc exits 0 inside debian (glibc) container +# +# Libc-dependency checks (require extracted aarch64 musl sysroot): +# B/build-libc-lib compile lib_foo_libc.c and link libfoo_libc.so with libc.so +# B/libc-needed libfoo_libc.so has a libc DT_NEEDED entry +# B/import-malloc malloc is UND in libfoo_libc.so .dynsym +# B/run-musl consumer-libc-musl exits 0 inside alpine container # ---- A: prerequisites -------------------------------------------------------- @@ -124,6 +131,10 @@ LIBFOO_SO="$work/libfoo.so" CONSUMER_O="$work/consumer.o" CONSUMER_MUSL="$work/consumer-musl" CONSUMER_GLIBC="$work/consumer-glibc" +LIBFOO_LIBC_O="$work/lib_foo_libc.o" +LIBFOO_LIBC_SO="$work/libfoo_libc.so" +CONSUMER_LIBC_MUSL="$work/consumer-libc-musl" +MUSL_SYSROOT="${KIT_MUSL_SYSROOT:-$ROOT/build/musl-sysroot}" SRC_DIR="$(cd "$(dirname "$0")" && pwd)" @@ -355,6 +366,90 @@ if [ $have_consumer_glibc -eq 1 ]; then fi fi +# ---- B: produced DSO depends on libc.so -------------------------------------- +# This lane links the shared object itself against the extracted aarch64 musl +# libc.so. The consumer only names libfoo_libc.so; libc is pulled transitively +# from libfoo_libc.so's DT_NEEDED entry at runtime. + +have_libc_lane=0 +if [ ! -f "$MUSL_SYSROOT/usr/lib/libc.so" ]; then + skip_test "B" "aarch64 musl sysroot unavailable (run: make test-libc-musl, or set KIT_MUSL_SYSROOT)" +else + if ! "$KIT" cc $KIT_TARGET \ + -O1 -fPIC \ + -c "$SRC_DIR/lib_foo_libc.c" -o "$LIBFOO_LIBC_O" \ + >"$work/compile-libc-lib.out" 2>"$work/compile-libc-lib.err"; then + not_ok "B/build-libc-lib" "$work/compile-libc-lib.err" + elif ! "$KIT" ld \ + -shared \ + -soname libfoo_libc.so \ + "$LIBFOO_LIBC_O" "$MUSL_SYSROOT/usr/lib/libc.so" \ + -o "$LIBFOO_LIBC_SO" \ + >"$work/link-libc-lib.out" 2>"$work/link-libc-lib.err"; then + not_ok "B/build-libc-lib" "$work/link-libc-lib.err" + else + ok "B/build-libc-lib" + have_libc_lane=1 + fi +fi + +if [ $have_libc_lane -eq 1 ]; then + DUMP_LIBC_DYN="$work/readelf-libc-dyn.txt" + DUMP_LIBC_DYNSYM="$work/readelf-libc-dynsym.txt" + "$READELF_BIN" -d "$LIBFOO_LIBC_SO" >"$DUMP_LIBC_DYN" 2>/dev/null + "$READELF_BIN" --dyn-syms "$LIBFOO_LIBC_SO" >"$DUMP_LIBC_DYNSYM" 2>/dev/null + + if grep -q "(NEEDED)" "$DUMP_LIBC_DYN" && grep "(NEEDED)" "$DUMP_LIBC_DYN" | grep -q "libc"; then + ok "B/libc-needed" + else + printf 'FAIL: produced DSO missing a libc DT_NEEDED entry\n' >"$work/B-libc-needed.diag" + grep "NEEDED\|libc" "$DUMP_LIBC_DYN" >>"$work/B-libc-needed.diag" || true + not_ok "B/libc-needed" "$work/B-libc-needed.diag" + fi + + if grep "malloc" "$DUMP_LIBC_DYNSYM" | grep -q "UND"; then + ok "B/import-malloc" + else + printf 'FAIL: malloc not in libfoo_libc.so .dynsym as UND\n' >"$work/B-import-malloc.diag" + grep "malloc" "$DUMP_LIBC_DYNSYM" >>"$work/B-import-malloc.diag" || true + not_ok "B/import-malloc" "$work/B-import-malloc.diag" + fi + + if ! "$KIT" cc $KIT_TARGET \ + -O1 -ffreestanding \ + -nostdlib -rdynamic \ + -e _start \ + -Wl,-dynamic-linker,"$MUSL_INTERP" \ + -Wl,-rpath,\$ORIGIN \ + "$SRC_DIR/consumer.c" \ + -L"$work" -lfoo_libc \ + -o "$CONSUMER_LIBC_MUSL" \ + >"$work/build-consumer-libc-musl.out" 2>"$work/build-consumer-libc-musl.err"; then + skip_test "B/run-musl" "consumer-libc-musl build failed; see $work/build-consumer-libc-musl.err" + have_consumer_libc_musl=0 + else + have_consumer_libc_musl=1 + fi + + if [ $have_consumer_libc_musl -eq 1 ]; then + if [ "${have_podman:-0}" -ne 1 ]; then + skip_test "B/run-musl" "podman unavailable" + elif ! _exec_target_image_present "aarch64-linux"; then + skip_test "B/run-musl" "aarch64 musl image not provisioned (run: make test-images)" + else + exec_dso_podman "aarch64-linux" "$CONSUMER_LIBC_MUSL" \ + "$work/run-libc-musl.out" "$work/run-libc-musl.err" + if [ "$RUN_RC" -eq 0 ]; then + ok "B/run-musl" + else + printf 'consumer-libc-musl exited %d\n' "$RUN_RC" >"$work/B-run-musl.diag" + [ -s "$work/run-libc-musl.err" ] && sed 's/^/ | /' "$work/run-libc-musl.err" >>"$work/B-run-musl.diag" + not_ok "B/run-musl" "$work/B-run-musl.diag" + fi + fi + fi +fi + # ---- summary ----------------------------------------------------------------- kit_summary test-link-dso-aa64 kit_exit