commit c7f1f204fb2c05ec3c7b083dd76e2e33d8225e4c
parent 3f5930bf4415e49b3a734938b04b96c8b9d0be84
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 16 Jul 2026 11:30:30 -0700
test: run linked PE stripping in Windows VM
Diffstat:
3 files changed, 243 insertions(+), 0 deletions(-)
diff --git a/mk/test.mk b/mk/test.mk
@@ -1064,6 +1064,14 @@ test-coff-windows-vm: bin rt-x86_64-pc-windows rt-aarch64-windows windows-ucrt-s
$(_WIN_VM_ENV) bash test/coff/windows-system-dlls-smoke.sh
$(_WIN_VM_ENV) bash test/coff/windows-o1-abi-smoke.sh
+# Opt-in linked-PE runtime acceptance. Both architectures execute in the one
+# provisioned Windows 11 ARM64 VM (x64 through the in-box compatibility layer).
+.PHONY: test-driver-strip-windows-vm
+test-driver-strip-windows-vm: bin windows-ucrt-sysroots
+ bash scripts/windows_vm.sh boot
+ bash scripts/windows_vm.sh wait-ssh 600
+ $(_WIN_VM_ENV) KIT=$(abspath $(BIN)) bash test/strip/windows-vm.sh
+
# The parse/asm/macho harnesses select a cross-target via KIT_TEST_ARCH
# (default aa64); the link rt dependency is resolved through the shared
# _TEST_RT_<arch> map defined above (near test-rt-runtime).
diff --git a/test/strip/windows-vm.sh b/test/strip/windows-vm.sh
@@ -0,0 +1,190 @@
+#!/usr/bin/env bash
+# Windows linked-image strip acceptance. Build hosted PE32+ programs for both
+# shipped Windows architectures, preserve their loader imports exactly, and run
+# the original/--strip-debug/--strip-all images in the provisioned Windows VM.
+
+set -uo pipefail
+
+ROOT=${KIT_TEST_ROOT:-$(cd "$(dirname "$0")/../.." && pwd)}
+KIT=${KIT:-"$ROOT/build/kit"}
+VM="$ROOT/scripts/windows_vm.sh"
+HOST_CC=${KIT_WINDOWS_HOST_CC:-clang}
+LLVM_OBJDUMP=${KIT_WINDOWS_LLVM_OBJDUMP:-llvm-objdump}
+
+KIT_KIT_DIR="$ROOT/test/lib"
+. "$ROOT/test/lib/kit_sh_kit.sh"
+kit_report_init
+kit_require_kit strip-windows-vm
+kit_workdir strip-windows-vm
+work=$KIT_WORK
+
+for tool in "$HOST_CC" "$LLVM_OBJDUMP"; do
+ if ! command -v "$tool" >/dev/null 2>&1; then
+ kit_fail strip-windows-vm-tools "missing required tool: $tool"
+ kit_summary strip-windows-vm
+ kit_exit
+ fi
+done
+
+sdk_for_arch() {
+ local arch=$1 token triple candidate
+ case "$arch" in
+ x86_64) token=x64; triple=x86_64-w64-mingw32 ;;
+ aarch64) token=aarch64; triple=aarch64-w64-mingw32 ;;
+ *) return 1 ;;
+ esac
+
+ if [ -n "${KIT_SYSROOT:-}" ]; then
+ for candidate in "$KIT_SYSROOT/$triple" "$KIT_SYSROOT"; do
+ if [ -r "$candidate/include/windows.h" ] &&
+ [ -r "$candidate/lib/libucrt.a" ]; then
+ printf '%s\n' "$candidate"
+ return 0
+ fi
+ done
+ fi
+
+ candidate=$("$ROOT/scripts/llvm_mingw_sysroot.sh" path "$token" 2>/dev/null) ||
+ return 1
+ [ -r "$candidate/include/windows.h" ] &&
+ [ -r "$candidate/lib/libucrt.a" ] || return 1
+ printf '%s\n' "$candidate"
+}
+
+import_dump() {
+ local input=$1 output=$2
+ "$KIT" objdump -p "$input" |
+ awk '/^[[:space:]]+DLL Name:/ || /^[[:space:]]+Name:/ {
+ sub(/^[[:space:]]+/, ""); print
+ }' > "$output"
+}
+
+section_dump() {
+ "$LLVM_OBJDUMP" -h "$1" > "$2"
+}
+
+assert_has_debug() {
+ local label=$1 dump=$2
+ if grep -E '(^|[[:space:],])\.debug_' "$dump" >/dev/null 2>&1; then
+ ok "$label"
+ else
+ printf 'expected a .debug_* section\n' > "$work/$label.diag"
+ not_ok "$label" "$work/$label.diag"
+ fi
+}
+
+assert_no_debug() {
+ local label=$1 dump=$2
+ if grep -E '(^|[[:space:],])\.debug_' "$dump" >/dev/null 2>&1; then
+ grep -E '(^|[[:space:],])\.debug_' "$dump" > "$work/$label.diag"
+ not_ok "$label" "$work/$label.diag"
+ else
+ ok "$label"
+ fi
+}
+
+run_guest() {
+ local label=$1 vm_arch=$2 exe=$3
+ local raw_out="$work/$label.out" raw_err="$work/$label.err"
+ local norm_out="$work/$label.normalized.out"
+ local norm_err="$work/$label.normalized.err"
+
+ if "$VM" run "$vm_arch" "$exe" strip-probe > "$raw_out" 2> "$raw_err"; then
+ ok "$label-status"
+ else
+ not_ok "$label-status" "$raw_err"
+ return
+ fi
+
+ tr -d '\r' < "$raw_out" > "$norm_out"
+ tr -d '\r' < "$raw_err" > "$norm_err"
+ same_file "$label-stdout" "$work/expected.out" "$norm_out"
+ same_file "$label-stderr" "$work/expected.err" "$norm_err"
+}
+
+printf 'strip-runtime-ok\nstrip-fini-ok\n' > "$work/expected.out"
+printf 'strip-signal-ok\n' > "$work/expected.err"
+
+for arch in x86_64 aarch64; do
+ case "$arch" in
+ x86_64)
+ target=x86_64-w64-windows-gnu
+ vm_arch=x64
+ label=x64
+ builtins_name=libclang_rt.builtins-x86_64.a
+ ;;
+ aarch64)
+ target=aarch64-w64-windows-gnu
+ vm_arch=aarch64
+ label=aarch64
+ builtins_name=libclang_rt.builtins-aarch64.a
+ ;;
+ esac
+
+ if ! sdk=$(sdk_for_arch "$arch"); then
+ kit_fail "$label-sysroot" "missing llvm-mingw UCRT sysroot"
+ continue
+ fi
+ builtins="$sdk/lib/windows/$builtins_name"
+ if [ ! -r "$builtins" ]; then
+ kit_fail "$label-builtins" "missing compiler-rt archive: $builtins"
+ continue
+ fi
+
+ original="$work/$label-original.exe"
+ debug="$work/$label-debug.exe"
+ all="$work/$label-all.exe"
+
+ # Use an independent producer: Kit-linked PE currently carries no linked
+ # DWARF sections, while clang/lld emits the /<string-table-offset> long names
+ # used by real llvm-mingw images. Kit remains the sole strip transformer.
+ run_ok "$label-build" "$HOST_CC" --target="$target" --sysroot="$sdk" \
+ -fuse-ld=lld -nodefaultlibs -L"$sdk/lib/windows" -O1 -g -gdwarf-4 \
+ -Wl,--subsystem,console "$ROOT/test/strip/windows_runtime.c" \
+ -o "$original" \
+ -lmingw32 "$builtins" -lmoldname -lmingwex -lmsvcrt -ladvapi32 \
+ -lshell32 -luser32 -lkernel32 -lmingw32 "$builtins" -lmoldname \
+ -lmingwex -lmsvcrt -lkernel32
+ [ -f "$original" ] || continue
+
+ run_ok "$label-sections-before" section_dump "$original" \
+ "$work/$label-sections-before.txt"
+ assert_has_debug "$label-debug-present-before" \
+ "$work/$label-sections-before.txt"
+ run_ok "$label-imports-before" import_dump "$original" \
+ "$work/$label-imports-before.txt"
+ contains "$label-import-kernel32" "$work/$label-imports-before.txt" \
+ "DLL Name: KERNEL32.dll"
+ contains "$label-import-writefile" "$work/$label-imports-before.txt" \
+ "Name: WriteFile"
+ contains "$label-import-signal" "$work/$label-imports-before.txt" \
+ "Name: signal"
+ contains "$label-import-raise" "$work/$label-imports-before.txt" \
+ "Name: raise"
+ run_ok "$label-strip-debug" "$KIT" strip --strip-debug -o "$debug" \
+ "$original"
+ run_ok "$label-strip-all" "$KIT" strip --strip-all -o "$all" "$original"
+
+ for variant in debug all; do
+ case "$variant" in
+ debug) image=$debug ;;
+ all) image=$all ;;
+ esac
+ [ -f "$image" ] || continue
+ run_ok "$label-$variant-sections" section_dump "$image" \
+ "$work/$label-$variant-sections.txt"
+ assert_no_debug "$label-$variant-no-debug" \
+ "$work/$label-$variant-sections.txt"
+ run_ok "$label-$variant-imports" import_dump "$image" \
+ "$work/$label-$variant-imports.txt"
+ same_file "$label-$variant-imports-preserved" \
+ "$work/$label-imports-before.txt" "$work/$label-$variant-imports.txt"
+ done
+
+ run_guest "$label-original-run" "$vm_arch" "$original"
+ [ ! -f "$debug" ] || run_guest "$label-debug-run" "$vm_arch" "$debug"
+ [ ! -f "$all" ] || run_guest "$label-all-run" "$vm_arch" "$all"
+done
+
+kit_summary strip-windows-vm
+kit_exit
diff --git a/test/strip/windows_runtime.c b/test/strip/windows_runtime.c
@@ -0,0 +1,45 @@
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <windows.h>
+
+static int ctor_seen;
+static volatile sig_atomic_t signal_seen;
+static _Thread_local int tls_value = 37;
+
+__attribute__((constructor)) static void probe_ctor(void) { ctor_seen = 17; }
+
+static void probe_signal(int signo) {
+ if (signo == SIGFPE) signal_seen = 1;
+}
+
+static void probe_fini(void) {
+ static const char message[] = "strip-fini-ok\n";
+ DWORD wrote = 0;
+ WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), message, sizeof(message) - 1,
+ &wrote, 0);
+}
+
+__attribute__((noinline)) static int imported_behavior(const char *arg) {
+ return strcmp(arg, "strip-probe") == 0 ? 5 : 0;
+}
+
+int main(int argc, char **argv) {
+ static const char out_message[] = "strip-runtime-ok\n";
+ static const char err_message[] = "strip-signal-ok\n";
+ DWORD wrote = 0;
+
+ if (argc != 2 || imported_behavior(argv[1]) != 5) return 10;
+ if (ctor_seen != 17) return 11;
+ if (signal(SIGFPE, probe_signal) == SIG_ERR) return 12;
+ if (raise(SIGFPE) != 0 || !signal_seen) return 13;
+ tls_value += 5;
+ if (tls_value != 42) return 14;
+ if (atexit(probe_fini) != 0) return 15;
+
+ if (!WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), out_message,
+ sizeof(out_message) - 1, &wrote, 0)) return 16;
+ if (!WriteFile(GetStdHandle(STD_ERROR_HANDLE), err_message,
+ sizeof(err_message) - 1, &wrote, 0)) return 17;
+ return 0;
+}