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; *(thread + 0x10C) = cpuNumber;
*(uint32_t*)(thread + PCR_SIZE + 0x10) = 0xFFFFFFFF; // that one TLS entry that felt quirky *(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.fn = (uint8_t*)g_codeCache.bucket;
ppcContext.r1.u64 = g_memory.MapVirtual(thread + PCR_SIZE + TLS_SIZE + TEB_SIZE + STACK_SIZE); // stack pointer 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); g_userHeap.Free(thread);
} }
DWORD GuestThread::Start(uint32_t function) static void GuestThreadFunc(GuestThreadHandle* hThread)
{ {
const GuestThreadParameter parameter{ function }; hThread->suspended.wait(true);
return Start(parameter); 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); const auto cpuNumber = procMask == 0 ? 0 : 7 - std::countl_zero(procMask);
GuestThreadContext ctx(cpuNumber); 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); if constexpr (sizeof(id) == 4)
const auto result = GuestThread::Start(*parameter); return *reinterpret_cast<const uint32_t*>(&id);
else
delete parameter; return XXH32(&id, sizeof(id), 0);
return result;
} }
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; auto hThread = CreateKernelObject<GuestThreadHandle>(params);
//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); 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) #pragma pack(push,8)
const DWORD MS_VC_EXCEPTION = 0x406D1388; const DWORD MS_VC_EXCEPTION = 0x406D1388;
@ -94,7 +134,7 @@ void GuestThread::SetThreadName(uint32_t id, const char* name)
THREADNAME_INFO info; THREADNAME_INFO info;
info.dwType = 0x1000; info.dwType = 0x1000;
info.szName = name; info.szName = name;
info.dwThreadID = id; info.dwThreadID = threadId;
info.dwFlags = 0; info.dwFlags = 0;
__try __try
@ -105,41 +145,31 @@ void GuestThread::SetThreadName(uint32_t id, const char* name)
{ {
} }
} }
#endif
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;
}
void SetThreadNameImpl(uint32_t a1, uint32_t threadId, uint32_t* name) void SetThreadNameImpl(uint32_t a1, uint32_t threadId, uint32_t* name)
{ {
#ifdef _WIN32
GuestThread::SetThreadName(threadId, (const char*)g_memory.Translate(ByteSwap(*name))); 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); GUEST_FUNCTION_HOOK(sub_82DFA2E8, SetThreadNameImpl);

View file

@ -1,12 +1,8 @@
#pragma once #pragma once
struct PPCContext; #include <kernel/xdm.h>
struct GuestThreadParameter
{ #define CURRENT_THREAD_HANDLE uint32_t(-2)
uint32_t function;
uint32_t value;
uint32_t flags;
};
struct GuestThreadContext struct GuestThreadContext
{ {
@ -17,13 +13,34 @@ struct GuestThreadContext
~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 struct GuestThread
{ {
static DWORD Start(uint32_t function); static uint32_t Start(const GuestThreadParams& params);
static DWORD Start(const GuestThreadParameter& parameter); static GuestThreadHandle* Start(const GuestThreadParams& params, uint32_t* threadId);
static HANDLE Start(uint32_t function, uint32_t parameter, uint32_t flags, LPDWORD threadId);
static void SetThreadName(uint32_t id, const char* name); static uint32_t GetCurrentThreadId();
static void SetLastError(DWORD error); static void SetLastError(uint32_t error);
static PPCContext* Invoke(uint32_t address);
#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) 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; return STATUS_SUCCESS;
@ -474,8 +484,9 @@ void ObDereferenceObject()
LOG_UTILITY("!!! STUB !!!"); LOG_UTILITY("!!! STUB !!!");
} }
void KeSetBasePriorityThread(uint32_t thread, int priority) void KeSetBasePriorityThread(GuestThreadHandle* hThread, int priority)
{ {
#ifdef _WIN32
if (priority == 16) if (priority == 16)
{ {
priority = 15; priority = 15;
@ -485,7 +496,8 @@ void KeSetBasePriorityThread(uint32_t thread, int priority)
priority = -15; 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) uint32_t ObReferenceObjectByHandle(uint32_t handle, uint32_t objectType, XLPDWORD object)
@ -499,15 +511,12 @@ void KeQueryBasePriorityThread()
LOG_UTILITY("!!! STUB !!!"); 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) hThread->suspended = true;
return E_FAIL; hThread->suspended.wait(true);
if (suspendCount != nullptr)
*suspendCount = ByteSwap(count);
return S_OK; return S_OK;
} }
@ -890,29 +899,32 @@ DWORD KeWaitForSingleObject(XDISPATCHER_HEADER* Object, DWORD WaitReason, DWORD
return WaitForSingleObjectEx(handle, timeout, Alertable); return WaitForSingleObjectEx(handle, timeout, Alertable);
} }
static thread_local std::vector<uint32_t> g_tlsValues;
static std::vector<size_t> g_tlsFreeIndices; static std::vector<size_t> g_tlsFreeIndices;
static size_t g_tlsNextIndex = 0; static size_t g_tlsNextIndex = 0;
static Mutex g_tlsAllocationMutex; 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) uint32_t KeTlsGetValue(DWORD dwTlsIndex)
{ {
KeTlsEnsureTlsCapacity(dwTlsIndex); return KeTlsGetValueRef(dwTlsIndex);
return g_tlsValues[dwTlsIndex];
} }
BOOL KeTlsSetValue(DWORD dwTlsIndex, DWORD lpTlsValue) BOOL KeTlsSetValue(DWORD dwTlsIndex, DWORD lpTlsValue)
{ {
KeTlsEnsureTlsCapacity(dwTlsIndex); KeTlsGetValueRef(dwTlsIndex) = lpTlsValue;
g_tlsValues[dwTlsIndex] = lpTlsValue;
return TRUE; return TRUE;
} }
@ -1169,15 +1181,12 @@ uint32_t NtClearEvent(uint32_t handle, uint32_t* previousState)
return ResetEvent((HANDLE)handle) ? 0 : 0xFFFFFFFF; 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) hThread->suspended = false;
return E_FAIL; hThread->suspended.notify_all();
if (suspendCount != nullptr)
*suspendCount = ByteSwap(count);
return S_OK; 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}", 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); (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) if (threadId != nullptr)
*threadId = hostThreadId; *threadId = hostThreadId;
@ -1391,10 +1400,13 @@ DWORD XAudioGetVoiceCategoryVolumeChangeMask(DWORD Driver, XLPDWORD Mask)
return 0; return 0;
} }
uint32_t KeResumeThread(uint32_t object) uint32_t KeResumeThread(GuestThreadHandle* object)
{ {
LOGF_UTILITY("0x{:x}", object); assert(object != GetKernelObject(CURRENT_THREAD_HANDLE));
return ResumeThread((HANDLE)object);
object->suspended = false;
object->suspended.notify_all();
return 0;
} }
void KeInitializeSemaphore(XKSEMAPHORE* semaphore, uint32_t count, uint32_t limit) void KeInitializeSemaphore(XKSEMAPHORE* semaphore, uint32_t count, uint32_t limit)

View file

@ -10,7 +10,11 @@ struct KernelObject
{ {
virtual ~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(); Video::StartPipelinePrecompilation();
GuestThread::Start(entry); GuestThread::Start({ entry, 0, 0 });
return 0; return 0;
} }