diff --git a/AGENTS.md b/AGENTS.md index eced3f9..fe4b889 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,9 +31,10 @@ - All fixtures must self-assert; use `test_assert.h` helpers so `ctest` fails on mismatched WinAPI behaviour. - Update `CMakeLists.txt` to add new fixture sources. - Rebuild, then run tests with `ctest --test-dir build --output-on-failure`. -- Always run tests against `wine` manually to confirm expected behaviour. If `wine` fails, the expected behaviour is likely wrong. (`wine` is not perfect, but we can assume it's closer to Windows than we are.) +- ALWAYS run tests against `wine` manually to confirm expected behaviour. If `wine` fails, the expected behaviour is likely wrong. (`wine` is not perfect, but we can assume it's closer to Windows than we are.) ## Debugging Workflow - Reproduce crashes under `gdb` (or `lldb`) with `-q -batch` to capture backtraces, register state, and the faulting instruction without interactive prompts. - Enable `WIBO_DEBUG=1` and output to a log (i.e. `&>/tmp/wibo.log`) when running the guest binary; loader traces often pinpoint missing imports, resource lookups, or API shims that misbehave. The answer is usually in the last few dozen lines before the crash. - Inspect relevant source right away—most issues stem from stubbed shims in `dll/`. +- Missing stubs generally do _not_ cause a crash; we return valid function pointers for unknown imports. Only when the missing stub is _called_ do we abort with a message. Therefore, don't preemptively add stubs for every missing import; wait until the binary actually calls it. diff --git a/CMakeLists.txt b/CMakeLists.txt index 630c8f1..cd9a557 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,6 +114,22 @@ if(BUILD_TESTING) WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/test/external_exports.c) + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/dll_attach_failure.dll + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 -shared + -o dll_attach_failure.dll + ${CMAKE_CURRENT_SOURCE_DIR}/test/dll_attach_failure.c + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/test/dll_attach_failure.c) + + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/thread_notifications.dll + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 -shared + -o thread_notifications.dll + ${CMAKE_CURRENT_SOURCE_DIR}/test/thread_notifications.c + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/test/thread_notifications.c) + add_custom_command( OUTPUT ${WIBO_TEST_BIN_DIR}/test_external_dll.exe COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 @@ -125,6 +141,28 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/test/test_external_dll.c ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h) + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/test_dll_attach_failure.exe + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 + -I${CMAKE_CURRENT_SOURCE_DIR}/test + -o test_dll_attach_failure.exe + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_dll_attach_failure.c + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_dll_attach_failure.c + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h) + + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/test_thread_notifications.exe + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 + -I${CMAKE_CURRENT_SOURCE_DIR}/test + -o test_thread_notifications.exe + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_thread_notifications.c + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_thread_notifications.c + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h) + add_custom_command( OUTPUT ${WIBO_TEST_BIN_DIR}/test_bcrypt.exe COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 @@ -213,17 +251,57 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/test/test_virtualalloc.c ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h) + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/test_rtl.exe + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 + -I${CMAKE_CURRENT_SOURCE_DIR}/test + -o test_rtl.exe + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_rtl.c + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_rtl.c + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h) + + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/test_ntquery.exe + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 + -I${CMAKE_CURRENT_SOURCE_DIR}/test + -o test_ntquery.exe + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_ntquery.c + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_ntquery.c + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h) + + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/test_sysdir.exe + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 + -I${CMAKE_CURRENT_SOURCE_DIR}/test + -o test_sysdir.exe + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_sysdir.c + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_sysdir.c + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h) + add_custom_target(wibo_test_fixtures DEPENDS ${WIBO_TEST_BIN_DIR}/external_exports.dll + ${WIBO_TEST_BIN_DIR}/dll_attach_failure.dll + ${WIBO_TEST_BIN_DIR}/thread_notifications.dll ${WIBO_TEST_BIN_DIR}/test_external_dll.exe + ${WIBO_TEST_BIN_DIR}/test_dll_attach_failure.exe + ${WIBO_TEST_BIN_DIR}/test_thread_notifications.exe ${WIBO_TEST_BIN_DIR}/test_bcrypt.exe ${WIBO_TEST_BIN_DIR}/test_resources.exe ${WIBO_TEST_BIN_DIR}/test_threading.exe ${WIBO_TEST_BIN_DIR}/test_heap.exe ${WIBO_TEST_BIN_DIR}/test_overlapped_io.exe ${WIBO_TEST_BIN_DIR}/test_time.exe - ${WIBO_TEST_BIN_DIR}/test_virtualalloc.exe) + ${WIBO_TEST_BIN_DIR}/test_virtualalloc.exe + ${WIBO_TEST_BIN_DIR}/test_rtl.exe + ${WIBO_TEST_BIN_DIR}/test_ntquery.exe + ${WIBO_TEST_BIN_DIR}/test_sysdir.exe) if(CMAKE_CONFIGURATION_TYPES) set(_wibo_fixture_build_command @@ -282,6 +360,36 @@ if(BUILD_TESTING) set_tests_properties(wibo.test_virtualalloc PROPERTIES WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} DEPENDS wibo.build_fixtures) + + add_test(NAME wibo.test_rtl + COMMAND $ ${WIBO_TEST_BIN_DIR}/test_rtl.exe) + set_tests_properties(wibo.test_rtl PROPERTIES + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS wibo.build_fixtures) + + add_test(NAME wibo.test_ntquery + COMMAND $ ${WIBO_TEST_BIN_DIR}/test_ntquery.exe) + set_tests_properties(wibo.test_ntquery PROPERTIES + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS wibo.build_fixtures) + + add_test(NAME wibo.test_sysdir + COMMAND $ ${WIBO_TEST_BIN_DIR}/test_sysdir.exe) + set_tests_properties(wibo.test_sysdir PROPERTIES + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS wibo.build_fixtures) + + add_test(NAME wibo.test_dll_attach_failure + COMMAND $ ${WIBO_TEST_BIN_DIR}/test_dll_attach_failure.exe) + set_tests_properties(wibo.test_dll_attach_failure PROPERTIES + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS wibo.build_fixtures) + + add_test(NAME wibo.test_thread_notifications + COMMAND $ ${WIBO_TEST_BIN_DIR}/test_thread_notifications.exe) + set_tests_properties(wibo.test_thread_notifications PROPERTIES + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS wibo.build_fixtures) endif() endif() endif() diff --git a/common.h b/common.h index e6a239b..7ad03e1 100644 --- a/common.h +++ b/common.h @@ -110,6 +110,47 @@ constexpr DWORD STD_INPUT_HANDLE = ((DWORD)-10); constexpr DWORD STD_OUTPUT_HANDLE = ((DWORD)-11); constexpr DWORD STD_ERROR_HANDLE = ((DWORD)-12); +struct UNICODE_STRING { + unsigned short Length; + unsigned short MaximumLength; + uint16_t *Buffer; +}; + +struct RTL_USER_PROCESS_PARAMETERS { + char Reserved1[16]; + void *Reserved2[10]; + UNICODE_STRING ImagePathName; + UNICODE_STRING CommandLine; +}; + +struct PEB { + char Reserved1[2]; + char BeingDebugged; + char Reserved2[1]; + void *Reserved3[2]; + void *Ldr; + RTL_USER_PROCESS_PARAMETERS *ProcessParameters; + char Reserved4[104]; + void *Reserved5[52]; + void *PostProcessInitRoutine; + char Reserved6[128]; + void *Reserved7[1]; + unsigned int SessionId; +}; + +struct TIB { + void *sehFrame; + void *stackBase; + void *stackLimit; + void *subSystemTib; + void *fiberData; + void *arbitraryDataSlot; + TIB *tib; + char reserved1[0x14]; + PEB *peb; + char reserved2[0x1000]; +}; + namespace wibo { extern thread_local uint32_t lastError; extern char **argv; @@ -121,6 +162,13 @@ extern std::vector commandLineW; extern bool debugEnabled; extern unsigned int debugIndent; extern uint16_t tibSelector; +extern int tibEntryNumber; +extern PEB *processPeb; + +TIB *allocateTib(); +void initializeTibStackInfo(TIB *tib); +bool installTibForCurrentThread(TIB *tib); +void destroyTib(TIB *tib); void debug_log(const char *fmt, ...); @@ -143,6 +191,9 @@ void registerOnExitTable(void *table); void addOnExitFunction(void *table, void (*func)()); void executeOnExitTable(void *table); void runPendingOnExit(ModuleInfo &info); +void notifyDllThreadAttach(); +void notifyDllThreadDetach(); +BOOL disableThreadNotifications(ModuleInfo *info); ModuleInfo *loadModule(const char *name); void freeModule(ModuleInfo *info); @@ -234,6 +285,7 @@ struct ModuleInfo { unsigned int refCount = 0; bool processAttachCalled = false; bool processAttachSucceeded = false; + bool threadNotificationsEnabled = true; uint32_t exportOrdinalBase = 0; std::vector exportsByOrdinal; std::unordered_map exportNameToOrdinal; diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index 2f64916..6b41033 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -222,6 +222,14 @@ void *resolveByName(const char *name) { return (void *)kernel32::LocalSize; if (strcmp(name, "LocalFlags") == 0) return (void *)kernel32::LocalFlags; + if (strcmp(name, "GetSystemDirectoryA") == 0) + return (void *)kernel32::GetSystemDirectoryA; + if (strcmp(name, "GetSystemDirectoryW") == 0) + return (void *)kernel32::GetSystemDirectoryW; + if (strcmp(name, "GetSystemWow64DirectoryA") == 0) + return (void *)kernel32::GetSystemWow64DirectoryA; + if (strcmp(name, "GetSystemWow64DirectoryW") == 0) + return (void *)kernel32::GetSystemWow64DirectoryW; if (strcmp(name, "GetCurrentDirectoryA") == 0) return (void *)kernel32::GetCurrentDirectoryA; if (strcmp(name, "GetCurrentDirectoryW") == 0) diff --git a/dll/kernel32/internal.h b/dll/kernel32/internal.h index c6fbca3..bf3d4e4 100644 --- a/dll/kernel32/internal.h +++ b/dll/kernel32/internal.h @@ -19,6 +19,7 @@ struct ThreadObject { pthread_mutex_t mutex{}; pthread_cond_t cond{}; unsigned int suspendCount = 0; + TIB *tib = nullptr; }; struct MutexObject { diff --git a/dll/kernel32/libloaderapi.cpp b/dll/kernel32/libloaderapi.cpp index 4305cfb..c24f5e8 100644 --- a/dll/kernel32/libloaderapi.cpp +++ b/dll/kernel32/libloaderapi.cpp @@ -32,7 +32,20 @@ namespace kernel32 { BOOL WIN_FUNC DisableThreadLibraryCalls(HMODULE hLibModule) { DEBUG_LOG("DisableThreadLibraryCalls(%p)\n", hLibModule); - (void)hLibModule; + if (!hLibModule) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + wibo::ModuleInfo *info = wibo::moduleInfoFromHandle(hLibModule); + if (!info) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + if (!wibo::disableThreadNotifications(info)) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + wibo::lastError = ERROR_SUCCESS; return TRUE; } @@ -208,6 +221,7 @@ HMODULE WIN_FUNC LoadLibraryA(LPCSTR lpLibFileName) { DEBUG_LOG("LoadLibraryA(%s)\n", lpLibFileName); const auto *info = wibo::loadModule(lpLibFileName); if (!info) { + // lastError is set by loadModule return nullptr; } wibo::lastError = ERROR_SUCCESS; diff --git a/dll/kernel32/memoryapi.cpp b/dll/kernel32/memoryapi.cpp index d51caf3..3714753 100644 --- a/dll/kernel32/memoryapi.cpp +++ b/dll/kernel32/memoryapi.cpp @@ -825,6 +825,7 @@ SIZE_T WIN_FUNC VirtualQuery(LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuff DEBUG_LOG("VirtualQuery(%p, %p, %zu)\n", lpAddress, lpBuffer, dwLength); if (!lpBuffer || dwLength < sizeof(MEMORY_BASIC_INFORMATION) || !lpAddress) { wibo::lastError = ERROR_INVALID_PARAMETER; + DEBUG_LOG("-> ERROR_INVALID_PARAMETER\n"); return 0; } @@ -837,12 +838,14 @@ SIZE_T WIN_FUNC VirtualQuery(LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuff VirtualAllocation *region = lookupRegion(pageBase); if (!region) { wibo::lastError = ERROR_INVALID_ADDRESS; + DEBUG_LOG("-> ERROR_INVALID_ADDRESS\n"); return 0; } const size_t pageIndex = (pageBase - region->base) / pageSize; if (pageIndex >= region->pageProtect.size()) { wibo::lastError = ERROR_INVALID_ADDRESS; + DEBUG_LOG("-> ERROR_INVALID_ADDRESS\n"); return 0; } const bool committed = region->pageProtect[pageIndex] != 0; diff --git a/dll/kernel32/processthreadsapi.cpp b/dll/kernel32/processthreadsapi.cpp index b25c9ea..d9ffbd0 100644 --- a/dll/kernel32/processthreadsapi.cpp +++ b/dll/kernel32/processthreadsapi.cpp @@ -66,6 +66,10 @@ void destroyThreadObject(ThreadObject *obj) { if (!obj) { return; } + if (obj->tib) { + wibo::destroyTib(obj->tib); + obj->tib = nullptr; + } pthread_cond_destroy(&obj->cond); pthread_mutex_destroy(&obj->mutex); delete obj; @@ -211,20 +215,33 @@ static void *threadTrampoline(void *param) { void *userParam = data->parameter; delete data; - uint16_t previousSegment = 0; - bool tibInstalled = false; - if (wibo::tibSelector) { - asm volatile("mov %%fs, %0" : "=r"(previousSegment)); - asm volatile("movw %0, %%fs" : : "r"(wibo::tibSelector) : "memory"); - tibInstalled = true; - } - g_currentThreadObject = obj; pthread_mutex_lock(&obj->mutex); while (obj->suspendCount > 0) { pthread_cond_wait(&obj->cond, &obj->mutex); } pthread_mutex_unlock(&obj->mutex); + + uint16_t previousSegment = 0; + bool tibInstalled = false; + TIB *threadTib = nullptr; + if (wibo::tibSelector) { + asm volatile("mov %%fs, %0" : "=r"(previousSegment)); + threadTib = wibo::allocateTib(); + if (threadTib) { + wibo::initializeTibStackInfo(threadTib); + if (wibo::installTibForCurrentThread(threadTib)) { + asm volatile("movw %0, %%fs" : : "r"(wibo::tibSelector) : "memory"); + tibInstalled = true; + obj->tib = threadTib; + } else { + fprintf(stderr, "!!! Failed to install TIB for new thread\n"); + wibo::destroyTib(threadTib); + threadTib = nullptr; + } + } + } + wibo::notifyDllThreadAttach(); DWORD result = startRoutine ? startRoutine(userParam) : 0; pthread_mutex_lock(&obj->mutex); obj->finished = true; @@ -233,6 +250,14 @@ static void *threadTrampoline(void *param) { bool shouldDelete = (obj->refCount == 0); bool detached = obj->detached; pthread_mutex_unlock(&obj->mutex); + wibo::notifyDllThreadDetach(); + if (tibInstalled) { + asm volatile("movw %0, %%fs" : : "r"(previousSegment) : "memory"); + } + if (threadTib) { + obj->tib = nullptr; + wibo::destroyTib(threadTib); + } g_currentThreadObject = nullptr; if (shouldDelete) { @@ -240,9 +265,6 @@ static void *threadTrampoline(void *param) { destroyThreadObject(obj); } - if (tibInstalled) { - asm volatile("movw %0, %%fs" : : "r"(previousSegment) : "memory"); - } return nullptr; } @@ -564,6 +586,7 @@ HANDLE WIN_FUNC CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dw void WIN_FUNC ExitThread(DWORD dwExitCode) { DEBUG_LOG("ExitThread(%u)\n", dwExitCode); ThreadObject *obj = g_currentThreadObject; + TIB *threadTib = obj ? obj->tib : nullptr; uint16_t previousSegment = 0; bool tibInstalled = false; if (wibo::tibSelector) { @@ -571,23 +594,32 @@ void WIN_FUNC ExitThread(DWORD dwExitCode) { asm volatile("movw %0, %%fs" : : "r"(wibo::tibSelector) : "memory"); tibInstalled = true; } + bool shouldDelete = false; + bool detached = false; if (obj) { pthread_mutex_lock(&obj->mutex); obj->finished = true; obj->exitCode = dwExitCode; pthread_cond_broadcast(&obj->cond); - bool shouldDelete = (obj->refCount == 0); - bool detached = obj->detached; + shouldDelete = (obj->refCount == 0); + detached = obj->detached; pthread_mutex_unlock(&obj->mutex); + } + wibo::notifyDllThreadDetach(); + if (tibInstalled) { + asm volatile("movw %0, %%fs" : : "r"(previousSegment) : "memory"); + } + if (obj && threadTib) { + obj->tib = nullptr; + wibo::destroyTib(threadTib); + } + if (obj) { g_currentThreadObject = nullptr; if (shouldDelete) { assert(detached && "ThreadObject must be detached when refCount reaches zero before completion"); destroyThreadObject(obj); } } - if (tibInstalled) { - asm volatile("movw %0, %%fs" : : "r"(previousSegment) : "memory"); - } pthread_exit(nullptr); } diff --git a/dll/kernel32/synchapi.cpp b/dll/kernel32/synchapi.cpp index cbc7967..e984ff8 100644 --- a/dll/kernel32/synchapi.cpp +++ b/dll/kernel32/synchapi.cpp @@ -233,7 +233,7 @@ HANDLE WIN_FUNC CreateEventW(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManu } else { nameLog = ""; } - DEBUG_LOG("CreateEventW(name=%s, manualReset=%d, initialState=%d)\n", nameLog.c_str(), bManualReset, bInitialState); + DEBUG_LOG("CreateEventW(%p, %d, %d, %s)\n", lpEventAttributes, bManualReset, bInitialState, nameLog.c_str()); (void)lpEventAttributes; std::u16string name = makeMutexName(lpName); diff --git a/dll/kernel32/winbase.cpp b/dll/kernel32/winbase.cpp index da55d3c..7c56dcf 100644 --- a/dll/kernel32/winbase.cpp +++ b/dll/kernel32/winbase.cpp @@ -433,21 +433,53 @@ UINT WIN_FUNC LocalFlags(HLOCAL hMem) { return 0; } +static constexpr const char *kSystemDirectoryA = "C:\\Windows\\System32"; + UINT WIN_FUNC GetSystemDirectoryA(LPSTR lpBuffer, UINT uSize) { DEBUG_LOG("GetSystemDirectoryA(%p, %u)\n", lpBuffer, uSize); if (!lpBuffer) { return 0; } - const char *systemDir = "C:\\Windows\\System32"; - const auto len = std::strlen(systemDir); + const auto len = std::strlen(kSystemDirectoryA); if (uSize < len + 1) { return static_cast(len + 1); } - std::strcpy(lpBuffer, systemDir); + std::strcpy(lpBuffer, kSystemDirectoryA); return static_cast(len); } +UINT WIN_FUNC GetSystemDirectoryW(LPWSTR lpBuffer, UINT uSize) { + DEBUG_LOG("GetSystemDirectoryW(%p, %u)\n", lpBuffer, uSize); + if (!lpBuffer) { + return 0; + } + + auto wide = stringToWideString(kSystemDirectoryA); + UINT length = static_cast(wide.size() - 1); + if (uSize < length + 1) { + return length + 1; + } + std::memcpy(lpBuffer, wide.data(), (length + 1) * sizeof(uint16_t)); + return length; +} + +UINT WIN_FUNC GetSystemWow64DirectoryA(LPSTR lpBuffer, UINT uSize) { + DEBUG_LOG("GetSystemWow64DirectoryA(%p, %u)\n", lpBuffer, uSize); + (void)lpBuffer; + (void)uSize; + wibo::lastError = ERROR_CALL_NOT_IMPLEMENTED; + return 0; +} + +UINT WIN_FUNC GetSystemWow64DirectoryW(LPWSTR lpBuffer, UINT uSize) { + DEBUG_LOG("GetSystemWow64DirectoryW(%p, %u)\n", lpBuffer, uSize); + (void)lpBuffer; + (void)uSize; + wibo::lastError = ERROR_CALL_NOT_IMPLEMENTED; + return 0; +} + UINT WIN_FUNC GetWindowsDirectoryA(LPSTR lpBuffer, UINT uSize) { DEBUG_LOG("GetWindowsDirectoryA(%p, %u)\n", lpBuffer, uSize); if (!lpBuffer) { diff --git a/dll/kernel32/winbase.h b/dll/kernel32/winbase.h index 776f8c6..992f4a7 100644 --- a/dll/kernel32/winbase.h +++ b/dll/kernel32/winbase.h @@ -33,6 +33,9 @@ SIZE_T WIN_FUNC LocalSize(HLOCAL hMem); UINT WIN_FUNC LocalFlags(HLOCAL hMem); UINT WIN_FUNC GetSystemDirectoryA(LPSTR lpBuffer, UINT uSize); +UINT WIN_FUNC GetSystemDirectoryW(LPWSTR lpBuffer, UINT uSize); +UINT WIN_FUNC GetSystemWow64DirectoryA(LPSTR lpBuffer, UINT uSize); +UINT WIN_FUNC GetSystemWow64DirectoryW(LPWSTR lpBuffer, UINT uSize); UINT WIN_FUNC GetWindowsDirectoryA(LPSTR lpBuffer, UINT uSize); DWORD WIN_FUNC GetCurrentDirectoryA(DWORD nBufferLength, LPSTR lpBuffer); DWORD WIN_FUNC GetCurrentDirectoryW(DWORD nBufferLength, LPWSTR lpBuffer); diff --git a/dll/ntdll.cpp b/dll/ntdll.cpp index f385dea..5644e60 100644 --- a/dll/ntdll.cpp +++ b/dll/ntdll.cpp @@ -1,8 +1,13 @@ #include "common.h" #include "errors.h" #include "files.h" +#include "handles.h" +#include "kernel32/processthreadsapi.h" +#include "processes.h" +#include "strutil.h" #include +#include #include @@ -16,6 +21,98 @@ typedef struct _IO_STATUS_BLOCK { ULONG_PTR Information; } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; +namespace { + +enum PROCESSINFOCLASS { + ProcessBasicInformation = 0, + ProcessWow64Information = 26, + ProcessImageFileName = 27, +}; + +struct PROCESS_BASIC_INFORMATION { + NTSTATUS ExitStatus; + PEB *PebBaseAddress; + ULONG_PTR AffinityMask; + LONG BasePriority; + ULONG_PTR UniqueProcessId; + ULONG_PTR InheritedFromUniqueProcessId; +}; + +struct ProcessHandleDetails { + pid_t pid = -1; + DWORD exitCode = STILL_ACTIVE; + PEB *peb = nullptr; + bool isCurrentProcess = false; +}; + +constexpr LONG kDefaultBasePriority = 8; + +struct RTL_OSVERSIONINFOW { + ULONG dwOSVersionInfoSize; + ULONG dwMajorVersion; + ULONG dwMinorVersion; + ULONG dwBuildNumber; + ULONG dwPlatformId; + WCHAR szCSDVersion[128]; +}; + +using PRTL_OSVERSIONINFOW = RTL_OSVERSIONINFOW *; + +struct RTL_OSVERSIONINFOEXW : RTL_OSVERSIONINFOW { + WORD wServicePackMajor; + WORD wServicePackMinor; + WORD wSuiteMask; + BYTE wProductType; + BYTE wReserved; +}; + +using PRTL_OSVERSIONINFOEXW = RTL_OSVERSIONINFOEXW *; + +constexpr ULONG kOsMajorVersion = 6; +constexpr ULONG kOsMinorVersion = 2; +constexpr ULONG kOsBuildNumber = 0; +constexpr ULONG kOsPlatformId = 2; // VER_PLATFORM_WIN32_NT +constexpr BYTE kProductTypeWorkstation = 1; // VER_NT_WORKSTATION + +static bool resolveProcessDetails(HANDLE processHandle, ProcessHandleDetails &details) { + uintptr_t rawHandle = reinterpret_cast(processHandle); + if (rawHandle == static_cast(-1)) { + details.pid = getpid(); + details.exitCode = STILL_ACTIVE; + details.peb = wibo::processPeb; + details.isCurrentProcess = true; + return true; + } + + auto data = handles::dataFromHandle(processHandle, false); + if (data.type != handles::TYPE_PROCESS || data.ptr == nullptr) { + return false; + } + + auto *process = reinterpret_cast(data.ptr); + details.pid = process->pid; + details.exitCode = process->exitCode; + details.isCurrentProcess = (process->pid == getpid()); + details.peb = details.isCurrentProcess ? wibo::processPeb : nullptr; + return true; +} + +static std::string windowsImagePathFor(const ProcessHandleDetails &details) { + if (details.isCurrentProcess && !wibo::guestExecutablePath.empty()) { + return files::pathToWindows(files::canonicalPath(wibo::guestExecutablePath)); + } + + std::error_code ec; + std::filesystem::path link = std::filesystem::path("/proc") / std::to_string(details.pid) / "exe"; + std::filesystem::path resolved = std::filesystem::read_symlink(link, ec); + if (!ec) { + return files::pathToWindows(files::canonicalPath(resolved)); + } + return std::string(); +} + +} // namespace + namespace kernel32 { BOOL WIN_FUNC SetEvent(HANDLE hEvent); BOOL WIN_FUNC ResetEvent(HANDLE hEvent); @@ -171,6 +268,136 @@ NTSTATUS WIN_FUNC NtProtectVirtualMemory(HANDLE ProcessHandle, PVOID *BaseAddres return STATUS_SUCCESS; } +NTSTATUS WIN_FUNC RtlGetVersion(PRTL_OSVERSIONINFOW lpVersionInformation) { + DEBUG_LOG("RtlGetVersion(%p) ", lpVersionInformation); + if (!lpVersionInformation) { + DEBUG_LOG("-> 0x%x\n", STATUS_INVALID_PARAMETER); + return STATUS_INVALID_PARAMETER; + } + + ULONG size = lpVersionInformation->dwOSVersionInfoSize; + if (size < sizeof(RTL_OSVERSIONINFOW)) { + DEBUG_LOG("-> 0x%x\n", STATUS_INVALID_PARAMETER); + return STATUS_INVALID_PARAMETER; + } + + std::memset(lpVersionInformation, 0, static_cast(size)); + lpVersionInformation->dwOSVersionInfoSize = size; + lpVersionInformation->dwMajorVersion = kOsMajorVersion; + lpVersionInformation->dwMinorVersion = kOsMinorVersion; + lpVersionInformation->dwBuildNumber = kOsBuildNumber; + lpVersionInformation->dwPlatformId = kOsPlatformId; + + if (size >= sizeof(RTL_OSVERSIONINFOEXW)) { + auto extended = reinterpret_cast(lpVersionInformation); + extended->wProductType = kProductTypeWorkstation; + } + + DEBUG_LOG("-> 0x%x\n", STATUS_SUCCESS); + return STATUS_SUCCESS; +} + +NTSTATUS WIN_FUNC NtQueryInformationProcess(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, + PVOID ProcessInformation, ULONG ProcessInformationLength, + PULONG ReturnLength) { + DEBUG_LOG("NtQueryInformationProcess(%p, %u, %p, %u, %p) ", ProcessHandle, ProcessInformationClass, + ProcessInformation, ProcessInformationLength, ReturnLength); + if (!ProcessInformation) { + DEBUG_LOG("-> 0x%x\n", STATUS_INVALID_PARAMETER); + return STATUS_INVALID_PARAMETER; + } + + ProcessHandleDetails details{}; + if (!resolveProcessDetails(ProcessHandle, details)) { + DEBUG_LOG("-> 0x%x\n", STATUS_INVALID_HANDLE); + return STATUS_INVALID_HANDLE; + } + + switch (ProcessInformationClass) { + case ProcessBasicInformation: { + size_t required = sizeof(PROCESS_BASIC_INFORMATION); + if (ReturnLength) { + *ReturnLength = static_cast(required); + } + if (ProcessInformationLength < required) { + DEBUG_LOG("-> 0x%x\n", STATUS_INFO_LENGTH_MISMATCH); + return STATUS_INFO_LENGTH_MISMATCH; + } + + auto *info = reinterpret_cast(ProcessInformation); + std::memset(info, 0, sizeof(*info)); + info->ExitStatus = static_cast(details.exitCode); + info->PebBaseAddress = details.peb; + DWORD_PTR processMask = 0; + DWORD_PTR systemMask = 0; + if (kernel32::GetProcessAffinityMask(ProcessHandle, &processMask, &systemMask)) { + info->AffinityMask = static_cast(processMask == 0 ? 1 : processMask); + } else { + info->AffinityMask = 1; + } + info->BasePriority = kDefaultBasePriority; + info->UniqueProcessId = static_cast(details.pid); + if (details.isCurrentProcess) { + info->InheritedFromUniqueProcessId = static_cast(getppid()); + } else { + info->InheritedFromUniqueProcessId = static_cast(getpid()); + } + DEBUG_LOG("-> 0x%x\n", STATUS_SUCCESS); + return STATUS_SUCCESS; + } + case ProcessWow64Information: { + size_t required = sizeof(ULONG_PTR); + if (ReturnLength) { + *ReturnLength = static_cast(required); + } + if (ProcessInformationLength < required) { + DEBUG_LOG("-> 0x%x\n", STATUS_INFO_LENGTH_MISMATCH); + return STATUS_INFO_LENGTH_MISMATCH; + } + auto *value = reinterpret_cast(ProcessInformation); + *value = 0; + DEBUG_LOG("-> 0x%x\n", STATUS_SUCCESS); + return STATUS_SUCCESS; + } + case ProcessImageFileName: { + size_t minimum = sizeof(UNICODE_STRING); + if (ProcessInformationLength < minimum) { + if (ReturnLength) { + *ReturnLength = static_cast(minimum); + } + DEBUG_LOG("-> 0x%x\n", STATUS_INFO_LENGTH_MISMATCH); + return STATUS_INFO_LENGTH_MISMATCH; + } + + std::string imagePath = windowsImagePathFor(details); + DEBUG_LOG(" NtQueryInformationProcess image path: %s\n", imagePath.c_str()); + auto widePath = stringToWideString(imagePath.c_str()); + size_t stringBytes = widePath.size() * sizeof(uint16_t); + size_t required = sizeof(UNICODE_STRING) + stringBytes; + if (ReturnLength) { + *ReturnLength = static_cast(required); + } + if (ProcessInformationLength < required) { + DEBUG_LOG("-> 0x%x\n", STATUS_INFO_LENGTH_MISMATCH); + return STATUS_INFO_LENGTH_MISMATCH; + } + + auto *unicode = reinterpret_cast(ProcessInformation); + auto *buffer = reinterpret_cast(reinterpret_cast(ProcessInformation) + sizeof(UNICODE_STRING)); + std::memcpy(buffer, widePath.data(), stringBytes); + size_t characterCount = widePath.empty() ? 0 : widePath.size() - 1; + unicode->Length = static_cast(characterCount * sizeof(uint16_t)); + unicode->MaximumLength = static_cast(widePath.size() * sizeof(uint16_t)); + unicode->Buffer = buffer; + DEBUG_LOG("-> 0x%x\n", STATUS_SUCCESS); + return STATUS_SUCCESS; + } + default: + DEBUG_LOG("-> 0x%x\n", STATUS_INVALID_INFO_CLASS); + return STATUS_INVALID_INFO_CLASS; + } +} + } // namespace ntdll static void *resolveByName(const char *name) { @@ -180,6 +407,10 @@ static void *resolveByName(const char *name) { return (void *)ntdll::NtAllocateVirtualMemory; if (strcmp(name, "NtProtectVirtualMemory") == 0) return (void *)ntdll::NtProtectVirtualMemory; + if (strcmp(name, "RtlGetVersion") == 0) + return (void *)ntdll::RtlGetVersion; + if (strcmp(name, "NtQueryInformationProcess") == 0) + return (void *)ntdll::NtQueryInformationProcess; return nullptr; } diff --git a/errors.h b/errors.h index deab53f..08e872d 100644 --- a/errors.h +++ b/errors.h @@ -34,6 +34,7 @@ #define ERROR_PROC_NOT_FOUND 127 #define ERROR_NEGATIVE_SEEK 131 #define ERROR_BAD_EXE_FORMAT 193 +#define ERROR_DLL_INIT_FAILED 1114 #define ERROR_ALREADY_EXISTS 183 #define ERROR_NOT_OWNER 288 @@ -44,6 +45,8 @@ typedef int NTSTATUS; #define STATUS_SUCCESS ((NTSTATUS)0x00000000) #define STATUS_INVALID_HANDLE ((NTSTATUS)0xC0000008) #define STATUS_INVALID_PARAMETER ((NTSTATUS)0xC000000D) +#define STATUS_INVALID_INFO_CLASS ((NTSTATUS)0xC0000003) +#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004) #define STATUS_NOT_IMPLEMENTED ((NTSTATUS)0xC0000002) #define STATUS_END_OF_FILE ((NTSTATUS)0xC0000011) #define STATUS_PENDING ((NTSTATUS)0x00000103) diff --git a/loader.cpp b/loader.cpp index 53a4add..2c3e2c4 100644 --- a/loader.cpp +++ b/loader.cpp @@ -1,7 +1,10 @@ #include "common.h" +#include "errors.h" #include +#include #include #include +#include #include #include #include @@ -300,6 +303,14 @@ bool wibo::Executable::resolveImports() { uint32_t *addressTable = fromRVA(dir->importAddressTable); ModuleInfo *module = loadModule(dllName); + if (!module && wibo::lastError != ERROR_MOD_NOT_FOUND) { + DEBUG_LOG("Failed to load import module %s\n", dllName); + // lastError is set by loadModule + importsResolved = false; + importsResolving = false; + return false; + } + while (*lookupTable) { uint32_t lookup = *lookupTable; if (lookup & 0x80000000) { diff --git a/main.cpp b/main.cpp index 2fde129..3cc8c10 100644 --- a/main.cpp +++ b/main.cpp @@ -5,10 +5,13 @@ #include #include #include +#include +#include #include #include #include #include +#include #include #include #include @@ -27,6 +30,8 @@ wibo::ModuleInfo *wibo::mainModule = nullptr; bool wibo::debugEnabled = false; unsigned int wibo::debugIndent = 0; uint16_t wibo::tibSelector = 0; +int wibo::tibEntryNumber = -1; +PEB *wibo::processPeb = nullptr; void wibo::debug_log(const char *fmt, ...) { va_list args; @@ -34,56 +39,81 @@ void wibo::debug_log(const char *fmt, ...) { if (wibo::debugEnabled) { for (size_t i = 0; i < wibo::debugIndent; i++) fprintf(stderr, "\t"); - + pthread_t threadId = pthread_self(); + fprintf(stderr, "[thread %lu] ", threadId); vfprintf(stderr, fmt, args); + fflush(stderr); } va_end(args); } -struct UNICODE_STRING { - unsigned short Length; - unsigned short MaximumLength; - uint16_t *Buffer; -}; +TIB *wibo::allocateTib() { + auto *newTib = static_cast(std::calloc(1, sizeof(TIB))); + if (!newTib) { + return nullptr; + } + newTib->tib = newTib; + newTib->peb = processPeb; + return newTib; +} -// Run Time Library (RTL) -struct RTL_USER_PROCESS_PARAMETERS { - char Reserved1[16]; - void *Reserved2[10]; - UNICODE_STRING ImagePathName; - UNICODE_STRING CommandLine; -}; +void wibo::destroyTib(TIB *tibPtr) { + if (!tibPtr) { + return; + } + std::free(tibPtr); +} -// Windows Process Environment Block (PEB) -struct PEB { - char Reserved1[2]; - char BeingDebugged; - char Reserved2[1]; - void *Reserved3[2]; - void *Ldr; - RTL_USER_PROCESS_PARAMETERS *ProcessParameters; - char Reserved4[104]; - void *Reserved5[52]; - void *PostProcessInitRoutine; - char Reserved6[128]; - void *Reserved7[1]; - unsigned int SessionId; -}; +void wibo::initializeTibStackInfo(TIB *tibPtr) { + if (!tibPtr) { + return; + } + pthread_attr_t attr; + if (pthread_getattr_np(pthread_self(), &attr) != 0) { + perror("Failed to get thread attributes"); + return; + } + void *stackAddr = nullptr; + size_t stackSize = 0; + if (pthread_attr_getstack(&attr, &stackAddr, &stackSize) == 0 && stackAddr && stackSize > 0) { + tibPtr->stackLimit = stackAddr; + tibPtr->stackBase = static_cast(stackAddr) + stackSize; + } else { + perror("Failed to get thread stack info"); + } + DEBUG_LOG("initializeTibStackInfo: stackBase=%p stackLimit=%p\n", tibPtr->stackBase, tibPtr->stackLimit); + pthread_attr_destroy(&attr); +} -// Windows Thread Information Block (TIB) -struct TIB { - /* 0x00 */ void *sehFrame; - /* 0x04 */ void *stackBase; - /* 0x08 */ void *stackLimit; - /* 0x0C */ void *subSystemTib; - /* 0x10 */ void *fiberData; - /* 0x14 */ void *arbitraryDataSlot; - /* 0x18 */ TIB *tib; - /* */ char pad[0x14]; - /* 0x30 */ PEB *peb; - /* */ char pad2[0x1000]; -}; +bool wibo::installTibForCurrentThread(TIB *tibPtr) { + if (!tibPtr) { + return false; + } + struct user_desc desc; + std::memset(&desc, 0, sizeof(desc)); + desc.entry_number = tibEntryNumber; + desc.base_addr = reinterpret_cast(tibPtr); + desc.limit = static_cast(sizeof(TIB) - 1); + desc.seg_32bit = 1; + desc.contents = 0; + desc.read_exec_only = 0; + desc.limit_in_pages = 0; + desc.seg_not_present = 0; + desc.useable = 1; + if (syscall(SYS_set_thread_area, &desc) != 0) { + perror("set_thread_area failed"); + return false; + } + if (tibSelector == 0) { + tibEntryNumber = static_cast(desc.entry_number); + tibSelector = static_cast((desc.entry_number << 3) | 3); + DEBUG_LOG("set_thread_area: allocated selector=0x%x entry=%d base=%p\n", tibSelector, tibEntryNumber, tibPtr); + } else { + DEBUG_LOG("set_thread_area: reused selector=0x%x entry=%d base=%p\n", tibSelector, tibEntryNumber, tibPtr); + } + return true; +} // Make this global to ease debugging TIB tib; @@ -309,25 +339,13 @@ int main(int argc, char **argv) { tib.tib = &tib; tib.peb = (PEB *)calloc(sizeof(PEB), 1); tib.peb->ProcessParameters = (RTL_USER_PROCESS_PARAMETERS *)calloc(sizeof(RTL_USER_PROCESS_PARAMETERS), 1); - - struct user_desc tibDesc; - memset(&tibDesc, 0, sizeof tibDesc); - tibDesc.entry_number = 0; - tibDesc.base_addr = (unsigned int)&tib; - tibDesc.limit = 0x1000; - tibDesc.seg_32bit = 1; - tibDesc.contents = 0; // hopefully this is ok - tibDesc.read_exec_only = 0; - tibDesc.limit_in_pages = 0; - tibDesc.seg_not_present = 0; - tibDesc.useable = 1; - if (syscall(SYS_modify_ldt, 1, &tibDesc, sizeof tibDesc) != 0) { - perror("Failed to modify LDT\n"); + wibo::processPeb = tib.peb; + wibo::initializeTibStackInfo(&tib); + if (!wibo::installTibForCurrentThread(&tib)) { + fprintf(stderr, "Failed to install TIB for main thread\n"); return 1; } - wibo::tibSelector = static_cast((tibDesc.entry_number << 3) | 7); - // Determine the guest program name auto guestArgs = processes::splitCommandLine(cmdLine.c_str()); std::string programName; @@ -374,7 +392,7 @@ int main(int argc, char **argv) { if (i != 0) { cmdLine += ' '; } - const std::string& arg = guestArgs[i]; + const std::string &arg = guestArgs[i]; bool needQuotes = arg.find_first_of("\" \t\n") != std::string::npos; if (needQuotes) cmdLine += '"'; @@ -458,8 +476,8 @@ int main(int argc, char **argv) { wibo::mainModule->executable->imageBase); if (!wibo::mainModule->executable->resolveImports()) { - fprintf(stderr, "Failed to resolve imports for main module\n"); - return 1; + fprintf(stderr, "Failed to resolve imports for main module (DLL initialization failure?)\n"); + abort(); } // Invoke the damn thing diff --git a/module_registry.cpp b/module_registry.cpp index 23bec32..cd318c2 100644 --- a/module_registry.cpp +++ b/module_registry.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,8 @@ namespace { constexpr DWORD DLL_PROCESS_DETACH = 0; constexpr DWORD DLL_PROCESS_ATTACH = 1; +constexpr DWORD DLL_THREAD_ATTACH = 2; +constexpr DWORD DLL_THREAD_DETACH = 3; struct PEExportDirectory { uint32_t characteristics; @@ -409,41 +412,83 @@ void registerBuiltinModule(ModuleRegistry ®, const wibo::Module *module) { } } -void callDllMain(wibo::ModuleInfo &info, DWORD reason) { - if (!info.executable) { - return; +BOOL callDllMain(wibo::ModuleInfo &info, DWORD reason, LPVOID reserved) { + if (&info == wibo::mainModule) { + return TRUE; } - using DllMainFunc = BOOL(WIN_FUNC *)(HMODULE, DWORD, LPVOID); - auto dllMain = reinterpret_cast(info.executable->entryPoint); - if (!dllMain) { - return; + if (!info.executable) { + return TRUE; + } + void *entry = info.executable->entryPoint; + if (!entry) { + return TRUE; } - auto invokeWithGuestTIB = [&](DWORD callReason) -> BOOL { - if (!wibo::tibSelector) { - return dllMain(reinterpret_cast(info.executable->imageBase), callReason, nullptr); + using DllMainFunc = BOOL(WIN_FUNC *)(HMODULE, DWORD, LPVOID); + auto dllMain = reinterpret_cast(entry); + + auto invokeWithGuestTIB = [&](DWORD callReason, LPVOID callReserved, bool force) -> BOOL { + if (!force) { + if (callReason == DLL_PROCESS_DETACH) { + if (!info.processAttachCalled || !info.processAttachSucceeded) { + return TRUE; + } + } + if (callReason == DLL_THREAD_ATTACH || callReason == DLL_THREAD_DETACH) { + if (!info.processAttachCalled || !info.processAttachSucceeded || !info.threadNotificationsEnabled) { + return TRUE; + } + } } - uint16_t previousSegment = 0; - asm volatile("mov %%fs, %0" : "=r"(previousSegment)); - asm volatile("movw %0, %%fs" : : "r"(wibo::tibSelector) : "memory"); - BOOL result = dllMain(reinterpret_cast(info.executable->imageBase), callReason, nullptr); - asm volatile("movw %0, %%fs" : : "r"(previousSegment) : "memory"); + DEBUG_LOG(" callDllMain: invoking DllMain(%p, %u, %p) for %s\n", + reinterpret_cast(info.executable->imageBase), callReason, callReserved, + info.normalizedName.c_str()); + + BOOL result = TRUE; + if (!wibo::tibSelector) { + result = dllMain(reinterpret_cast(info.executable->imageBase), callReason, callReserved); + } else { + uint16_t previousSegment = 0; + asm volatile("mov %%fs, %0" : "=r"(previousSegment)); + asm volatile("movw %0, %%fs" : : "r"(wibo::tibSelector) : "memory"); + result = dllMain(reinterpret_cast(info.executable->imageBase), callReason, callReserved); + asm volatile("movw %0, %%fs" : : "r"(previousSegment) : "memory"); + } + DEBUG_LOG(" callDllMain: %s DllMain returned %d\n", info.normalizedName.c_str(), result); return result; }; - if (reason == DLL_PROCESS_ATTACH) { + switch (reason) { + case DLL_PROCESS_ATTACH: { if (info.processAttachCalled) { - return; + return info.processAttachSucceeded ? TRUE : FALSE; } info.processAttachCalled = true; - BOOL result = invokeWithGuestTIB(reason); - info.processAttachSucceeded = result != 0; - } else if (reason == DLL_PROCESS_DETACH) { - if (info.processAttachCalled && info.processAttachSucceeded) { - invokeWithGuestTIB(reason); + info.processAttachSucceeded = false; + BOOL result = invokeWithGuestTIB(DLL_PROCESS_ATTACH, reserved, true); + if (!result) { + invokeWithGuestTIB(DLL_PROCESS_DETACH, nullptr, true); + return FALSE; } + info.processAttachSucceeded = true; + return TRUE; } + case DLL_PROCESS_DETACH: { + BOOL result = invokeWithGuestTIB(DLL_PROCESS_DETACH, reserved, false); + if (info.processAttachSucceeded) { + info.processAttachSucceeded = false; + } + return result; + } + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + return invokeWithGuestTIB(reason, reserved, false); + default: + break; + } + + return TRUE; } void registerExternalModuleAliases(ModuleRegistry ®, const std::string &requestedName, @@ -473,6 +518,25 @@ wibo::ModuleInfo *moduleFromAddress(ModuleRegistry ®, void *addr) { return nullptr; } +bool shouldDeliverThreadNotifications(const wibo::ModuleInfo &info) { + if (&info == wibo::mainModule) { + return false; + } + if (info.module != nullptr) { + return false; + } + if (!info.executable) { + return false; + } + if (!info.processAttachCalled || !info.processAttachSucceeded) { + return false; + } + if (!info.threadNotificationsEnabled) { + return false; + } + return true; +} + void ensureExportsInitialized(wibo::ModuleInfo &info) { if (info.module || info.exportsInitialized) return; @@ -598,7 +662,7 @@ void shutdownModuleRegistry() { } runPendingOnExit(*info); if (info->processAttachCalled && info->processAttachSucceeded) { - callDllMain(*info, DLL_PROCESS_DETACH); + callDllMain(*info, DLL_PROCESS_DETACH, reinterpret_cast(1)); } } reg->modulesByKey.clear(); @@ -612,7 +676,23 @@ ModuleInfo *moduleInfoFromHandle(HMODULE module) { if (isMainModule(module)) { return wibo::mainModule; } - return static_cast(module); + if (!module) { + return nullptr; + } + auto reg = registry(); + for (auto &pair : reg->modulesByKey) { + wibo::ModuleInfo *info = pair.second.get(); + if (!info) { + continue; + } + if (info->handle == module) { + return info; + } + if (info->executable && info->executable->imageBase == module) { + return info; + } + } + return nullptr; } void setDllDirectoryOverride(const std::filesystem::path &path) { @@ -687,6 +767,46 @@ void executeOnExitTable(void *table) { } } +void notifyDllThreadAttach() { + auto reg = registry(); + std::vector targets; + targets.reserve(reg->modulesByKey.size()); + for (auto &pair : reg->modulesByKey) { + wibo::ModuleInfo *info = pair.second.get(); + if (info && shouldDeliverThreadNotifications(*info)) { + targets.push_back(info); + } + } + for (wibo::ModuleInfo *info : targets) { + callDllMain(*info, DLL_THREAD_ATTACH, nullptr); + } +} + +void notifyDllThreadDetach() { + auto reg = registry(); + std::vector targets; + targets.reserve(reg->modulesByKey.size()); + for (auto &pair : reg->modulesByKey) { + wibo::ModuleInfo *info = pair.second.get(); + if (info && shouldDeliverThreadNotifications(*info)) { + targets.push_back(info); + } + } + for (auto it = targets.rbegin(); it != targets.rend(); ++it) { + callDllMain(**it, DLL_THREAD_DETACH, nullptr); + } +} + +BOOL disableThreadNotifications(ModuleInfo *info) { + if (!info) { + return FALSE; + } + auto reg = registry(); + (void)reg; + info->threadNotificationsEnabled = false; + return TRUE; +} + ModuleInfo *findLoadedModule(const char *name) { if (!name || *name == '\0') { return wibo::mainModule; @@ -727,6 +847,7 @@ ModuleInfo *loadModule(const char *dllName) { return info; } + DEBUG_LOG(" loading external module from %s\n", path.c_str()); FILE *file = fopen(path.c_str(), "rb"); if (!file) { perror("loadModule"); @@ -756,8 +877,35 @@ ModuleInfo *loadModule(const char *dllName) { reg->modulesByKey[key] = std::move(info); registerExternalModuleAliases(*reg, requested, raw->resolvedPath, raw); ensureExportsInitialized(*raw); - raw->executable->resolveImports(); - callDllMain(*raw, DLL_PROCESS_ATTACH); + if (!raw->executable->resolveImports()) { + DEBUG_LOG(" resolveImports failed for %s\n", raw->originalName.c_str()); + reg->modulesByKey.erase(key); + diskError = wibo::lastError; + return nullptr; + } + if (!callDllMain(*raw, DLL_PROCESS_ATTACH, nullptr)) { + DEBUG_LOG(" DllMain failed for %s\n", raw->originalName.c_str()); + runPendingOnExit(*raw); + for (auto it = reg->onExitTables.begin(); it != reg->onExitTables.end();) { + if (it->second == raw) { + it = reg->onExitTables.erase(it); + } else { + ++it; + } + } + for (auto it = reg->modulesByAlias.begin(); it != reg->modulesByAlias.end();) { + if (it->second == raw) { + it = reg->modulesByAlias.erase(it); + } else { + ++it; + } + } + reg->pinnedModules.erase(raw); + reg->modulesByKey.erase(key); + diskError = ERROR_DLL_INIT_FAILED; + wibo::lastError = ERROR_DLL_INIT_FAILED; + return nullptr; + } return raw; }; @@ -765,6 +913,7 @@ ModuleInfo *loadModule(const char *dllName) { auto resolvedPath = resolveModuleOnDisk(*reg, requested, false); if (!resolvedPath) { DEBUG_LOG(" module not found on disk\n"); + diskError = ERROR_MOD_NOT_FOUND; return nullptr; } return tryLoadExternal(*resolvedPath); @@ -791,6 +940,9 @@ ModuleInfo *loadModule(const char *dllName) { DEBUG_LOG(" replaced builtin module %s with external copy\n", requested.c_str()); lastError = ERROR_SUCCESS; return external; + } else if (diskError != ERROR_MOD_NOT_FOUND) { + lastError = diskError; + return nullptr; } } lastError = ERROR_SUCCESS; @@ -802,6 +954,9 @@ ModuleInfo *loadModule(const char *dllName) { DEBUG_LOG(" loaded external module %s\n", requested.c_str()); lastError = ERROR_SUCCESS; return external; + } else if (diskError != ERROR_MOD_NOT_FOUND) { + lastError = diskError; + return nullptr; } auto fallbackAlias = normalizedBaseKey(parsed); @@ -844,7 +999,7 @@ void freeModule(ModuleInfo *info) { } } runPendingOnExit(*info); - callDllMain(*info, DLL_PROCESS_DETACH); + callDllMain(*info, DLL_PROCESS_DETACH, nullptr); std::string key = info->resolvedPath.empty() ? storageKeyForBuiltin(info->normalizedName) : storageKeyForPath(info->resolvedPath); reg->modulesByKey.erase(key); diff --git a/test/dll_attach_failure.c b/test/dll_attach_failure.c new file mode 100644 index 0000000..37337e6 --- /dev/null +++ b/test/dll_attach_failure.c @@ -0,0 +1,10 @@ +#include + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { + (void)hinstDLL; + (void)lpReserved; + if (fdwReason == DLL_PROCESS_ATTACH) { + return FALSE; + } + return TRUE; +} diff --git a/test/test_dll_attach_failure.c b/test/test_dll_attach_failure.c new file mode 100644 index 0000000..03c4a77 --- /dev/null +++ b/test/test_dll_attach_failure.c @@ -0,0 +1,17 @@ +#include +#include +#include + +#include "test_assert.h" + +int main(void) { + SetLastError(0); + HMODULE mod = LoadLibraryA("dll_attach_failure.dll"); + DWORD error = GetLastError(); + + TEST_CHECK_MSG(mod == NULL, "LoadLibraryA unexpectedly succeeded: %p", mod); + TEST_CHECK_EQ(ERROR_DLL_INIT_FAILED, error); + + printf("dll_attach_failure: error=%lu\n", (unsigned long)error); + return EXIT_SUCCESS; +} diff --git a/test/test_ntquery.c b/test/test_ntquery.c new file mode 100644 index 0000000..be42e43 --- /dev/null +++ b/test/test_ntquery.c @@ -0,0 +1,144 @@ +#include +#include +#include +#include + +#include "test_assert.h" + +#ifndef STATUS_SUCCESS +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#endif + +#ifndef STATUS_INFO_LENGTH_MISMATCH +#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) +#endif + +#ifndef STATUS_INVALID_INFO_CLASS +#define STATUS_INVALID_INFO_CLASS ((NTSTATUS)0xC0000003L) +#endif + +typedef NTSTATUS(NTAPI *NtQueryInformationProcess_t)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG); + +static WCHAR wide_to_upper(WCHAR ch) { + if (ch >= L'a' && ch <= L'z') { + return ch - (WCHAR)32; + } + return ch; +} + +static int wide_cmp_case_insensitive(const WCHAR *lhs, const WCHAR *rhs) { + while (*lhs && *rhs) { + WCHAR a = wide_to_upper(*lhs); + WCHAR b = wide_to_upper(*rhs); + if (a != b) { + break; + } + ++lhs; + ++rhs; + } + return (int)wide_to_upper(*lhs) - (int)wide_to_upper(*rhs); +} + +static const WCHAR *skip_nt_path_prefix(const WCHAR *path) { + if (!path) { + return path; + } + if (path[0] == L'\\' && path[1] == L'?' && path[2] == L'?' && path[3] == L'\\') { + return path + 4; + } + return path; +} + +static NtQueryInformationProcess_t load_ntquery(void) { + HMODULE ntdll = GetModuleHandleW(L"ntdll.dll"); + if (!ntdll) { + ntdll = LoadLibraryW(L"ntdll.dll"); + } + TEST_CHECK(ntdll != NULL); + FARPROC proc = GetProcAddress(ntdll, "NtQueryInformationProcess"); + TEST_CHECK(proc != NULL); + NtQueryInformationProcess_t fn = NULL; + TEST_CHECK(sizeof(fn) == sizeof(proc)); + memcpy(&fn, &proc, sizeof(fn)); + return fn; +} + +static void test_basic_information(NtQueryInformationProcess_t fn) { + PROCESS_BASIC_INFORMATION info; + ULONG returnLength = 0; + NTSTATUS status = fn(GetCurrentProcess(), ProcessBasicInformation, &info, sizeof(info), &returnLength); + TEST_CHECK_EQ((NTSTATUS)STATUS_SUCCESS, status); + TEST_CHECK(returnLength >= sizeof(info)); + TEST_CHECK_EQ((ULONG_PTR)GetCurrentProcessId(), info.UniqueProcessId); + TEST_CHECK(info.PebBaseAddress != NULL); + TEST_CHECK_EQ(STILL_ACTIVE, (DWORD)info.ExitStatus); +} + +static void test_wow64_information(NtQueryInformationProcess_t fn) { + ULONG_PTR wow64 = (ULONG_PTR)0xDEADBEEF; + ULONG returnLength = 0; + NTSTATUS status = fn(GetCurrentProcess(), ProcessWow64Information, &wow64, sizeof(wow64), &returnLength); + TEST_CHECK_EQ((NTSTATUS)STATUS_SUCCESS, status); + TEST_CHECK_EQ((ULONG)sizeof(ULONG_PTR), returnLength); + + BOOL isWow64 = FALSE; + if (IsWow64Process(GetCurrentProcess(), &isWow64) && isWow64) { + TEST_CHECK(wow64 != 0); + } else { + TEST_CHECK_EQ((ULONG_PTR)0, wow64); + } +} + +static void test_image_file_name(NtQueryInformationProcess_t fn) { + unsigned char buffer[sizeof(UNICODE_STRING) + 1024]; + ULONG returnLength = 0; + NTSTATUS status = fn(GetCurrentProcess(), ProcessImageFileName, buffer, sizeof(buffer), &returnLength); + TEST_CHECK_EQ((NTSTATUS)STATUS_SUCCESS, status); + TEST_CHECK(returnLength > sizeof(UNICODE_STRING)); + + UNICODE_STRING *u = (UNICODE_STRING *)buffer; + TEST_CHECK(u->Buffer != NULL); + TEST_CHECK(u->Length % sizeof(WCHAR) == 0); + + int charCount = (int)(u->Length / sizeof(WCHAR)); + WCHAR from_ntquery[512]; + TEST_CHECK(charCount < (int)(sizeof(from_ntquery) / sizeof(from_ntquery[0]))); + memcpy(from_ntquery, u->Buffer, u->Length); + from_ntquery[charCount] = L'\0'; + + WCHAR expected[512]; + DWORD len = GetModuleFileNameW(NULL, expected, (DWORD)(sizeof(expected) / sizeof(expected[0]))); + TEST_CHECK(len > 0); + const WCHAR *normalized_expected = skip_nt_path_prefix(expected); + const WCHAR *normalized_actual = skip_nt_path_prefix(from_ntquery); + TEST_CHECK_MSG(wide_cmp_case_insensitive(normalized_expected, normalized_actual) == 0, + "expected %ls, got %ls", normalized_expected, normalized_actual); +} + +static void test_invalid_lengths(NtQueryInformationProcess_t fn) { + PROCESS_BASIC_INFORMATION info; + ULONG returnLength = 0; + NTSTATUS status = fn(GetCurrentProcess(), ProcessBasicInformation, &info, sizeof(info) - 1, &returnLength); + TEST_CHECK_EQ((NTSTATUS)STATUS_INFO_LENGTH_MISMATCH, status); + TEST_CHECK_EQ((ULONG)sizeof(PROCESS_BASIC_INFORMATION), returnLength); + + ULONG_PTR wow64 = 0; + status = fn(GetCurrentProcess(), ProcessWow64Information, &wow64, sizeof(wow64) - 1, &returnLength); + TEST_CHECK_EQ((NTSTATUS)STATUS_INFO_LENGTH_MISMATCH, status); +} + +static void test_invalid_class(NtQueryInformationProcess_t fn) { + ULONG dummy = 0; + NTSTATUS status = fn(GetCurrentProcess(), (PROCESSINFOCLASS)1234, &dummy, sizeof(dummy), NULL); + TEST_CHECK_EQ((NTSTATUS)STATUS_INVALID_INFO_CLASS, status); +} + +int main(void) { + NtQueryInformationProcess_t fn = load_ntquery(); + test_basic_information(fn); + test_wow64_information(fn); + test_image_file_name(fn); + test_invalid_lengths(fn); + test_invalid_class(fn); + return 0; +} diff --git a/test/test_rtl.c b/test/test_rtl.c new file mode 100644 index 0000000..48ebd69 --- /dev/null +++ b/test/test_rtl.c @@ -0,0 +1,80 @@ +#include +#include +#include + +#include "test_assert.h" + +#ifndef STATUS_SUCCESS +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#endif + +#ifndef STATUS_INVALID_PARAMETER +#define STATUS_INVALID_PARAMETER ((NTSTATUS)0xC000000DL) +#endif + +typedef NTSTATUS(WINAPI *RtlGetVersionFn)(PRTL_OSVERSIONINFOW); + +static RtlGetVersionFn load_rtl_get_version(void) { + HMODULE ntdll = GetModuleHandleW(L"ntdll.dll"); + if (!ntdll) { + ntdll = LoadLibraryW(L"ntdll.dll"); + } + TEST_CHECK(ntdll != NULL); + FARPROC proc = GetProcAddress(ntdll, "RtlGetVersion"); + TEST_CHECK(proc != NULL); + RtlGetVersionFn fn = NULL; + TEST_CHECK(sizeof(fn) == sizeof(proc)); + memcpy(&fn, &proc, sizeof(fn)); + return fn; +} + +static void test_basic_version(RtlGetVersionFn rtl_get_version) { + RTL_OSVERSIONINFOW info; + memset(&info, 0xCC, sizeof(info)); + info.dwOSVersionInfoSize = sizeof(info); + + NTSTATUS status = rtl_get_version(&info); + TEST_CHECK_EQ((NTSTATUS)STATUS_SUCCESS, status); + TEST_CHECK_EQ(sizeof(info), info.dwOSVersionInfoSize); + TEST_CHECK_EQ(6u, info.dwMajorVersion); + TEST_CHECK_EQ(2u, info.dwMinorVersion); + TEST_CHECK_EQ(0u, info.dwBuildNumber); + TEST_CHECK_EQ(2u, info.dwPlatformId); + TEST_CHECK_EQ(0, info.szCSDVersion[0]); +} + +static void test_extended_version(RtlGetVersionFn rtl_get_version) { + RTL_OSVERSIONINFOEXW info; + memset(&info, 0xCC, sizeof(info)); + info.dwOSVersionInfoSize = sizeof(info); + + NTSTATUS status = rtl_get_version((PRTL_OSVERSIONINFOW)&info); + TEST_CHECK_EQ((NTSTATUS)STATUS_SUCCESS, status); + TEST_CHECK_EQ(sizeof(info), info.dwOSVersionInfoSize); + TEST_CHECK_EQ(6u, info.dwMajorVersion); + TEST_CHECK_EQ(2u, info.dwMinorVersion); + TEST_CHECK_EQ(0u, info.dwBuildNumber); + TEST_CHECK_EQ(2u, info.dwPlatformId); + TEST_CHECK_EQ(1u, info.wProductType); + TEST_CHECK_EQ(0u, info.wServicePackMajor); + TEST_CHECK_EQ(0u, info.wServicePackMinor); + TEST_CHECK_EQ(0u, info.wSuiteMask); + TEST_CHECK_EQ(0u, info.wReserved); +} + +static void test_invalid_size(RtlGetVersionFn rtl_get_version) { + RTL_OSVERSIONINFOW info; + memset(&info, 0xCC, sizeof(info)); + info.dwOSVersionInfoSize = sizeof(info) - 4; + + NTSTATUS status = rtl_get_version(&info); + TEST_CHECK_EQ((NTSTATUS)STATUS_INVALID_PARAMETER, status); +} + +int main(void) { + RtlGetVersionFn rtl_get_version = load_rtl_get_version(); + test_basic_version(rtl_get_version); + test_extended_version(rtl_get_version); + test_invalid_size(rtl_get_version); + return 0; +} diff --git a/test/test_sysdir.c b/test/test_sysdir.c new file mode 100644 index 0000000..e6e9532 --- /dev/null +++ b/test/test_sysdir.c @@ -0,0 +1,91 @@ +#include + +#include "test_assert.h" + +static void normalize_uppercase(char *dst, const char *src, size_t size) { + size_t i; + for (i = 0; i + 1 < size && src[i]; ++i) { + unsigned char ch = (unsigned char)src[i]; + if (ch >= 'a' && ch <= 'z') { + ch = (unsigned char)(ch - ('a' - 'A')); + } + dst[i] = (char)ch; + } + dst[i] = '\0'; +} + +static void expect_directory(const char *expected, const char *actual) { + char expectedUpper[MAX_PATH]; + char actualUpper[MAX_PATH]; + normalize_uppercase(expectedUpper, expected, sizeof(expectedUpper)); + normalize_uppercase(actualUpper, actual, sizeof(actualUpper)); + TEST_CHECK_STR_EQ(expectedUpper, actualUpper); +} + +static void test_ascii(void) { + char buffer[MAX_PATH]; + UINT written = GetSystemDirectoryA(buffer, sizeof(buffer)); + TEST_CHECK(written > 0); + TEST_CHECK(written < sizeof(buffer)); + expect_directory("C:\\Windows\\System32", buffer); + + /* Request length only */ + written = GetSystemDirectoryA(buffer, 0); + TEST_CHECK_EQ((UINT)20, written); + + SetLastError(0xdeadbeef); + written = GetSystemWow64DirectoryA(buffer, sizeof(buffer)); + if (written == 0) { + TEST_CHECK_EQ(ERROR_CALL_NOT_IMPLEMENTED, GetLastError()); + } else { + expect_directory("C:\\Windows\\SysWOW64", buffer); + } + + SetLastError(0xdeadbeef); + written = GetSystemWow64DirectoryA(buffer, 0); + if (written == 0) { + TEST_CHECK_EQ(ERROR_CALL_NOT_IMPLEMENTED, GetLastError()); + } else { + TEST_CHECK_EQ((UINT)20, written); + } +} + +static void test_unicode(void) { + WCHAR buffer[MAX_PATH]; + UINT written = GetSystemDirectoryW(buffer, MAX_PATH); + TEST_CHECK(written > 0); + TEST_CHECK(written < MAX_PATH); + + char narrow[MAX_PATH]; + WideCharToMultiByte(CP_UTF8, 0, buffer, -1, narrow, sizeof(narrow), NULL, NULL); + expect_directory("C:\\Windows\\System32", narrow); + + /* Insufficient buffer */ + WCHAR tiny[8]; + written = GetSystemDirectoryW(tiny, 4); + TEST_CHECK_EQ((UINT)20, written); + + SetLastError(0xdeadbeef); + written = GetSystemWow64DirectoryW(buffer, MAX_PATH); + if (written == 0) { + TEST_CHECK_EQ(ERROR_CALL_NOT_IMPLEMENTED, GetLastError()); + } else { + char narrow[MAX_PATH]; + WideCharToMultiByte(CP_UTF8, 0, buffer, -1, narrow, sizeof(narrow), NULL, NULL); + expect_directory("C:\\Windows\\SysWOW64", narrow); + } + + SetLastError(0xdeadbeef); + written = GetSystemWow64DirectoryW(tiny, 4); + if (written == 0) { + TEST_CHECK_EQ(ERROR_CALL_NOT_IMPLEMENTED, GetLastError()); + } else { + TEST_CHECK_EQ((UINT)20, written); + } +} + +int main(void) { + test_ascii(); + test_unicode(); + return 0; +} diff --git a/test/test_thread_notifications.c b/test/test_thread_notifications.c new file mode 100644 index 0000000..8f56a14 --- /dev/null +++ b/test/test_thread_notifications.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include + +#include "test_assert.h" + +static DWORD WINAPI workerProc(LPVOID param) { + (void)param; + return 0; +} + +static FARPROC loadExport(HMODULE module, const char *name) { + FARPROC proc = GetProcAddress(module, name); +#if defined(__i386__) || defined(_M_IX86) + if (!proc) { + char decorated[64]; + snprintf(decorated, sizeof(decorated), "_%s@0", name); + proc = GetProcAddress(module, decorated); + } +#endif + return proc; +} + +static BOOL isRunningUnderWine(void) { + HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll"); + return hNtdll != NULL && GetProcAddress(hNtdll, "wine_get_version") != NULL; +} + +int main(void) { + typedef LONG (*get_count_fn)(void); + typedef void (*reset_counts_fn)(void); + typedef BOOL (*disable_fn)(void); + + HMODULE mod = LoadLibraryA("thread_notifications.dll"); + TEST_CHECK_MSG(mod != NULL, "LoadLibraryA failed: %lu", (unsigned long)GetLastError()); + + get_count_fn getAttach = (get_count_fn)(uintptr_t)loadExport(mod, "get_thread_attach_count"); + get_count_fn getDetach = (get_count_fn)(uintptr_t)loadExport(mod, "get_thread_detach_count"); + reset_counts_fn resetCounts = (reset_counts_fn)(uintptr_t)loadExport(mod, "reset_thread_counts"); + disable_fn disableNotifications = (disable_fn)(uintptr_t)loadExport(mod, "disable_thread_notifications"); + + TEST_CHECK_MSG(getAttach != NULL, "Missing get_thread_attach_count: %lu", (unsigned long)GetLastError()); + TEST_CHECK_MSG(getDetach != NULL, "Missing get_thread_detach_count: %lu", (unsigned long)GetLastError()); + TEST_CHECK_MSG(resetCounts != NULL, "Missing reset_thread_counts: %lu", (unsigned long)GetLastError()); + TEST_CHECK_MSG(disableNotifications != NULL, "Missing disable_thread_notifications: %lu", + (unsigned long)GetLastError()); + + resetCounts(); + TEST_CHECK_EQ(0, getAttach()); + TEST_CHECK_EQ(0, getDetach()); + + HANDLE thread = CreateThread(NULL, 0, workerProc, NULL, 0, NULL); + TEST_CHECK_MSG(thread != NULL, "CreateThread failed: %lu", (unsigned long)GetLastError()); + DWORD wait_result = WaitForSingleObject(thread, INFINITE); + TEST_CHECK_EQ(WAIT_OBJECT_0, wait_result); + TEST_CHECK_MSG(CloseHandle(thread) != 0, "CloseHandle(thread) failed: %lu", (unsigned long)GetLastError()); + + TEST_CHECK_EQ(1, getAttach()); + TEST_CHECK_EQ(1, getDetach()); + + resetCounts(); + + // Wine throws ERR_MOD_NOT_FOUND from DisableThreadLibraryCalls, even if we pass in hinstDLL from DllMain + // DLL_PROCESS_ATTACH, which is strange. + TEST_CHECK_MSG(disableNotifications() || isRunningUnderWine(), "DisableThreadLibraryCalls failed: %lu", + (unsigned long)GetLastError()); + + thread = CreateThread(NULL, 0, workerProc, NULL, 0, NULL); + TEST_CHECK_MSG(thread != NULL, "CreateThread after disable failed: %lu", (unsigned long)GetLastError()); + wait_result = WaitForSingleObject(thread, INFINITE); + TEST_CHECK_EQ(WAIT_OBJECT_0, wait_result); + TEST_CHECK_MSG(CloseHandle(thread) != 0, "CloseHandle(second thread) failed: %lu", (unsigned long)GetLastError()); + + if (!isRunningUnderWine()) { + TEST_CHECK_EQ(0, getAttach()); + TEST_CHECK_EQ(0, getDetach()); + } + + LONG final_attach = getAttach(); + LONG final_detach = getDetach(); + + TEST_CHECK_MSG(FreeLibrary(mod) != 0, "FreeLibrary failed: %lu", (unsigned long)GetLastError()); + + printf("thread_notifications: attach=%ld detach=%ld\n", (long)final_attach, (long)final_detach); + return EXIT_SUCCESS; +} diff --git a/test/thread_notifications.c b/test/thread_notifications.c new file mode 100644 index 0000000..da44c3c --- /dev/null +++ b/test/thread_notifications.c @@ -0,0 +1,45 @@ +#include + +static HMODULE g_module = NULL; +static volatile LONG g_threadAttachCount = 0; +static volatile LONG g_threadDetachCount = 0; + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { + (void)lpReserved; + switch (fdwReason) { + case DLL_PROCESS_ATTACH: + g_module = hinstDLL; + g_threadAttachCount = 0; + g_threadDetachCount = 0; + break; + case DLL_THREAD_ATTACH: + InterlockedIncrement(&g_threadAttachCount); + break; + case DLL_THREAD_DETACH: + InterlockedIncrement(&g_threadDetachCount); + break; + case DLL_PROCESS_DETACH: + g_module = NULL; + break; + default: + break; + } + return TRUE; +} + +__declspec(dllexport) LONG get_thread_attach_count(void) { + return g_threadAttachCount; +} + +__declspec(dllexport) LONG get_thread_detach_count(void) { + return g_threadDetachCount; +} + +__declspec(dllexport) void reset_thread_counts(void) { + g_threadAttachCount = 0; + g_threadDetachCount = 0; +} + +__declspec(dllexport) BOOL disable_thread_notifications(void) { + return DisableThreadLibraryCalls(g_module); +}