Skip to content
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ add_executable(wibo
dll/kernel32/stringapiset.cpp
dll/kernel32/synchapi.cpp
dll/kernel32/sysinfoapi.cpp
dll/kernel32/msvc_compat_stubs.cpp
dll/kernel32/timezoneapi.cpp
dll/kernel32/winbase.cpp
dll/kernel32/wincon.cpp
Expand All @@ -219,6 +220,7 @@ add_executable(wibo
dll/lmgr.cpp
dll/mscoree.cpp
dll/ntdll.cpp
dll/msvc_compat_ntdll.cpp
dll/ole32.cpp
dll/rpcrt4.cpp
dll/shlwapi.cpp
Expand Down Expand Up @@ -383,7 +385,7 @@ wibo_codegen_module(NAME vcruntime HEADERS dll/vcruntime.h)
wibo_codegen_module(NAME lmgr HEADERS dll/lmgr.h)
wibo_codegen_module(NAME ole32 HEADERS dll/ole32.h)
wibo_codegen_module(NAME user32 HEADERS dll/user32.h)
wibo_codegen_module(NAME ntdll HEADERS dll/ntdll.h)
wibo_codegen_module(NAME ntdll HEADERS dll/ntdll.h dll/msvc_compat_ntdll.h)
wibo_codegen_module(NAME kernel32 HEADERS
dll/kernel32/debugapi.h
dll/kernel32/errhandlingapi.h
Expand All @@ -402,6 +404,7 @@ wibo_codegen_module(NAME kernel32 HEADERS
dll/kernel32/stringapiset.h
dll/kernel32/synchapi.h
dll/kernel32/sysinfoapi.h
dll/kernel32/msvc_compat_stubs.h
dll/kernel32/timezoneapi.h
dll/kernel32/winbase.h
dll/kernel32/wincon.h
Expand Down
20 changes: 16 additions & 4 deletions dll/kernel32/fibersapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@
#include "errors.h"
#include "internal.h"

#include <mutex>

namespace {

// FLS without fibers is THREAD-local storage on
// Windows (every thread is exactly one fiber); only the index allocation map
// is process-wide. The previous process-global value array made all threads
// share one cell per index, which clobbers msvcr120's per-thread data (_ptd) --
// including the _beginthreadex entry/argument that _threadstartex re-reads
// through FlsGetValue -- so concurrently-starting threads could duplicate or
// swap their start arguments.
constexpr DWORD kMaxFlsValues = 0x100;
std::mutex g_flsMutex;
bool g_flsValuesUsed[kMaxFlsValues] = {false};
LPVOID g_flsValues[kMaxFlsValues] = {nullptr};
thread_local LPVOID t_flsValues[kMaxFlsValues] = {nullptr};

} // namespace

Expand All @@ -19,10 +29,11 @@ DWORD WINAPI FlsAlloc(PFLS_CALLBACK_FUNCTION lpCallback) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("FlsAlloc(%p)", lpCallback);
// If the function succeeds, the return value is an FLS index initialized to zero.
std::lock_guard lk(g_flsMutex);
for (DWORD i = 0; i < kMaxFlsValues; i++) {
if (g_flsValuesUsed[i] == false) {
g_flsValuesUsed[i] = true;
g_flsValues[i] = nullptr;
t_flsValues[i] = nullptr;
DEBUG_LOG(" -> %d\n", i);
return i;
}
Expand All @@ -35,6 +46,7 @@ DWORD WINAPI FlsAlloc(PFLS_CALLBACK_FUNCTION lpCallback) {
BOOL WINAPI FlsFree(DWORD dwFlsIndex) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("FlsFree(%u)\n", dwFlsIndex);
std::lock_guard lk(g_flsMutex);
if (dwFlsIndex < kMaxFlsValues && g_flsValuesUsed[dwFlsIndex]) {
g_flsValuesUsed[dwFlsIndex] = false;
return TRUE;
Expand All @@ -49,7 +61,7 @@ PVOID WINAPI FlsGetValue(DWORD dwFlsIndex) {
VERBOSE_LOG("FlsGetValue(%u)\n", dwFlsIndex);
PVOID result = nullptr;
if (dwFlsIndex < kMaxFlsValues && g_flsValuesUsed[dwFlsIndex]) {
result = g_flsValues[dwFlsIndex];
result = t_flsValues[dwFlsIndex];
// See https://learn.microsoft.com/en-us/windows/win32/api/fibersapi/nf-fibersapi-flsgetvalue
setLastError(ERROR_SUCCESS);
} else {
Expand All @@ -63,7 +75,7 @@ BOOL WINAPI FlsSetValue(DWORD dwFlsIndex, PVOID lpFlsData) {
HOST_CONTEXT_GUARD();
VERBOSE_LOG("FlsSetValue(%u, %p)\n", dwFlsIndex, lpFlsData);
if (dwFlsIndex < kMaxFlsValues && g_flsValuesUsed[dwFlsIndex]) {
g_flsValues[dwFlsIndex] = lpFlsData;
t_flsValues[dwFlsIndex] = lpFlsData;
return TRUE;
} else {
setLastError(ERROR_INVALID_PARAMETER);
Expand Down
45 changes: 45 additions & 0 deletions dll/kernel32/interlockedapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "context.h"

#include <cstring>
#include <mutex>

namespace kernel32 {

Expand Down Expand Up @@ -48,4 +49,48 @@ void WINAPI InitializeSListHead(PSLIST_HEADER ListHead) {
std::memset(ListHead, 0, sizeof(*ListHead));
}

// These are "Interlocked" ops on a shared list; mspdbcore's worker-thread pool
// pushes/pops the same SLIST concurrently, so they MUST be atomic. A global
// mutex serializes all SLIST operations (correct, if not strictly lock-free).
static std::mutex g_slistMutex;

PSLIST_ENTRY WINAPI InterlockedPushEntrySList(PSLIST_HEADER ListHead, PSLIST_ENTRY ListEntry) {
HOST_CONTEXT_GUARD();
if (!ListHead || !ListEntry) return nullptr;
std::lock_guard<std::mutex> lk(g_slistMutex);
GUEST_PTR prevHead = ListHead->Head;
ListEntry->Next = prevHead;
ListHead->Head = toGuestPtr(ListEntry);
ListHead->Depth++;
return fromGuestPtr<SLIST_ENTRY>(prevHead);
}

PSLIST_ENTRY WINAPI InterlockedPopEntrySList(PSLIST_HEADER ListHead) {
HOST_CONTEXT_GUARD();
if (!ListHead) return nullptr;
std::lock_guard<std::mutex> lk(g_slistMutex);
if (!ListHead->Head) return nullptr;
PSLIST_ENTRY entry = fromGuestPtr<SLIST_ENTRY>(ListHead->Head);
ListHead->Head = entry->Next;
if (ListHead->Depth) ListHead->Depth--;
return entry;
}

PSLIST_ENTRY WINAPI InterlockedFlushSList(PSLIST_HEADER ListHead) {
HOST_CONTEXT_GUARD();
if (!ListHead) return nullptr;
std::lock_guard<std::mutex> lk(g_slistMutex);
PSLIST_ENTRY first = fromGuestPtr<SLIST_ENTRY>(ListHead->Head);
ListHead->Head = GUEST_NULL;
ListHead->Depth = 0;
return first;
}

USHORT WINAPI QueryDepthSList(PSLIST_HEADER ListHead) {
HOST_CONTEXT_GUARD();
if (!ListHead) return 0;
std::lock_guard<std::mutex> lk(g_slistMutex);
return ListHead->Depth;
}

} // namespace kernel32
4 changes: 4 additions & 0 deletions dll/kernel32/interlockedapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,9 @@ LONG WINAPI InterlockedDecrement(LONG volatile *Addend);
LONG WINAPI InterlockedExchange(LONG volatile *Target, LONG Value);
LONG WINAPI InterlockedCompareExchange(LONG volatile *Destination, LONG Exchange, LONG Comperand);
void WINAPI InitializeSListHead(PSLIST_HEADER ListHead);
PSLIST_ENTRY WINAPI InterlockedPushEntrySList(PSLIST_HEADER ListHead, PSLIST_ENTRY ListEntry);
PSLIST_ENTRY WINAPI InterlockedPopEntrySList(PSLIST_HEADER ListHead);
PSLIST_ENTRY WINAPI InterlockedFlushSList(PSLIST_HEADER ListHead);
USHORT WINAPI QueryDepthSList(PSLIST_HEADER ListHead);

} // namespace kernel32
33 changes: 33 additions & 0 deletions dll/kernel32/libloaderapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,39 @@ HMODULE WINAPI GetModuleHandleW(LPCWSTR lpModuleName) {
return GetModuleHandleA(nullptr);
}

static BOOL getModuleHandleExImpl(DWORD dwFlags, const void *nameOrAddr, bool wide, HMODULE *phModule) {
constexpr DWORD GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 0x00000004;
wibo::ModuleInfo *info = nullptr;
if (dwFlags & GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS) {
info = wibo::moduleInfoFromAddress(const_cast<void *>(nameOrAddr));
} else if (nameOrAddr) {
std::string name = wide ? wideStringToString(static_cast<LPCWSTR>(nameOrAddr))
: std::string(static_cast<LPCSTR>(nameOrAddr));
info = wibo::findLoadedModule(name.c_str());
} else {
info = wibo::mainModule;
}
if (!info) {
if (phModule) *phModule = NO_HANDLE;
kernel32::setLastError(ERROR_MOD_NOT_FOUND);
return FALSE;
}
if (phModule) *phModule = info->handle;
return TRUE;
}

BOOL WINAPI GetModuleHandleExA(DWORD dwFlags, LPCSTR lpModuleName, HMODULE *phModule) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("GetModuleHandleExA(0x%x, %p, %p)\n", dwFlags, (const void *)lpModuleName, (void *)phModule);
return getModuleHandleExImpl(dwFlags, lpModuleName, false, phModule);
}

BOOL WINAPI GetModuleHandleExW(DWORD dwFlags, LPCWSTR lpModuleName, HMODULE *phModule) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("GetModuleHandleExW(0x%x, %p, %p)\n", dwFlags, (const void *)lpModuleName, (void *)phModule);
return getModuleHandleExImpl(dwFlags, lpModuleName, true, phModule);
}

DWORD WINAPI GetModuleFileNameA(HMODULE hModule, LPSTR lpFilename, DWORD nSize) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("GetModuleFileNameA(%p, %p, %u)\n", hModule, lpFilename, nSize);
Expand Down
2 changes: 2 additions & 0 deletions dll/kernel32/libloaderapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ namespace kernel32 {
BOOL WINAPI DisableThreadLibraryCalls(HMODULE hLibModule);
HMODULE WINAPI GetModuleHandleA(LPCSTR lpModuleName);
HMODULE WINAPI GetModuleHandleW(LPCWSTR lpModuleName);
BOOL WINAPI GetModuleHandleExA(DWORD dwFlags, LPCSTR lpModuleName, HMODULE *phModule);
BOOL WINAPI GetModuleHandleExW(DWORD dwFlags, LPCWSTR lpModuleName, HMODULE *phModule);
DWORD WINAPI GetModuleFileNameA(HMODULE hModule, LPSTR lpFilename, DWORD nSize);
DWORD WINAPI GetModuleFileNameW(HMODULE hModule, LPWSTR lpFilename, DWORD nSize);
HRSRC WINAPI FindResourceA(HMODULE hModule, LPCSTR lpName, LPCSTR lpType);
Expand Down
64 changes: 64 additions & 0 deletions dll/kernel32/msvc_compat_stubs.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#include "msvc_compat_stubs.h"
#include "common.h"
#include "context.h"
#include "winnls.h"
#include "synchapi.h"

namespace kernel32 {

ULONGLONG WINAPI GetEnabledXStateFeatures() {
HOST_CONTEXT_GUARD();
DEBUG_LOG("STUB: GetEnabledXStateFeatures()\n");
return 0;
}

BOOL WINAPI SetThreadPreferredUILanguages(DWORD dwFlags, void *langs, unsigned int *pnum) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("STUB: SetThreadPreferredUILanguages()\n");
(void)dwFlags; (void)langs; if (pnum) *pnum = 0; return TRUE;
}

BOOL WINAPI GetThreadPreferredUILanguages(DWORD dwFlags, unsigned int *pulNumLanguages, unsigned short *pwszBuf, unsigned int *pcchBuf) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("STUB: GetThreadPreferredUILanguages()\n");
(void)dwFlags;
if (pulNumLanguages) *pulNumLanguages = 0;
if (pcchBuf) {
if (pwszBuf && *pcchBuf >= 2) { pwszBuf[0] = 0; pwszBuf[1] = 0; }
*pcchBuf = 2;
}
return TRUE;
}

BOOL WINAPI IsValidLocaleName(void *lpLocaleName) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("STUB: IsValidLocaleName()\n");
(void)lpLocaleName; return TRUE;
}

VOID WINAPI InitializeSRWLock(void **SRWLock) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("STUB: InitializeSRWLock()\n");
if (SRWLock) *SRWLock = nullptr;
}

DWORD WINAPI WaitForSingleObjectEx(HANDLE hHandle, DWORD dwMilliseconds, int bAlertable) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("STUB: WaitForSingleObjectEx()\n");
(void)bAlertable; return WaitForSingleObject(hHandle, dwMilliseconds);
}

DWORD WINAPI WaitForMultipleObjectsEx(DWORD nCount, const HANDLE *lpHandles, int bWaitAll, DWORD dwMilliseconds, int bAlertable) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("STUB: WaitForMultipleObjectsEx()\n");
(void)bAlertable; return WaitForMultipleObjects(nCount, lpHandles, bWaitAll, dwMilliseconds);
}

int WINAPI CompareStringEx(void *lpLocaleName, DWORD dwCmpFlags, LPCWCH lpString1, int cchCount1, LPCWCH lpString2, int cchCount2, void *lpVersionInformation, void *lpReserved, void *lParam) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("STUB: CompareStringEx()\n");
(void)lpLocaleName; (void)lpVersionInformation; (void)lpReserved; (void)lParam;
return CompareStringW(0x0409, dwCmpFlags, lpString1, cchCount1, lpString2, cchCount2);
}

} // namespace kernel32
15 changes: 15 additions & 0 deletions dll/kernel32/msvc_compat_stubs.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once
#include "types.h"

namespace kernel32 {

ULONGLONG WINAPI GetEnabledXStateFeatures();
BOOL WINAPI SetThreadPreferredUILanguages(DWORD dwFlags, void *langs, unsigned int *pnum);
BOOL WINAPI GetThreadPreferredUILanguages(DWORD dwFlags, unsigned int *pulNumLanguages, unsigned short *pwszBuf, unsigned int *pcchBuf);
BOOL WINAPI IsValidLocaleName(void *lpLocaleName);
VOID WINAPI InitializeSRWLock(void **SRWLock);
DWORD WINAPI WaitForSingleObjectEx(HANDLE hHandle, DWORD dwMilliseconds, int bAlertable);
DWORD WINAPI WaitForMultipleObjectsEx(DWORD nCount, const HANDLE *lpHandles, int bWaitAll, DWORD dwMilliseconds, int bAlertable);
int WINAPI CompareStringEx(void *lpLocaleName, DWORD dwCmpFlags, LPCWCH lpString1, int cchCount1, LPCWCH lpString2, int cchCount2, void *lpVersionInformation, void *lpReserved, void *lParam);

} // namespace kernel32
44 changes: 26 additions & 18 deletions dll/kernel32/synchapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -455,18 +455,29 @@ inline void setOwningThread(LPCRITICAL_SECTION crit, DWORD threadId) {
}

void waitForCriticalSection(LPCRITICAL_SECTION cs) {
auto *sequence = reinterpret_cast<LONG volatile *>(&cs->LockSemaphore);
LONG observed = __atomic_load_n(sequence, __ATOMIC_ACQUIRE);
while (owningThreadId(cs) != 0) {
kernel32::WaitOnAddress(sequence, &observed, sizeof(observed), INFINITE);
observed = __atomic_load_n(sequence, __ATOMIC_ACQUIRE);
// Ticket handoff: Windows hands a contended section to exactly one
// waiter per Leave through a counted semaphore (LockSemaphore). Model the
// semaphore as a ticket count on the LockSemaphore word: Leave posts one
// ticket, each blocked Enter consumes one. The woken waiter owns the
// section by construction (no claim race on OwningThread).
auto *tickets = reinterpret_cast<LONG volatile *>(&cs->LockSemaphore);
for (;;) {
LONG available = __atomic_load_n(tickets, __ATOMIC_ACQUIRE);
if (available > 0) {
if (__atomic_compare_exchange_n(tickets, &available, available - 1, false, __ATOMIC_ACQ_REL,
__ATOMIC_ACQUIRE)) {
return;
}
continue;
}
kernel32::WaitOnAddress(tickets, &available, sizeof(available), INFINITE);
}
}

void signalCriticalSection(LPCRITICAL_SECTION cs) {
auto *sequence = reinterpret_cast<LONG *>(&cs->LockSemaphore);
kernel32::InterlockedIncrement(const_cast<LONG volatile *>(sequence));
kernel32::WakeByAddressSingle(sequence);
auto *tickets = reinterpret_cast<LONG *>(&cs->LockSemaphore);
kernel32::InterlockedIncrement(const_cast<LONG volatile *>(tickets));
kernel32::WakeByAddressSingle(tickets);
}

inline bool trySpinAcquireCriticalSection(LPCRITICAL_SECTION cs, DWORD threadId) {
Expand Down Expand Up @@ -1086,7 +1097,7 @@ void WINAPI EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection) {
lpCriticalSection->RecursionCount++;
return;
}
waitForCriticalSection(lpCriticalSection);
waitForCriticalSection(lpCriticalSection); // ticket handoff: we own the section now
}
setOwningThread(lpCriticalSection, threadId);
lpCriticalSection->RecursionCount = 1;
Expand All @@ -1100,16 +1111,13 @@ void WINAPI LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection) {
return;
}

const DWORD threadId = GetCurrentThreadId();
if (owningThreadId(lpCriticalSection) != threadId || lpCriticalSection->RecursionCount <= 0) {
DEBUG_LOG("LeaveCriticalSection: thread %u does not own %p (owner=%u, recursion=%ld)\n", threadId,
lpCriticalSection, owningThreadId(lpCriticalSection),
static_cast<long>(lpCriticalSection->RecursionCount));
return;
}

// Windows LeaveCriticalSection performs NO
// caller/ownership validation; it unconditionally decrements
// RecursionCount/LockCount and releases one waiter. Guest patterns that
// rely on that (cross-thread leave, reinitialized sections) must behave
// identically here.
auto *lockCount = const_cast<LONG volatile *>(&lpCriticalSection->LockCount);
if (--lpCriticalSection->RecursionCount > 0) {
if (--lpCriticalSection->RecursionCount != 0) {
kernel32::InterlockedDecrement(lockCount);
return;
}
Expand Down
8 changes: 8 additions & 0 deletions dll/kernel32/winnls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,14 @@ int WINAPI LCMapStringW(LCID Locale, DWORD dwMapFlags, LPCWCH lpSrcStr, int cchS
return static_cast<int>(srcLen);
}

int WINAPI LCMapStringEx(LPCWSTR lpLocaleName, DWORD dwMapFlags, LPCWCH lpSrcStr, int cchSrc, LPWSTR lpDestStr,
int cchDest, void *lpVersionInformation, void *lpReserved, void *sortHandle) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("LCMapStringEx(%p, 0x%x, %p, %d, %p, %d)\n", lpLocaleName, dwMapFlags, lpSrcStr, cchSrc, lpDestStr, cchDest);
(void)lpLocaleName; (void)lpVersionInformation; (void)lpReserved; (void)sortHandle;
return LCMapStringW(0x0409, dwMapFlags, lpSrcStr, cchSrc, lpDestStr, cchDest);
}

int WINAPI LCMapStringA(LCID Locale, DWORD dwMapFlags, LPCCH lpSrcStr, int cchSrc, LPSTR lpDestStr, int cchDest) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("LCMapStringA(%u, 0x%x, %p, %d, %p, %d)\n", Locale, dwMapFlags, lpSrcStr, cchSrc, lpDestStr, cchDest);
Expand Down
2 changes: 2 additions & 0 deletions dll/kernel32/winnls.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,7 @@ BOOL WINAPI IsDBCSLeadByte(BYTE TestChar);
BOOL WINAPI IsDBCSLeadByteEx(UINT CodePage, BYTE TestChar);
int WINAPI LCMapStringW(LCID Locale, DWORD dwMapFlags, LPCWCH lpSrcStr, int cchSrc, LPWSTR lpDestStr, int cchDest);
int WINAPI LCMapStringA(LCID Locale, DWORD dwMapFlags, LPCCH lpSrcStr, int cchSrc, LPSTR lpDestStr, int cchDest);
int WINAPI LCMapStringEx(LPCWSTR lpLocaleName, DWORD dwMapFlags, LPCWCH lpSrcStr, int cchSrc, LPWSTR lpDestStr,
int cchDest, void *lpVersionInformation, void *lpReserved, void *sortHandle);

} // namespace kernel32
Loading
Loading