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