windows-system-dlls-smoke.sh (14762B)
1 #!/usr/bin/env bash 2 # test/coff/windows-system-dlls-smoke.sh — Type-K (mode P) Windows system-DLL 3 # coverage smoke, on the shared scripted-shell kit (test/lib/kit_sh_kit.sh). 4 # 5 # Companion to windows-ucrt-hosted-smoke.sh: that proves the UCRT console + GUI 6 # round-trip for one program per surface; this broadens coverage across the 7 # large system DLLs an application links against: 8 # 9 # user32 + gdi32 (GUI window + drawing) 10 # advapi32 (registry) 11 # ws2_32 (Winsock lifecycle) 12 # ole32 (COM init) 13 # shell32 (CommandLineToArgvW) 14 # comctl32 (InitCommonControls) 15 # libucrt.a (mixed short-import + long-form members) 16 # 17 # Each program is built with `kit cc` for both x86_64-windows and 18 # aarch64-windows; the link-level check inspects the PE import directory via 19 # `kit objdump -p` (run_ok + contains). The Wine runtime check is run 20 # conditionally and self-skips when the matching podman/Wine container is 21 # absent. 22 # 23 # Serial by design (mode P): all programs share one $work sandbox. 24 # 25 # Self-skip: prints SKIP and exits 0 when no llvm-mingw UCRT sysroot is found. 26 set -u 27 28 ROOT=${KIT_TEST_ROOT:-$(cd "$(dirname "$0")/../.." && pwd)} 29 KIT=${KIT:-"$ROOT/build/kit"} 30 SDK=${KIT_SYSROOT:-} 31 32 KIT_KIT_DIR="$ROOT/test/lib" 33 . "$ROOT/test/lib/kit_sh_kit.sh" 34 kit_report_init 35 36 LABEL_SUITE=windows-system-dlls-smoke 37 38 find_sdk() { 39 local arch=$1 40 local d 41 for d in \ 42 "${XDG_CACHE_HOME:-$HOME/.cache}"/kit/sysroots/windows/*/ucrt/"$arch"-w64-mingw32 \ 43 "$ROOT"/build/llvm-mingw/*/ucrt/"$arch"-w64-mingw32 \ 44 /tmp/llvm-mingw*/llvm-mingw-*-ucrt-*/"$arch"-w64-mingw32 \ 45 /tmp/llvm-mingw*/"$arch"-w64-mingw32 \ 46 /private/tmp/llvm-mingw*/llvm-mingw-*-ucrt-*/"$arch"-w64-mingw32 \ 47 /private/tmp/llvm-mingw*/"$arch"-w64-mingw32; do 48 if [ -d "$d/lib" ] && [ -r "$d/include/windows.h" ]; then 49 printf '%s\n' "$d" 50 return 0 51 fi 52 done 53 return 1 54 } 55 56 sdk_for_arch() { 57 local arch=$1 58 local base 59 if [ -n "$SDK" ]; then 60 if [ "$(basename "$SDK")" = "$arch-w64-mingw32" ]; then 61 printf '%s\n' "$SDK" 62 return 0 63 fi 64 base=$(dirname "$SDK") 65 if [ -d "$base/$arch-w64-mingw32/lib" ] && 66 [ -r "$base/$arch-w64-mingw32/include/windows.h" ]; then 67 printf '%s\n' "$base/$arch-w64-mingw32" 68 return 0 69 fi 70 fi 71 find_sdk "$arch" 72 } 73 74 # kit presence: was a hard FAIL+exit 1 in the original. Preserve that verdict. 75 if [ ! -x "$KIT" ]; then 76 kit_fail "$LABEL_SUITE/kit-present" "kit binary not found: $KIT" 77 kit_summary "$LABEL_SUITE" 78 kit_exit 79 fi 80 81 TMP=${TMPDIR:-/tmp} 82 work=$(mktemp -d "$TMP/kit-windows-system-dlls-smoke.XXXXXX") 83 WORK_REAL=$(cd "$work" && pwd -P) 84 trap 'rm -rf "$work"' EXIT 85 86 GUI_C=$work/gui_hello_window.c 87 GDI_C=$work/gdi_drawing.c 88 REG_C=$work/advapi32_registry.c 89 WS_C=$work/ws2_32_socket.c 90 OLE_C=$work/ole32_coinit.c 91 SHELL_C=$work/shell32_argv.c 92 COMCTL_C=$work/comctl32_init.c 93 MIXED_C=$work/mixed_ucrt.c 94 95 # A hidden window + minimal message-pump program. Wine in a headless 96 # container may legitimately refuse to create a real window; the 97 # program tolerates that and returns 0 so the link-level imports check 98 # is what gates the test. 99 cat >"$GUI_C" <<'SRC' 100 #define UNICODE 101 #define _UNICODE 102 #include <windows.h> 103 104 static LRESULT CALLBACK wp(HWND h, UINT m, WPARAM w, LPARAM l) { 105 if (m == WM_DESTROY) PostQuitMessage(0); 106 return DefWindowProcW(h, m, w, l); 107 } 108 109 int WINAPI WinMain(HINSTANCE i, HINSTANCE p, LPSTR c, int s) { 110 (void)p; (void)c; (void)s; 111 WNDCLASSEXW wc; 112 ZeroMemory(&wc, sizeof(wc)); 113 wc.cbSize = sizeof(wc); 114 wc.lpfnWndProc = wp; 115 wc.hInstance = i; 116 wc.lpszClassName = L"kit_hello"; 117 wc.hCursor = LoadCursorW(NULL, IDC_ARROW); 118 (void)RegisterClassExW(&wc); 119 HWND h = CreateWindowExW(0, L"kit_hello", L"kit", WS_OVERLAPPEDWINDOW, 120 0, 0, 16, 16, NULL, NULL, i, NULL); 121 if (h) { 122 PostMessageW(h, WM_QUIT, 0, 0); 123 MSG msg; 124 while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { 125 if (msg.message == WM_QUIT) break; 126 TranslateMessage(&msg); 127 DispatchMessageW(&msg); 128 } 129 DestroyWindow(h); 130 } 131 return 0; 132 } 133 SRC 134 135 # gdi32 surface: create a memory DC, select a stock font and brush, 136 # release. Stock objects do not require an active display, so this 137 # runs cleanly under Wine in headless containers. 138 cat >"$GDI_C" <<'SRC' 139 #include <windows.h> 140 141 int main(void) { 142 HDC screen = GetDC(NULL); 143 HDC mem = CreateCompatibleDC(screen); 144 HGDIOBJ old_font = SelectObject(mem, GetStockObject(SYSTEM_FONT)); 145 HGDIOBJ old_brush = SelectObject(mem, GetStockObject(WHITE_BRUSH)); 146 TEXTMETRICW tm; 147 GetTextMetricsW(mem, &tm); 148 SelectObject(mem, old_font); 149 SelectObject(mem, old_brush); 150 DeleteDC(mem); 151 ReleaseDC(NULL, screen); 152 return tm.tmHeight > 0 ? 0 : 1; 153 } 154 SRC 155 156 # advapi32 surface: open a well-known read-only registry key and close 157 # it. HKEY_CURRENT_USER\Environment exists by default under Wine. 158 cat >"$REG_C" <<'SRC' 159 #include <windows.h> 160 161 int main(void) { 162 HKEY h = NULL; 163 LONG rc = RegOpenKeyExW(HKEY_CURRENT_USER, L"Environment", 0, KEY_READ, &h); 164 if (rc == ERROR_SUCCESS) { 165 DWORD subkeys = 0, values = 0; 166 RegQueryInfoKeyW(h, NULL, NULL, NULL, &subkeys, NULL, NULL, &values, 167 NULL, NULL, NULL, NULL); 168 RegCloseKey(h); 169 return 0; 170 } 171 /* Some Wine configurations may not pre-create the Environment key. 172 * The link-level test (imports satisfied) is what we care about. */ 173 return rc == ERROR_FILE_NOT_FOUND ? 0 : 2; 174 } 175 SRC 176 177 # ws2_32 surface: full WSAStartup/socket/closesocket/WSACleanup 178 # lifecycle with no network traffic. 179 cat >"$WS_C" <<'SRC' 180 #include <winsock2.h> 181 #include <windows.h> 182 183 int main(void) { 184 WSADATA wsa; 185 if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return 1; 186 SOCKET s = socket(AF_INET, SOCK_DGRAM, 0); 187 if (s == INVALID_SOCKET) { WSACleanup(); return 2; } 188 closesocket(s); 189 WSACleanup(); 190 return 0; 191 } 192 SRC 193 194 # ole32 surface: COM apartment init/teardown. 195 cat >"$OLE_C" <<'SRC' 196 #include <windows.h> 197 #include <objbase.h> 198 199 int main(void) { 200 HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); 201 if (FAILED(hr)) return 1; 202 CoUninitialize(); 203 return 0; 204 } 205 SRC 206 207 # shell32 surface: CommandLineToArgvW round-trip. 208 cat >"$SHELL_C" <<'SRC' 209 #include <windows.h> 210 #include <shellapi.h> 211 212 int main(void) { 213 int argc = 0; 214 LPWSTR cmd = GetCommandLineW(); 215 LPWSTR* argv = CommandLineToArgvW(cmd, &argc); 216 int ok = (argv != NULL && argc >= 1); 217 if (argv) LocalFree(argv); 218 return ok ? 0 : 1; 219 } 220 SRC 221 222 # comctl32 surface: legacy InitCommonControls. Pulls in comctl32.dll 223 # imports without needing a real display. 224 cat >"$COMCTL_C" <<'SRC' 225 #include <windows.h> 226 #include <commctrl.h> 227 228 int main(void) { 229 INITCOMMONCONTROLSEX icc; 230 ZeroMemory(&icc, sizeof(icc)); 231 icc.dwSize = sizeof(icc); 232 icc.dwICC = ICC_STANDARD_CLASSES; 233 /* Both spellings exist in libcomctl32.a; we use the simpler one. */ 234 InitCommonControls(); 235 return InitCommonControlsEx(&icc) ? 0 : 0; 236 } 237 SRC 238 239 # Mixed libucrt.a members: pulls in both short-import members 240 # (api-ms-win-crt-stdio for puts/fflush) and a long-form COFF helper 241 # (fabsf lives in lib64_libmingwex_a-*.o as a real .o member). 242 cat >"$MIXED_C" <<'SRC' 243 #include <math.h> 244 #include <stdio.h> 245 #include <windows.h> 246 247 int main(void) { 248 float x = fabsf(-1.5f); 249 HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); 250 DWORD wrote = 0; 251 WriteFile(out, "mixed-ok\n", 9, &wrote, 0); 252 fflush(stdout); 253 return x == 1.5f ? 0 : 1; 254 } 255 SRC 256 257 # ---- assert helpers (mode-P verbs, $work-confined) ------------------------- 258 259 no_legacy_crt_imports() { 260 local name=$1 dump=$2 261 if grep -Eiq 'DLL Name: (msvcrt|ucrt)\.dll' "$dump"; then 262 grep -Ei 'DLL Name: (msvcrt|ucrt)\.dll' "$dump" > "$work/$name.diag" 263 not_ok "$name" "$work/$name.diag" 264 else 265 ok "$name" 266 fi 267 } 268 269 run_wine_if_available() { 270 local label=$1 image=$2 pod_arch=$3 exe=$4 271 shift 4 272 case "$pod_arch" in 273 amd64) 274 if [ -n "${KIT_WINDOWS_VM_X64:-${KIT_WINDOWS_VM_AMD64:-}}" ]; then 275 if "$ROOT/scripts/windows_vm.sh" run x64 "$exe" "$@" \ 276 > "$work/$label-vm.out" 2> "$work/$label-vm.err"; then 277 ok "$label-vm" 278 else 279 not_ok "$label-vm" "$work/$label-vm.err" 280 fi 281 return 0 282 fi 283 ;; 284 arm64) 285 if [ -n "${KIT_WINDOWS_VM_AARCH64:-${KIT_WINDOWS_VM_ARM64:-}}" ]; then 286 if "$ROOT/scripts/windows_vm.sh" run aarch64 "$exe" "$@" \ 287 > "$work/$label-vm.out" 2> "$work/$label-vm.err"; then 288 ok "$label-vm" 289 else 290 not_ok "$label-vm" "$work/$label-vm.err" 291 fi 292 return 0 293 fi 294 ;; 295 esac 296 if ! command -v podman >/dev/null 2>&1; then 297 skip_test "$label-wine" "podman unavailable" 298 return 0 299 fi 300 if ! podman image exists "$image" >/dev/null 2>&1; then 301 skip_test "$label-wine" "$image unavailable" 302 return 0 303 fi 304 if podman run --rm --arch "$pod_arch" -v "$WORK_REAL:/probe:ro" "$image" \ 305 bash -lc " 306 export WINEDEBUG=-all WINEPREFIX=/tmp/wineprefix 307 timeout 120s /usr/lib/wine/wine64 /probe/$(basename "$exe") $* 308 rc=\$? 309 echo \"$label exit=\$rc\" 310 test \"\$rc\" -eq 0 311 " > "$work/$label-wine.out" 2> "$work/$label-wine.err"; then 312 ok "$label-wine" 313 else 314 not_ok "$label-wine" "$work/$label-wine.err" 315 fi 316 } 317 318 # build_and_check <label> <c-source> <exe> <dump> <link-mode> <libs> 319 # <expected-dll-1> [<expected-dll-2> ...] -- [<expected-sym-1> ...] 320 # 321 # link-mode is "console" or "windows" (drives -mconsole vs -mwindows). libs is 322 # a space-separated list of `-l<name>` archives to add (e.g. "gdi32 ws2_32") 323 # beyond the driver-auto-linked set (kernel32/user32/advapi32/shell32/ucrt/ 324 # mingwex/mingw32/moldname). Each expected DLL/symbol is one mode-P assert. 325 build_and_check() { 326 local label=$1 csrc=$2 exe=$3 dump=$4 mode=$5 libs=$6 327 shift 6 328 329 local dlls=() syms=() in_syms=0 330 while [ $# -gt 0 ]; do 331 if [ "$1" = "--" ]; then in_syms=1; shift; continue; fi 332 if [ "$in_syms" -eq 0 ]; then dlls+=("$1"); else syms+=("$1"); fi 333 shift 334 done 335 336 local mode_flag=-mconsole 337 if [ "$mode" = "windows" ]; then mode_flag=-mwindows; fi 338 339 local extra_lflags=() lib 340 for lib in $libs; do extra_lflags+=("-l$lib"); done 341 342 local bn; bn=$(printf '%s' "$label" | tr ' ' '-') 343 344 run_ok "$bn-build" "$KIT" cc -target "$TARGET" --sysroot "$ARCH_SDK" \ 345 "$mode_flag" "$csrc" "${extra_lflags[@]}" -o "$exe" 346 if [ ! -f "$exe" ]; then return 1; fi 347 run_ok "$bn-objdump" "$KIT" objdump -p "$exe" 348 if [ ! -s "$work/$bn-objdump.out" ]; then return 1; fi 349 cp "$work/$bn-objdump.out" "$dump" 350 351 no_legacy_crt_imports "$bn-no-legacy-crt" "$dump" 352 353 local d 354 for d in "${dlls[@]}"; do 355 contains "$bn-dll-$d" "$dump" "DLL Name: $d" 356 done 357 358 local s 359 for s in "${syms[@]}"; do 360 contains "$bn-sym-$s" "$dump" "Name: $s" 361 done 362 363 if [ "$mode" = "windows" ]; then 364 contains "$bn-subsystem-gui" "$dump" "Subsystem: 2 (WINDOWS_GUI)" 365 fi 366 } 367 368 ran=0 369 for arch in x86_64 aarch64; do 370 case "$arch" in 371 x86_64) 372 TARGET=x86_64-windows 373 LABEL=x64 374 IMAGE=localhost/kit-wine-amd64 375 POD_ARCH=amd64 376 ;; 377 aarch64) 378 TARGET=aarch64-windows 379 LABEL=aarch64 380 IMAGE=localhost/kit-wine-arm64 381 POD_ARCH=arm64 382 ;; 383 esac 384 385 if ! ARCH_SDK=$(sdk_for_arch "$arch"); then 386 skip_test "$LABEL_SUITE/$LABEL-sysroot" "no $arch llvm-mingw UCRT sysroot" 387 continue 388 fi 389 # Discovered-but-invalid sysroot was a SKIP (exit 0 via continue) originally. 390 if [ ! -r "$ARCH_SDK/include/windows.h" ] || 391 [ ! -r "$ARCH_SDK/lib/libucrt.a" ]; then 392 skip_test "$LABEL_SUITE/$LABEL-sysroot" "invalid UCRT llvm-mingw sysroot: $ARCH_SDK" 393 continue 394 fi 395 396 ran=1 397 398 # ---- GUI hello window (user32 + gdi32 + kernel32) ---- 399 GUI_EXE=$work/gui_hello_window-$LABEL.exe 400 GUI_DUMP=$work/gui_hello_window-$LABEL.dump 401 build_and_check "$LABEL gui_hello_window" "$GUI_C" "$GUI_EXE" "$GUI_DUMP" \ 402 windows "" USER32.dll KERNEL32.dll -- RegisterClassExW CreateWindowExW \ 403 DefWindowProcW PeekMessageW DispatchMessageW 404 run_wine_if_available "$LABEL gui_hello_window" "$IMAGE" "$POD_ARCH" "$GUI_EXE" 405 406 # ---- gdi32 surface ---- 407 GDI_EXE=$work/gdi_drawing-$LABEL.exe 408 GDI_DUMP=$work/gdi_drawing-$LABEL.dump 409 build_and_check "$LABEL gdi_drawing" "$GDI_C" "$GDI_EXE" "$GDI_DUMP" \ 410 console gdi32 GDI32.dll USER32.dll -- CreateCompatibleDC GetStockObject \ 411 SelectObject DeleteDC 412 run_wine_if_available "$LABEL gdi_drawing" "$IMAGE" "$POD_ARCH" "$GDI_EXE" 413 414 # ---- advapi32 surface ---- 415 REG_EXE=$work/advapi32_registry-$LABEL.exe 416 REG_DUMP=$work/advapi32_registry-$LABEL.dump 417 build_and_check "$LABEL advapi32_registry" "$REG_C" "$REG_EXE" "$REG_DUMP" \ 418 console "" ADVAPI32.dll KERNEL32.dll -- RegOpenKeyExW RegCloseKey \ 419 RegQueryInfoKeyW 420 run_wine_if_available "$LABEL advapi32_registry" "$IMAGE" "$POD_ARCH" "$REG_EXE" 421 422 # ---- ws2_32 surface ---- 423 WS_EXE=$work/ws2_32_socket-$LABEL.exe 424 WS_DUMP=$work/ws2_32_socket-$LABEL.dump 425 build_and_check "$LABEL ws2_32_socket" "$WS_C" "$WS_EXE" "$WS_DUMP" \ 426 console ws2_32 WS2_32.dll KERNEL32.dll -- WSAStartup WSACleanup socket \ 427 closesocket 428 run_wine_if_available "$LABEL ws2_32_socket" "$IMAGE" "$POD_ARCH" "$WS_EXE" 429 430 # ---- ole32 surface ---- 431 OLE_EXE=$work/ole32_coinit-$LABEL.exe 432 OLE_DUMP=$work/ole32_coinit-$LABEL.dump 433 build_and_check "$LABEL ole32_coinit" "$OLE_C" "$OLE_EXE" "$OLE_DUMP" \ 434 console ole32 ole32.dll KERNEL32.dll -- CoInitializeEx CoUninitialize 435 run_wine_if_available "$LABEL ole32_coinit" "$IMAGE" "$POD_ARCH" "$OLE_EXE" 436 437 # ---- shell32 surface ---- 438 SHELL_EXE=$work/shell32_argv-$LABEL.exe 439 SHELL_DUMP=$work/shell32_argv-$LABEL.dump 440 build_and_check "$LABEL shell32_argv" "$SHELL_C" "$SHELL_EXE" "$SHELL_DUMP" \ 441 console "" SHELL32.dll KERNEL32.dll -- CommandLineToArgvW 442 run_wine_if_available "$LABEL shell32_argv" "$IMAGE" "$POD_ARCH" "$SHELL_EXE" 443 444 # ---- comctl32 surface ---- 445 COMCTL_EXE=$work/comctl32_init-$LABEL.exe 446 COMCTL_DUMP=$work/comctl32_init-$LABEL.dump 447 build_and_check "$LABEL comctl32_init" "$COMCTL_C" "$COMCTL_EXE" \ 448 "$COMCTL_DUMP" console comctl32 COMCTL32.dll KERNEL32.dll -- \ 449 InitCommonControls InitCommonControlsEx 450 run_wine_if_available "$LABEL comctl32_init" "$IMAGE" "$POD_ARCH" "$COMCTL_EXE" 451 452 # ---- mixed libucrt.a (short-import + long-form helper) ---- 453 MIXED_EXE=$work/mixed_ucrt-$LABEL.exe 454 MIXED_DUMP=$work/mixed_ucrt-$LABEL.dump 455 build_and_check "$LABEL mixed_ucrt" "$MIXED_C" "$MIXED_EXE" "$MIXED_DUMP" \ 456 console "" KERNEL32.dll api-ms-win-crt-stdio-l1-1-0.dll -- fflush 457 run_wine_if_available "$LABEL mixed_ucrt" "$IMAGE" "$POD_ARCH" "$MIXED_EXE" 458 done 459 460 if [ "$ran" -eq 0 ]; then 461 skip_test "$LABEL_SUITE" "set KIT_SYSROOT or install llvm-mingw UCRT under /tmp/llvm-mingw*" 462 fi 463 464 kit_summary "$LABEL_SUITE" 465 kit_exit