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 <noreply@anthropic.com>
This commit is contained in:
fdfawef324 2026-10-13 02:10:53 -04:00
parent ea2bbefb2b
commit 0f77f3e07f
6 changed files with 43 additions and 0 deletions

View File

@ -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__);
}
}

View File

@ -952,6 +952,15 @@ if(BUILD_TESTING)
set_tests_properties(guest_software_version PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
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:$<TARGET_FILE_DIR:libkernel>")
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)

View File

@ -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;

View File

@ -0,0 +1,9 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdlib>
extern "C" int APS5_VABI sceKernelIsThreadSanitizerEnabled(void);
int main() {
if (sceKernelIsThreadSanitizerEnabled() != 0) std::abort();
return 0;
}

View File

@ -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

View File

@ -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