run.sh (12980B)
1 #!/usr/bin/env bash 2 # test/libc/musl/run.sh — drive kit ld against a real musl sysroot, on the 3 # shared corpus harness (test/lib/kit_corpus.sh). 4 # 5 # Cases under test/libc/cases/*.c are compiled, linked, and run against a 6 # podman-pinned Alpine/musl sysroot, in two link variants (two lanes): 7 # 8 # S static — non-PIC object + libc.a, classic static-exe link 9 # kit ld -static -o case.exe \ 10 # $SYSROOT/usr/lib/crt1.o $SYSROOT/usr/lib/crti.o \ 11 # case.o \ 12 # $SYSROOT/usr/lib/libc.a $KIT_RT \ 13 # $SYSROOT/usr/lib/crtn.o 14 # 15 # D dynamic — PIE object + libc.so, expects PT_INTERP /lib/ld-musl-<arch>.so.1 16 # kit ld -pie -o case.exe \ 17 # $SYSROOT/usr/lib/Scrt1.o $SYSROOT/usr/lib/crti.o \ 18 # case.o \ 19 # $SYSROOT/usr/lib/libc.so $KIT_RT \ 20 # $SYSROOT/usr/lib/crtn.o 21 # (musl ships ld-musl-<arch>.so.1 *as* libc — same file. The harness 22 # intentionally has no -dynamic-linker flag yet because kit ld 23 # currently doesn't accept one; this is one of the gaps we expect the 24 # dynamic variant to surface.) 25 # 26 # Each case file may carry an `expected` companion (default 0) and an optional 27 # `expected_stdout` (.stdout) file checked with substring match. 28 # 29 # The cross-exec is LANE-LOCAL: the lane runs the case under podman with musl's 30 # own arch-pinned Alpine image, whose rootfs already carries the musl loader at 31 # /lib/ld-musl-<arch>.so.1 — so NO QEMU_LD_PREFIX and no -dynamic-linker are 32 # needed (unlike glibc). This is deliberately NOT routed through exec_target's 33 # default-image queue; the image + loader provenance are musl-specific. 34 # 35 # Arch selection — KIT_LIBC_ARCHES (default "aa64"), space-separated; each 36 # token becomes an "<arch>-elf" corpus tuple: 37 # aa64 -> $XDG_CACHE_HOME/kit/sysroots/linux-musl/ + build/rt/aarch64-linux/libkit_rt.a 38 # + --target=aarch64-linux-musl 39 # x64 -> $XDG_CACHE_HOME/kit/sysroots/linux-musl-x64/ + build/rt/x86_64-linux/libkit_rt.a 40 # + --target=x86_64-linux-musl 41 # rv64 -> $XDG_CACHE_HOME/kit/sysroots/linux-musl-rv64/ + build/rt/riscv64-linux/libkit_rt.a 42 # + --target=riscv64-linux-musl 43 # A missing sysroot / rt / clang-target for an enabled arch is reported as a 44 # non-gating SKIP-NA (never a failure); only test failures cause a nonzero exit. 45 set -u 46 47 ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" 48 _KIT_SYSROOTS="${XDG_CACHE_HOME:-$HOME/.cache}/kit/sysroots" 49 export KIT_LIB_DIR="$ROOT/test/lib" 50 . "$ROOT/test/lib/kit_corpus.sh" 51 52 CASES_DIR="$ROOT/test/libc/cases" 53 BUILD_DIR="$ROOT/build/musl" 54 KIT="${KIT:-$ROOT/build/kit}" 55 56 if [ ! -x "$KIT" ]; then 57 echo "kit driver missing at $KIT — run 'make' first" >&2 58 exit 2 59 fi 60 61 mkdir -p "$BUILD_DIR" 62 63 # ---- arch lookup tables (LANE-LOCAL config) ------------------------------- 64 # Keyed on the arch token (aa64/x64/rv64), which is the tuple's arch half. 65 66 arch_sysroot() { 67 case "$1" in 68 aa64) echo "$_KIT_SYSROOTS/linux-musl" ;; 69 x64) echo "$_KIT_SYSROOTS/linux-musl-x64" ;; 70 rv64) echo "$_KIT_SYSROOTS/linux-musl-rv64" ;; 71 *) echo "" ;; 72 esac 73 } 74 75 arch_rt() { 76 case "$1" in 77 aa64) echo "$ROOT/build/rt/aarch64-linux/libkit_rt.a" ;; 78 x64) echo "$ROOT/build/rt/x86_64-linux/libkit_rt.a" ;; 79 rv64) echo "$ROOT/build/rt/riscv64-linux/libkit_rt.a" ;; 80 *) echo "" ;; 81 esac 82 } 83 84 arch_target() { 85 case "$1" in 86 aa64) echo "aarch64-linux-musl" ;; 87 x64) echo "x86_64-linux-musl" ;; 88 rv64) echo "riscv64-linux-musl" ;; 89 *) echo "" ;; 90 esac 91 } 92 93 # Container image carrying the musl loader at /lib/ld-musl-<arch>.so.1, used by 94 # the lane-local runner. Pinned to arch-specific repos (not multi-arch tags) to 95 # dodge the cached-wrong-arch-manifest trap and to avoid --platform (which would 96 # force a registry manifest lookup on every run). 97 arch_image() { 98 case "$1" in 99 # arm64v8/alpine ships the musl loader at /lib/ld-musl-aarch64.so.1. 100 aa64) echo "docker.io/arm64v8/alpine:latest" ;; 101 # amd64v2/alpine isn't a thing; amd64/alpine is the canonical pin. 102 # Ships /lib/ld-musl-x86_64.so.1. 103 x64) echo "docker.io/amd64/alpine:latest" ;; 104 # alpine:edge currently carries the riscv64 musl loader. 105 rv64) echo "docker.io/riscv64/alpine:edge" ;; 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 # qemu-user fallback binary per arch (used when no native exec is possible). 119 arch_qemu() { 120 case "$1" in 121 aa64) echo "$QEMU_AA64" ;; 122 x64) echo "$QEMU_X64" ;; 123 rv64) echo "$QEMU_RV64" ;; 124 *) echo "" ;; 125 esac 126 } 127 128 # ---- host capability detection -------------------------------------------- 129 # 130 # Native linux/<arch> hosts can exec ELFs directly under podman without 131 # binfmt; otherwise we fall back to qemu-<arch>-static. 132 133 arch_raw="$(uname -m 2>/dev/null || true)" 134 is_aarch64=0 135 { [ "$arch_raw" = "aarch64" ] || [ "$arch_raw" = "arm64" ]; } && is_aarch64=1 136 is_x86_64=0 137 { [ "$arch_raw" = "x86_64" ] || [ "$arch_raw" = "amd64" ]; } && is_x86_64=1 138 139 QEMU_AA64="$(command -v qemu-aarch64-static 2>/dev/null || command -v qemu-aarch64 2>/dev/null || true)" 140 QEMU_X64="$(command -v qemu-x86_64-static 2>/dev/null || command -v qemu-x86_64 2>/dev/null || true)" 141 QEMU_RV64="$(command -v qemu-riscv64-static 2>/dev/null || command -v qemu-riscv64 2>/dev/null || true)" 142 have_podman=0; command -v podman >/dev/null 2>&1 && have_podman=1 143 144 # ---- lane-local target runner --------------------------------------------- 145 # run_target <arch> <exe> <out> <err> -> sets RUN_RC 146 # Musl: rootfs-baked loader, NO QEMU_LD_PREFIX. 147 run_target() { 148 local arch="$1" exe="$2" out="$3" err="$4" 149 local qemu image 150 qemu="$(arch_qemu "$arch")" 151 image="$(arch_image "$arch")" 152 if [ -n "$qemu" ]; then 153 "$qemu" "$exe" >"$out" 2>"$err"; RUN_RC=$?; return 154 fi 155 if [ $have_podman -eq 1 ]; then 156 local dir base 157 dir="$(cd "$(dirname "$exe")" && pwd)"; base="$(basename "$exe")" 158 podman run --rm --pull=never --net=none \ 159 -v "$dir":/work:Z -w /work \ 160 "$image" "./$base" \ 161 >"$out" 2>"$err" 162 RUN_RC=$?; return 163 fi 164 RUN_RC=127 165 } 166 167 # ---- per-case marker reading (KIT_READ_CASE) -------------------------------- 168 # Reads the optional .stdout substring oracle and resolves this tuple's arch 169 # config. The .expected sidecar (default 0) is read by the engine already 170 # (KIT_EXPECTED_EXT=.expected). A tuple whose sysroot/rt/clang-target is 171 # unavailable is marked SKIP-NA (non-gating), mirroring the original's 172 # SKIP_ARCHES informational handling. 173 libc_read_case() { 174 LC_ARCH="$KIT_ARCH" 175 LC_SYSROOT="$(arch_sysroot "$LC_ARCH")" 176 LC_RT="$(arch_rt "$LC_ARCH")" 177 LC_TARGET="$(arch_target "$LC_ARCH")" 178 179 if [ -z "$LC_SYSROOT" ] || [ -z "$LC_RT" ] || [ -z "$LC_TARGET" ]; then 180 KIT_SKIP_NA_CASE=1; return 181 fi 182 if [ ! -d "$LC_SYSROOT" ]; then 183 KIT_SKIP_NA_CASE=1; return 184 fi 185 if [ ! -f "$LC_RT" ]; then 186 KIT_SKIP_NA_CASE=1; return 187 fi 188 # clang must understand --target=<target>. Recent clang ships linux-musl as 189 # a target alias of linux-gnu for our purposes (we override every system 190 # path via --sysroot). 191 if ! clang --target="$LC_TARGET" -c -x c - -o /dev/null < /dev/null 2>/dev/null; then 192 KIT_SKIP_NA_CASE=1; return 193 fi 194 195 LC_EXPECT_STDOUT="" 196 if [ -f "$CASES_DIR/${KIT_BASE}.stdout" ]; then 197 LC_EXPECT_STDOUT="$(cat "$CASES_DIR/${KIT_BASE}.stdout")" 198 fi 199 } 200 201 # ---- shared per-variant pipeline (compile -> link -> run -> stdout) -------- 202 # libc_run_variant <variant> <label> 203 # variant ∈ {static, dynamic}; emits exactly one kit_pass/kit_fail. 204 # All artifacts live under $KIT_WORK (parallel-safe). 205 libc_run_variant() { 206 local variant="$1" label="$2" 207 local work="$KIT_WORK/$variant" 208 mkdir -p "$work" 209 210 # ---- compile ---- 211 # -nostdinc strips clang's default include path (resource dir + 212 # /usr/include) so the sysroot's musl + linux-headers tree is the sole 213 # source. -isystem $sysroot/usr/include picks it up. 214 local cc_flags=(--target="$LC_TARGET" --sysroot="$LC_SYSROOT" 215 -nostdinc 216 -isystem "$LC_SYSROOT/usr/include" 217 -O0) 218 case "$variant" in 219 static) cc_flags+=(-fno-PIC -fno-pie) ;; 220 dynamic) cc_flags+=(-fPIE -fpic) ;; 221 esac 222 223 local obj="$work/${KIT_BASE}.o" 224 if ! clang "${cc_flags[@]}" -c "$KIT_SRC" -o "$obj" 2>"$work/cc.err"; then 225 kit_fail "$label (compile)" 226 sed 's/^/ cc| /' "$work/cc.err" 227 return 228 fi 229 230 # ---- link ---- 231 local exe="$work/${KIT_BASE}.exe" 232 local link_cmd 233 case "$variant" in 234 static) 235 # Link order mirrors a typical static-musl invocation: 236 # crt1.o crti.o obj libc.a libkit_rt.a crtn.o 237 # libkit_rt provides the TF / soft-float builtins (__addtf3, 238 # __extenddftf2 etc.) that musl's libc.a calls from printf's 239 # long-double formatting. Archive ingestion iterates demand-load 240 # to a fixed point so one trailing libkit_rt.a is enough. 241 link_cmd=("$KIT" "ld" -static -o "$exe" 242 "$LC_SYSROOT/usr/lib/crt1.o" "$LC_SYSROOT/usr/lib/crti.o" 243 "$obj" 244 "$LC_SYSROOT/usr/lib/libc.a" "$LC_RT" 245 "$LC_SYSROOT/usr/lib/crtn.o") 246 ;; 247 dynamic) 248 # Dynamic-exe link: PIE start file, libc.so as a *shared* input 249 # (not an archive), expects kit ld to: 250 # - accept ET_DYN ELF objects as input, 251 # - emit PT_INTERP "/lib/ld-musl-<arch>.so.1", 252 # - emit PT_DYNAMIC with DT_NEEDED libc.so, 253 # - emit a .dynsym/.dynstr/.gnu.hash + .rela.plt/.got.plt so the 254 # loader can bind imported symbols at runtime. 255 # libkit_rt.a stays — soft-float TF helpers are still 256 # static-bound from our side. crti/crtn are unchanged. 257 link_cmd=("$KIT" "ld" -pie -o "$exe" 258 "$LC_SYSROOT/usr/lib/Scrt1.o" "$LC_SYSROOT/usr/lib/crti.o" 259 "$obj" 260 "$LC_SYSROOT/usr/lib/libc.so" "$LC_RT" 261 "$LC_SYSROOT/usr/lib/crtn.o") 262 ;; 263 esac 264 265 if ! "${link_cmd[@]}" >"$work/link.out" 2>"$work/link.err"; then 266 kit_fail "$label (link)" 267 sed 's/^/ ld| /' "$work/link.err" | head -10 268 return 269 fi 270 271 # ---- run (lane-local cross-exec) ---- 272 run_target "$LC_ARCH" "$exe" "$work/run.out" "$work/run.err" 273 if [ "$RUN_RC" -ne "$KIT_EXPECTED" ]; then 274 kit_fail "$label (run rc=$RUN_RC, want $KIT_EXPECTED)" 275 [ -s "$work/run.err" ] && sed 's/^/ err| /' "$work/run.err" | head -5 276 [ -s "$work/run.out" ] && sed 's/^/ out| /' "$work/run.out" | head -5 277 return 278 fi 279 280 if [ -n "$LC_EXPECT_STDOUT" ]; then 281 if ! grep -qF -- "$LC_EXPECT_STDOUT" "$work/run.out"; then 282 kit_fail "$label (stdout mismatch)" 283 printf ' expected substring: %s\n' "$LC_EXPECT_STDOUT" 284 sed 's/^/ got| /' "$work/run.out" | head -5 285 return 286 fi 287 fi 288 289 kit_pass "$label" 290 } 291 292 # ---- lanes (static / dynamic variant axis) -------------------------------- 293 kit_lane_S() { libc_run_variant static "$KIT_ARCH/$KIT_BASE [static]"; } 294 kit_lane_D() { libc_run_variant dynamic "$KIT_ARCH/$KIT_BASE [dynamic]"; } 295 296 # ---- drive the corpus ------------------------------------------------------ 297 # One kit_corpus_run per arch (single "<arch>-elf" tuple), so each arch gets a 298 # distinct per-case $KIT_WORK ($KIT_BUILD_DIR/<arch>/<base>) and the static/ 299 # dynamic lanes for one arch never collide with another arch's under parallel 300 # dispatch. Results accumulate into the shared counters; one summary at the end. 301 printf 'test-libc-musl arches=%s\n' "${KIT_LIBC_ARCHES:-aa64}" 302 303 PAR="${KIT_MUSL_PARALLEL:-1}" 304 for arch in ${KIT_LIBC_ARCHES:-aa64}; do 305 KIT_LABEL=test-libc-musl KIT_BUILD_DIR="$BUILD_DIR/$arch" \ 306 KIT_CORPUS_GLOBS="$CASES_DIR/*.c" KIT_CORPUS_EXT=c KIT_SIDECAR_DIR="$CASES_DIR" \ 307 KIT_LANES="S D" KIT_OPT_LEVELS="" KIT_TUPLES="$arch-elf" \ 308 KIT_EXPECTED_EXT=.expected KIT_TARGETS_EXT="" \ 309 KIT_READ_CASE=libc_read_case KIT_PARALLELIZABLE="$PAR" \ 310 kit_corpus_run 311 done 312 313 kit_summary test-libc-musl 314 kit_exit