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