kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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:
Mmk/test.mk | 8++++++++
Atest/strip/windows-vm.sh | 190+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/strip/windows_runtime.c | 45+++++++++++++++++++++++++++++++++++++++++++++
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; +}