Files
raven/native/nt/sync_api.cpp
T

271 lines
10 KiB
C++

#include "kernel_context.h"
#include <cstring>
#include <map>
namespace raven {
namespace {
// CRITICAL_SECTION (x64): debug info, lock count, recursion count, owning thread, semaphore, spin count.
constexpr uint64_t lock_count = 8, recursion_count = 12, owning_thread = 16, spin_count = 32;
struct Waiter {
bool woken = false;
};
}
void register_sync_api(ApiRegistry &registry, KernelContext &context)
{
auto &threads = context.threads;
const auto me = [&threads] { return uint64_t(threads.current_id()); };
const auto initialize = [](ApiCall &call) {
std::vector<uint8_t> bytes(40);
const int32_t unlocked = -1;
std::memcpy(bytes.data() + lock_count, &unlocked, 4);
call.write(call[0], bytes);
call.ret(1);
};
registry.add("InitializeCriticalSection", 1, initialize);
registry.add("InitializeCriticalSectionAndSpinCount", 2, [initialize](ApiCall &call) {
initialize(call);
call.store<uint64_t>(call[0] + spin_count, call[1] & 0x00ffffff);
});
registry.add("InitializeCriticalSectionEx", 3, [initialize](ApiCall &call) {
if (call.u32(2) & ~0x03000000U) {
call.fail(87);
return;
}
initialize(call);
call.store<uint64_t>(call[0] + spin_count, call[1] & 0x00ffffff);
});
registry.add("DeleteCriticalSection", 1, [](ApiCall &call) {
call.store<uint64_t>(call[0] + owning_thread, 0);
call.store<int32_t>(call[0] + lock_count, -1);
call.store<int32_t>(call[0] + recursion_count, 0);
});
// A section is free when nothing owns it; an owner nests, anyone else waits for the owner to leave.
const auto take = [me](ApiCall &call) {
const auto owner = call.load<uint64_t>(call[0] + owning_thread);
const auto recursion = call.load<int32_t>(call[0] + recursion_count);
if (recursion > 0 && owner == me()) {
call.store<int32_t>(call[0] + recursion_count, recursion + 1);
call.store<int32_t>(call[0] + lock_count, call.load<int32_t>(call[0] + lock_count) + 1);
return true;
}
if (recursion > 0 && owner)
return false;
call.store<uint64_t>(call[0] + owning_thread, me());
call.store<int32_t>(call[0] + recursion_count, 1);
call.store<int32_t>(call[0] + lock_count, 0);
return true;
};
registry.add("EnterCriticalSection", 1, [take, &context, &threads](ApiCall &call) {
const uint64_t section = call[0];
while (!take(call)) {
threads.block([&context, section] {
return context.process.read_memory(section + recursion_count, 4) == std::vector<uint8_t>(4, 0) ||
context.process.read_memory(section + owning_thread, 8) == std::vector<uint8_t>(8, 0);
}, std::nullopt);
}
});
registry.add("TryEnterCriticalSection", 1, [take](ApiCall &call) { call.ret(take(call)); });
registry.add("LeaveCriticalSection", 1, [](ApiCall &call) {
const auto recursion = call.load<int32_t>(call[0] + recursion_count);
if (recursion <= 0)
return;
call.store<int32_t>(call[0] + recursion_count, recursion - 1);
if (recursion == 1) {
call.store<uint64_t>(call[0] + owning_thread, 0);
call.store<int32_t>(call[0] + lock_count, -1);
} else {
call.store<int32_t>(call[0] + lock_count, call.load<int32_t>(call[0] + lock_count) - 1);
}
});
// SRW locks are one pointer-sized word: 1 is exclusive, shared holders count in steps of 2.
const auto acquire_exclusive = [&context, &threads](ApiCall &call) {
const uint64_t lock = call[0];
while (call.load<uint64_t>(lock))
threads.block([&context, lock] { return context.process.read_memory(lock, 8) == std::vector<uint8_t>(8, 0); }, std::nullopt);
call.store<uint64_t>(lock, 1);
};
const auto acquire_shared = [&context, &threads](ApiCall &call) {
const uint64_t lock = call[0];
while (call.load<uint64_t>(lock) & 1)
threads.block([&context, lock] { return !(context.process.read_memory(lock, 1)[0] & 1); }, std::nullopt);
call.store<uint64_t>(lock, call.load<uint64_t>(lock) + 2);
};
registry.add("InitializeSRWLock", 1, [](ApiCall &call) { call.store<uint64_t>(call[0], 0); });
registry.add("AcquireSRWLockExclusive", 1, acquire_exclusive);
registry.add("TryAcquireSRWLockExclusive", 1, [](ApiCall &call) {
const bool free = !call.load<uint64_t>(call[0]);
if (free)
call.store<uint64_t>(call[0], 1);
call.ret(free);
});
registry.add("ReleaseSRWLockExclusive", 1, [](ApiCall &call) { call.store<uint64_t>(call[0], 0); });
registry.add("AcquireSRWLockShared", 1, acquire_shared);
registry.add("TryAcquireSRWLockShared", 1, [](ApiCall &call) {
const auto value = call.load<uint64_t>(call[0]);
if (!(value & 1))
call.store<uint64_t>(call[0], value + 2);
call.ret(!(value & 1));
});
registry.add("ReleaseSRWLockShared", 1, [](ApiCall &call) {
const auto value = call.load<uint64_t>(call[0]);
if (value >= 2)
call.store<uint64_t>(call[0], value - 2);
});
// Condition variables keep their waiters host-side, keyed by the variable's address.
auto variables = std::make_shared<std::map<uint64_t, std::vector<std::shared_ptr<Waiter>>>>();
const auto sleep_on = [&threads, variables](ApiCall &call, std::function<void()> release, std::function<void()> reacquire, uint32_t milliseconds) {
auto waiter = std::make_shared<Waiter>();
(*variables)[call[0]].push_back(waiter);
release();
std::optional<std::chrono::steady_clock::time_point> wake;
if (milliseconds != UINT32_MAX)
wake = std::chrono::steady_clock::now() + std::chrono::milliseconds(milliseconds);
threads.block([waiter] { return waiter->woken; }, wake);
auto &list = (*variables)[call[0]];
list.erase(std::remove(list.begin(), list.end(), waiter), list.end());
reacquire();
if (!waiter->woken) {
call.fail(1460);
return;
}
call.ret(1);
};
registry.add("InitializeConditionVariable", 1, [](ApiCall &call) { call.store<uint64_t>(call[0], 0); });
registry.add("SleepConditionVariableSRW", 4, [sleep_on, acquire_exclusive, acquire_shared](ApiCall &call) {
const bool shared = call.u32(3) & 1;
const uint64_t lock = call[1];
auto sub = ApiCall(call.process, call.environment, {lock});
sleep_on(call, [&] { if (shared) { const auto v = call.load<uint64_t>(lock); call.store<uint64_t>(lock, v >= 2 ? v - 2 : 0); } else call.store<uint64_t>(lock, 0); },
[&] { if (shared) acquire_shared(sub); else acquire_exclusive(sub); }, call.u32(2));
});
registry.add("SleepConditionVariableCS", 3, [sleep_on, take, &threads, &context](ApiCall &call) {
const uint64_t section = call[1];
auto sub = ApiCall(call.process, call.environment, {section});
int32_t saved = 0;
sleep_on(call, [&] {
saved = sub.load<int32_t>(section + recursion_count);
sub.store<uint64_t>(section + owning_thread, 0);
sub.store<int32_t>(section + recursion_count, 0);
sub.store<int32_t>(section + lock_count, -1);
}, [&] {
while (!take(sub))
threads.block([&context, section] { return context.process.read_memory(section + recursion_count, 4) == std::vector<uint8_t>(4, 0); }, std::nullopt);
sub.store<int32_t>(section + recursion_count, saved);
}, call.u32(2));
});
const auto wake = [variables](ApiCall &call, bool all) {
auto &list = (*variables)[call[0]];
for (auto &waiter : list)
if (!waiter->woken) {
waiter->woken = true;
if (!all)
break;
}
};
registry.add("WakeConditionVariable", 1, [wake](ApiCall &call) { wake(call, false); });
registry.add("WakeAllConditionVariable", 1, [wake](ApiCall &call) { wake(call, true); });
// INIT_ONCE is one word: 0 untouched, 1 running, otherwise the context pointer with low bits 2.
registry.add("InitOnceInitialize", 1, [](ApiCall &call) { call.store<uint64_t>(call[0], 0); });
registry.add("InitOnceBeginInitialize", 4, [&context, &threads](ApiCall &call) {
const uint32_t flags = call.u32(1);
if (flags & ~3U) {
call.fail(87);
return;
}
const uint64_t once = call[0];
auto state = call.load<uint64_t>(once);
if ((state & 3) == 1 && !(flags & 1)) {
threads.block([&context, once] { return (context.process.read_memory(once, 1)[0] & 3) != 1; }, std::nullopt);
state = call.load<uint64_t>(once);
}
if ((state & 3) == 2) {
call.store<int32_t>(call[2], 0);
if (call[3]) call.store<uint64_t>(call[3], state & ~3ULL);
call.ret(1);
return;
}
if (flags & 1) {
call.fail(1627);
return;
}
call.store<uint64_t>(once, 1);
call.store<int32_t>(call[2], 1);
call.ret(1);
});
registry.add("InitOnceComplete", 3, [](ApiCall &call) {
const uint32_t flags = call.u32(1);
if (call.load<uint64_t>(call[0]) != 1 || (flags & 4 && call[2])) {
call.fail(87);
return;
}
call.store<uint64_t>(call[0], flags & 4 ? 0 : (call[2] & ~3ULL) | 2);
call.ret(1);
});
registry.add("InitOnceExecuteOnce", 4, [&](ApiCall &call) {
const uint64_t once = call[0];
auto state = call.load<uint64_t>(once);
if ((state & 3) == 1) {
threads.block([&context, once] { return (context.process.read_memory(once, 1)[0] & 3) != 1; }, std::nullopt);
state = call.load<uint64_t>(once);
}
if ((state & 3) == 2) {
if (call[3]) call.store<uint64_t>(call[3], state & ~3ULL);
call.ret(1);
return;
}
call.store<uint64_t>(once, 1);
const auto scratch = context.allocator.allocate(16);
if (!scratch) {
call.fail(8);
return;
}
const uint64_t arguments[] = {once, call[2], scratch};
const auto result = context.call(call[1], arguments);
if (result.stop != NativeStop::returned)
throw result;
const auto produced = call.load<uint64_t>(scratch);
context.allocator.release(scratch);
if (!uint32_t(result.value)) {
call.store<uint64_t>(once, 0);
call.fail(1627);
return;
}
call.store<uint64_t>(once, (produced & ~3ULL) | 2);
if (call[3]) call.store<uint64_t>(call[3], produced);
call.ret(1);
});
// WaitOnAddress watches memory directly; wakeups are implicit because waiting threads re-check the value.
registry.add("WaitOnAddress", 4, [&context, &threads](ApiCall &call) {
const size_t size = call[2];
if (size != 1 && size != 2 && size != 4 && size != 8) {
call.fail(87);
return;
}
const uint64_t address = call[0];
const auto expected = call.read(call[1], size);
std::optional<std::chrono::steady_clock::time_point> wake;
if (call.u32(3) != UINT32_MAX)
wake = std::chrono::steady_clock::now() + std::chrono::milliseconds(call.u32(3));
const bool changed = threads.block([&context, address, size, expected] { return context.process.read_memory(address, size) != expected; }, wake);
if (!changed) {
call.fail(1460);
return;
}
call.ret(1);
});
registry.add("WakeByAddressSingle", 1, [](ApiCall &) {});
registry.add("WakeByAddressAll", 1, [](ApiCall &) {});
}
}