windows-ucrt-hosted-smoke.sh (16826B)
1 #!/usr/bin/env bash 2 # test/coff/windows-ucrt-hosted-smoke.sh — Type-K (mode P) hosted Windows UCRT 3 # smoke, on the shared scripted-shell kit (test/lib/kit_sh_kit.sh). 4 # 5 # One program per UCRT surface (Sleep, windows.h coverage, runtime, UCRT stdio, 6 # imported-data, TLS, GUI WinMain) is built with `kit cc` for both 7 # x86_64-windows and aarch64-windows; the link-level checks inspect the PE 8 # import directory via `kit objdump -p` (run_ok + contains), and a negative 9 # check asserts no legacy CRT DLL is imported directly. The Wine runtime check 10 # is run conditionally and self-skips when podman/Wine are absent. 11 # 12 # Serial by design (mode P): all programs share one $work sandbox. 13 # 14 # Self-skip: prints SKIP and exits 0 when no llvm-mingw UCRT sysroot is found. 15 set -u 16 17 ROOT=${KIT_TEST_ROOT:-$(cd "$(dirname "$0")/../.." && pwd)} 18 KIT=${KIT:-"$ROOT/build/kit"} 19 SDK=${KIT_SYSROOT:-} 20 21 KIT_KIT_DIR="$ROOT/test/lib" 22 . "$ROOT/test/lib/kit_sh_kit.sh" 23 kit_report_init 24 25 LABEL_SUITE=windows-ucrt-hosted-smoke 26 27 find_sdk() { 28 local arch=$1 29 local d 30 for d in \ 31 "$ROOT"/build/llvm-mingw/*/ucrt/"$arch"-w64-mingw32 \ 32 /tmp/llvm-mingw*/llvm-mingw-*-ucrt-*/"$arch"-w64-mingw32 \ 33 /tmp/llvm-mingw*/"$arch"-w64-mingw32 \ 34 /private/tmp/llvm-mingw*/llvm-mingw-*-ucrt-*/"$arch"-w64-mingw32 \ 35 /private/tmp/llvm-mingw*/"$arch"-w64-mingw32; do 36 if [ -d "$d/lib" ] && [ -r "$d/include/windows.h" ]; then 37 printf '%s\n' "$d" 38 return 0 39 fi 40 done 41 return 1 42 } 43 44 sdk_for_arch() { 45 local arch=$1 46 local base 47 if [ -n "$SDK" ]; then 48 if [ "$(basename "$SDK")" = "$arch-w64-mingw32" ]; then 49 printf '%s\n' "$SDK" 50 return 0 51 fi 52 base=$(dirname "$SDK") 53 if [ -d "$base/$arch-w64-mingw32/lib" ] && 54 [ -r "$base/$arch-w64-mingw32/include/windows.h" ]; then 55 printf '%s\n' "$base/$arch-w64-mingw32" 56 return 0 57 fi 58 fi 59 find_sdk "$arch" 60 } 61 62 # kit presence: was a hard FAIL+exit 1 in the original. Preserve that verdict. 63 if [ ! -x "$KIT" ]; then 64 kit_fail "$LABEL_SUITE/kit-present" "kit binary not found: $KIT" 65 kit_summary "$LABEL_SUITE" 66 kit_exit 67 fi 68 69 TMP=${TMPDIR:-/tmp} 70 work=$(mktemp -d "$TMP/kit-windows-ucrt-smoke.XXXXXX") 71 WORK_REAL=$(cd "$work" && pwd -P) 72 trap 'rm -rf "$work"' EXIT 73 74 CONSOLE_C=$work/windows-h.c 75 HEADER_C=$work/windows-h-coverage.c 76 RUNTIME_C=$work/runtime.c 77 STDIO_C=$work/stdio.c 78 IMPORTDATA_C=$work/import-data.c 79 GUI_C=$work/gui.c 80 TLS_C=$work/tls.c 81 82 cat >"$CONSOLE_C" <<'SRC' 83 #include <windows.h> 84 int main(void) { Sleep(1); return 0; } 85 SRC 86 87 cat >"$HEADER_C" <<'SRC' 88 #include <windows.h> 89 #include <fileapi.h> 90 #include <processthreadsapi.h> 91 #include <synchapi.h> 92 #include <errhandlingapi.h> 93 #include <winuser.h> 94 95 _Static_assert(sizeof(long) == 4, "windows long is LLP64"); 96 _Static_assert(sizeof(WCHAR) == 2, "WCHAR is UTF-16"); 97 _Static_assert(sizeof(void*) == 8, "PE32+ pointer size"); 98 99 static DWORD WINAPI thread_proc(LPVOID ctx) { 100 return (DWORD)(ULONG_PTR)ctx; 101 } 102 103 static BOOL CALLBACK enum_windows_proc(HWND hwnd, LPARAM lparam) { 104 RECT r; 105 POINT p; 106 WINDOWPLACEMENT wp; 107 ZeroMemory(&wp, sizeof(wp)); 108 wp.length = sizeof(wp); 109 GetClientRect(hwnd, &r); 110 p.x = r.left; 111 p.y = r.top; 112 ClientToScreen(hwnd, &p); 113 SetLastError((DWORD)lparam); 114 return TRUE; 115 } 116 117 int main(void) { 118 HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); 119 DWORD wrote = 0; 120 const char msg[] = "windows coverage\n"; 121 WriteFile(out, msg, sizeof(msg) - 1, &wrote, NULL); 122 123 SECURITY_ATTRIBUTES sa; 124 ZeroMemory(&sa, sizeof(sa)); 125 sa.nLength = sizeof(sa); 126 sa.bInheritHandle = FALSE; 127 128 WCHAR tmp_path[MAX_PATH]; 129 WCHAR file_path[MAX_PATH]; 130 GetTempPathW(MAX_PATH, tmp_path); 131 GetTempFileNameW(tmp_path, L"cfr", 0, file_path); 132 HANDLE h = CreateFileW(file_path, GENERIC_READ | GENERIC_WRITE, 0, &sa, 133 CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL); 134 if (h != INVALID_HANDLE_VALUE) { 135 LARGE_INTEGER pos; 136 pos.QuadPart = 0; 137 SetFilePointerEx(h, pos, NULL, FILE_BEGIN); 138 CloseHandle(h); 139 DeleteFileW(file_path); 140 } 141 142 CRITICAL_SECTION cs; 143 InitializeCriticalSection(&cs); 144 EnterCriticalSection(&cs); 145 LeaveCriticalSection(&cs); 146 DeleteCriticalSection(&cs); 147 148 DWORD tid = 0; 149 HANDLE th = CreateThread(NULL, 0, thread_proc, (LPVOID)(ULONG_PTR)3, 0, &tid); 150 if (th) { 151 WaitForSingleObject(th, INFINITE); 152 CloseHandle(th); 153 } 154 155 EnumWindows(enum_windows_proc, 0); 156 MessageBoxW(NULL, L"", L"", MB_OK | MB_SETFOREGROUND); 157 return 0; 158 } 159 SRC 160 161 cat >"$RUNTIME_C" <<'SRC' 162 #include <windows.h> 163 #include <stdlib.h> 164 #include <string.h> 165 166 static int cmp_ints(const void *a, const void *b) { 167 int ia = *(const int *)a; 168 int ib = *(const int *)b; 169 return (ia > ib) - (ia < ib); 170 } 171 172 static int has_env(char **envp, const char *prefix) { 173 size_t n = strlen(prefix); 174 if (!envp) return 0; 175 for (; *envp; ++envp) { 176 if (strncmp(*envp, prefix, n) == 0) return 1; 177 } 178 return 0; 179 } 180 181 int main(int argc, char **argv, char **envp) { 182 if (argc < 3) return 10; 183 if (strcmp(argv[1], "alpha") != 0 || strcmp(argv[2], "beta") != 0) return 11; 184 if (!has_env(envp, "KIT_WIN_PROBE=present")) return 12; 185 186 HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); 187 HANDLE err = GetStdHandle(STD_ERROR_HANDLE); 188 DWORD wrote = 0; 189 WriteFile(out, "stdout-ok\n", 10, &wrote, 0); 190 WriteFile(err, "stderr-ok\n", 10, &wrote, 0); 191 192 HANDLE heap = GetProcessHeap(); 193 char *mem = (char *)HeapAlloc(heap, 0, 32); 194 if (!mem) return 13; 195 strcpy(mem, "heap-ok"); 196 if (strcmp(mem, "heap-ok") != 0) return 14; 197 HeapFree(heap, 0, mem); 198 199 int vals[4] = {4, 1, 3, 2}; 200 qsort(vals, 4, sizeof(vals[0]), cmp_ints); 201 if (vals[0] != 1 || vals[3] != 4) return 15; 202 203 SetLastError(1234); 204 if (GetLastError() != 1234) return 16; 205 206 char dir[MAX_PATH]; 207 char path[MAX_PATH]; 208 if (!GetTempPathA(MAX_PATH, dir)) return 17; 209 wsprintfA(path, "%skit-runtime-%lu.tmp", dir, 210 (unsigned long)GetCurrentProcessId()); 211 HANDLE f = CreateFileA(path, GENERIC_READ | GENERIC_WRITE, 0, 0, 212 CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, 0); 213 if (f == INVALID_HANDLE_VALUE) return 18; 214 if (!WriteFile(f, "file-ok", 7, &wrote, 0) || wrote != 7) return 19; 215 SetFilePointer(f, 0, 0, FILE_BEGIN); 216 char buf[8]; 217 DWORD got = 0; 218 memset(buf, 0, sizeof(buf)); 219 if (!ReadFile(f, buf, 7, &got, 0)) return 20; 220 CloseHandle(f); 221 DeleteFileA(path); 222 if (got != 7 || strcmp(buf, "file-ok") != 0) return 21; 223 return 0; 224 } 225 SRC 226 227 cat >"$STDIO_C" <<'SRC' 228 #define _INC_STDIO_S 229 #include <stdio.h> 230 231 int main(void) { 232 puts("puts-ok"); 233 fputs("fputs-ok\n", stdout); 234 printf("printf-ok\n"); 235 fflush(stdout); 236 return 0; 237 } 238 SRC 239 240 cat >"$IMPORTDATA_C" <<'SRC' 241 #include <windows.h> 242 243 extern char **__dcrt_initial_narrow_environment; 244 245 int main(void) { 246 HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); 247 DWORD wrote = 0; 248 WriteFile(out, "importdata\n", 11, &wrote, 0); 249 if (&__dcrt_initial_narrow_environment == 0) return 10; 250 if (__dcrt_initial_narrow_environment == 0) return 11; 251 return 0; 252 } 253 SRC 254 255 cat >"$GUI_C" <<'SRC' 256 #include <windows.h> 257 int WINAPI WinMain(HINSTANCE hinst, HINSTANCE prev, LPSTR cmd, int show) { 258 (void)hinst; 259 (void)prev; 260 (void)cmd; 261 (void)show; 262 return 0; 263 } 264 SRC 265 266 cat >"$TLS_C" <<'SRC' 267 struct tlsdir { 268 unsigned long long start; 269 unsigned long long end; 270 unsigned long long index; 271 unsigned long long callbacks; 272 unsigned int zero_fill; 273 unsigned int characteristics; 274 }; 275 276 extern unsigned char __ImageBase; 277 extern const struct tlsdir _tls_used; 278 279 _Thread_local int tls_init = 7; 280 _Thread_local int tls_zero; 281 282 static unsigned int rd32(const unsigned char *p) { 283 return (unsigned int)p[0] | 284 ((unsigned int)p[1] << 8) | 285 ((unsigned int)p[2] << 16) | 286 ((unsigned int)p[3] << 24); 287 } 288 289 static int check_tls_directory(void) { 290 const unsigned char *image = &__ImageBase; 291 unsigned int pe = rd32(image + 0x3c); 292 const unsigned char *opt = image + pe + 4 + 20; 293 const unsigned char *dir = opt + 112 + 9 * 8; 294 unsigned int tls_rva = rd32(dir); 295 unsigned int tls_size = rd32(dir + 4); 296 if (tls_size != 40) return 34; 297 if ((const unsigned char *)&_tls_used != image + tls_rva) return 35; 298 if (_tls_used.start == 0 || _tls_used.end <= _tls_used.start) return 36; 299 if (_tls_used.index == 0) return 37; 300 if (_tls_used.zero_fill != 0 || _tls_used.characteristics != 0) return 38; 301 return 0; 302 } 303 304 static int bump(void) { 305 tls_zero += 3; 306 tls_init += tls_zero; 307 return tls_init; 308 } 309 310 int main(void) { 311 int dir = check_tls_directory(); 312 if (dir) return dir; 313 int a = bump(); 314 int b = bump(); 315 if (a != 10) return 31; 316 if (b != 16) return 32; 317 if (tls_zero != 6) return 33; 318 return 0; 319 } 320 SRC 321 322 # ---- assert helpers (mode-P verbs, $work-confined) ------------------------- 323 324 # no_legacy_crt_imports NAME DUMP : pass iff DUMP imports no msvcrt/ucrt DLL. 325 no_legacy_crt_imports() { 326 local name=$1 dump=$2 327 if grep -Eiq 'DLL Name: (msvcrt|ucrt)\.dll' "$dump"; then 328 grep -Ei 'DLL Name: (msvcrt|ucrt)\.dll' "$dump" > "$work/$name.diag" 329 not_ok "$name" "$work/$name.diag" 330 else 331 ok "$name" 332 fi 333 } 334 335 # not_contains NAME FILE NEEDLE : pass iff NEEDLE is absent from FILE. 336 not_contains() { 337 local name=$1 file=$2 needle=$3 338 if grep -Fq "$needle" "$file"; then 339 { printf 'unexpected text: %s\n' "$needle"; } > "$work/$name.diag" 340 not_ok "$name" "$work/$name.diag" 341 else 342 ok "$name" 343 fi 344 } 345 346 # matches NAME FILE EREGEX : pass iff FILE has a line matching EREGEX. 347 matches() { 348 local name=$1 file=$2 re=$3 349 if grep -Eq "$re" "$file"; then ok "$name" 350 else { printf 'no line matched: %s\n' "$re"; } > "$work/$name.diag"; not_ok "$name" "$work/$name.diag"; fi 351 } 352 353 # run_wine_if_available LABEL IMAGE POD_ARCH EXE [ARGS...] : conditional Wine 354 # run. Self-skips (kit_skip) when podman or the container image is absent; 355 # otherwise pass iff the program exits 0 under Wine. 356 run_wine_if_available() { 357 local label=$1 image=$2 pod_arch=$3 exe=$4 358 shift 4 359 case "$pod_arch" in 360 amd64) 361 if [ -n "${KIT_WINDOWS_VM_X64:-${KIT_WINDOWS_VM_AMD64:-}}" ]; then 362 if KIT_WIN_PROBE=present "$ROOT/scripts/windows_vm.sh" run x64 "$exe" "$@" \ 363 > "$work/$label-vm.out" 2> "$work/$label-vm.err"; then 364 ok "$label-vm" 365 else 366 not_ok "$label-vm" "$work/$label-vm.err" 367 fi 368 return 0 369 fi 370 ;; 371 arm64) 372 if [ -n "${KIT_WINDOWS_VM_AARCH64:-${KIT_WINDOWS_VM_ARM64:-}}" ]; then 373 if KIT_WIN_PROBE=present "$ROOT/scripts/windows_vm.sh" run aarch64 "$exe" "$@" \ 374 > "$work/$label-vm.out" 2> "$work/$label-vm.err"; then 375 ok "$label-vm" 376 else 377 not_ok "$label-vm" "$work/$label-vm.err" 378 fi 379 return 0 380 fi 381 ;; 382 esac 383 if ! command -v podman >/dev/null 2>&1; then 384 skip_test "$label-wine" "podman unavailable" 385 return 0 386 fi 387 if ! podman image exists "$image" >/dev/null 2>&1; then 388 skip_test "$label-wine" "$image unavailable" 389 return 0 390 fi 391 if podman run --rm --arch "$pod_arch" -v "$WORK_REAL:/probe:ro" "$image" \ 392 bash -lc " 393 export WINEDEBUG=-all WINEPREFIX=/tmp/wineprefix KIT_WIN_PROBE=present 394 timeout 120s /usr/lib/wine/wine64 /probe/$(basename "$exe") $* 395 rc=\$? 396 echo \"$label exit=\$rc\" 397 test \"\$rc\" -eq 0 398 " > "$work/$label-wine.out" 2> "$work/$label-wine.err"; then 399 ok "$label-wine" 400 else 401 not_ok "$label-wine" "$work/$label-wine.err" 402 fi 403 } 404 405 ran=0 406 for arch in x86_64 aarch64; do 407 case "$arch" in 408 x86_64) 409 target=x86_64-windows 410 label=x64 411 image=localhost/kit-wine-amd64 412 pod_arch=amd64 413 ;; 414 aarch64) 415 target=aarch64-windows 416 label=aarch64 417 image=localhost/kit-wine-arm64 418 pod_arch=arm64 419 ;; 420 esac 421 422 if ! ARCH_SDK=$(sdk_for_arch "$arch"); then 423 skip_test "$LABEL_SUITE/$label-sysroot" "no $arch llvm-mingw UCRT sysroot" 424 continue 425 fi 426 # Discovered-but-invalid sysroot was a hard FAIL+exit 1 originally. 427 if [ ! -r "$ARCH_SDK/include/windows.h" ] || 428 [ ! -r "$ARCH_SDK/lib/libucrt.a" ]; then 429 echo "invalid UCRT llvm-mingw sysroot: $ARCH_SDK" > "$work/$label-sysroot.diag" 430 not_ok "$LABEL_SUITE/$label-sysroot" "$work/$label-sysroot.diag" 431 kit_summary "$LABEL_SUITE" 432 kit_exit 433 fi 434 435 ran=1 436 CONSOLE_EXE=$work/windows-h-$arch.exe 437 CONSOLE_DUMP=$work/windows-h-$arch.dump 438 HEADER_EXE=$work/windows-h-coverage-$arch.exe 439 HEADER_DUMP=$work/windows-h-coverage-$arch.dump 440 RUNTIME_EXE=$work/runtime-$arch.exe 441 RUNTIME_DUMP=$work/runtime-$arch.dump 442 STDIO_EXE=$work/stdio-$arch.exe 443 STDIO_DUMP=$work/stdio-$arch.dump 444 IMPORTDATA_EXE=$work/import-data-$arch.exe 445 IMPORTDATA_DUMP=$work/import-data-$arch.dump 446 TLS_EXE=$work/tls-$arch.exe 447 TLS_DUMP=$work/tls-$arch.dump 448 GUI_EXE=$work/gui-$arch.exe 449 GUI_DUMP=$work/gui-$arch.dump 450 451 # build_dump NAME EXE DUMP -- CC_ARGS... : build EXE, then `objdump -p` it 452 # into DUMP. Returns 0 only if both the EXE and the DUMP were produced, so 453 # the per-program import asserts below run iff the link round-tripped. 454 build_dump() { 455 local bn=$1 exe=$2 dump=$3; shift 3 456 [ "$1" = "--" ] && shift 457 run_ok "$bn-build" "$KIT" cc -target "$target" --sysroot "$ARCH_SDK" "$@" -o "$exe" 458 [ -f "$exe" ] || return 1 459 run_ok "$bn-objdump" "$KIT" objdump -p "$exe" 460 [ -s "$work/$bn-objdump.out" ] || return 1 461 cp "$work/$bn-objdump.out" "$dump" 462 } 463 464 # ---- console Sleep ---- 465 if build_dump "$label-console" "$CONSOLE_EXE" "$CONSOLE_DUMP" -- -mconsole "$CONSOLE_C"; then 466 no_legacy_crt_imports "$label-console-no-legacy-crt" "$CONSOLE_DUMP" 467 not_contains "$label-console-no-weak-set-app-type" "$CONSOLE_DUMP" "Name: __set_app_type" 468 contains "$label-console-kernel32" "$CONSOLE_DUMP" "DLL Name: KERNEL32.dll" 469 contains "$label-console-sleep" "$CONSOLE_DUMP" "Name: Sleep" 470 contains "$label-console-crt-runtime-dll" "$CONSOLE_DUMP" "DLL Name: api-ms-win-crt-runtime-l1-1-0.dll" 471 contains "$label-console-set-app-type" "$CONSOLE_DUMP" "Name: _set_app_type" 472 run_wine_if_available "$label-console-Sleep" "$image" "$pod_arch" "$CONSOLE_EXE" 473 fi 474 475 # ---- windows.h coverage ---- 476 if build_dump "$label-header" "$HEADER_EXE" "$HEADER_DUMP" -- -mconsole "$HEADER_C"; then 477 no_legacy_crt_imports "$label-header-no-legacy-crt" "$HEADER_DUMP" 478 contains "$label-header-CreateFileW" "$HEADER_DUMP" "Name: CreateFileW" 479 contains "$label-header-CreateThread" "$HEADER_DUMP" "Name: CreateThread" 480 contains "$label-header-WaitForSingleObject" "$HEADER_DUMP" "Name: WaitForSingleObject" 481 contains "$label-header-MessageBoxW" "$HEADER_DUMP" "Name: MessageBoxW" 482 fi 483 484 # ---- runtime ---- 485 if build_dump "$label-runtime" "$RUNTIME_EXE" "$RUNTIME_DUMP" -- "$RUNTIME_C"; then 486 no_legacy_crt_imports "$label-runtime-no-legacy-crt" "$RUNTIME_DUMP" 487 contains "$label-runtime-HeapAlloc" "$RUNTIME_DUMP" "Name: HeapAlloc" 488 contains "$label-runtime-CreateFileA" "$RUNTIME_DUMP" "Name: CreateFileA" 489 contains "$label-runtime-qsort" "$RUNTIME_DUMP" "Name: qsort" 490 run_wine_if_available "$label-runtime" "$image" "$pod_arch" "$RUNTIME_EXE" alpha beta 491 fi 492 493 # ---- UCRT stdio ---- 494 if build_dump "$label-stdio" "$STDIO_EXE" "$STDIO_DUMP" -- "$STDIO_C"; then 495 no_legacy_crt_imports "$label-stdio-no-legacy-crt" "$STDIO_DUMP" 496 contains "$label-stdio-crt-stdio-dll" "$STDIO_DUMP" "DLL Name: api-ms-win-crt-stdio-l1-1-0.dll" 497 contains "$label-stdio-fflush" "$STDIO_DUMP" "Name: fflush" 498 run_wine_if_available "$label-UCRT-stdio" "$image" "$pod_arch" "$STDIO_EXE" 499 fi 500 501 # ---- imported-data ---- 502 if build_dump "$label-importdata" "$IMPORTDATA_EXE" "$IMPORTDATA_DUMP" -- "$IMPORTDATA_C"; then 503 no_legacy_crt_imports "$label-importdata-no-legacy-crt" "$IMPORTDATA_DUMP" 504 contains "$label-importdata-crt-private-dll" "$IMPORTDATA_DUMP" "DLL Name: api-ms-win-crt-private-l1-1-0.dll" 505 contains "$label-importdata-dcrt-env" "$IMPORTDATA_DUMP" "Name: __dcrt_initial_narrow_environment" 506 run_wine_if_available "$label-imported-data" "$image" "$pod_arch" "$IMPORTDATA_EXE" 507 fi 508 509 # ---- TLS ---- 510 if build_dump "$label-tls" "$TLS_EXE" "$TLS_DUMP" -- "$TLS_C"; then 511 no_legacy_crt_imports "$label-tls-no-legacy-crt" "$TLS_DUMP" 512 matches "$label-tls-directory" "$TLS_DUMP" \ 513 '^[[:space:]]*9[[:space:]]+TLS[[:space:]]+0x[0-9a-fA-F]+[[:space:]]+0x00000028' 514 fi 515 516 # ---- GUI WinMain ---- 517 if build_dump "$label-gui" "$GUI_EXE" "$GUI_DUMP" -- -mwindows "$GUI_C"; then 518 contains "$label-gui-subsystem" "$GUI_DUMP" "Subsystem: 2 (WINDOWS_GUI)" 519 no_legacy_crt_imports "$label-gui-no-legacy-crt" "$GUI_DUMP" 520 fi 521 522 run_wine_if_available "$label-TLS" "$image" "$pod_arch" "$TLS_EXE" 523 done 524 525 if [ "$ran" -eq 0 ]; then 526 skip_test "$LABEL_SUITE" "set KIT_SYSROOT or install llvm-mingw UCRT under /tmp/llvm-mingw*" 527 fi 528 529 kit_summary "$LABEL_SUITE" 530 kit_exit