Rework thread TIB handling & DLL initialization handling

This commit is contained in:
Luke Street 2025-10-02 16:40:23 -06:00
parent 8330f27479
commit d8150e33b9
23 changed files with 1256 additions and 110 deletions

View File

@ -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.

View File

@ -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 $<TARGET_FILE:wibo> ${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 $<TARGET_FILE:wibo> ${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 $<TARGET_FILE:wibo> ${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 $<TARGET_FILE:wibo> ${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 $<TARGET_FILE:wibo> ${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()

View File

@ -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<uint16_t> 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<void *> exportsByOrdinal;
std::unordered_map<std::string, uint16_t> exportNameToOrdinal;

View File

@ -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)

View File

@ -19,6 +19,7 @@ struct ThreadObject {
pthread_mutex_t mutex{};
pthread_cond_t cond{};
unsigned int suspendCount = 0;
TIB *tib = nullptr;
};
struct MutexObject {

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -233,7 +233,7 @@ HANDLE WIN_FUNC CreateEventW(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManu
} else {
nameLog = "<unnamed>";
}
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);

View File

@ -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<UINT>(len + 1);
}
std::strcpy(lpBuffer, systemDir);
std::strcpy(lpBuffer, kSystemDirectoryA);
return static_cast<UINT>(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<UINT>(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) {

View File

@ -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);

View File

@ -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 <sys/mman.h>
#include <unistd.h>
#include <optional>
@ -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<uintptr_t>(processHandle);
if (rawHandle == static_cast<uintptr_t>(-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<processes::Process *>(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_t>(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<PRTL_OSVERSIONINFOEXW>(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<ULONG>(required);
}
if (ProcessInformationLength < required) {
DEBUG_LOG("-> 0x%x\n", STATUS_INFO_LENGTH_MISMATCH);
return STATUS_INFO_LENGTH_MISMATCH;
}
auto *info = reinterpret_cast<PROCESS_BASIC_INFORMATION *>(ProcessInformation);
std::memset(info, 0, sizeof(*info));
info->ExitStatus = static_cast<NTSTATUS>(details.exitCode);
info->PebBaseAddress = details.peb;
DWORD_PTR processMask = 0;
DWORD_PTR systemMask = 0;
if (kernel32::GetProcessAffinityMask(ProcessHandle, &processMask, &systemMask)) {
info->AffinityMask = static_cast<ULONG_PTR>(processMask == 0 ? 1 : processMask);
} else {
info->AffinityMask = 1;
}
info->BasePriority = kDefaultBasePriority;
info->UniqueProcessId = static_cast<ULONG_PTR>(details.pid);
if (details.isCurrentProcess) {
info->InheritedFromUniqueProcessId = static_cast<ULONG_PTR>(getppid());
} else {
info->InheritedFromUniqueProcessId = static_cast<ULONG_PTR>(getpid());
}
DEBUG_LOG("-> 0x%x\n", STATUS_SUCCESS);
return STATUS_SUCCESS;
}
case ProcessWow64Information: {
size_t required = sizeof(ULONG_PTR);
if (ReturnLength) {
*ReturnLength = static_cast<ULONG>(required);
}
if (ProcessInformationLength < required) {
DEBUG_LOG("-> 0x%x\n", STATUS_INFO_LENGTH_MISMATCH);
return STATUS_INFO_LENGTH_MISMATCH;
}
auto *value = reinterpret_cast<ULONG_PTR *>(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<ULONG>(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<ULONG>(required);
}
if (ProcessInformationLength < required) {
DEBUG_LOG("-> 0x%x\n", STATUS_INFO_LENGTH_MISMATCH);
return STATUS_INFO_LENGTH_MISMATCH;
}
auto *unicode = reinterpret_cast<UNICODE_STRING *>(ProcessInformation);
auto *buffer = reinterpret_cast<uint16_t *>(reinterpret_cast<uint8_t *>(ProcessInformation) + sizeof(UNICODE_STRING));
std::memcpy(buffer, widePath.data(), stringBytes);
size_t characterCount = widePath.empty() ? 0 : widePath.size() - 1;
unicode->Length = static_cast<unsigned short>(characterCount * sizeof(uint16_t));
unicode->MaximumLength = static_cast<unsigned short>(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;
}

View File

@ -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)

View File

@ -1,7 +1,10 @@
#include "common.h"
#include "errors.h"
#include <algorithm>
#include <cstring>
#include <errno.h>
#include <memory>
#include <strings.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>
@ -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) {

138
main.cpp
View File

@ -5,10 +5,13 @@
#include <asm/ldt.h>
#include <charconv>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <filesystem>
#include <memory>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <system_error>
@ -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<TIB *>(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<char *>(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<unsigned int>(tibPtr);
desc.limit = static_cast<unsigned int>(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<int>(desc.entry_number);
tibSelector = static_cast<uint16_t>((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<uint16_t>((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

View File

@ -8,6 +8,7 @@
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <mutex>
#include <optional>
@ -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 &reg, 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<DllMainFunc>(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<HMODULE>(info.executable->imageBase), callReason, nullptr);
using DllMainFunc = BOOL(WIN_FUNC *)(HMODULE, DWORD, LPVOID);
auto dllMain = reinterpret_cast<DllMainFunc>(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<HMODULE>(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<HMODULE>(info.executable->imageBase), callReason, callReserved,
info.normalizedName.c_str());
BOOL result = TRUE;
if (!wibo::tibSelector) {
result = dllMain(reinterpret_cast<HMODULE>(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<HMODULE>(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 &reg, const std::string &requestedName,
@ -473,6 +518,25 @@ wibo::ModuleInfo *moduleFromAddress(ModuleRegistry &reg, 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<LPVOID>(1));
}
}
reg->modulesByKey.clear();
@ -612,7 +676,23 @@ ModuleInfo *moduleInfoFromHandle(HMODULE module) {
if (isMainModule(module)) {
return wibo::mainModule;
}
return static_cast<ModuleInfo *>(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<wibo::ModuleInfo *> 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<wibo::ModuleInfo *> 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);

10
test/dll_attach_failure.c Normal file
View File

@ -0,0 +1,10 @@
#include <windows.h>
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
(void)hinstDLL;
(void)lpReserved;
if (fdwReason == DLL_PROCESS_ATTACH) {
return FALSE;
}
return TRUE;
}

View File

@ -0,0 +1,17 @@
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#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;
}

144
test/test_ntquery.c Normal file
View File

@ -0,0 +1,144 @@
#include <windows.h>
#include <winternl.h>
#include <wchar.h>
#include <string.h>
#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;
}

80
test/test_rtl.c Normal file
View File

@ -0,0 +1,80 @@
#include <windows.h>
#include <string.h>
#include <winternl.h>
#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;
}

91
test/test_sysdir.c Normal file
View File

@ -0,0 +1,91 @@
#include <windows.h>
#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;
}

View File

@ -0,0 +1,87 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#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;
}

View File

@ -0,0 +1,45 @@
#include <windows.h>
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);
}