From 0f77f3e07f7dcc4e8eb03c08d8fe762c68f230ed Mon Sep 17 00:00:00 2001 From: fdfawef324 <287205748+fdfawef324@users.noreply.github.com> Date: Tue, 13 Oct 2026 02:10:53 -0400 Subject: [PATCH] feat(libSceDbgThreadSanitizer): declare the TSan annotation calls and report the sanitizer disabled Black Myth: Wukong (PPSA23226) exits at start-up with STATUS_DLL_NOT_FOUND after "Failed to load module: libs\libSceDbgThreadSanitizer.prx". The executable does not need the library: its bundled sce_module/libSceJobManager_nosubmission.prx lists it in DT_NEEDED and imports __tsan_acquire and __tsan_release from it, plus sceKernelIsThreadSanitizerEnabled from libkernel. The relinker converts every module in sce_module and loads it at start-up (only --module-dir directories are deferred), and GuestModuleBuilder adds each module's host dependencies to the executable's DT_NEEDED, which the Windows start-up stub loads. libSceDbgThreadSanitizer now exports __tsan_acquire and __tsan_release with the void(void* addr) signatures of LLVM compiler-rt's tsan_interface.h (llvm-project 87f0227cb601, LLVM 20.1.8). Both throw: AnyPS5 runs no race detector, and the module calls them only after sceKernelIsThreadSanitizerEnabled reports the sanitizer enabled. libkernel's sceKernelIsThreadSanitizerEnabled returns 0, as sceKernelIsAddressSanitizerEnabled does; its int(void) signature is assumed and recorded in the libkernel debt file. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceDbgThreadSanitizer/Export.cpp | 14 ++++++++++++++ core/libs/prx/libkernel/CMakeLists.txt | 9 +++++++++ core/libs/prx/libkernel/System/src/Sanitizer.cpp | 4 ++++ core/libs/tests/GuestThreadSanitizer.cpp | 9 +++++++++ docs/dev/debt/libSceDbgThreadSanitizer.md | 5 +++++ docs/dev/debt/libkernel.md | 2 ++ 6 files changed, 43 insertions(+) create mode 100644 core/libs/prx/libSceDbgThreadSanitizer/Export.cpp create mode 100644 core/libs/tests/GuestThreadSanitizer.cpp create mode 100644 docs/dev/debt/libSceDbgThreadSanitizer.md diff --git a/core/libs/prx/libSceDbgThreadSanitizer/Export.cpp b/core/libs/prx/libSceDbgThreadSanitizer/Export.cpp new file mode 100644 index 000000000..f2bbed9a3 --- /dev/null +++ b/core/libs/prx/libSceDbgThreadSanitizer/Export.cpp @@ -0,0 +1,14 @@ +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +void APS5_VABI __tsan_acquire(void*) { + NotImplemented_nid_no_patch(__func__); +} + +void APS5_VABI __tsan_release(void*) { + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 33f26ae0a..866c15640 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -952,6 +952,15 @@ if(BUILD_TESTING) set_tests_properties(guest_software_version PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_thread_sanitizer_tests ${LIBS_INCLUDE_DIR}/tests/GuestThreadSanitizer.cpp) + target_include_directories(guest_thread_sanitizer_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_thread_sanitizer_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_thread_sanitizer_tests) + add_test(NAME guest_thread_sanitizer COMMAND guest_thread_sanitizer_tests) + if(WIN32) + set_tests_properties(guest_thread_sanitizer PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_system_command_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemCommand.cpp) target_include_directories(guest_system_command_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_system_command_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/System/src/Sanitizer.cpp b/core/libs/prx/libkernel/System/src/Sanitizer.cpp index 15f2d3f5f..0586eef7d 100644 --- a/core/libs/prx/libkernel/System/src/Sanitizer.cpp +++ b/core/libs/prx/libkernel/System/src/Sanitizer.cpp @@ -6,6 +6,10 @@ int APS5_VABI sceKernelIsAddressSanitizerEnabled(void) { return 0; } +int APS5_VABI sceKernelIsThreadSanitizerEnabled(void) { + return 0; +} + MallocReplace* APS5_VABI sceKernelGetSanitizerMallocReplaceExternal(void) { static MallocReplace replacement{}; return &replacement; diff --git a/core/libs/tests/GuestThreadSanitizer.cpp b/core/libs/tests/GuestThreadSanitizer.cpp new file mode 100644 index 000000000..4403a3156 --- /dev/null +++ b/core/libs/tests/GuestThreadSanitizer.cpp @@ -0,0 +1,9 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +extern "C" int APS5_VABI sceKernelIsThreadSanitizerEnabled(void); + +int main() { + if (sceKernelIsThreadSanitizerEnabled() != 0) std::abort(); + return 0; +} diff --git a/docs/dev/debt/libSceDbgThreadSanitizer.md b/docs/dev/debt/libSceDbgThreadSanitizer.md new file mode 100644 index 000000000..5dc8905e1 --- /dev/null +++ b/docs/dev/debt/libSceDbgThreadSanitizer.md @@ -0,0 +1,5 @@ +# libSceDbgThreadSanitizer technical debt + +### Unknown function info + +- [__tsan_acquire, __tsan_release](../../../core/libs/prx/libSceDbgThreadSanitizer/Export.cpp) (libSceDbgThreadSanitizer, NIDs `XyX1104ACcU` and `IkoI44e0OXc`) - declared as throwing stubs: AnyPS5 runs no race detector. The `void (void* addr)` signatures are those of LLVM compiler-rt's [tsan_interface.h](https://github.com/llvm/llvm-project/blob/87f0227cb60147a26a1eeb4fb06e3b505e9c7261/compiler-rt/include/sanitizer/tsan_interface.h#L24-L25), whose names hash to the imported NIDs; that the console library has the same signatures is assumed. PPSA23226's bundled `libSceJobManager_nosubmission.prx` imports both and calls them only after [sceKernelIsThreadSanitizerEnabled](../../../core/libs/prx/libkernel/System/src/Sanitizer.cpp) reports the sanitizer enabled, which it never does here diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 111da3fce..4d4c9aa81 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -7,6 +7,7 @@ - [sceKernelGetMainSocId and sceKernelGetCpumode](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) both return 0, and [sceKernelGetCpuFrequency](../../../core/libs/prx/libkernel/Time/Time.cpp) returns the host TSC frequency measured for `sceKernelGetTscFrequency`, not the console CPU clock. The PS5's SoC id and CPU mode are unknown; shadPS4's PS4 values are [0x710f10/0x740f30 and 0, 2 or 5 from the param.sfo CPU-mode flags](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1a/src/core/libraries/kernel/process.cpp). PPSA99203 only logs them (`[console] main SoC id: result=%#x out=%#x`, `CPU: %llu Hz, cpumode %d`), passing a pointer that `sceKernelGetMainSocId` leaves untouched - [sceKernelEnableDmemAliasing](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) returns 0 and changes nothing: direct memory can always be mapped more than once here, as `DoMapDirect` in [DirectMemory.cpp](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) only checks that the physical pages are allocated, so there is no aliasing to turn on. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1aacbc8db8253be685bf7ab4d9daae/src/core/libraries/kernel/memory.cpp#L39-L43) (PS4) sets a flag there that lifts the `EBUSY` its [`sceKernelMapDirectMemory2`](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1aacbc8db8253be685bf7ab4d9daae/src/core/libraries/kernel/memory.cpp#L299-L301), and [`sceKernelMapDirectMemory` for SDK 2.50 and later](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1aacbc8db8253be685bf7ab4d9daae/src/core/libraries/kernel/memory.cpp#L237-L242), return for direct memory that is [already mapped](https://github.com/shadps4-emu/shadPS4/blob/e0a466753a1aacbc8db8253be685bf7ab4d9daae/src/core/memory.cpp#L548-L553); that check is not applied here. The no-argument `int` signature follows shadPS4 and ProsperoEden (PPSA99008), which calls it before mapping the same direct memory again. Whether the PS5 refuses such a mapping without the call is unverified - [sceKernelInternalMemoryGetAvailableSize](../../../core/libs/prx/libkernel/DirectMemory/Export.cpp) (libkernel) reports 0 bytes and returns 0: what the console's internal memory pool holds and how large it is are unknown. The `int (size_t*)` signature is from [xUtilty](https://github.com/Backporter/xUtilty/blob/328a856af1ef/source/SystemWrapper.cpp#L85) (PS4). PPSA99203 calls it once beside `sceKernelAvailableFlexibleMemorySize` while starting its GS renderer and never reads the result ([GS.cpp](https://github.com/Swordpdf/PS5SX2/blob/b4cf55e87c34/pcsx2/GS/GS.cpp#L233-L247)) +- [sceKernelIsThreadSanitizerEnabled](../../../core/libs/prx/libkernel/System/src/Sanitizer.cpp) (libkernel, NID `LzoM-wVLJDE`) returns 0, as `sceKernelIsAddressSanitizerEnabled` does: a converted title never runs under the thread sanitizer, so a title that checks it skips the [libSceDbgThreadSanitizer](../../../core/libs/prx/libSceDbgThreadSanitizer/Export.cpp) annotation calls, which throw. PPSA23226's bundled `libSceJobManager_nosubmission.prx` makes those calls only when it reports nonzero ### Unknown function info @@ -86,6 +87,7 @@ - [LinuxProtFromSce](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - protection bits 0x100 and 0x200 are accepted and assumed not to affect host CPU access; host protection is derived only from the read, write and execute bits - [sched_get_priority_max, sched_get_priority_min](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - measured on a PS5 (firmware 13.60): `SCHED_FIFO` (1) and `SCHED_RR` (3) report a maximum of 767 and a minimum of 256, and every other policy, `SCHED_OTHER` (2) included, returns -1 with `EINVAL`. `pthread_setschedparam` ignores the policy and does not check the priority, where the console rejects `SCHED_OTHER` and priorities below 256 with `EINVAL` - [inet_pton, inet_ntop](../../../core/libs/prx/libkernel/Socket/src/AddressConversion.cpp) (libkernel) - the text forms are assumed to be those of FreeBSD 11.4 and are unverified on a console: `inet_pton` accepts only four decimal parts without leading zeros for `AF_INET` and the RFC 4291 forms for `AF_INET6` (one `::` standing for at least one group, a dotted-quad tail only as the last 32 bits, no zone id), and `inet_ntop` prints lowercase hex with the first longest run of two or more zero groups compressed, and a dotted tail only for `::a.b.c.d` and `::ffff:a.b.c.d`. They no longer depend on the host resolver library +- [sceKernelIsThreadSanitizerEnabled](../../../core/libs/prx/libkernel/System/src/Sanitizer.cpp) (libkernel, NID `LzoM-wVLJDE`) - no source gives the signature; `int (void)` is assumed from `sceKernelIsAddressSanitizerEnabled` and from PPSA23226's bundled `libSceJobManager_nosubmission.prx`, which passes no arguments and tests only the 32-bit result ### Functional