kit

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

run.sh (14409B)


      1 #!/usr/bin/env bash
      2 # test/libc/glibc/run.sh — drive kit ld against a real glibc sysroot, on the
      3 # shared corpus harness (test/lib/kit_corpus.sh).
      4 #
      5 # Dynamic-link only — static-linked glibc is officially discouraged (libc.a
      6 # relies on dlopen-loaded NSS modules, has its own entire reloc surface area,
      7 # and isn't a real-world deployment shape), so we don't carry the variant.
      8 # Each case in test/libc/cases/*.c is exercised once, in a single lane:
      9 #
     10 #   D  dynamic — PIE object + libc.so.6, with explicit dynamic linker
     11 #       kit ld -pie                                                         \
     12 #           -dynamic-linker <loader>                                           \
     13 #           -o case.exe                                                       \
     14 #           $SYSROOT/usr/lib/Scrt1.o $SYSROOT/usr/lib/crti.o                   \
     15 #           case.o                                                             \
     16 #           $SYSROOT/usr/lib/libc.so.6 $SYSROOT/usr/lib/libc_nonshared.a $KIT_RT \
     17 #           $SYSROOT/usr/lib/crtn.o
     18 #
     19 # Unlike musl, where ld-musl-<arch>.so.1 is the same file as libc, glibc's
     20 # loader is a separate ELF — kit ld's default interp is musl, so we override
     21 # via -dynamic-linker. The per-arch loader path is:
     22 #   aa64 -> /lib/ld-linux-aarch64.so.1
     23 #   x64  -> /lib64/ld-linux-x86-64.so.2
     24 #   rv64 -> /lib/ld-linux-riscv64-lp64d.so.1
     25 # libc.so.6 carries SONAME=libc.so.6 so DT_NEEDED is correct without a
     26 # linker-script intermediary (the on-disk libc.so is a GROUP script that kit
     27 # ld doesn't parse — we hand the SO directly). libc_nonshared.a contributes the
     28 # handful of non-shared callbacks every glibc dyn-exe pulls in — atexit,
     29 # __stack_chk_fail_local, __libc_csu_init/fini on older glibc, etc. — and must
     30 # follow libc.so.6 in the demand chain.
     31 #
     32 # Each case file may carry an `expected` companion (default 0) and an optional
     33 # `expected_stdout` (.stdout) file checked with substring match.
     34 #
     35 # The cross-exec is LANE-LOCAL: the lane runs the case either under qemu-user
     36 # with QEMU_LD_PREFIX=$sysroot (so the loader search resolves to the SYSROOT
     37 # copy), or under podman with an arch-pinned debian/glibc image (which ships the
     38 # loader + libc at the expected paths). This is deliberately NOT routed through
     39 # exec_target's default-image queue; the image + loader provenance + the
     40 # QEMU_LD_PREFIX requirement are glibc-specific and differ from musl.
     41 #
     42 # Arch selection — KIT_LIBC_ARCHES (default "aa64"), space-separated; each
     43 # token becomes an "<arch>-elf" corpus tuple:
     44 #   aa64 -> $XDG_CACHE_HOME/kit/sysroots/linux-glibc/  + build/rt/aarch64-linux/libkit_rt.a
     45 #                                        + --target=aarch64-linux-gnu
     46 #   x64  -> $XDG_CACHE_HOME/kit/sysroots/linux-glibc-x64/ + build/rt/x86_64-linux/libkit_rt.a
     47 #                                        + --target=x86_64-linux-gnu
     48 #   rv64 -> $XDG_CACHE_HOME/kit/sysroots/linux-glibc-rv64/ + build/rt/riscv64-linux/libkit_rt.a
     49 #                                        + --target=riscv64-linux-gnu
     50 # A missing sysroot / rt / clang-target for an enabled arch is reported as a
     51 # non-gating SKIP-NA (never a failure); only test failures cause a nonzero exit.
     52 set -u
     53 
     54 ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
     55 _KIT_SYSROOTS="${XDG_CACHE_HOME:-$HOME/.cache}/kit/sysroots"
     56 export KIT_LIB_DIR="$ROOT/test/lib"
     57 . "$ROOT/test/lib/kit_corpus.sh"
     58 
     59 CASES_DIR="$ROOT/test/libc/cases"
     60 BUILD_DIR="$ROOT/build/glibc"
     61 KIT="${KIT:-$ROOT/build/kit}"
     62 
     63 if [ ! -x "$KIT" ]; then
     64     echo "kit driver missing at $KIT — run 'make' first" >&2
     65     exit 2
     66 fi
     67 
     68 mkdir -p "$BUILD_DIR"
     69 
     70 # ---- arch lookup tables (LANE-LOCAL config) -------------------------------
     71 # Keyed on the arch token (aa64/x64/rv64), which is the tuple's arch half.
     72 
     73 arch_sysroot() {
     74     case "$1" in
     75         aa64) echo "$_KIT_SYSROOTS/linux-glibc" ;;
     76         x64)  echo "$_KIT_SYSROOTS/linux-glibc-x64" ;;
     77         rv64) echo "$_KIT_SYSROOTS/linux-glibc-rv64" ;;
     78         *)    echo "" ;;
     79     esac
     80 }
     81 
     82 arch_rt() {
     83     case "$1" in
     84         aa64) echo "$ROOT/build/rt/aarch64-linux/libkit_rt.a" ;;
     85         x64)  echo "$ROOT/build/rt/x86_64-linux/libkit_rt.a" ;;
     86         rv64) echo "$ROOT/build/rt/riscv64-linux/libkit_rt.a" ;;
     87         *)    echo "" ;;
     88     esac
     89 }
     90 
     91 arch_target() {
     92     case "$1" in
     93         aa64) echo "aarch64-linux-gnu" ;;
     94         x64)  echo "x86_64-linux-gnu" ;;
     95         rv64) echo "riscv64-linux-gnu" ;;
     96         *)    echo "" ;;
     97     esac
     98 }
     99 
    100 arch_triple_include() {
    101     # The per-arch multi-arch include subdir under sysroot/usr/include/.
    102     case "$1" in
    103         aa64) echo "aarch64-linux-gnu" ;;
    104         x64)  echo "x86_64-linux-gnu" ;;
    105         rv64) echo "riscv64-linux-gnu" ;;
    106         *)    echo "" ;;
    107     esac
    108 }
    109 
    110 # Spelling extract.sh accepts for `-a`: aa64 -> aarch64; x64 -> x64.
    111 arch_extract_name() {
    112     case "$1" in
    113         aa64) echo "aarch64" ;;
    114         *)    echo "$1" ;;
    115     esac
    116 }
    117 
    118 arch_loader() {
    119     # Dynamic-linker path baked into PT_INTERP. All paths are the canonical
    120     # Linux glibc loader locations and match the layout the extracted sysroots
    121     # ship.
    122     case "$1" in
    123         aa64) echo "/lib/ld-linux-aarch64.so.1" ;;
    124         x64)  echo "/lib64/ld-linux-x86-64.so.2" ;;
    125         rv64) echo "/lib/ld-linux-riscv64-lp64d.so.1" ;;
    126         *)    echo "" ;;
    127     esac
    128 }
    129 
    130 # Container image carrying the glibc loader + libc at the expected paths, used
    131 # by the lane-local runner. Pinned to arch-specific repos (not multi-arch tags)
    132 # to dodge the cached-wrong-arch-manifest trap and avoid --platform (which would
    133 # force a registry manifest lookup on every run).
    134 arch_image() {
    135     case "$1" in
    136         # arm64v8/debian:bookworm-slim ships the matching glibc loader.
    137         aa64) echo "docker.io/arm64v8/debian:bookworm-slim" ;;
    138         # amd64-pinned debian peer; ships /lib64/ld-linux-x86-64.so.2 + libc.
    139         x64)  echo "docker.io/amd64/debian:bookworm-slim" ;;
    140         # trixie ships the riscv64 glibc loader at
    141         # /lib/ld-linux-riscv64-lp64d.so.1.
    142         rv64) echo "docker.io/riscv64/debian:trixie-slim" ;;
    143         *)    echo "" ;;
    144     esac
    145 }
    146 
    147 # qemu-user fallback binary per arch (used when no native exec is possible).
    148 arch_qemu() {
    149     case "$1" in
    150         aa64) echo "$QEMU_AA64" ;;
    151         x64)  echo "$QEMU_X64" ;;
    152         rv64) echo "$QEMU_RV64" ;;
    153         *)    echo "" ;;
    154     esac
    155 }
    156 
    157 # ---- host capability detection --------------------------------------------
    158 #
    159 # Native linux/<arch> hosts can exec ELFs directly under podman without
    160 # binfmt; otherwise we fall back to qemu-<arch>-static.
    161 
    162 arch_raw="$(uname -m 2>/dev/null || true)"
    163 is_aarch64=0
    164 { [ "$arch_raw" = "aarch64" ] || [ "$arch_raw" = "arm64" ]; } && is_aarch64=1
    165 is_x86_64=0
    166 { [ "$arch_raw" = "x86_64" ] || [ "$arch_raw" = "amd64" ]; } && is_x86_64=1
    167 
    168 QEMU_AA64="$(command -v qemu-aarch64-static 2>/dev/null || command -v qemu-aarch64 2>/dev/null || true)"
    169 QEMU_X64="$(command -v qemu-x86_64-static 2>/dev/null || command -v qemu-x86_64 2>/dev/null || true)"
    170 QEMU_RV64="$(command -v qemu-riscv64-static 2>/dev/null || command -v qemu-riscv64 2>/dev/null || true)"
    171 have_podman=0; command -v podman >/dev/null 2>&1 && have_podman=1
    172 
    173 # ---- lane-local target runner ---------------------------------------------
    174 # run_target <arch> <sysroot> <exe> <out> <err>  -> sets RUN_RC
    175 #
    176 # Dynamic exes need /lib/ld-linux-<arch>.so.{1,2} + libc.so.6 to load.
    177 # qemu-user resolves them relative to QEMU_LD_PREFIX=$sysroot; the podman
    178 # fallback uses an arch-pinned debian image which ships them at the expected
    179 # paths.
    180 run_target() {
    181     local arch="$1" sysroot="$2" exe="$3" out="$4" err="$5"
    182     local qemu image
    183     qemu="$(arch_qemu "$arch")"
    184     image="$(arch_image "$arch")"
    185     if [ -n "$qemu" ]; then
    186         # Point qemu-user at our extracted sysroot so the loader search
    187         # resolves to the SYSROOT copy rather than the (possibly-absent) host
    188         # one.
    189         QEMU_LD_PREFIX="$sysroot" \
    190             "$qemu" "$exe" >"$out" 2>"$err"
    191         RUN_RC=$?; return
    192     fi
    193     if [ $have_podman -eq 1 ]; then
    194         local dir base
    195         dir="$(cd "$(dirname "$exe")" && pwd)"; base="$(basename "$exe")"
    196         podman run --rm --pull=never --net=none \
    197             -v "$dir":/work:Z -w /work \
    198             "$image" "./$base" \
    199             >"$out" 2>"$err"
    200         RUN_RC=$?; return
    201     fi
    202     RUN_RC=127
    203 }
    204 
    205 # ---- per-case marker reading (KIT_READ_CASE) --------------------------------
    206 # Reads the optional .stdout substring oracle and resolves this tuple's arch
    207 # config. The .expected sidecar (default 0) is read by the engine already
    208 # (KIT_EXPECTED_EXT=.expected). A tuple whose sysroot/rt/clang-target is
    209 # unavailable is marked SKIP-NA (non-gating), mirroring the original's
    210 # SKIP_ARCHES informational handling.
    211 libc_read_case() {
    212     LC_ARCH="$KIT_ARCH"
    213     LC_SYSROOT="$(arch_sysroot "$LC_ARCH")"
    214     LC_RT="$(arch_rt "$LC_ARCH")"
    215     LC_TARGET="$(arch_target "$LC_ARCH")"
    216     LC_LOADER="$(arch_loader "$LC_ARCH")"
    217     LC_TRIPLE_INC="$(arch_triple_include "$LC_ARCH")"
    218 
    219     if [ -z "$LC_SYSROOT" ] || [ -z "$LC_RT" ] || [ -z "$LC_TARGET" ]; then
    220         KIT_SKIP_NA_CASE=1; return
    221     fi
    222     if [ ! -d "$LC_SYSROOT" ]; then
    223         KIT_SKIP_NA_CASE=1; return
    224     fi
    225     if [ ! -f "$LC_RT" ]; then
    226         KIT_SKIP_NA_CASE=1; return
    227     fi
    228     # clang must understand --target=<target>. Every system path is overridden
    229     # via --sysroot / -isystem so the host's headers / libraries are not
    230     # consulted.
    231     if ! clang --target="$LC_TARGET" -c -x c - -o /dev/null < /dev/null 2>/dev/null; then
    232         KIT_SKIP_NA_CASE=1; return
    233     fi
    234 
    235     LC_EXPECT_STDOUT=""
    236     if [ -f "$CASES_DIR/${KIT_BASE}.stdout" ]; then
    237         LC_EXPECT_STDOUT="$(cat "$CASES_DIR/${KIT_BASE}.stdout")"
    238     fi
    239 }
    240 
    241 # ---- dynamic-link lane (compile -> link -> run -> stdout) ------------------
    242 # Emits exactly one kit_pass/kit_fail. All artifacts live under $KIT_WORK
    243 # (parallel-safe).
    244 kit_lane_D() {
    245     local label="$KIT_ARCH/$KIT_BASE"
    246     local work="$KIT_WORK"
    247 
    248     # ---- compile ----
    249     # Three -isystem layers, in order of precedence:
    250     #   sysroot/usr/include/              — glibc + linux-libc-dev headers
    251     #                                       (top-level uapi).
    252     #   sysroot/usr/include/<triple>      — glibc multi-arch (bits/*,
    253     #                                       gnu/stubs-lp64.h, ...);
    254     #                                       <features.h> reaches in.
    255     #   rt/include/                       — kit's freestanding overlay
    256     #                                       (stddef.h, stdarg.h, stdint.h).
    257     #                                       glibc's stdio.h #include <stddef.h>
    258     #                                       for size_t; glibc doesn't ship
    259     #                                       compiler headers so rt/include must
    260     #                                       be reachable.
    261     # -nostdinc strips clang's default include path so cross targets don't
    262     # accidentally pick up the host's compiler headers.
    263     local cc_flags=(--target="$LC_TARGET" --sysroot="$LC_SYSROOT"
    264                     -nostdinc
    265                     -isystem "$LC_SYSROOT/usr/include"
    266                     -isystem "$LC_SYSROOT/usr/include/$LC_TRIPLE_INC"
    267                     -isystem "$ROOT/rt/include"
    268                     -fPIE -fpic -O0)
    269 
    270     local obj="$work/${KIT_BASE}.o"
    271     if ! clang "${cc_flags[@]}" -c "$KIT_SRC" -o "$obj" 2>"$work/cc.err"; then
    272         kit_fail "$label (compile)"
    273         sed 's/^/    cc| /' "$work/cc.err"
    274         return
    275     fi
    276 
    277     # ---- link ----
    278     # PIE start file, libc.so.6 as the *shared* input (kit ld doesn't read
    279     # the libc.so linker script, so we hand the actual SO directly), with
    280     # -dynamic-linker overriding the musl default. Expects kit ld to:
    281     #   - accept ET_DYN ELF objects as input,
    282     #   - emit PT_INTERP "$loader",
    283     #   - emit PT_DYNAMIC with DT_NEEDED libc.so.6,
    284     #   - emit a .dynsym/.dynstr/.gnu.hash + .rela.plt/.got.plt so the loader
    285     #     can bind imported symbols at runtime.
    286     # libc_nonshared.a still links statically; libkit_rt.a stays — soft-float
    287     # TF helpers are static-bound from our side. crti/crtn are unchanged.
    288     local exe="$work/${KIT_BASE}.exe"
    289     local link_cmd=("$KIT" "ld" -pie
    290                     -dynamic-linker "$LC_LOADER"
    291                     -o "$exe"
    292                     "$LC_SYSROOT/usr/lib/Scrt1.o" "$LC_SYSROOT/usr/lib/crti.o"
    293                     "$obj"
    294                     "$LC_SYSROOT/usr/lib/libc.so.6" "$LC_SYSROOT/usr/lib/libc_nonshared.a"
    295                     "$LC_RT"
    296                     "$LC_SYSROOT/usr/lib/crtn.o")
    297 
    298     if ! "${link_cmd[@]}" >"$work/link.out" 2>"$work/link.err"; then
    299         kit_fail "$label (link)"
    300         sed 's/^/    ld| /' "$work/link.err" | head -10
    301         return
    302     fi
    303 
    304     # ---- run (lane-local cross-exec) ----
    305     run_target "$LC_ARCH" "$LC_SYSROOT" "$exe" "$work/run.out" "$work/run.err"
    306     if [ "$RUN_RC" -ne "$KIT_EXPECTED" ]; then
    307         kit_fail "$label (run rc=$RUN_RC, want $KIT_EXPECTED)"
    308         [ -s "$work/run.err" ] && sed 's/^/    err| /' "$work/run.err" | head -5
    309         [ -s "$work/run.out" ] && sed 's/^/    out| /' "$work/run.out" | head -5
    310         return
    311     fi
    312 
    313     if [ -n "$LC_EXPECT_STDOUT" ]; then
    314         if ! grep -qF -- "$LC_EXPECT_STDOUT" "$work/run.out"; then
    315             kit_fail "$label (stdout mismatch)"
    316             printf '    expected substring: %s\n' "$LC_EXPECT_STDOUT"
    317             sed 's/^/    got| /' "$work/run.out" | head -5
    318             return
    319         fi
    320     fi
    321 
    322     kit_pass "$label"
    323 }
    324 
    325 # ---- drive the corpus ------------------------------------------------------
    326 # One kit_corpus_run per arch (single "<arch>-elf" tuple), so each arch gets a
    327 # distinct per-case $KIT_WORK ($KIT_BUILD_DIR/<arch>/<base>) and never collides
    328 # with another arch's under parallel dispatch. Results accumulate into the
    329 # shared counters; one summary at the end.
    330 printf 'test-libc-glibc arches=%s\n' "${KIT_LIBC_ARCHES:-aa64}"
    331 
    332 PAR="${KIT_GLIBC_PARALLEL:-1}"
    333 for arch in ${KIT_LIBC_ARCHES:-aa64}; do
    334     KIT_LABEL=test-libc-glibc KIT_BUILD_DIR="$BUILD_DIR/$arch" \
    335       KIT_CORPUS_GLOBS="$CASES_DIR/*.c" KIT_CORPUS_EXT=c KIT_SIDECAR_DIR="$CASES_DIR" \
    336       KIT_LANES="D" KIT_OPT_LEVELS="" KIT_TUPLES="$arch-elf" \
    337       KIT_EXPECTED_EXT=.expected KIT_TARGETS_EXT="" \
    338       KIT_READ_CASE=libc_read_case KIT_PARALLELIZABLE="$PAR" \
    339       kit_corpus_run
    340 done
    341 
    342 kit_summary test-libc-glibc
    343 kit_exit