diff --git a/CMakeLists.txt b/CMakeLists.txt index 965e7f5..b705ccc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -126,12 +126,24 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h ${WIBO_TEST_BIN_DIR}/test_resources_res.o) + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/test_threading.exe + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 + -I${CMAKE_CURRENT_SOURCE_DIR}/test + -o test_threading.exe + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_threading.c + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_threading.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}/test_external_dll.exe ${WIBO_TEST_BIN_DIR}/test_bcrypt.exe - ${WIBO_TEST_BIN_DIR}/test_resources.exe) + ${WIBO_TEST_BIN_DIR}/test_resources.exe + ${WIBO_TEST_BIN_DIR}/test_threading.exe) if(CMAKE_CONFIGURATION_TYPES) set(_wibo_fixture_build_command @@ -160,6 +172,12 @@ if(BUILD_TESTING) set_tests_properties(wibo.test_resources PROPERTIES WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} DEPENDS wibo.build_fixtures) + + add_test(NAME wibo.test_threading + COMMAND $ ${WIBO_TEST_BIN_DIR}/test_threading.exe) + set_tests_properties(wibo.test_threading PROPERTIES + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS wibo.build_fixtures) endif() endif() endif() diff --git a/common.h b/common.h index b132019..b119114 100644 --- a/common.h +++ b/common.h @@ -75,6 +75,8 @@ typedef unsigned char BYTE; #define ERROR_ALREADY_EXISTS 183 #define ERROR_NOT_OWNER 288 +#define STILL_ACTIVE 259 + #define INVALID_SET_FILE_POINTER ((DWORD)-1) #define INVALID_HANDLE_VALUE ((HANDLE)-1) diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index 73d2042..032f2fc 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include #include @@ -158,6 +160,28 @@ namespace kernel32 { return ret; } + static void maybeMarkExecutable(void *mem) { + if (!mem) { + return; + } + size_t usable = mi_usable_size(mem); + if (usable == 0) { + return; + } + long pageSize = sysconf(_SC_PAGESIZE); + if (pageSize <= 0) { + return; + } + uintptr_t start = reinterpret_cast(mem); + uintptr_t alignedStart = start & ~static_cast(pageSize - 1); + uintptr_t end = (start + usable + pageSize - 1) & ~static_cast(pageSize - 1); + size_t length = static_cast(end - alignedStart); + if (length == 0) { + return; + } + mprotect(reinterpret_cast(alignedStart), length, PROT_READ | PROT_WRITE | PROT_EXEC); + } + struct MutexObject { pthread_mutex_t mutex; bool ownerValid = false; @@ -170,6 +194,67 @@ namespace kernel32 { static std::mutex mutexRegistryLock; static std::unordered_map namedMutexes; + struct EventObject { + pthread_mutex_t mutex; + pthread_cond_t cond; + bool manualReset = false; + bool signaled = false; + std::u16string name; + int refCount = 1; + }; + + static std::mutex eventRegistryLock; + static std::unordered_map namedEvents; + + static void releaseEventObject(EventObject *obj) { + if (!obj) { + return; + } + std::lock_guard lock(eventRegistryLock); + obj->refCount--; + if (obj->refCount == 0) { + if (!obj->name.empty()) { + namedEvents.erase(obj->name); + } + pthread_cond_destroy(&obj->cond); + pthread_mutex_destroy(&obj->mutex); + delete obj; + } + } + + typedef DWORD (WIN_FUNC *LPTHREAD_START_ROUTINE)(LPVOID); + + struct ThreadObject { + pthread_t thread; + bool finished = false; + bool joined = false; + bool detached = false; + DWORD exitCode = 0; + int refCount = 1; + pthread_mutex_t mutex; + pthread_cond_t cond; + }; + + struct ThreadStartData { + LPTHREAD_START_ROUTINE startRoutine; + void *parameter; + ThreadObject *threadObject; + }; + + static void destroyThreadObject(ThreadObject *obj) { + if (!obj) { + return; + } + pthread_cond_destroy(&obj->cond); + pthread_mutex_destroy(&obj->mutex); + delete obj; + } + + static void releaseThreadObject(ThreadObject *obj); + static void *threadTrampoline(void *param); + static thread_local ThreadObject *currentThreadObject = nullptr; + static constexpr uintptr_t PSEUDO_CURRENT_THREAD_HANDLE_VALUE = 0x100007u; + static std::u16string makeMutexName(LPCWSTR name) { if (!name) { return std::u16string(); @@ -196,6 +281,82 @@ namespace kernel32 { } } + static void releaseThreadObject(ThreadObject *obj) { + if (!obj) { + return; + } + pthread_t thread = 0; + bool shouldDelete = false; + bool shouldDetach = false; + bool finished = false; + bool joined = false; + bool detached = false; + pthread_mutex_lock(&obj->mutex); + obj->refCount--; + finished = obj->finished; + joined = obj->joined; + detached = obj->detached; + thread = obj->thread; + if (obj->refCount == 0) { + if (finished) { + shouldDelete = true; + } else if (!detached) { + obj->detached = true; + shouldDetach = true; + detached = true; + } + } + pthread_mutex_unlock(&obj->mutex); + + if (shouldDetach) { + pthread_detach(thread); + } + + if (shouldDelete) { + if (!joined && !detached) { + pthread_join(thread, nullptr); + } + destroyThreadObject(obj); + } + } + + static void *threadTrampoline(void *param) { + ThreadStartData *data = static_cast(param); + ThreadObject *obj = data->threadObject; + LPTHREAD_START_ROUTINE startRoutine = data->startRoutine; + 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; + } + + currentThreadObject = obj; + DWORD result = startRoutine ? startRoutine(userParam) : 0; + pthread_mutex_lock(&obj->mutex); + obj->finished = true; + obj->exitCode = result; + pthread_cond_broadcast(&obj->cond); + bool shouldDelete = (obj->refCount == 0); + bool detached = obj->detached; + pthread_mutex_unlock(&obj->mutex); + 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"); + } + return nullptr; + } + static int doCompareString(const std::string &a, const std::string &b, unsigned int dwCmpFlags) { for (size_t i = 0; ; i++) { if (i == a.size()) { @@ -544,6 +705,47 @@ namespace kernel32 { wibo::lastError = ERROR_SUCCESS; return 0; } + case handles::TYPE_EVENT: { + EventObject *obj = reinterpret_cast(data.ptr); + if (dwMilliseconds != 0xffffffff) { + DEBUG_LOG("WaitForSingleObject: timeout for event not supported\n"); + wibo::lastError = ERROR_NOT_SUPPORTED; + return 0xFFFFFFFF; + } + pthread_mutex_lock(&obj->mutex); + while (!obj->signaled) { + pthread_cond_wait(&obj->cond, &obj->mutex); + } + if (!obj->manualReset) { + obj->signaled = false; + } + pthread_mutex_unlock(&obj->mutex); + wibo::lastError = ERROR_SUCCESS; + return 0; + } + case handles::TYPE_THREAD: { + ThreadObject *obj = reinterpret_cast(data.ptr); + if (dwMilliseconds != 0xffffffff) { + DEBUG_LOG("WaitForSingleObject: timeout for thread not supported\n"); + wibo::lastError = ERROR_NOT_SUPPORTED; + return 0xFFFFFFFF; + } + pthread_mutex_lock(&obj->mutex); + while (!obj->finished) { + pthread_cond_wait(&obj->cond, &obj->mutex); + } + bool needJoin = !obj->joined && !obj->detached; + pthread_t thread = obj->thread; + if (needJoin) { + obj->joined = true; + } + pthread_mutex_unlock(&obj->mutex); + if (needJoin) { + pthread_join(thread, nullptr); + } + wibo::lastError = ERROR_SUCCESS; + return 0; + } case handles::TYPE_MUTEX: { MutexObject *obj = reinterpret_cast(data.ptr); if (dwMilliseconds != 0xffffffff) { @@ -744,6 +946,74 @@ namespace kernel32 { return 0; } + constexpr uint32_t LMEM_MOVEABLE = 0x0002; + constexpr uint32_t LMEM_ZEROINIT = 0x0040; + + void *WIN_FUNC LocalAlloc(uint32_t uFlags, size_t uBytes) { + DEBUG_LOG("LocalAlloc(flags=%x, size=%zu)\n", uFlags, uBytes); + bool zero = (uFlags & LMEM_ZEROINIT) != 0; + if ((uFlags & LMEM_MOVEABLE) != 0) { + DEBUG_LOG(" ignoring LMEM_MOVEABLE\n"); + } + void *result = doAlloc(uBytes, zero); + if (!result) { + wibo::lastError = ERROR_NOT_SUPPORTED; + return nullptr; + } + DEBUG_LOG(" -> %p\n", result); + maybeMarkExecutable(result); + wibo::lastError = ERROR_SUCCESS; + return result; + } + + void *WIN_FUNC LocalFree(void *hMem) { + // Windows returns NULL on success. + free(hMem); + wibo::lastError = ERROR_SUCCESS; + return nullptr; + } + + void *WIN_FUNC LocalReAlloc(void *hMem, size_t uBytes, uint32_t uFlags) { + DEBUG_LOG("LocalReAlloc(%p, size=%zu, flags=%x)\n", hMem, uBytes, uFlags); + bool zero = (uFlags & LMEM_ZEROINIT) != 0; + if ((uFlags & LMEM_MOVEABLE) != 0) { + DEBUG_LOG(" ignoring LMEM_MOVEABLE\n"); + } + void *result = doRealloc(hMem, uBytes, zero); + if (!result && uBytes != 0) { + wibo::lastError = ERROR_NOT_SUPPORTED; + return nullptr; + } + DEBUG_LOG(" -> %p\n", result); + maybeMarkExecutable(result); + wibo::lastError = ERROR_SUCCESS; + return result; + } + + void *WIN_FUNC LocalHandle(void *hMem) { + return hMem; + } + + void *WIN_FUNC LocalLock(void *hMem) { + wibo::lastError = ERROR_SUCCESS; + return hMem; + } + + unsigned int WIN_FUNC LocalUnlock(void *hMem) { + (void)hMem; + wibo::lastError = ERROR_SUCCESS; + return 1; + } + + size_t WIN_FUNC LocalSize(void *hMem) { + return hMem ? mi_usable_size(hMem) : 0; + } + + unsigned int WIN_FUNC LocalFlags(void *hMem) { + (void)hMem; + return 0; + } + /* * Environment */ @@ -858,20 +1128,24 @@ namespace kernel32 { if (!(fp == stdin || fp == stdout || fp == stderr)) { fclose(fp); } - } else if (data.type == handles::TYPE_MAPPED) { - auto *mapping = reinterpret_cast(data.ptr); - if (mapping) { - mapping->closed = true; - tryReleaseMapping(mapping); - } - } else if (data.type == handles::TYPE_PROCESS) { - delete (processes::Process*) data.ptr; - } else if (data.type == handles::TYPE_TOKEN) { - advapi32::releaseToken(data.ptr); - } else if (data.type == handles::TYPE_MUTEX) { - releaseMutexObject(reinterpret_cast(data.ptr)); - } - return TRUE; + } else if (data.type == handles::TYPE_MAPPED) { + auto *mapping = reinterpret_cast(data.ptr); + if (mapping) { + mapping->closed = true; + tryReleaseMapping(mapping); + } + } else if (data.type == handles::TYPE_PROCESS) { + delete (processes::Process*) data.ptr; + } else if (data.type == handles::TYPE_TOKEN) { + advapi32::releaseToken(data.ptr); + } else if (data.type == handles::TYPE_MUTEX) { + releaseMutexObject(reinterpret_cast(data.ptr)); + } else if (data.type == handles::TYPE_EVENT) { + releaseEventObject(reinterpret_cast(data.ptr)); + } else if (data.type == handles::TYPE_THREAD) { + releaseThreadObject(reinterpret_cast(data.ptr)); + } + return TRUE; } DWORD WIN_FUNC GetFullPathNameA(LPCSTR lpFileName, DWORD nBufferLength, LPSTR lpBuffer, LPSTR *lpFilePart) { @@ -1064,6 +1338,10 @@ namespace kernel32 { return fileTimeFromDuration(total); } + static uint64_t fileTimeToDuration(const FILETIME &value) { + return (static_cast(value.dwHighDateTime) << 32) | value.dwLowDateTime; + } + template struct WIN32_FIND_DATA { uint32_t dwFileAttributes; @@ -1680,8 +1958,53 @@ namespace kernel32 { return DeleteFileA(name.c_str()); } + BOOL WIN_FUNC MoveFileA(const char *lpExistingFileName, const char *lpNewFileName) { + DEBUG_LOG("MoveFileA(%s, %s)\n", + lpExistingFileName ? lpExistingFileName : "(null)", + lpNewFileName ? lpNewFileName : "(null)"); + if (!lpExistingFileName || !lpNewFileName) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + auto fromPath = files::pathFromWindows(lpExistingFileName); + auto toPath = files::pathFromWindows(lpNewFileName); + std::error_code ec; + if (std::filesystem::exists(toPath, ec)) { + wibo::lastError = ERROR_ALREADY_EXISTS; + return FALSE; + } + if (ec) { + errno = ec.value(); + setLastErrorFromErrno(); + return FALSE; + } + std::filesystem::rename(fromPath, toPath, ec); + if (ec) { + errno = ec.value(); + setLastErrorFromErrno(); + return FALSE; + } + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + BOOL WIN_FUNC MoveFileW(const uint16_t *lpExistingFileName, const uint16_t *lpNewFileName) { + DEBUG_LOG("MoveFileW\n"); + if (!lpExistingFileName || !lpNewFileName) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + std::string from = wideStringToString(lpExistingFileName); + std::string to = wideStringToString(lpNewFileName); + return MoveFileA(from.c_str(), to.c_str()); + } + DWORD WIN_FUNC SetFilePointer(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod) { DEBUG_LOG("SetFilePointer(%p, %d, %d)\n", hFile, lDistanceToMove, dwMoveMethod); + if (hFile == nullptr) { + wibo::lastError = ERROR_INVALID_HANDLE; + return INVALID_SET_FILE_POINTER; + } assert(!lpDistanceToMoveHigh || *lpDistanceToMoveHigh == 0); FILE *fp = files::fpFromHandle(hFile); wibo::lastError = ERROR_SUCCESS; @@ -1702,6 +2025,10 @@ namespace kernel32 { BOOL WIN_FUNC SetFilePointerEx(HANDLE hFile, LARGE_INTEGER lDistanceToMove, PLARGE_INTEGER lpDistanceToMoveHigh, DWORD dwMoveMethod) { + if (hFile == nullptr) { + wibo::lastError = ERROR_INVALID_HANDLE; + return 0; + } assert(!lpDistanceToMoveHigh || *lpDistanceToMoveHigh == 0); DEBUG_LOG("SetFilePointerEx(%p, %ld, %d)\n", hFile, lDistanceToMove, dwMoveMethod); FILE *fp = files::fpFromHandle(hFile); @@ -1856,6 +2183,81 @@ namespace kernel32 { return 1; } + int WIN_FUNC LocalFileTimeToFileTime(const FILETIME *lpLocalFileTime, FILETIME *lpFileTime) { + DEBUG_LOG("LocalFileTimeToFileTime\n"); + if (!lpLocalFileTime || !lpFileTime) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + *lpFileTime = *lpLocalFileTime; + wibo::lastError = ERROR_SUCCESS; + return 1; + } + + int WIN_FUNC DosDateTimeToFileTime(WORD wFatDate, WORD wFatTime, FILETIME *lpFileTime) { + DEBUG_LOG("DosDateTimeToFileTime(date=%04x, time=%04x)\n", wFatDate, wFatTime); + if (!lpFileTime) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + unsigned day = wFatDate & 0x1F; + unsigned month = (wFatDate >> 5) & 0x0F; + unsigned year = ((wFatDate >> 9) & 0x7F) + 1980; + unsigned second = (wFatTime & 0x1F) * 2; + unsigned minute = (wFatTime >> 5) & 0x3F; + unsigned hour = (wFatTime >> 11) & 0x1F; + if (day == 0 || month == 0 || month > 12 || day > 31 || hour > 23 || minute > 59 || second > 59) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + struct tm tmValue {}; + tmValue.tm_year = static_cast(year) - 1900; + tmValue.tm_mon = static_cast(month) - 1; + tmValue.tm_mday = static_cast(day); + tmValue.tm_hour = static_cast(hour); + tmValue.tm_min = static_cast(minute); + tmValue.tm_sec = static_cast(second); + tmValue.tm_isdst = -1; + time_t localSeconds = mktime(&tmValue); + if (localSeconds == (time_t)-1) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + uint64_t ticks = (static_cast(localSeconds) + 11644473600ULL) * 10000000ULL; + lpFileTime->dwLowDateTime = static_cast(ticks & 0xFFFFFFFFULL); + lpFileTime->dwHighDateTime = static_cast(ticks >> 32); + wibo::lastError = ERROR_SUCCESS; + return 1; + } + + int WIN_FUNC FileTimeToDosDateTime(const FILETIME *lpFileTime, WORD *lpFatDate, WORD *lpFatTime) { + DEBUG_LOG("FileTimeToDosDateTime\n"); + if (!lpFileTime || !lpFatDate || !lpFatTime) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + uint64_t ticks = fileTimeToDuration(*lpFileTime); + if (ticks < UNIX_TIME_ZERO) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + time_t utcSeconds = static_cast((ticks / 10000000ULL) - 11644473600ULL); + struct tm tmValue {}; + if (!localtime_r(&utcSeconds, &tmValue)) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + int year = tmValue.tm_year + 1900; + if (year < 1980 || year > 2107) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + *lpFatDate = static_cast(((year - 1980) << 9) | ((tmValue.tm_mon + 1) << 5) | tmValue.tm_mday); + *lpFatTime = static_cast(((tmValue.tm_hour & 0x1F) << 11) | ((tmValue.tm_min & 0x3F) << 5) | ((tmValue.tm_sec / 2) & 0x1F)); + wibo::lastError = ERROR_SUCCESS; + return 1; + } + struct BY_HANDLE_FILE_INFORMATION { unsigned long dwFileAttributes; FILETIME ftCreationTime; @@ -1928,6 +2330,14 @@ namespace kernel32 { return 1; } + int WIN_FUNC SetConsoleMode(void *hConsoleHandle, unsigned int dwMode) { + DEBUG_LOG("STUB: SetConsoleMode(%p, 0x%x)\n", hConsoleHandle, dwMode); + (void)hConsoleHandle; + (void)dwMode; + wibo::lastError = ERROR_SUCCESS; + return 1; + } + unsigned int WIN_FUNC GetConsoleOutputCP(){ DEBUG_LOG("GetConsoleOutputCP\n"); return 65001; // UTF-8 @@ -1985,6 +2395,30 @@ namespace kernel32 { return FALSE; } + int WIN_FUNC PeekConsoleInputA(void *hConsoleInput, void *lpBuffer, DWORD nLength, LPDWORD lpNumberOfEventsRead) { + DEBUG_LOG("STUB: PeekConsoleInputA(%p, %p, %u)\n", hConsoleInput, lpBuffer, nLength); + (void)hConsoleInput; + (void)lpBuffer; + (void)nLength; + if (lpNumberOfEventsRead) { + *lpNumberOfEventsRead = 0; + } + wibo::lastError = ERROR_SUCCESS; + return 1; + } + + int WIN_FUNC ReadConsoleInputA(void *hConsoleInput, void *lpBuffer, DWORD nLength, LPDWORD lpNumberOfEventsRead) { + DEBUG_LOG("STUB: ReadConsoleInputA(%p, %p, %u)\n", hConsoleInput, lpBuffer, nLength); + (void)hConsoleInput; + (void)lpBuffer; + (void)nLength; + if (lpNumberOfEventsRead) { + *lpNumberOfEventsRead = 0; + } + wibo::lastError = ERROR_SUCCESS; + return 1; + } + unsigned int WIN_FUNC GetSystemDirectoryA(char *lpBuffer, unsigned int uSize) { DEBUG_LOG("GetSystemDirectoryA(%p, %u)\n", lpBuffer, uSize); if (lpBuffer == nullptr) { @@ -2055,6 +2489,34 @@ namespace kernel32 { return path.size(); } + int WIN_FUNC SetCurrentDirectoryA(const char *lpPathName) { + DEBUG_LOG("SetCurrentDirectoryA(%s)\n", lpPathName ? lpPathName : "(null)"); + if (!lpPathName) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + auto hostPath = files::pathFromWindows(lpPathName); + std::error_code ec; + std::filesystem::current_path(hostPath, ec); + if (ec) { + errno = ec.value(); + setLastErrorFromErrno(); + return 0; + } + wibo::lastError = ERROR_SUCCESS; + return 1; + } + + int WIN_FUNC SetCurrentDirectoryW(const uint16_t *lpPathName) { + DEBUG_LOG("SetCurrentDirectoryW\n"); + if (!lpPathName) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + std::string path = wideStringToString(lpPathName); + return SetCurrentDirectoryA(path.c_str()); + } + HMODULE WIN_FUNC GetModuleHandleA(LPCSTR lpModuleName) { DEBUG_LOG("GetModuleHandleA(%s)\n", lpModuleName); @@ -2546,7 +3008,7 @@ namespace kernel32 { HANDLE WIN_FUNC GetCurrentThread() { DEBUG_LOG("STUB: GetCurrentThread\n"); - return (HANDLE)0x100007; + return reinterpret_cast(PSEUDO_CURRENT_THREAD_HANDLE_VALUE); } HRESULT WIN_FUNC SetThreadDescription(HANDLE hThread, const void * /* PCWSTR */ lpThreadDescription) { @@ -2554,6 +3016,138 @@ namespace kernel32 { return S_OK; } + HANDLE WIN_FUNC CreateThread(void *lpThreadAttributes, size_t dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, void *lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId) { + DEBUG_LOG("CreateThread(stack=%zu, flags=0x%x)\n", dwStackSize, dwCreationFlags); + (void)lpThreadAttributes; + constexpr DWORD SUPPORTED_FLAGS = 0x00010000; // STACK_SIZE_PARAM_IS_A_RESERVATION + if ((dwCreationFlags & ~SUPPORTED_FLAGS) != 0) { + DEBUG_LOG("CreateThread: unsupported creation flags 0x%x\n", dwCreationFlags); + wibo::lastError = ERROR_NOT_SUPPORTED; + return nullptr; + } + + ThreadObject *obj = new ThreadObject(); + pthread_mutex_init(&obj->mutex, nullptr); + pthread_cond_init(&obj->cond, nullptr); + obj->finished = false; + obj->joined = false; + obj->detached = false; + obj->exitCode = 0; + obj->refCount = 1; + + ThreadStartData *startData = new ThreadStartData{lpStartAddress, lpParameter, obj}; + + pthread_attr_t attr; + pthread_attr_t *attrPtr = nullptr; + if (dwStackSize != 0) { + pthread_attr_init(&attr); + size_t stackSize = dwStackSize; +#ifdef PTHREAD_STACK_MIN + if (stackSize < static_cast(PTHREAD_STACK_MIN)) { + stackSize = PTHREAD_STACK_MIN; + } +#endif + if (pthread_attr_setstacksize(&attr, stackSize) == 0) { + attrPtr = &attr; + } else { + pthread_attr_destroy(&attr); + } + } + + int rc = pthread_create(&obj->thread, attrPtr, threadTrampoline, startData); + if (attrPtr) { + pthread_attr_destroy(attrPtr); + } + if (rc != 0) { + delete startData; + destroyThreadObject(obj); + errno = rc; + setLastErrorFromErrno(); + return nullptr; + } + + if (lpThreadId) { + std::size_t hashed = std::hash{}(obj->thread); + *lpThreadId = static_cast(hashed & 0xffffffffu); + } + + wibo::lastError = ERROR_SUCCESS; + return handles::allocDataHandle({handles::TYPE_THREAD, obj, 0}); + } + + void WIN_FUNC ExitThread(DWORD dwExitCode) { + DEBUG_LOG("ExitThread(%u)\n", dwExitCode); + ThreadObject *obj = currentThreadObject; + 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; + } + 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; + pthread_mutex_unlock(&obj->mutex); + 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); + } + + BOOL WIN_FUNC GetExitCodeThread(HANDLE hThread, LPDWORD lpExitCode) { + DEBUG_LOG("GetExitCodeThread(%p, %p)\n", hThread, lpExitCode); + if (!lpExitCode) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + if (reinterpret_cast(hThread) == PSEUDO_CURRENT_THREAD_HANDLE_VALUE) { + ThreadObject *obj = currentThreadObject; + if (obj) { + pthread_mutex_lock(&obj->mutex); + DWORD code = obj->finished ? obj->exitCode : STILL_ACTIVE; + pthread_mutex_unlock(&obj->mutex); + *lpExitCode = code; + } else { + *lpExitCode = STILL_ACTIVE; + } + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + auto data = handles::dataFromHandle(hThread, false); + if (data.type != handles::TYPE_THREAD) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + ThreadObject *obj = reinterpret_cast(data.ptr); + pthread_mutex_lock(&obj->mutex); + DWORD code = obj->finished ? obj->exitCode : STILL_ACTIVE; + pthread_mutex_unlock(&obj->mutex); + *lpExitCode = code; + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + HANDLE WIN_FUNC CreateMutexW(void *lpMutexAttributes, BOOL bInitialOwner, LPCWSTR lpName); + + HANDLE WIN_FUNC CreateMutexA(void *lpMutexAttributes, BOOL bInitialOwner, LPCSTR lpName) { + std::vector wideName; + if (lpName) { + wideName = stringToWideString(lpName); + } + return CreateMutexW(lpMutexAttributes, bInitialOwner, lpName ? reinterpret_cast(wideName.data()) : nullptr); + } + HANDLE WIN_FUNC CreateMutexW(void *lpMutexAttributes, BOOL bInitialOwner, LPCWSTR lpName) { std::string nameLog; if (lpName) { @@ -2630,6 +3224,90 @@ namespace kernel32 { return TRUE; } + HANDLE WIN_FUNC CreateEventW(void *lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCWSTR lpName) { + std::string nameLog; + if (lpName) { + nameLog = wideStringToString(reinterpret_cast(lpName)); + } else { + nameLog = ""; + } + DEBUG_LOG("CreateEventW(name=%s, manualReset=%d, initialState=%d)\n", nameLog.c_str(), bManualReset, bInitialState); + (void)lpEventAttributes; + + std::u16string name = makeMutexName(lpName); + EventObject *obj = nullptr; + bool alreadyExists = false; + { + std::lock_guard lock(eventRegistryLock); + if (!name.empty()) { + auto it = namedEvents.find(name); + if (it != namedEvents.end()) { + obj = it->second; + obj->refCount++; + alreadyExists = true; + } + } + if (!obj) { + obj = new EventObject(); + pthread_mutex_init(&obj->mutex, nullptr); + pthread_cond_init(&obj->cond, nullptr); + obj->manualReset = bManualReset; + obj->signaled = bInitialState; + obj->name = name; + obj->refCount = 1; + if (!name.empty()) { + namedEvents[name] = obj; + } + } + } + HANDLE handle = handles::allocDataHandle({handles::TYPE_EVENT, obj, 0}); + wibo::lastError = alreadyExists ? ERROR_ALREADY_EXISTS : ERROR_SUCCESS; + return handle; + } + + HANDLE WIN_FUNC CreateEventA(void *lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCSTR lpName) { + std::vector wideName; + if (lpName) { + wideName = stringToWideString(lpName); + } + return CreateEventW(lpEventAttributes, bManualReset, bInitialState, lpName ? reinterpret_cast(wideName.data()) : nullptr); + } + + BOOL WIN_FUNC SetEvent(HANDLE hEvent) { + DEBUG_LOG("SetEvent(%p)\n", hEvent); + auto data = handles::dataFromHandle(hEvent, false); + if (data.type != handles::TYPE_EVENT) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + EventObject *obj = reinterpret_cast(data.ptr); + pthread_mutex_lock(&obj->mutex); + obj->signaled = true; + if (obj->manualReset) { + pthread_cond_broadcast(&obj->cond); + } else { + pthread_cond_signal(&obj->cond); + } + pthread_mutex_unlock(&obj->mutex); + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + BOOL WIN_FUNC ResetEvent(HANDLE hEvent) { + DEBUG_LOG("ResetEvent(%p)\n", hEvent); + auto data = handles::dataFromHandle(hEvent, false); + if (data.type != handles::TYPE_EVENT) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + EventObject *obj = reinterpret_cast(data.ptr); + pthread_mutex_lock(&obj->mutex); + obj->signaled = false; + pthread_mutex_unlock(&obj->mutex); + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + BOOL WIN_FUNC GetThreadTimes(HANDLE hThread, FILETIME *lpCreationTime, FILETIME *lpExitTime, @@ -3463,6 +4141,9 @@ static void *resolveByName(const char *name) { if (strcmp(name, "GetExitCodeProcess") == 0) return (void *) kernel32::GetExitCodeProcess; if (strcmp(name, "CreateProcessW") == 0) return (void *) kernel32::CreateProcessW; if (strcmp(name, "CreateProcessA") == 0) return (void *) kernel32::CreateProcessA; + if (strcmp(name, "CreateThread") == 0) return (void *) kernel32::CreateThread; + if (strcmp(name, "ExitThread") == 0) return (void *) kernel32::ExitThread; + if (strcmp(name, "GetExitCodeThread") == 0) return (void *) kernel32::GetExitCodeThread; if (strcmp(name, "TlsAlloc") == 0) return (void *) kernel32::TlsAlloc; if (strcmp(name, "TlsFree") == 0) return (void *) kernel32::TlsFree; if (strcmp(name, "TlsGetValue") == 0) return (void *) kernel32::TlsGetValue; @@ -3507,7 +4188,12 @@ static void *resolveByName(const char *name) { if (strcmp(name, "ReleaseSRWLockExclusive") == 0) return (void *) kernel32::ReleaseSRWLockExclusive; if (strcmp(name, "TryAcquireSRWLockExclusive") == 0) return (void *) kernel32::TryAcquireSRWLockExclusive; if (strcmp(name, "WaitForSingleObject") == 0) return (void *) kernel32::WaitForSingleObject; + if (strcmp(name, "CreateMutexA") == 0) return (void *) kernel32::CreateMutexA; if (strcmp(name, "CreateMutexW") == 0) return (void *) kernel32::CreateMutexW; + if (strcmp(name, "CreateEventA") == 0) return (void *) kernel32::CreateEventA; + if (strcmp(name, "CreateEventW") == 0) return (void *) kernel32::CreateEventW; + if (strcmp(name, "SetEvent") == 0) return (void *) kernel32::SetEvent; + if (strcmp(name, "ResetEvent") == 0) return (void *) kernel32::ResetEvent; if (strcmp(name, "ReleaseMutex") == 0) return (void *) kernel32::ReleaseMutex; // winbase.h @@ -3515,8 +4201,18 @@ static void *resolveByName(const char *name) { if (strcmp(name, "GlobalReAlloc") == 0) return (void *) kernel32::GlobalReAlloc; if (strcmp(name, "GlobalFree") == 0) return (void *) kernel32::GlobalFree; if (strcmp(name, "GlobalFlags") == 0) return (void *) kernel32::GlobalFlags; + if (strcmp(name, "LocalAlloc") == 0) return (void *) kernel32::LocalAlloc; + if (strcmp(name, "LocalReAlloc") == 0) return (void *) kernel32::LocalReAlloc; + if (strcmp(name, "LocalFree") == 0) return (void *) kernel32::LocalFree; + if (strcmp(name, "LocalHandle") == 0) return (void *) kernel32::LocalHandle; + if (strcmp(name, "LocalLock") == 0) return (void *) kernel32::LocalLock; + if (strcmp(name, "LocalUnlock") == 0) return (void *) kernel32::LocalUnlock; + if (strcmp(name, "LocalSize") == 0) return (void *) kernel32::LocalSize; + if (strcmp(name, "LocalFlags") == 0) return (void *) kernel32::LocalFlags; if (strcmp(name, "GetCurrentDirectoryA") == 0) return (void *) kernel32::GetCurrentDirectoryA; if (strcmp(name, "GetCurrentDirectoryW") == 0) return (void *) kernel32::GetCurrentDirectoryW; + if (strcmp(name, "SetCurrentDirectoryA") == 0) return (void *) kernel32::SetCurrentDirectoryA; + if (strcmp(name, "SetCurrentDirectoryW") == 0) return (void *) kernel32::SetCurrentDirectoryW; if (strcmp(name, "FindResourceA") == 0) return (void *) kernel32::FindResourceA; if (strcmp(name, "FindResourceExA") == 0) return (void *) kernel32::FindResourceExA; if (strcmp(name, "FindResourceW") == 0) return (void *) kernel32::FindResourceW; @@ -3550,10 +4246,13 @@ static void *resolveByName(const char *name) { if (strcmp(name, "DuplicateHandle") == 0) return (void *) kernel32::DuplicateHandle; if (strcmp(name, "CloseHandle") == 0) return (void *) kernel32::CloseHandle; if (strcmp(name, "GetConsoleMode") == 0) return (void *) kernel32::GetConsoleMode; + if (strcmp(name, "SetConsoleMode") == 0) return (void *) kernel32::SetConsoleMode; if (strcmp(name, "SetConsoleCtrlHandler") == 0) return (void *) kernel32::SetConsoleCtrlHandler; if (strcmp(name, "GetConsoleScreenBufferInfo") == 0) return (void *) kernel32::GetConsoleScreenBufferInfo; if (strcmp(name, "WriteConsoleW") == 0) return (void *) kernel32::WriteConsoleW; if (strcmp(name, "GetConsoleOutputCP") == 0) return (void *) kernel32::GetConsoleOutputCP; + if (strcmp(name, "PeekConsoleInputA") == 0) return (void *) kernel32::PeekConsoleInputA; + if (strcmp(name, "ReadConsoleInputA") == 0) return (void *) kernel32::ReadConsoleInputA; // fileapi.h if (strcmp(name, "GetFullPathNameA") == 0) return (void *) kernel32::GetFullPathNameA; @@ -3577,6 +4276,8 @@ static void *resolveByName(const char *name) { if (strcmp(name, "UnmapViewOfFile") == 0) return (void *) kernel32::UnmapViewOfFile; if (strcmp(name, "DeleteFileA") == 0) return (void *) kernel32::DeleteFileA; if (strcmp(name, "DeleteFileW") == 0) return (void *) kernel32::DeleteFileW; + if (strcmp(name, "MoveFileA") == 0) return (void *) kernel32::MoveFileA; + if (strcmp(name, "MoveFileW") == 0) return (void *) kernel32::MoveFileW; if (strcmp(name, "SetFilePointer") == 0) return (void *) kernel32::SetFilePointer; if (strcmp(name, "SetFilePointerEx") == 0) return (void *) kernel32::SetFilePointerEx; if (strcmp(name, "SetEndOfFile") == 0) return (void *) kernel32::SetEndOfFile; @@ -3588,6 +4289,9 @@ static void *resolveByName(const char *name) { if (strcmp(name, "SetFileTime") == 0) return (void *) kernel32::SetFileTime; if (strcmp(name, "GetFileType") == 0) return (void *) kernel32::GetFileType; if (strcmp(name, "FileTimeToLocalFileTime") == 0) return (void *) kernel32::FileTimeToLocalFileTime; + if (strcmp(name, "LocalFileTimeToFileTime") == 0) return (void *) kernel32::LocalFileTimeToFileTime; + if (strcmp(name, "DosDateTimeToFileTime") == 0) return (void *) kernel32::DosDateTimeToFileTime; + if (strcmp(name, "FileTimeToDosDateTime") == 0) return (void *) kernel32::FileTimeToDosDateTime; if (strcmp(name, "GetFileInformationByHandle") == 0) return (void *) kernel32::GetFileInformationByHandle; if (strcmp(name, "GetTempFileNameA") == 0) return (void *) kernel32::GetTempFileNameA; if (strcmp(name, "GetTempPathA") == 0) return (void *) kernel32::GetTempPathA; diff --git a/handles.h b/handles.h index cfe0f38..1ddc2a2 100644 --- a/handles.h +++ b/handles.h @@ -9,7 +9,9 @@ namespace handles { TYPE_MAPPED, TYPE_PROCESS, TYPE_TOKEN, - TYPE_MUTEX + TYPE_MUTEX, + TYPE_EVENT, + TYPE_THREAD }; struct Data { diff --git a/main.cpp b/main.cpp index c50b286..af75cbe 100644 --- a/main.cpp +++ b/main.cpp @@ -309,7 +309,7 @@ int main(int argc, char **argv) { cmdLine += ' '; arg = guestArgv[i]; } - bool needQuotes = arg.find_first_of("\\\" \t\n") != std::string::npos; + bool needQuotes = arg.find_first_of("\" \t\n") != std::string::npos; if (needQuotes) cmdLine += '"'; int backslashes = 0; diff --git a/test/test_threading.c b/test/test_threading.c new file mode 100644 index 0000000..b64a2b1 --- /dev/null +++ b/test/test_threading.c @@ -0,0 +1,102 @@ +#include +#include +#include "test_assert.h" + +typedef struct { + HANDLE readyEvent; + HANDLE goEvent; + DWORD exitCode; +} WorkerContext; + +static DWORD WINAPI worker_main(LPVOID param) { + WorkerContext *ctx = (WorkerContext *)param; + TEST_CHECK(SetEvent(ctx->readyEvent)); + DWORD waitResult = WaitForSingleObject(ctx->goEvent, INFINITE); + TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); + return ctx->exitCode; +} + +static DWORD WINAPI exit_thread_worker(LPVOID param) { + DWORD code = *(DWORD *)param; + ExitThread(code); + return 0; /* unreachable */ +} + +int main(void) { + HANDLE readyEvent = CreateEventA(NULL, TRUE, FALSE, NULL); + TEST_CHECK(readyEvent != NULL); + + HANDLE goEvent = CreateEventA(NULL, FALSE, FALSE, NULL); + TEST_CHECK(goEvent != NULL); + + WorkerContext ctx; + ctx.readyEvent = readyEvent; + ctx.goEvent = goEvent; + ctx.exitCode = 0x1234; + + HANDLE thread = CreateThread(NULL, 0, worker_main, &ctx, 0, NULL); + TEST_CHECK(thread != NULL); + + DWORD waitResult = WaitForSingleObject(readyEvent, INFINITE); + TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); + + TEST_CHECK(ResetEvent(readyEvent)); + + TEST_CHECK(SetEvent(goEvent)); + + waitResult = WaitForSingleObject(thread, INFINITE); + TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); + + DWORD exitCode = 0; + TEST_CHECK(GetExitCodeThread(thread, &exitCode)); + TEST_CHECK_EQ(ctx.exitCode, exitCode); + + TEST_CHECK(CloseHandle(thread)); + + HANDLE autoEvent = CreateEventA(NULL, FALSE, FALSE, NULL); + TEST_CHECK(autoEvent != NULL); + TEST_CHECK(SetEvent(autoEvent)); + waitResult = WaitForSingleObject(autoEvent, INFINITE); + TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); + TEST_CHECK(SetEvent(autoEvent)); + waitResult = WaitForSingleObject(autoEvent, INFINITE); + TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); + TEST_CHECK(CloseHandle(autoEvent)); + + HANDLE manualEvent = CreateEventA(NULL, TRUE, FALSE, NULL); + TEST_CHECK(manualEvent != NULL); + TEST_CHECK(SetEvent(manualEvent)); + waitResult = WaitForSingleObject(manualEvent, INFINITE); + TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); + TEST_CHECK(ResetEvent(manualEvent)); + TEST_CHECK(SetEvent(manualEvent)); + waitResult = WaitForSingleObject(manualEvent, INFINITE); + TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); + TEST_CHECK(CloseHandle(manualEvent)); + + DWORD selfExitCode = 0; + TEST_CHECK(GetExitCodeThread(GetCurrentThread(), &selfExitCode)); + TEST_CHECK_EQ(STILL_ACTIVE, selfExitCode); + + HANDLE mutex = CreateMutexA(NULL, FALSE, NULL); + TEST_CHECK(mutex != NULL); + waitResult = WaitForSingleObject(mutex, INFINITE); + TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); + TEST_CHECK(ReleaseMutex(mutex)); + TEST_CHECK(CloseHandle(mutex)); + + DWORD secondExitCode = 0x55AA; + HANDLE exitThread = CreateThread(NULL, 0, exit_thread_worker, &secondExitCode, 0, NULL); + TEST_CHECK(exitThread != NULL); + waitResult = WaitForSingleObject(exitThread, INFINITE); + TEST_CHECK_EQ(WAIT_OBJECT_0, waitResult); + exitCode = 0; + TEST_CHECK(GetExitCodeThread(exitThread, &exitCode)); + TEST_CHECK_EQ(secondExitCode, exitCode); + TEST_CHECK(CloseHandle(exitThread)); + + TEST_CHECK(CloseHandle(goEvent)); + TEST_CHECK(CloseHandle(readyEvent)); + + return EXIT_SUCCESS; +}