From d50f6db71739a5d5241fa89cf568c1ac8d35f568 Mon Sep 17 00:00:00 2001 From: Mark Street Date: Fri, 5 Jun 2026 15:49:52 +0100 Subject: [PATCH 1/3] Add support for ghs 5.3.22 --- CMakeLists.txt | 4 ++ Dockerfile | 4 +- dll/kernel32/handleapi.cpp | 10 +++ dll/kernel32/handleapi.h | 1 + dll/shlwapi.cpp | 44 +++++++++++++ dll/shlwapi.h | 9 +++ dll/ws2.cpp | 132 ++++++++++++++++++++++++++++++++++++- dll/ws2.h | 24 +++++++ src/modules.cpp | 4 +- test/test_shlwapi.c | 48 ++++++++++++++ 10 files changed, 276 insertions(+), 4 deletions(-) create mode 100644 dll/shlwapi.cpp create mode 100644 dll/shlwapi.h create mode 100644 dll/ws2.h create mode 100644 test/test_shlwapi.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 324eb12..22d5074 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -221,6 +221,7 @@ add_executable(wibo dll/ntdll.cpp dll/ole32.cpp dll/rpcrt4.cpp + dll/shlwapi.cpp dll/user32.cpp dll/vcruntime.cpp dll/version.cpp @@ -376,6 +377,8 @@ wibo_codegen_module(NAME entry HEADERS src/entry.h) wibo_codegen_module(NAME mscoree HEADERS dll/mscoree.h) wibo_codegen_module(NAME version HEADERS dll/version.h) wibo_codegen_module(NAME rpcrt4 HEADERS dll/rpcrt4.h) +wibo_codegen_module(NAME shlwapi HEADERS dll/shlwapi.h) +wibo_codegen_module(NAME ws2 HEADERS dll/ws2.h) wibo_codegen_module(NAME vcruntime HEADERS dll/vcruntime.h) wibo_codegen_module(NAME lmgr HEADERS dll/lmgr.h) wibo_codegen_module(NAME ole32 HEADERS dll/ole32.h) @@ -475,6 +478,7 @@ if (WIBO_ENABLE_FIXTURE_TESTS) wibo_add_fixture_bin(NAME test_dll_attach_failure SOURCES test/test_dll_attach_failure.c) wibo_add_fixture_bin(NAME test_thread_notifications SOURCES test/test_thread_notifications.c) wibo_add_fixture_bin(NAME test_bcrypt SOURCES test/test_bcrypt.c COMPILE_OPTIONS -lbcrypt) + wibo_add_fixture_bin(NAME test_shlwapi SOURCES test/test_shlwapi.c COMPILE_OPTIONS -lshlwapi) wibo_add_fixture_bin(NAME test_resources SOURCES test/test_resources.c ${WIBO_TEST_BIN_DIR}/test_resources_res.o COMPILE_OPTIONS -lversion) wibo_add_fixture_bin(NAME test_threading SOURCES test/test_threading.c) wibo_add_fixture_bin(NAME test_tls SOURCES test/test_tls.c) diff --git a/Dockerfile b/Dockerfile index 377044c..90b16ac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build stage -FROM alpine:latest AS build +FROM alpine:latest AS dependencies # Install dependencies RUN apk add --no-cache \ @@ -19,6 +19,8 @@ RUN apk add --no-cache \ ninja \ python3 +FROM dependencies as build + # Copy source files WORKDIR /wibo COPY . /wibo diff --git a/dll/kernel32/handleapi.cpp b/dll/kernel32/handleapi.cpp index 1ec5895..6f7dfec 100644 --- a/dll/kernel32/handleapi.cpp +++ b/dll/kernel32/handleapi.cpp @@ -72,4 +72,14 @@ BOOL WINAPI CloseHandle(HANDLE hObject) { return TRUE; } +BOOL WINAPI SetHandleInformation(HANDLE hObject, DWORD dwMask, DWORD dwFlags) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("SetHandleInformation(%p, 0x%x, 0x%x)\n", hObject, dwMask, dwFlags); + if (!wibo::handles().setInformation(hObject, dwMask, dwFlags)) { + setLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + return TRUE; +} + } // namespace kernel32 diff --git a/dll/kernel32/handleapi.h b/dll/kernel32/handleapi.h index 8b7c8cb..65fbf76 100644 --- a/dll/kernel32/handleapi.h +++ b/dll/kernel32/handleapi.h @@ -5,6 +5,7 @@ namespace kernel32 { BOOL WINAPI CloseHandle(HANDLE hObject); +BOOL WINAPI SetHandleInformation(HANDLE hObject, DWORD dwMask, DWORD dwFlags); BOOL WINAPI DuplicateHandle(HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE hTargetProcessHandle, LPHANDLE lpTargetHandle, DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions); diff --git a/dll/shlwapi.cpp b/dll/shlwapi.cpp new file mode 100644 index 0000000..7933810 --- /dev/null +++ b/dll/shlwapi.cpp @@ -0,0 +1,44 @@ +#include "shlwapi.h" + +#include "common.h" +#include "context.h" +#include "kernel32/minwinbase.h" +#include "modules.h" + +#include + +namespace shlwapi { + +LPSTR WINAPI PathAddBackslashA(LPSTR pszPath) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("PathAddBackslashA(%s)\n", pszPath ? pszPath : "(null)"); + if (!pszPath) { + return nullptr; + } + + size_t length = std::strlen(pszPath); + if (length > 0 && pszPath[length - 1] == '\\') { + return pszPath + length; + } + + if (length + 1 >= MAX_PATH) { + return nullptr; + } + + pszPath[length] = '\\'; + pszPath[length + 1] = '\0'; + return pszPath + length + 1; +} + +} // namespace shlwapi + +#include "shlwapi_trampolines.h" + +extern const wibo::ModuleStub lib_shlwapi = { + (const char *[]){ + "shlwapi", + nullptr, + }, + shlwapiThunkByName, + nullptr, +}; diff --git a/dll/shlwapi.h b/dll/shlwapi.h new file mode 100644 index 0000000..a38a5ea --- /dev/null +++ b/dll/shlwapi.h @@ -0,0 +1,9 @@ +#pragma once + +#include "types.h" + +namespace shlwapi { + +LPSTR WINAPI PathAddBackslashA(LPSTR pszPath); + +} // namespace shlwapi diff --git a/dll/ws2.cpp b/dll/ws2.cpp index 76cfb8b..7bc7b05 100644 --- a/dll/ws2.cpp +++ b/dll/ws2.cpp @@ -1,10 +1,138 @@ +#include "ws2.h" + +#include "common.h" +#include "context.h" #include "modules.h" +#include +#include + +namespace { + +constexpr int SOCKET_ERROR = -1; +constexpr int WSAEFAULT = 10014; +constexpr int WSAHOST_NOT_FOUND = 11001; +constexpr int WSANOTINITIALISED = 10093; + +thread_local int g_lastError = 0; +int g_startupCount = 0; + +void setLastError(int error) { g_lastError = error; } + +WORD makeVersion(BYTE major, BYTE minor) { return static_cast(major | (minor << 8)); } + +} // namespace + +namespace ws2 { + +int WINAPI WSAStartup(WORD wVersionRequired, WSADATA *lpWSAData) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("WSAStartup(0x%x, %p)\n", wVersionRequired, lpWSAData); + if (!lpWSAData) { + setLastError(WSAEFAULT); + return WSAEFAULT; + } + + std::memset(lpWSAData, 0, sizeof(*lpWSAData)); + lpWSAData->wVersion = wVersionRequired; + lpWSAData->wHighVersion = makeVersion(2, 2); + std::strncpy(lpWSAData->szDescription, "wibo fake Winsock", sizeof(lpWSAData->szDescription) - 1); + std::strncpy(lpWSAData->szSystemStatus, "Running", sizeof(lpWSAData->szSystemStatus) - 1); + lpWSAData->iMaxSockets = 0x7fff; + lpWSAData->iMaxUdpDg = 65467; + + ++g_startupCount; + setLastError(0); + return 0; +} + +int WINAPI WSACleanup() { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("WSACleanup()\n"); + if (g_startupCount <= 0) { + setLastError(WSANOTINITIALISED); + return SOCKET_ERROR; + } + + --g_startupCount; + setLastError(0); + return 0; +} + +int WINAPI WSAGetLastError() { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("WSAGetLastError() -> %d\n", g_lastError); + return g_lastError; +} + +int WINAPI gethostname(LPSTR name, int namelen) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("gethostname(%p, %d)\n", name, namelen); + if (!name || namelen <= 0) { + setLastError(WSAEFAULT); + return SOCKET_ERROR; + } + + char host[256] = {}; + if (::gethostname(host, sizeof(host) - 1) != 0 || host[0] == '\0') { + std::strncpy(host, "localhost", sizeof(host) - 1); + } + + size_t length = std::strlen(host); + if (static_cast(namelen) <= length) { + setLastError(WSAEFAULT); + return SOCKET_ERROR; + } + + std::memcpy(name, host, length + 1); + setLastError(0); + return 0; +} + +GUEST_PTR WINAPI gethostbyname(LPCSTR name) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("gethostbyname(%s)\n", name ? name : "(null)"); + setLastError(WSAHOST_NOT_FOUND); + return GUEST_NULL; +} + +int WINAPI select(int nfds, LPVOID readfds, LPVOID writefds, LPVOID exceptfds, const void *timeout) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("select(%d, %p, %p, %p, %p)\n", nfds, readfds, writefds, exceptfds, timeout); + (void)nfds; + (void)readfds; + (void)writefds; + (void)exceptfds; + (void)timeout; + setLastError(0); + return 0; +} + +} // namespace ws2 + +#include "ws2_trampolines.h" + +static void *resolveByOrdinal(uint16_t ordinal) { + switch (ordinal) { + case 18: + return (void *)thunk_ws2_select; + case 52: + return (void *)thunk_ws2_gethostbyname; + case 57: + return (void *)thunk_ws2_gethostname; + case 115: + return (void *)thunk_ws2_WSAStartup; + case 116: + return (void *)thunk_ws2_WSACleanup; + } + return nullptr; +} + extern const wibo::ModuleStub lib_ws2 = { (const char *[]){ "WS2_32", nullptr, }, - nullptr, - nullptr, + ws2ThunkByName, + resolveByOrdinal, }; diff --git a/dll/ws2.h b/dll/ws2.h new file mode 100644 index 0000000..e5649b1 --- /dev/null +++ b/dll/ws2.h @@ -0,0 +1,24 @@ +#pragma once + +#include "types.h" + +struct WSADATA { + WORD wVersion; + WORD wHighVersion; + CHAR szDescription[257]; + CHAR szSystemStatus[129]; + WORD iMaxSockets; + WORD iMaxUdpDg; + GUEST_PTR lpVendorInfo; +}; + +namespace ws2 { + +int WINAPI WSAStartup(WORD wVersionRequired, WSADATA *lpWSAData); +int WINAPI WSACleanup(); +int WINAPI WSAGetLastError(); +int WINAPI gethostname(LPSTR name, int namelen); +GUEST_PTR WINAPI gethostbyname(LPCSTR name); +int WINAPI select(int nfds, LPVOID readfds, LPVOID writefds, LPVOID exceptfds, const void *timeout); + +} // namespace ws2 diff --git a/src/modules.cpp b/src/modules.cpp index 9d32cd1..5c930d2 100644 --- a/src/modules.cpp +++ b/src/modules.cpp @@ -53,6 +53,7 @@ extern const wibo::ModuleStub lib_ucrtbase; extern const wibo::ModuleStub lib_ntdll; extern const wibo::ModuleStub lib_rpcrt4; extern const wibo::ModuleStub lib_ole32; +extern const wibo::ModuleStub lib_shlwapi; extern const wibo::ModuleStub lib_user32; extern const wibo::ModuleStub lib_vcruntime; extern const wibo::ModuleStub lib_version; @@ -203,7 +204,8 @@ LockedRegistry registry() { reg.initialized = true; const wibo::ModuleStub *builtins[] = { &lib_advapi32, &lib_bcrypt, &lib_kernel32, &lib_lmgr, &lib_mscoree, &lib_ntdll, - &lib_ole32, &lib_rpcrt4, &lib_user32, &lib_vcruntime, &lib_version, &lib_ws2, + &lib_ole32, &lib_rpcrt4, &lib_shlwapi, &lib_user32, &lib_vcruntime, &lib_version, + &lib_ws2, #if WIBO_HAS_MSVCRT &lib_msvcrt, #endif diff --git a/test/test_shlwapi.c b/test/test_shlwapi.c new file mode 100644 index 0000000..9334af6 --- /dev/null +++ b/test/test_shlwapi.c @@ -0,0 +1,48 @@ +#include "test_assert.h" + +#include +#include +#include + +static void test_appends_backslash(void) { + char path[MAX_PATH] = "C:\\foo"; + char *end = PathAddBackslashA(path); + TEST_CHECK(end != NULL); + TEST_CHECK_STR_EQ("C:\\foo\\", path); + TEST_CHECK(end == path + strlen(path)); +} + +static void test_existing_backslash(void) { + char path[MAX_PATH] = "C:\\foo\\"; + char *end = PathAddBackslashA(path); + TEST_CHECK(end != NULL); + TEST_CHECK_STR_EQ("C:\\foo\\", path); + TEST_CHECK(end == path + strlen(path)); +} + +static void test_empty_string(void) { + char path[MAX_PATH] = ""; + char *end = PathAddBackslashA(path); + TEST_CHECK(end != NULL); + TEST_CHECK_STR_EQ("\\", path); + TEST_CHECK(end == path + strlen(path)); +} + +static void test_max_path_limit(void) { + char path[MAX_PATH]; + memset(path, 'a', sizeof(path)); + path[MAX_PATH - 1] = '\0'; + + char *end = PathAddBackslashA(path); + TEST_CHECK(end == NULL); + TEST_CHECK(path[MAX_PATH - 2] == 'a'); + TEST_CHECK(path[MAX_PATH - 1] == '\0'); +} + +int main(void) { + test_appends_backslash(); + test_existing_backslash(); + test_empty_string(); + test_max_path_limit(); + return 0; +} From 72b78a321a82a4f9d5e9bfc80f160672d35b0fb1 Mon Sep 17 00:00:00 2001 From: Mark Street Date: Fri, 5 Jun 2026 16:03:44 +0100 Subject: [PATCH 2/3] tweaks --- CMakeLists.txt | 1 + Dockerfile | 2 +- Dockerfile.ubuntu | 4 +++- dll/ws2.cpp | 19 +++++++++++++++++++ test/test_handleapi.c | 9 +++++++++ test/test_ws2.c | 24 ++++++++++++++++++++++++ 6 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 test/test_ws2.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 22d5074..50cae49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -479,6 +479,7 @@ if (WIBO_ENABLE_FIXTURE_TESTS) wibo_add_fixture_bin(NAME test_thread_notifications SOURCES test/test_thread_notifications.c) wibo_add_fixture_bin(NAME test_bcrypt SOURCES test/test_bcrypt.c COMPILE_OPTIONS -lbcrypt) wibo_add_fixture_bin(NAME test_shlwapi SOURCES test/test_shlwapi.c COMPILE_OPTIONS -lshlwapi) + wibo_add_fixture_bin(NAME test_ws2 SOURCES test/test_ws2.c COMPILE_OPTIONS -lws2_32) wibo_add_fixture_bin(NAME test_resources SOURCES test/test_resources.c ${WIBO_TEST_BIN_DIR}/test_resources_res.o COMPILE_OPTIONS -lversion) wibo_add_fixture_bin(NAME test_threading SOURCES test/test_threading.c) wibo_add_fixture_bin(NAME test_tls SOURCES test/test_tls.c) diff --git a/Dockerfile b/Dockerfile index 90b16ac..0c70ce0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ RUN apk add --no-cache \ ninja \ python3 -FROM dependencies as build +FROM dependencies AS build # Copy source files WORKDIR /wibo diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu index fd04d4f..5b732dd 100644 --- a/Dockerfile.ubuntu +++ b/Dockerfile.ubuntu @@ -1,5 +1,5 @@ # Build stage -FROM ubuntu:24.04 AS build +FROM ubuntu:24.04 AS dependencies # Install dependencies ENV DEBIAN_FRONTEND=noninteractive @@ -30,6 +30,8 @@ RUN apt-get update \ wget \ && rm -rf /var/lib/apt/lists/* +FROM dependencies AS build + # Copy source files WORKDIR /wibo COPY . /wibo diff --git a/dll/ws2.cpp b/dll/ws2.cpp index 7bc7b05..01e698c 100644 --- a/dll/ws2.cpp +++ b/dll/ws2.cpp @@ -21,6 +21,14 @@ void setLastError(int error) { g_lastError = error; } WORD makeVersion(BYTE major, BYTE minor) { return static_cast(major | (minor << 8)); } +bool requireStarted() { + if (g_startupCount > 0) { + return true; + } + setLastError(WSANOTINITIALISED); + return false; +} + } // namespace namespace ws2 { @@ -68,6 +76,9 @@ int WINAPI WSAGetLastError() { int WINAPI gethostname(LPSTR name, int namelen) { HOST_CONTEXT_GUARD(); DEBUG_LOG("gethostname(%p, %d)\n", name, namelen); + if (!requireStarted()) { + return SOCKET_ERROR; + } if (!name || namelen <= 0) { setLastError(WSAEFAULT); return SOCKET_ERROR; @@ -92,6 +103,9 @@ int WINAPI gethostname(LPSTR name, int namelen) { GUEST_PTR WINAPI gethostbyname(LPCSTR name) { HOST_CONTEXT_GUARD(); DEBUG_LOG("gethostbyname(%s)\n", name ? name : "(null)"); + if (!requireStarted()) { + return GUEST_NULL; + } setLastError(WSAHOST_NOT_FOUND); return GUEST_NULL; } @@ -99,6 +113,9 @@ GUEST_PTR WINAPI gethostbyname(LPCSTR name) { int WINAPI select(int nfds, LPVOID readfds, LPVOID writefds, LPVOID exceptfds, const void *timeout) { HOST_CONTEXT_GUARD(); DEBUG_LOG("select(%d, %p, %p, %p, %p)\n", nfds, readfds, writefds, exceptfds, timeout); + if (!requireStarted()) { + return SOCKET_ERROR; + } (void)nfds; (void)readfds; (void)writefds; @@ -113,6 +130,8 @@ int WINAPI select(int nfds, LPVOID readfds, LPVOID writefds, LPVOID exceptfds, c #include "ws2_trampolines.h" static void *resolveByOrdinal(uint16_t ordinal) { + // GHS 5.3.22 imports WS2_32.dll with the legacy winsock ordinal table. + // Keep these mappings tied to observed call sites rather than modern WS2_32 export ordinals. switch (ordinal) { case 18: return (void *)thunk_ws2_select; diff --git a/test/test_handleapi.c b/test/test_handleapi.c index 19209ea..d5424dd 100644 --- a/test/test_handleapi.c +++ b/test/test_handleapi.c @@ -90,6 +90,14 @@ static void test_duplicate_handle_after_close(void) { TEST_CHECK(dup == NULL); } +static void test_set_handle_information_invalid_handle(void) { + HANDLE bogus = (HANDLE)(uintptr_t)0x1234; + + SetLastError(0); + TEST_CHECK(!SetHandleInformation(bogus, HANDLE_FLAG_INHERIT, 0)); + TEST_CHECK_EQ(ERROR_INVALID_HANDLE, GetLastError()); +} + int main(void) { test_duplicate_handle_basic(); test_duplicate_handle_close_source(); @@ -97,5 +105,6 @@ int main(void) { test_duplicate_handle_invalid_target_process(); test_duplicate_pseudo_process_handle(); test_duplicate_handle_after_close(); + test_set_handle_information_invalid_handle(); return EXIT_SUCCESS; } diff --git a/test/test_ws2.c b/test/test_ws2.c new file mode 100644 index 0000000..43c2184 --- /dev/null +++ b/test/test_ws2.c @@ -0,0 +1,24 @@ +#include "test_assert.h" +#include +#include +#include + +static void test_startup_gethostname_cleanup(void) { + WSADATA data; + memset(&data, 0, sizeof(data)); + + TEST_CHECK_EQ(0, WSAStartup(MAKEWORD(1, 1), &data)); + TEST_CHECK_EQ(1, data.wVersion & 0xff); + TEST_CHECK_EQ(1, (data.wVersion >> 8) & 0xff); + + char hostname[256] = {0}; + TEST_CHECK_EQ(0, gethostname(hostname, sizeof(hostname))); + TEST_CHECK(hostname[0] != '\0'); + + TEST_CHECK_EQ(0, WSACleanup()); +} + +int main(void) { + test_startup_gethostname_cleanup(); + return EXIT_SUCCESS; +} From 8f30aadb093599607cd6e52d613fdf67334b9fb5 Mon Sep 17 00:00:00 2001 From: Mark Street Date: Fri, 5 Jun 2026 16:06:59 +0100 Subject: [PATCH 3/3] explain magic --- dll/ws2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dll/ws2.cpp b/dll/ws2.cpp index 01e698c..cef1b5c 100644 --- a/dll/ws2.cpp +++ b/dll/ws2.cpp @@ -47,7 +47,7 @@ int WINAPI WSAStartup(WORD wVersionRequired, WSADATA *lpWSAData) { std::strncpy(lpWSAData->szDescription, "wibo fake Winsock", sizeof(lpWSAData->szDescription) - 1); std::strncpy(lpWSAData->szSystemStatus, "Running", sizeof(lpWSAData->szSystemStatus) - 1); lpWSAData->iMaxSockets = 0x7fff; - lpWSAData->iMaxUdpDg = 65467; + lpWSAData->iMaxUdpDg = 65467; // 65535 - max IPv4 header (60) - UDP header (8) ++g_startupCount; setLastError(0);