From f1039fcc78f7a0cf8bee8464346b7ddf4dc11128 Mon Sep 17 00:00:00 2001 From: Skyth <19259897+blueskythlikesclouds@users.noreply.github.com> Date: Sun, 15 Dec 2024 19:46:26 +0300 Subject: [PATCH] Cross-platform thread implementation. --- UnleashedRecomp/cpu/guest_thread.cpp | 122 +++++++++++++++++---------- UnleashedRecomp/cpu/guest_thread.h | 43 +++++++--- UnleashedRecomp/kernel/imports.cpp | 80 ++++++++++-------- UnleashedRecomp/kernel/xdm.h | 6 +- UnleashedRecomp/main.cpp | 2 +- 5 files changed, 158 insertions(+), 95 deletions(-) diff --git a/UnleashedRecomp/cpu/guest_thread.cpp b/UnleashedRecomp/cpu/guest_thread.cpp index 9d37a9ca..51e9a0be 100644 --- a/UnleashedRecomp/cpu/guest_thread.cpp +++ b/UnleashedRecomp/cpu/guest_thread.cpp @@ -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(pParameter); - const auto result = GuestThread::Start(*parameter); - - delete parameter; - return result; + if constexpr (sizeof(id) == 4) + return *reinterpret_cast(&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(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); diff --git a/UnleashedRecomp/cpu/guest_thread.h b/UnleashedRecomp/cpu/guest_thread.h index 7fb6e9b5..91ec9834 100644 --- a/UnleashedRecomp/cpu/guest_thread.h +++ b/UnleashedRecomp/cpu/guest_thread.h @@ -1,12 +1,8 @@ #pragma once -struct PPCContext; -struct GuestThreadParameter -{ - uint32_t function; - uint32_t value; - uint32_t flags; -}; +#include + +#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 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 }; diff --git a/UnleashedRecomp/kernel/imports.cpp b/UnleashedRecomp/kernel/imports.cpp index 8fb0962d..8b275dc8 100644 --- a/UnleashedRecomp/kernel/imports.cpp +++ b/UnleashedRecomp/kernel/imports.cpp @@ -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 g_tlsValues; static std::vector 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 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) diff --git a/UnleashedRecomp/kernel/xdm.h b/UnleashedRecomp/kernel/xdm.h index afd02da2..4b50d714 100644 --- a/UnleashedRecomp/kernel/xdm.h +++ b/UnleashedRecomp/kernel/xdm.h @@ -10,7 +10,11 @@ struct KernelObject { virtual ~KernelObject() { - ; + } + + virtual void Wait(uint32_t timeout) + { + assert(false && "Wait not implemented for this kernel object."); } }; diff --git a/UnleashedRecomp/main.cpp b/UnleashedRecomp/main.cpp index a847beee..8c55149b 100644 --- a/UnleashedRecomp/main.cpp +++ b/UnleashedRecomp/main.cpp @@ -182,7 +182,7 @@ int main(int argc, char *argv[]) Video::StartPipelinePrecompilation(); - GuestThread::Start(entry); + GuestThread::Start({ entry, 0, 0 }); return 0; }