From f47269864a428c60bdd87a100567518cf87f7206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 25 Sep 2026 09:44:26 -0600 Subject: [PATCH] _sceKernelAllocateTlspl: Check user pointers, support the timeout The third argument is a timeout pointer, as threadman.prx shows. A kernel address from user mode is ILLEGAL_ADDR there; we used to write through it. Also, no lookup by index: the syscall requires the exact uid. Adds the threads/tls/allocate test, recorded on hardware. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HLE/sceKernel.cpp | 2 +- Core/HLE/sceKernelMemory.cpp | 47 ++++++++++++++++++++++++++++++------ Core/HLE/sceKernelMemory.h | 2 +- pspautotests | 2 +- test.py | 1 + 5 files changed, 43 insertions(+), 11 deletions(-) diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index 5a002a3f90..197fe28021 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -899,7 +899,7 @@ const HLEFunction ThreadManForUser[] = {0XB435DEC5, &WrapI_V, "sceKernelDcacheWritebackInvalidateAll", 'i', "" }, // Internal, the syscall behind sceKernelGetTlsAddr in usersystemlib. - {0x65F54FFB, &WrapI_IUU<_sceKernelAllocateTlspl>, "_sceKernelAllocateTlspl", 'i', "ixx" }, + {0x65F54FFB, &WrapI_IUU<_sceKernelAllocateTlspl>, "_sceKernelAllocateTlspl", 'i', "ixp" }, }; const HLEFunction ThreadManForKernel[] = diff --git a/Core/HLE/sceKernelMemory.cpp b/Core/HLE/sceKernelMemory.cpp index 281d505c5a..e8008425e8 100644 --- a/Core/HLE/sceKernelMemory.cpp +++ b/Core/HLE/sceKernelMemory.cpp @@ -54,6 +54,7 @@ BlockAllocator volatileMemory(256); static int vplWaitTimer = -1; static int fplWaitTimer = -1; +static int tlsplWaitTimer = -1; static bool tlsplUsedIndexes[TLSPL_NUM_INDEXES]; // Thread -> TLSPL uids for thread end. @@ -303,6 +304,7 @@ void VPL::DoState(PointerWrap &p) { void __KernelVplTimeout(u64 userdata, int cyclesLate); void __KernelFplTimeout(u64 userdata, int cyclesLate); +void __KernelTlsplTimeout(u64 userdata, int cyclesLate); void __KernelTlsplThreadEnd(SceUID threadID); void __KernelVplBeginCallback(SceUID threadID, SceUID prevCallbackId); @@ -327,6 +329,7 @@ void __KernelMemoryInit() vplWaitTimer = CoreTiming::RegisterEvent("VplTimeout", __KernelVplTimeout); fplWaitTimer = CoreTiming::RegisterEvent("FplTimeout", __KernelFplTimeout); + tlsplWaitTimer = CoreTiming::RegisterEvent("TlsplTimeout", __KernelTlsplTimeout); flags_ = 0; sdkVersion_ = 0; @@ -346,7 +349,7 @@ void __KernelMemoryInit() void __KernelMemoryDoState(PointerWrap &p) { - auto s = p.Section("sceKernelMemory", 1, 3); + auto s = p.Section("sceKernelMemory", 1, 4); if (!s) return; @@ -366,6 +369,12 @@ void __KernelMemoryDoState(PointerWrap &p) if (s >= 2) { Do(p, tlsplThreadEndChecks); } + if (s >= 4) { + Do(p, tlsplWaitTimer); + } else { + tlsplWaitTimer = -1; + } + CoreTiming::RestoreRegisterEvent(tlsplWaitTimer, "TlsplTimeout", __KernelTlsplTimeout); MemBlockInfoDoState(p); } @@ -1827,6 +1836,8 @@ int __KernelFreeTls(TLSPL *tls, SceUID threadID) tls->usage[freeBlock] = waitingThreadID; // _sceKernelAllocateTlspl waits with its address pointer as the wait value, sceKernelGetTlsAddr with 1. u32 error; + u32 timeoutPtr = __KernelGetWaitTimeoutPtr(waitingThreadID, error); + HLEKernel::WriteRemainingTimeout(tlsplWaitTimer, waitingThreadID, timeoutPtr); u32 addrPtr = __KernelGetWaitValue(waitingThreadID, error); if (addrPtr != TLSPL_WAITVALUE_RETURN_ADDR) { Memory::WriteOrException_U32(freedAddress, addrPtr); @@ -2000,6 +2011,8 @@ int sceKernelDeleteTlspl(SceUID uid) for (SceUID threadID : tls->waitingThreads) { // sceKernelGetTlsAddr returns a null address, _sceKernelAllocateTlspl an error. u32 error; + u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error); + HLEKernel::WriteRemainingTimeout(tlsplWaitTimer, threadID, timeoutPtr); u32 result = __KernelGetWaitValue(threadID, error) != TLSPL_WAITVALUE_RETURN_ADDR ? SCE_KERNEL_ERROR_WAIT_DELETE : 0; HLEKernel::ResumeFromWait(threadID, WAITTYPE_TLSPL, uid, result); } @@ -2108,19 +2121,33 @@ int sceKernelGetTlsAddr(SceUID uid) { return hleLogDebug(Log::sceKernel, allocAddress); } -// The syscall behind usersystemlib's sceKernelGetTlsAddr, which calls it as (uid, &addr, 0) when +void __KernelTlsplTimeout(u64 userdata, int cyclesLate) { + SceUID threadID = (SceUID)userdata; + HLEKernel::WaitExecTimeout(threadID); +} + +// The kernel's check on a pointer from user mode: neither end may be a kernel address. +static bool __KernelIsBadUserPtr(u32 ptr, u32 size) { + if (!__KernelCurThreadIsKernelMode() && ((ptr | (ptr + size)) & 0x80000000) != 0) + return true; + return ptr != 0 && !Memory::IsValidRange(ptr, size); +} + +// The syscall behind usersystemlib's sceKernelGetTlsAddr, which calls it as (uid, &addr, NULL) when // the thread's cached address is null. Homebrew that has to run before usersystemlib.prx is loaded -// (like plugins) inlines that code, so it imports this directly. The third argument is unknown. +// (like plugins) inlines that code, so it imports this directly. Checked against threadman.prx. // We don't fill in the per-thread cache at $k0+0x40, so callers always take this path. -int _sceKernelAllocateTlspl(SceUID uid, u32 addrPtr, u32 unknown) { +int _sceKernelAllocateTlspl(SceUID uid, u32 addrPtr, u32 timeoutPtr) { + if (__KernelIsBadUserPtr(addrPtr, 4) || addrPtr == 0 || __KernelIsBadUserPtr(timeoutPtr, 4)) + return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ADDR, "bad pointer"); if (__IsInInterrupt()) return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_CONTEXT, "in interrupt"); if (!__KernelIsDispatchEnabled()) return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_CAN_NOT_WAIT, "dispatch disabled"); - if (!Memory::IsValidRange(addrPtr, 4)) - return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ADDR, "bad addr pointer"); - TLSPL *tls = __KernelFindTlspl(uid); + // Unlike sceKernelGetTlsAddr, no lookup by index. + u32 error; + TLSPL *tls = kernelObjects.Get(uid, error); if (!tls) return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_UNKNOWN_TLSPL_ID, "tlspl not found"); @@ -2128,7 +2155,11 @@ int _sceKernelAllocateTlspl(SceUID uid, u32 addrPtr, u32 unknown) { if (allocAddress == 0) { SceUID threadID = __KernelGetCurThread(); tls->waitingThreads.push_back(threadID); - __KernelWaitCurThread(WAITTYPE_TLSPL, tls->GetUID(), addrPtr, 0, false, "allocate tls"); + if (timeoutPtr != 0 && tlsplWaitTimer != -1) { + int micro = (int)Memory::ReadOrException_U32(timeoutPtr); + CoreTiming::ScheduleEvent(usToCycles(micro), tlsplWaitTimer, threadID); + } + __KernelWaitCurThread(WAITTYPE_TLSPL, uid, addrPtr, timeoutPtr, false, "allocate tls"); return hleLogDebug(Log::sceKernel, 0, "waiting for tls alloc"); } diff --git a/Core/HLE/sceKernelMemory.h b/Core/HLE/sceKernelMemory.h index f35e137c7a..15174759fd 100644 --- a/Core/HLE/sceKernelMemory.h +++ b/Core/HLE/sceKernelMemory.h @@ -221,7 +221,7 @@ int sceKernelGetCompiledSdkVersion(); SceUID sceKernelCreateTlspl(const char *name, u32 partitionid, u32 attr, u32 size, u32 count, u32 optionsPtr); int sceKernelDeleteTlspl(SceUID uid); int sceKernelGetTlsAddr(SceUID uid); -int _sceKernelAllocateTlspl(SceUID uid, u32 addrPtr, u32 unknown); +int _sceKernelAllocateTlspl(SceUID uid, u32 addrPtr, u32 timeoutPtr); int sceKernelFreeTlspl(SceUID uid); int sceKernelReferTlsplStatus(SceUID uid, u32 infoPtr); diff --git a/pspautotests b/pspautotests index 6f03ee6457..8d51020081 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit 6f03ee6457804144add92bfc47563cf60c443e4f +Subproject commit 8d510200812366931e7becb743cdbaea51174999 diff --git a/test.py b/test.py index 99bc7cf629..c1455dc759 100755 --- a/test.py +++ b/test.py @@ -392,6 +392,7 @@ tests_good = [ "threads/threads/threadmanidlist", "threads/threads/threadmanidtype", "threads/threads/threads", + "threads/tls/allocate", "threads/tls/create", "threads/tls/partition", "threads/tls/kernel/partition",