kit

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

run.sh (14256B)


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