Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ if (WIBO_ENABLE_FIXTURE_TESTS)
wibo_add_fixture_bin(NAME test_threading SOURCES test/test_threading.c)
wibo_add_fixture_bin(NAME test_tls SOURCES test/test_tls.c)
wibo_add_fixture_bin(NAME test_tls_reloc SOURCES test/test_tls_reloc.c)
wibo_add_fixture_bin(NAME test_fls SOURCES test/test_fls.c)
wibo_add_fixture_bin(NAME test_handleapi SOURCES test/test_handleapi.c)
wibo_add_fixture_bin(NAME test_findfile SOURCES test/test_findfile.c)
wibo_add_fixture_bin(NAME test_getfileattributesex SOURCES test/test_getfileattributesex.c)
Expand Down
34 changes: 30 additions & 4 deletions dll/kernel32/fibersapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,30 @@
#include "errors.h"
#include "internal.h"

#include <array>
#include <mutex>

namespace {

constexpr DWORD kMaxFlsValues = 0x100;

struct FlsSlot {
DWORD generation = 0;
LPVOID value = nullptr;
};

std::mutex g_flsMutex;
bool g_flsValuesUsed[kMaxFlsValues] = {false};
LPVOID g_flsValues[kMaxFlsValues] = {nullptr};
DWORD g_flsGenerations[kMaxFlsValues] = {0};
thread_local std::array<FlsSlot, kMaxFlsValues> t_flsValues;

void resetThreadFlsSlot(DWORD index) {
FlsSlot &slot = t_flsValues[index];
if (slot.generation != g_flsGenerations[index]) {
slot.generation = g_flsGenerations[index];
slot.value = nullptr;
}
}

} // namespace

Expand All @@ -19,10 +38,12 @@ 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;
g_flsGenerations[i]++;
resetThreadFlsSlot(i);
DEBUG_LOG(" -> %d\n", i);
return i;
}
Expand All @@ -35,6 +56,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 @@ -48,8 +70,10 @@ PVOID WINAPI FlsGetValue(DWORD dwFlsIndex) {
HOST_CONTEXT_GUARD();
VERBOSE_LOG("FlsGetValue(%u)\n", dwFlsIndex);
PVOID result = nullptr;
std::lock_guard lk(g_flsMutex);
if (dwFlsIndex < kMaxFlsValues && g_flsValuesUsed[dwFlsIndex]) {
result = g_flsValues[dwFlsIndex];
resetThreadFlsSlot(dwFlsIndex);
result = t_flsValues[dwFlsIndex].value;
// See https://learn.microsoft.com/en-us/windows/win32/api/fibersapi/nf-fibersapi-flsgetvalue
setLastError(ERROR_SUCCESS);
} else {
Expand All @@ -62,8 +86,10 @@ PVOID WINAPI FlsGetValue(DWORD dwFlsIndex) {
BOOL WINAPI FlsSetValue(DWORD dwFlsIndex, PVOID lpFlsData) {
HOST_CONTEXT_GUARD();
VERBOSE_LOG("FlsSetValue(%u, %p)\n", dwFlsIndex, lpFlsData);
std::lock_guard lk(g_flsMutex);
if (dwFlsIndex < kMaxFlsValues && g_flsValuesUsed[dwFlsIndex]) {
g_flsValues[dwFlsIndex] = lpFlsData;
resetThreadFlsSlot(dwFlsIndex);
t_flsValues[dwFlsIndex].value = lpFlsData;
return TRUE;
} else {
setLastError(ERROR_INVALID_PARAMETER);
Expand Down
32 changes: 15 additions & 17 deletions dll/kernel32/synchapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -455,18 +455,24 @@ 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);
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 @@ -1100,16 +1106,8 @@ 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;
}

auto *lockCount = const_cast<LONG volatile *>(&lpCriticalSection->LockCount);
if (--lpCriticalSection->RecursionCount > 0) {
if (--lpCriticalSection->RecursionCount != 0) {
kernel32::InterlockedDecrement(lockCount);
return;
}
Expand Down
89 changes: 89 additions & 0 deletions test/test_fls.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#define _WIN32_WINNT 0x0600

#include "test_assert.h"
#include <stdlib.h>
#include <windows.h>

#ifndef FLS_OUT_OF_INDEXES
#define FLS_OUT_OF_INDEXES ((DWORD)0xffffffff)
#endif

typedef struct {
DWORD index;
HANDLE readyEvent;
HANDLE continueEvent;
int threadValue;
int reuseValue;
} FlsThreadContext;

static DWORD WINAPI fls_thread_proc(LPVOID param) {
FlsThreadContext *ctx = (FlsThreadContext *)param;
TEST_CHECK(ctx != NULL);

SetLastError(ERROR_GEN_FAILURE);
TEST_CHECK(FlsGetValue(ctx->index) == NULL);
TEST_CHECK_EQ(ERROR_SUCCESS, GetLastError());

TEST_CHECK(FlsSetValue(ctx->index, &ctx->threadValue));
TEST_CHECK(FlsGetValue(ctx->index) == &ctx->threadValue);
TEST_CHECK(SetEvent(ctx->readyEvent));

TEST_CHECK_EQ(WAIT_OBJECT_0, WaitForSingleObject(ctx->continueEvent, 1000));

SetLastError(ERROR_GEN_FAILURE);
TEST_CHECK(FlsGetValue(ctx->index) == NULL);
TEST_CHECK_EQ(ERROR_SUCCESS, GetLastError());

TEST_CHECK(FlsSetValue(ctx->index, &ctx->reuseValue));
TEST_CHECK(FlsGetValue(ctx->index) == &ctx->reuseValue);

return 0;
}

int main(void) {
DWORD index = FlsAlloc(NULL);
TEST_CHECK(index != FLS_OUT_OF_INDEXES);

SetLastError(ERROR_GEN_FAILURE);
TEST_CHECK(FlsGetValue(index) == NULL);
TEST_CHECK_EQ(ERROR_SUCCESS, GetLastError());

int mainValue = 0x1234;
TEST_CHECK(FlsSetValue(index, &mainValue));
TEST_CHECK(FlsGetValue(index) == &mainValue);

FlsThreadContext ctx = {0};
ctx.index = index;
ctx.threadValue = 0x5678;
ctx.reuseValue = 0x9abc;
ctx.readyEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
ctx.continueEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
TEST_CHECK(ctx.readyEvent != NULL);
TEST_CHECK(ctx.continueEvent != NULL);

HANDLE thread = CreateThread(NULL, 0, fls_thread_proc, &ctx, 0, NULL);
TEST_CHECK(thread != NULL);

TEST_CHECK_EQ(WAIT_OBJECT_0, WaitForSingleObject(ctx.readyEvent, 1000));
TEST_CHECK(FlsGetValue(index) == &mainValue);

TEST_CHECK(FlsFree(index));
TEST_CHECK(FlsGetValue(index) == NULL);

DWORD reusedIndex = FlsAlloc(NULL);
TEST_CHECK_EQ(index, reusedIndex);

SetLastError(ERROR_GEN_FAILURE);
TEST_CHECK(FlsGetValue(reusedIndex) == NULL);
TEST_CHECK_EQ(ERROR_SUCCESS, GetLastError());

TEST_CHECK(SetEvent(ctx.continueEvent));
TEST_CHECK_EQ(WAIT_OBJECT_0, WaitForSingleObject(thread, 1000));

TEST_CHECK(CloseHandle(thread));
TEST_CHECK(CloseHandle(ctx.readyEvent));
TEST_CHECK(CloseHandle(ctx.continueEvent));
TEST_CHECK(FlsFree(reusedIndex));

return EXIT_SUCCESS;
}
Loading