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