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