mirror of
https://github.com/decompals/wibo.git
synced 2025-10-15 14:45:12 +00:00
Rework thread TIB handling & DLL initialization handling
This commit is contained in:
parent
8330f27479
commit
d8150e33b9
@ -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.
|
||||
|
110
CMakeLists.txt
110
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 $<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()
|
||||
|
52
common.h
52
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<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;
|
||||
|
@ -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)
|
||||
|
@ -19,6 +19,7 @@ struct ThreadObject {
|
||||
pthread_mutex_t mutex{};
|
||||
pthread_cond_t cond{};
|
||||
unsigned int suspendCount = 0;
|
||||
TIB *tib = nullptr;
|
||||
};
|
||||
|
||||
struct MutexObject {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
231
dll/ntdll.cpp
231
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 <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;
|
||||
}
|
||||
|
||||
|
3
errors.h
3
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)
|
||||
|
11
loader.cpp
11
loader.cpp
@ -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
138
main.cpp
@ -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
|
||||
|
@ -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 ®, 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 ®, 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<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
10
test/dll_attach_failure.c
Normal 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;
|
||||
}
|
17
test/test_dll_attach_failure.c
Normal file
17
test/test_dll_attach_failure.c
Normal 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
144
test/test_ntquery.c
Normal 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
80
test/test_rtl.c
Normal 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
91
test/test_sysdir.c
Normal 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;
|
||||
}
|
87
test/test_thread_notifications.c
Normal file
87
test/test_thread_notifications.c
Normal 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;
|
||||
}
|
45
test/thread_notifications.c
Normal file
45
test/thread_notifications.c
Normal 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);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user