Cross-platform thread implementation.

This commit is contained in:
Skyth 2024-12-15 19:46:26 +03:00
parent 52b4f0ee5e
commit f1039fcc78
5 changed files with 158 additions and 95 deletions

View file

@ -27,7 +27,7 @@ GuestThreadContext::GuestThreadContext(uint32_t cpuNumber)
*(thread + 0x10C) = cpuNumber;
*(uint32_t*)(thread + PCR_SIZE + 0x10) = 0xFFFFFFFF; // that one TLS entry that felt quirky
*(uint32_t*)(thread + PCR_SIZE + TLS_SIZE + 0x14C) = ByteSwap(GetCurrentThreadId()); // thread id
*(uint32_t*)(thread + PCR_SIZE + TLS_SIZE + 0x14C) = ByteSwap(GuestThread::GetCurrentThreadId()); // thread id
ppcContext.fn = (uint8_t*)g_codeCache.bucket;
ppcContext.r1.u64 = g_memory.MapVirtual(thread + PCR_SIZE + TLS_SIZE + TEB_SIZE + STACK_SIZE); // stack pointer
@ -42,42 +42,82 @@ GuestThreadContext::~GuestThreadContext()
g_userHeap.Free(thread);
}
DWORD GuestThread::Start(uint32_t function)
static void GuestThreadFunc(GuestThreadHandle* hThread)
{
const GuestThreadParameter parameter{ function };
return Start(parameter);
hThread->suspended.wait(true);
GuestThread::Start(hThread->params);
}
DWORD GuestThread::Start(const GuestThreadParameter& parameter)
GuestThreadHandle::GuestThreadHandle(const GuestThreadParams& params)
: params(params), suspended((params.flags & 0x1) != 0), thread(GuestThreadFunc, this)
{
const auto procMask = (uint8_t)(parameter.flags >> 24);
}
GuestThreadHandle::~GuestThreadHandle()
{
if (thread.joinable())
thread.join();
}
void GuestThreadHandle::Wait(uint32_t timeout)
{
assert(timeout == INFINITE);
if (thread.joinable())
thread.join();
}
uint32_t GuestThread::Start(const GuestThreadParams& params)
{
const auto procMask = (uint8_t)(params.flags >> 24);
const auto cpuNumber = procMask == 0 ? 0 : 7 - std::countl_zero(procMask);
GuestThreadContext ctx(cpuNumber);
ctx.ppcContext.r3.u64 = parameter.value;
ctx.ppcContext.r3.u64 = params.value;
GuestCode::Run(g_codeCache.Find(parameter.function), &ctx.ppcContext, g_memory.Translate(0));
GuestCode::Run(g_codeCache.Find(params.function), &ctx.ppcContext, g_memory.Translate(0));
return (DWORD)ctx.ppcContext.r3.u64;
return ctx.ppcContext.r3.u32;
}
DWORD HostThreadStart(void* pParameter)
static uint32_t GetThreadId(const std::thread::id& id)
{
auto* parameter = static_cast<GuestThreadParameter*>(pParameter);
const auto result = GuestThread::Start(*parameter);
delete parameter;
return result;
if constexpr (sizeof(id) == 4)
return *reinterpret_cast<const uint32_t*>(&id);
else
return XXH32(&id, sizeof(id), 0);
}
HANDLE GuestThread::Start(uint32_t function, uint32_t parameter, uint32_t flags, LPDWORD threadId)
GuestThreadHandle* GuestThread::Start(const GuestThreadParams& params, uint32_t* threadId)
{
const auto hostCreationFlags = (flags & 1) != 0 ? CREATE_SUSPENDED : 0;
//return CreateThread(nullptr, 0, Start, (void*)((uint64_t(parameter) << 32) | function), suspended ? CREATE_SUSPENDED : 0, threadId);
return CreateThread(nullptr, 0, HostThreadStart, new GuestThreadParameter{ function, parameter, flags }, hostCreationFlags, threadId);
auto hThread = CreateKernelObject<GuestThreadHandle>(params);
if (threadId != nullptr)
*threadId = GetThreadId(hThread->thread.get_id());
return hThread;
}
void GuestThread::SetThreadName(uint32_t id, const char* name)
uint32_t GuestThread::GetCurrentThreadId()
{
return GetThreadId(std::this_thread::get_id());
}
void GuestThread::SetLastError(uint32_t error)
{
auto* thread = (char*)g_memory.Translate(GetPPCContext()->r13.u32);
if (*(uint32_t*)(thread + 0x150))
{
// Program doesn't want errors
return;
}
// TEB + 0x160 : Win32LastError
*(uint32_t*)(thread + TEB_OFFSET + 0x160) = ByteSwap(error);
}
#ifdef _WIN32
void GuestThread::SetThreadName(uint32_t threadId, const char* name)
{
#pragma pack(push,8)
const DWORD MS_VC_EXCEPTION = 0x406D1388;
@ -94,7 +134,7 @@ void GuestThread::SetThreadName(uint32_t id, const char* name)
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = name;
info.dwThreadID = id;
info.dwThreadID = threadId;
info.dwFlags = 0;
__try
@ -105,41 +145,31 @@ void GuestThread::SetThreadName(uint32_t id, const char* name)
{
}
}
void GuestThread::SetLastError(DWORD error)
{
auto* thread = (char*)g_memory.Translate(GetPPCContext()->r13.u32);
if (*(DWORD*)(thread + 0x150))
{
// Program doesn't want errors
return;
}
// TEB + 0x160 : Win32LastError
*(DWORD*)(thread + TEB_OFFSET + 0x160) = ByteSwap(error);
}
PPCContext* GuestThread::Invoke(uint32_t address)
{
auto* ctx = GetPPCContext();
GuestCode::Run(g_codeCache.Find(address), ctx);
return ctx;
}
#endif
void SetThreadNameImpl(uint32_t a1, uint32_t threadId, uint32_t* name)
{
#ifdef _WIN32
GuestThread::SetThreadName(threadId, (const char*)g_memory.Translate(ByteSwap(*name)));
#endif
}
int GetThreadPriorityImpl(uint32_t hThread)
int GetThreadPriorityImpl(GuestThreadHandle* hThread)
{
return GetThreadPriority((HANDLE)hThread);
#ifdef _WIN32
return GetThreadPriority(hThread == GetKernelObject(CURRENT_THREAD_HANDLE) ? GetCurrentThread() : hThread->thread.native_handle());
#else
return 0;
#endif
}
DWORD SetThreadIdealProcessorImpl(uint32_t hThread, DWORD dwIdealProcessor)
uint32_t SetThreadIdealProcessorImpl(GuestThreadHandle* hThread, uint32_t dwIdealProcessor)
{
return SetThreadIdealProcessor((HANDLE)hThread, dwIdealProcessor);
#ifdef _WIN32
return SetThreadIdealProcessor(hThread == GetKernelObject(CURRENT_THREAD_HANDLE) ? GetCurrentThread() : hThread->thread.native_handle(), dwIdealProcessor);
#else
return 0;
#endif
}
GUEST_FUNCTION_HOOK(sub_82DFA2E8, SetThreadNameImpl);

View file

@ -1,12 +1,8 @@
#pragma once
struct PPCContext;
struct GuestThreadParameter
{
uint32_t function;
uint32_t value;
uint32_t flags;
};
#include <kernel/xdm.h>
#define CURRENT_THREAD_HANDLE uint32_t(-2)
struct GuestThreadContext
{
@ -17,13 +13,34 @@ struct GuestThreadContext
~GuestThreadContext();
};
struct GuestThreadParams
{
uint32_t function;
uint32_t value;
uint32_t flags;
};
struct GuestThreadHandle : KernelObject
{
GuestThreadParams params;
std::atomic<bool> suspended;
std::thread thread;
GuestThreadHandle(const GuestThreadParams& params);
~GuestThreadHandle() override;
void Wait(uint32_t timeout) override;
};
struct GuestThread
{
static DWORD Start(uint32_t function);
static DWORD Start(const GuestThreadParameter& parameter);
static HANDLE Start(uint32_t function, uint32_t parameter, uint32_t flags, LPDWORD threadId);
static uint32_t Start(const GuestThreadParams& params);
static GuestThreadHandle* Start(const GuestThreadParams& params, uint32_t* threadId);
static void SetThreadName(uint32_t id, const char* name);
static void SetLastError(DWORD error);
static PPCContext* Invoke(uint32_t address);
static uint32_t GetCurrentThreadId();
static void SetLastError(uint32_t error);
#ifdef _WIN32
static void SetThreadName(uint32_t threadId, const char* name);
#endif
};

View file

@ -256,15 +256,25 @@ uint32_t FscSetCacheElementCount()
DWORD NtWaitForSingleObjectEx(DWORD Handle, DWORD WaitMode, DWORD Alertable, XLPQWORD Timeout)
{
const auto status = WaitForSingleObjectEx((HANDLE)Handle, GuestTimeoutToMilliseconds(Timeout), Alertable);
uint32_t timeout = GuestTimeoutToMilliseconds(Timeout);
assert(timeout == 0 || timeout == INFINITE);
if (status == WAIT_IO_COMPLETION)
if (IsKernelObject(Handle))
{
return STATUS_USER_APC;
GetKernelObject(Handle)->Wait(timeout);
}
else if (status)
else
{
return STATUS_ALERTED;
const auto status = WaitForSingleObjectEx((HANDLE)Handle, timeout, Alertable);
if (status == WAIT_IO_COMPLETION)
{
return STATUS_USER_APC;
}
else if (status)
{
return STATUS_ALERTED;
}
}
return STATUS_SUCCESS;
@ -474,8 +484,9 @@ void ObDereferenceObject()
LOG_UTILITY("!!! STUB !!!");
}
void KeSetBasePriorityThread(uint32_t thread, int priority)
void KeSetBasePriorityThread(GuestThreadHandle* hThread, int priority)
{
#ifdef _WIN32
if (priority == 16)
{
priority = 15;
@ -485,7 +496,8 @@ void KeSetBasePriorityThread(uint32_t thread, int priority)
priority = -15;
}
SetThreadPriority((HANDLE)thread, priority);
SetThreadPriority(hThread == GetKernelObject(CURRENT_THREAD_HANDLE) ? GetCurrentThread() : hThread->thread.native_handle(), priority);
#endif
}
uint32_t ObReferenceObjectByHandle(uint32_t handle, uint32_t objectType, XLPDWORD object)
@ -499,15 +511,12 @@ void KeQueryBasePriorityThread()
LOG_UTILITY("!!! STUB !!!");
}
uint32_t NtSuspendThread(uint32_t hThread, uint32_t* suspendCount)
uint32_t NtSuspendThread(GuestThreadHandle* hThread, uint32_t* suspendCount)
{
DWORD count = SuspendThread((HANDLE)hThread);
assert(hThread != GetKernelObject(CURRENT_THREAD_HANDLE) && hThread->thread.get_id() == std::this_thread::get_id());
if (count == (DWORD)-1)
return E_FAIL;
if (suspendCount != nullptr)
*suspendCount = ByteSwap(count);
hThread->suspended = true;
hThread->suspended.wait(true);
return S_OK;
}
@ -890,29 +899,32 @@ DWORD KeWaitForSingleObject(XDISPATCHER_HEADER* Object, DWORD WaitReason, DWORD
return WaitForSingleObjectEx(handle, timeout, Alertable);
}
static thread_local std::vector<uint32_t> g_tlsValues;
static std::vector<size_t> g_tlsFreeIndices;
static size_t g_tlsNextIndex = 0;
static Mutex g_tlsAllocationMutex;
static void KeTlsEnsureTlsCapacity(size_t index)
static uint32_t& KeTlsGetValueRef(size_t index)
{
if (g_tlsValues.size() <= index)
// Having this a global thread_local variable
// for some reason crashes on boot in debug builds.
thread_local std::vector<uint32_t> s_tlsValues;
if (s_tlsValues.size() <= index)
{
g_tlsValues.resize(index + 1, 0);
s_tlsValues.resize(index + 1, 0);
}
return s_tlsValues[index];
}
uint32_t KeTlsGetValue(DWORD dwTlsIndex)
{
KeTlsEnsureTlsCapacity(dwTlsIndex);
return g_tlsValues[dwTlsIndex];
return KeTlsGetValueRef(dwTlsIndex);
}
BOOL KeTlsSetValue(DWORD dwTlsIndex, DWORD lpTlsValue)
{
KeTlsEnsureTlsCapacity(dwTlsIndex);
g_tlsValues[dwTlsIndex] = lpTlsValue;
KeTlsGetValueRef(dwTlsIndex) = lpTlsValue;
return TRUE;
}
@ -1169,15 +1181,12 @@ uint32_t NtClearEvent(uint32_t handle, uint32_t* previousState)
return ResetEvent((HANDLE)handle) ? 0 : 0xFFFFFFFF;
}
uint32_t NtResumeThread(uint32_t hThread, uint32_t* suspendCount)
uint32_t NtResumeThread(GuestThreadHandle* hThread, uint32_t* suspendCount)
{
DWORD count = ResumeThread((HANDLE)hThread);
assert(hThread != GetKernelObject(CURRENT_THREAD_HANDLE));
if (count == (DWORD)-1)
return E_FAIL;
if (suspendCount != nullptr)
*suspendCount = ByteSwap(count);
hThread->suspended = false;
hThread->suspended.notify_all();
return S_OK;
}
@ -1270,9 +1279,9 @@ uint32_t ExCreateThread(XLPDWORD handle, uint32_t stackSize, XLPDWORD threadId,
LOGF_UTILITY("0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}",
(intptr_t)handle, stackSize, (intptr_t)threadId, xApiThreadStartup, startAddress, startContext, creationFlags);
DWORD hostThreadId;
uint32_t hostThreadId;
*handle = (uint32_t)GuestThread::Start(startAddress, startContext, creationFlags, &hostThreadId);
*handle = GetKernelHandle(GuestThread::Start({ startAddress, startContext, creationFlags }, &hostThreadId));
if (threadId != nullptr)
*threadId = hostThreadId;
@ -1391,10 +1400,13 @@ DWORD XAudioGetVoiceCategoryVolumeChangeMask(DWORD Driver, XLPDWORD Mask)
return 0;
}
uint32_t KeResumeThread(uint32_t object)
uint32_t KeResumeThread(GuestThreadHandle* object)
{
LOGF_UTILITY("0x{:x}", object);
return ResumeThread((HANDLE)object);
assert(object != GetKernelObject(CURRENT_THREAD_HANDLE));
object->suspended = false;
object->suspended.notify_all();
return 0;
}
void KeInitializeSemaphore(XKSEMAPHORE* semaphore, uint32_t count, uint32_t limit)

View file

@ -10,7 +10,11 @@ struct KernelObject
{
virtual ~KernelObject()
{
;
}
virtual void Wait(uint32_t timeout)
{
assert(false && "Wait not implemented for this kernel object.");
}
};

View file

@ -182,7 +182,7 @@ int main(int argc, char *argv[])
Video::StartPipelinePrecompilation();
GuestThread::Start(entry);
GuestThread::Start({ entry, 0, 0 });
return 0;
}