diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index ce564fc87..fb689577f 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -567,6 +567,16 @@ if(BUILD_TESTING) set_tests_properties(log_macros PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_heap_report_for_game_tests ${LIBS_INCLUDE_DIR}/tests/GuestHeapReportForGame.cpp) + target_include_directories(guest_heap_report_for_game_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_heap_report_for_game_tests PRIVATE libc) + configure_windows_unwind(guest_heap_report_for_game_tests) + add_test(NAME guest_heap_report_for_game COMMAND guest_heap_report_for_game_tests) + if(WIN32) + set_tests_properties(guest_heap_report_for_game PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() if(WIN32) diff --git a/core/libs/prx/libc/src/HeapDiagnostics.cpp b/core/libs/prx/libc/src/HeapDiagnostics.cpp index 39aaf3951..135c46f92 100644 --- a/core/libs/prx/libc/src/HeapDiagnostics.cpp +++ b/core/libs/prx/libc/src/HeapDiagnostics.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace { @@ -28,18 +29,14 @@ void LibcHeapTraceInfo_nid_no_patch(LibcHeapInfo* info) { info->mstate_table = mstateTable.data(); } -// unknown signature void APS5_VABI sceLibcInternalBacktraceForGame_nid_postfix(const char* heapName) { - (void)heapName; - NotImplemented_nid_no_patch(__func__); + std::fprintf(stderr, "[%s] backtrace requested from guest 0x%llx\n", heapName ? heapName : "", + static_cast(reinterpret_cast(__builtin_return_address(0)))); } -// unknown signature void APS5_VABI sceLibcInternalHeapErrorReportForGame_nid_postfix(void* heap, void* block, std::uint32_t error) { - (void)heap; - (void)block; - (void)error; - NotImplemented_nid_no_patch(__func__); + std::fprintf(stderr, "[libc] heap error 0x%x: heap %p, block %p, reported from guest 0x%llx\n", error, heap, block, + static_cast(reinterpret_cast(__builtin_return_address(0)))); } } diff --git a/core/libs/tests/GuestHeapReportForGame.cpp b/core/libs/tests/GuestHeapReportForGame.cpp new file mode 100644 index 000000000..9238d7bdb --- /dev/null +++ b/core/libs/tests/GuestHeapReportForGame.cpp @@ -0,0 +1,44 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +void APS5_VABI sceLibcInternalBacktraceForGame_nid_postfix(const char* heapName); +void APS5_VABI sceLibcInternalHeapErrorReportForGame_nid_postfix(void* heap, void* block, std::uint32_t error); +} + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stdout, "%s\n", message); + std::abort(); +} + +int main() { + const auto path = std::filesystem::temp_directory_path() / ("anyps5-heap-report-" + std::to_string(std::rand()) + ".txt"); + Require(std::freopen(path.string().c_str(), "w", stderr) != nullptr, "stderr redirection failed"); + + char heapName[] = "SceLibcHeap"; + sceLibcInternalBacktraceForGame_nid_postfix(heapName); + sceLibcInternalBacktraceForGame_nid_postfix(nullptr); + int heap = 0; + int block = 0; + sceLibcInternalHeapErrorReportForGame_nid_postfix(&heap, &block, 0x1234u); + std::fflush(stderr); + std::fclose(stderr); + + std::ifstream file(path); + std::stringstream text; + text << file.rdbuf(); + file.close(); + std::filesystem::remove(path); + const std::string report = text.str(); + Require(report.find("[SceLibcHeap] backtrace requested from guest 0x") != std::string::npos, "heap name not reported"); + Require(report.find("[] backtrace requested from guest 0x") != std::string::npos, "null heap name not reported"); + Require(report.find("[libc] heap error 0x1234: heap ") != std::string::npos, "heap error not reported"); + Require(std::string(heapName) == "SceLibcHeap", "heap name modified"); +} diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index 644922138..9f5db1ebe 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -6,8 +6,8 @@ - [nearbyintf](../../../core/libs/prx/libc/src/Math.cpp) (libc, NID `c+4r-T-tEIc`) uses the host rounding mode, not the console's; console rounding mode state remains unverified. - [_Thrd_sleep](../../../core/libs/prx/libc/src/Threads.cpp) (libc, NID `jfRI3snge3o`) follows the Dinkumware threads library shipped with [Visual C++ 2013](https://github.com/adityaramesh/concurrency_runtime/tree/master/VS2013_SP2/crt/src/thr) (`xtime.c`) and C11 Annex K, without console verification. It reads a 16-byte xtime (64-bit seconds and nanoseconds) on the guest `CLOCK_REALTIME`, normalizes the nanoseconds and sleeps through `nanosleep`, retrying on `EINTR`, so it is a cancellation point; a null xtime or one whose normalization overflows throws. The console's `_Thrd_sleep` (disassembled from a title's `libc.prx`) also takes an optional `xtime*` of remaining time and returns `int`; here the sleep is never cut short, so it returns 0 and leaves the remaining time untouched. - [Guest arena](../../../core/libs/prx/libc/src/GuestArena.cpp) (libc) - the application map area is assumed to end at 0xFC_0000_0000, as `SCE_KERNEL_APP_MAP_AREA_END_ADDR` does on the PS4. On Windows, host allocations made in the arena before libc loads stay, and a fixed guest mapping over one throws -- [sceLibcInternalBacktraceForGame](../../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature -- [sceLibcInternalHeapErrorReportForGame](../../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature +- [sceLibcInternalBacktraceForGame](../../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - `(const char* heapName)` and its return follow the `libc.prx` ASTRO BOT (PPSA21564 01.007.000) bundles: its four calls (0x6801, 0x7F46, 0xE75E, 0xEE02) are in the `malloc_stats` and `sceLibcMspaceMallocStats` paths, each after a `[%s] ...` line printed with the same heap name buffer, and the caller ignores the result and continues. The console's backtrace output is not reproduced: the heap name and the guest return address are written to the host standard error +- [sceLibcInternalHeapErrorReportForGame](../../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - `(void* heap, void* block, uint32 error)` follows the same `libc.prx` (call at 0xF634): it passes the heap, the block and the nonzero result of its block check, and then calls a function that never returns, so the report only has to return. The meaning of the error values is unknown; the three values and the guest return address are written to the host standard error - [malloc_usable_size](../../../core/libs/prx/libc/src/ApplicationHeap.cpp) (libc) - with the default allocator it returns the size the block was requested with (the range registered for it), not the rounded-up chunk size Sony's allocator reports; both satisfy "at least the requested size", and bytes past the request would not be write-watched. With an application allocator it calls entry 9 of the replacement table (`malloc_usable_size` in KytyPS5's `MallocReplace` order) and throws when the table leaves it null, like `malloc_stats_fast` - [reallocf](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libSceLibcInternal, implemented in libc) - follows FreeBSD 11 `lib/libc/stdlib/reallocf.c` on top of this libc's `realloc`, not measured on the console: size 0 frees the block and returns null as `realloc` does here, so the block is not freed twice; when `realloc` fails because the application allocator returns null or the default heap rejects the size, the block is freed through the same allocator, `errno` is `ENOMEM` and null is returned. Other failures (an exhausted guest arena, an unknown pointer) throw, as they do for `realloc` - [acosl](../../../core/libs/prx/libc/src/Math.cpp) (libc) - computed by the host's math library, like the other libc math functions here, not by the console's (FreeBSD msun ld80 code in Sony's `libc.prx`): results can differ from the console's in the last bit, and on Linux glibc sets `errno` to `EDOM` for `acosl` outside [-1, 1] where msun only raises the floating-point exception. `long double` is the x87 80-bit format, as Sony's `frexpl` uses it (`fldt 0x8(%rsp)`, result in `st(0)`)