From 7b130700f673c36b0c89b11c52da8e015b0e9ba8 Mon Sep 17 00:00:00 2001 From: Aryan Sharma Date: Fri, 9 Oct 2026 11:04:35 +0530 Subject: [PATCH 1/3] feat(libc): implement malloc_stats_fast through the application allocator A title that replaces the allocator through its libc parameters also supplies malloc_stats_fast in that table, and PPSA28997 calls it. It is now forwarded to the title's function. Without a replacement allocator that provides it, the call throws. --- core/libs/prx/libc/include/ApplicationHeap.hpp | 1 + core/libs/prx/libc/src/ApplicationHeap.cpp | 15 +++++++++++++++ core/libs/prx/libc/src/FileAndHeap.cpp | 4 ++++ 3 files changed, 20 insertions(+) diff --git a/core/libs/prx/libc/include/ApplicationHeap.hpp b/core/libs/prx/libc/include/ApplicationHeap.hpp index 79398ada9..22212a31d 100644 --- a/core/libs/prx/libc/include/ApplicationHeap.hpp +++ b/core/libs/prx/libc/include/ApplicationHeap.hpp @@ -15,6 +15,7 @@ void* ApplicationHeapAlign_nid_no_patch(std::size_t alignment, std::size_t bytes void* ApplicationHeapRealign_nid_no_patch(void* pointer, std::size_t bytes, std::size_t alignment); void* ApplicationHeapCalloc_nid_no_patch(std::size_t count, std::size_t bytes); int ApplicationHeapPosixAlign_nid_no_patch(void** pointer, std::size_t alignment, std::size_t bytes); +int ApplicationHeapStatsFast_nid_no_patch(void* stats); } diff --git a/core/libs/prx/libc/src/ApplicationHeap.cpp b/core/libs/prx/libc/src/ApplicationHeap.cpp index cc12f6571..4812d1a34 100644 --- a/core/libs/prx/libc/src/ApplicationHeap.cpp +++ b/core/libs/prx/libc/src/ApplicationHeap.cpp @@ -22,6 +22,7 @@ using Align = void* (APS5_VABI *)(std::size_t, std::size_t); using Realign = void* (APS5_VABI *)(void*, std::size_t, std::size_t); using PosixAlign = int (APS5_VABI *)(void**, std::size_t, std::size_t); using Initialize = void (APS5_VABI *)(); +using Stats = int (APS5_VABI *)(void*); std::mutex heapMutex; std::array heapApi{}; @@ -233,3 +234,17 @@ int ApplicationHeapPosixAlign_nid_no_patch(void** pointer, std::size_t alignment *pointer = result; return 0; } + +int ApplicationHeapStatsFast_nid_no_patch(void* stats) { + Stats statsFast; + { + std::lock_guard lock(heapMutex); + if (heapFailure) std::rethrow_exception(heapFailure); + if (heapFinalized) throw std::runtime_error("application heap: allocator has been finalized"); + if (heapApi[0] == nullptr) throw std::runtime_error("application heap: allocator API is not registered"); + if (heapApi[8] == nullptr) throw std::runtime_error("malloc_stats_fast: implemented only for an application allocator that provides it"); + std::memcpy(&statsFast, &heapApi[8], sizeof(statsFast)); + } + CallbackScope scope; + return statsFast(stats); +} diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index 7c15cffde..e6f82f47a 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -232,6 +232,10 @@ int APS5_VABI fflush_nid_postfix(FileStream* stream) { return 0; } +int APS5_VABI malloc_stats_fast_nid_postfix(void* stats) { + return ApplicationHeapStatsFast_nid_no_patch(stats); +} + void* APS5_VABI malloc_nid_postfix(size_t size) { return ApplicationHeapAllocate_nid_no_patch(size); } From bdeef942f767cf7a9e330fa1adf315e0a95d5f6a Mon Sep 17 00:00:00 2001 From: Aryan Sharma Date: Fri, 9 Oct 2026 15:58:34 +0530 Subject: [PATCH 2/3] test(libc): check malloc_stats_fast forwarding and its error paths --- core/libs/prx/libc/CMakeLists.txt | 2 ++ core/libs/tests/ApplicationHeap.cpp | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 30aaf71ba..783bba931 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -334,8 +334,10 @@ if(BUILD_TESTING) add_test(NAME application_heap_invalid_metadata COMMAND application_heap_tests invalid) add_test(NAME application_heap_default COMMAND application_heap_tests default) add_test(NAME application_heap_exit_order COMMAND application_heap_tests exit-order) + add_test(NAME application_heap_missing_stats COMMAND application_heap_tests missing-stats) if(WIN32) set_tests_properties(application_heap_replacement application_heap_invalid_metadata application_heap_default application_heap_exit_order + application_heap_missing_stats PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() if(CMAKE_SYSTEM_NAME STREQUAL "Linux") diff --git a/core/libs/tests/ApplicationHeap.cpp b/core/libs/tests/ApplicationHeap.cpp index 7ea5cec5b..c9785d02e 100644 --- a/core/libs/tests/ApplicationHeap.cpp +++ b/core/libs/tests/ApplicationHeap.cpp @@ -23,6 +23,7 @@ char* APS5_VABI strdup_nid_postfix(const char*); char* APS5_VABI strndup_nid_postfix(const char*, std::size_t); int* APS5_VABI __error_nid_postfix(); int APS5_VABI atexit_nid_postfix(void (APS5_VABI*)()); +int APS5_VABI malloc_stats_fast_nid_postfix(void*); } namespace { @@ -33,6 +34,9 @@ std::size_t lastAlignment = 0; unsigned posixCalls = 0; unsigned initializes = 0; unsigned frees = 0; +unsigned statsCalls = 0; +void* lastStats = nullptr; +bool statsAllocate = false; bool fail = false; bool recurse = false; bool nullPosixResult = false; @@ -93,6 +97,13 @@ int APS5_VABI posixAlign(void** pointer, std::size_t alignment, std::size_t byte return 0; } +int APS5_VABI statsFast(void* stats) { + ++statsCalls; + lastStats = stats; + if (statsAllocate) ApplicationHeapAllocate_nid_no_patch(16); + return 0x2a; +} + template void write(std::array& data, std::size_t offset, TValue value) { require(offset <= data.size() && sizeof(value) <= data.size() - offset); @@ -103,6 +114,7 @@ void write(std::array& data, std::size_t offset, TValue value) int main(int argc, char** argv) { reject([] { ApplicationHeapAllocate_nid_no_patch(64); }); + reject([] { malloc_stats_fast_nid_postfix(storage.data()); }); reject([] { ApplicationHeapRegister_nid_no_patch(nullptr); }); std::array process{}; std::array libc{}; @@ -123,6 +135,7 @@ int main(int argc, char** argv) { write(replacement, 0x40, &align); write(replacement, 0x48, &realign); write(replacement, 0x50, &posixAlign); + write(replacement, 0x60, &statsFast); if (argc > 1 && std::strcmp(argv[1], "exit-order") == 0) { require(atexit_nid_postfix(exitCallbackAllocates) == 0); ApplicationHeapInitialize_nid_no_patch(process.data()); @@ -159,10 +172,19 @@ int main(int argc, char** argv) { ApplicationHeapFree_nid_no_patch(nullptr); reject([] { ApplicationHeapCalloc_nid_no_patch(SIZE_MAX, 2); }); reject([] { ApplicationHeapAlign_nid_no_patch(3, 16); }); + reject([] { malloc_stats_fast_nid_postfix(storage.data()); }); ApplicationHeapInitialize_nid_no_patch(process.data()); require(initializes == 1); return 0; } + if (argc > 1 && std::strcmp(argv[1], "missing-stats") == 0) { + write(replacement, 0x60, static_cast(nullptr)); + ApplicationHeapInitialize_nid_no_patch(process.data()); + require(ApplicationHeapAllocate_nid_no_patch(16) == storage.data()); + reject([] { malloc_stats_fast_nid_postfix(storage.data()); }); + require(statsCalls == 0); + return 0; + } if (argc > 1) { write(replacement, 8, std::uint64_t{99}); reject([&] { ApplicationHeapInitialize_nid_no_patch(process.data()); }); @@ -175,6 +197,12 @@ int main(int argc, char** argv) { ApplicationHeapInitialize_nid_no_patch(process.data()); ApplicationHeapInitialize_nid_no_patch(process.data()); require(initializes == 1); + std::array stats{}; + require(malloc_stats_fast_nid_postfix(stats.data()) == 0x2a && statsCalls == 1 && lastStats == stats.data()); + statsAllocate = true; + reject([&] { malloc_stats_fast_nid_postfix(stats.data()); }); + statsAllocate = false; + require(statsCalls == 2 && malloc_stats_fast_nid_postfix(nullptr) == 0x2a && statsCalls == 3 && lastStats == nullptr); void* pointer = ApplicationHeapAlign_nid_no_patch(4, 64); require(pointer == storage.data() && lastAlignment == 4 && lastSize == 64); release(pointer); From 7fc6fac1af58ef537a60697d0ca08181ff1218a2 Mon Sep 17 00:00:00 2001 From: Aryan Sharma Date: Fri, 9 Oct 2026 16:22:39 +0530 Subject: [PATCH 3/3] test(libc): move the malloc_stats_fast checks to their own test --- core/libs/prx/libc/CMakeLists.txt | 13 ++- core/libs/tests/ApplicationHeap.cpp | 28 ------- core/libs/tests/ApplicationHeapStats.cpp | 102 +++++++++++++++++++++++ 3 files changed, 113 insertions(+), 30 deletions(-) create mode 100644 core/libs/tests/ApplicationHeapStats.cpp diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 783bba931..75f90e9c5 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -334,10 +334,19 @@ if(BUILD_TESTING) add_test(NAME application_heap_invalid_metadata COMMAND application_heap_tests invalid) add_test(NAME application_heap_default COMMAND application_heap_tests default) add_test(NAME application_heap_exit_order COMMAND application_heap_tests exit-order) - add_test(NAME application_heap_missing_stats COMMAND application_heap_tests missing-stats) if(WIN32) set_tests_properties(application_heap_replacement application_heap_invalid_metadata application_heap_default application_heap_exit_order - application_heap_missing_stats + PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() + add_test_executable(application_heap_stats_tests ${LIBS_INCLUDE_DIR}/tests/ApplicationHeapStats.cpp) + target_include_directories(application_heap_stats_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(application_heap_stats_tests PRIVATE libc) + configure_windows_unwind(application_heap_stats_tests) + add_test(NAME application_heap_stats COMMAND application_heap_stats_tests) + add_test(NAME application_heap_stats_default COMMAND application_heap_stats_tests default) + add_test(NAME application_heap_stats_missing COMMAND application_heap_stats_tests missing) + if(WIN32) + set_tests_properties(application_heap_stats application_heap_stats_default application_heap_stats_missing PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() if(CMAKE_SYSTEM_NAME STREQUAL "Linux") diff --git a/core/libs/tests/ApplicationHeap.cpp b/core/libs/tests/ApplicationHeap.cpp index c9785d02e..7ea5cec5b 100644 --- a/core/libs/tests/ApplicationHeap.cpp +++ b/core/libs/tests/ApplicationHeap.cpp @@ -23,7 +23,6 @@ char* APS5_VABI strdup_nid_postfix(const char*); char* APS5_VABI strndup_nid_postfix(const char*, std::size_t); int* APS5_VABI __error_nid_postfix(); int APS5_VABI atexit_nid_postfix(void (APS5_VABI*)()); -int APS5_VABI malloc_stats_fast_nid_postfix(void*); } namespace { @@ -34,9 +33,6 @@ std::size_t lastAlignment = 0; unsigned posixCalls = 0; unsigned initializes = 0; unsigned frees = 0; -unsigned statsCalls = 0; -void* lastStats = nullptr; -bool statsAllocate = false; bool fail = false; bool recurse = false; bool nullPosixResult = false; @@ -97,13 +93,6 @@ int APS5_VABI posixAlign(void** pointer, std::size_t alignment, std::size_t byte return 0; } -int APS5_VABI statsFast(void* stats) { - ++statsCalls; - lastStats = stats; - if (statsAllocate) ApplicationHeapAllocate_nid_no_patch(16); - return 0x2a; -} - template void write(std::array& data, std::size_t offset, TValue value) { require(offset <= data.size() && sizeof(value) <= data.size() - offset); @@ -114,7 +103,6 @@ void write(std::array& data, std::size_t offset, TValue value) int main(int argc, char** argv) { reject([] { ApplicationHeapAllocate_nid_no_patch(64); }); - reject([] { malloc_stats_fast_nid_postfix(storage.data()); }); reject([] { ApplicationHeapRegister_nid_no_patch(nullptr); }); std::array process{}; std::array libc{}; @@ -135,7 +123,6 @@ int main(int argc, char** argv) { write(replacement, 0x40, &align); write(replacement, 0x48, &realign); write(replacement, 0x50, &posixAlign); - write(replacement, 0x60, &statsFast); if (argc > 1 && std::strcmp(argv[1], "exit-order") == 0) { require(atexit_nid_postfix(exitCallbackAllocates) == 0); ApplicationHeapInitialize_nid_no_patch(process.data()); @@ -172,19 +159,10 @@ int main(int argc, char** argv) { ApplicationHeapFree_nid_no_patch(nullptr); reject([] { ApplicationHeapCalloc_nid_no_patch(SIZE_MAX, 2); }); reject([] { ApplicationHeapAlign_nid_no_patch(3, 16); }); - reject([] { malloc_stats_fast_nid_postfix(storage.data()); }); ApplicationHeapInitialize_nid_no_patch(process.data()); require(initializes == 1); return 0; } - if (argc > 1 && std::strcmp(argv[1], "missing-stats") == 0) { - write(replacement, 0x60, static_cast(nullptr)); - ApplicationHeapInitialize_nid_no_patch(process.data()); - require(ApplicationHeapAllocate_nid_no_patch(16) == storage.data()); - reject([] { malloc_stats_fast_nid_postfix(storage.data()); }); - require(statsCalls == 0); - return 0; - } if (argc > 1) { write(replacement, 8, std::uint64_t{99}); reject([&] { ApplicationHeapInitialize_nid_no_patch(process.data()); }); @@ -197,12 +175,6 @@ int main(int argc, char** argv) { ApplicationHeapInitialize_nid_no_patch(process.data()); ApplicationHeapInitialize_nid_no_patch(process.data()); require(initializes == 1); - std::array stats{}; - require(malloc_stats_fast_nid_postfix(stats.data()) == 0x2a && statsCalls == 1 && lastStats == stats.data()); - statsAllocate = true; - reject([&] { malloc_stats_fast_nid_postfix(stats.data()); }); - statsAllocate = false; - require(statsCalls == 2 && malloc_stats_fast_nid_postfix(nullptr) == 0x2a && statsCalls == 3 && lastStats == nullptr); void* pointer = ApplicationHeapAlign_nid_no_patch(4, 64); require(pointer == storage.data() && lastAlignment == 4 && lastSize == 64); release(pointer); diff --git a/core/libs/tests/ApplicationHeapStats.cpp b/core/libs/tests/ApplicationHeapStats.cpp new file mode 100644 index 000000000..42d97d01d --- /dev/null +++ b/core/libs/tests/ApplicationHeapStats.cpp @@ -0,0 +1,102 @@ +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI malloc_stats_fast_nid_postfix(void*); +} + +namespace { + +alignas(64) std::array storage{}; +unsigned statsCalls = 0; +void* lastStats = nullptr; +bool statsAllocate = false; + +void require(bool condition) { + if (!condition) throw std::runtime_error("application heap stats test failed"); +} + +template +void reject(TAction action) { + bool rejected = false; + try { action(); } catch (const std::exception&) { rejected = true; } + require(rejected); +} + +void* APS5_VABI allocate(std::size_t) { return storage.data(); } +void APS5_VABI release(void*) {} +void* APS5_VABI reallocate(void*, std::size_t) { return storage.data(); } +void* APS5_VABI allocateZeroed(std::size_t, std::size_t) { return storage.data(); } +void* APS5_VABI align(std::size_t, std::size_t) { return storage.data(); } +void* APS5_VABI realign(void*, std::size_t, std::size_t) { return storage.data(); } +int APS5_VABI posixAlign(void** pointer, std::size_t, std::size_t) { + *pointer = storage.data(); + return 0; +} + +int APS5_VABI statsFast(void* stats) { + ++statsCalls; + lastStats = stats; + if (statsAllocate) ApplicationHeapAllocate_nid_no_patch(16); + return 0x2a; +} + +template +void write(std::array& data, std::size_t offset, TValue value) { + require(offset <= data.size() && sizeof(value) <= data.size() - offset); + std::memcpy(data.data() + offset, &value, sizeof(value)); +} + +} + +int main(int argc, char** argv) { + std::array stats{}; + reject([&] { malloc_stats_fast_nid_postfix(stats.data()); }); + std::array process{}; + std::array libc{}; + std::array replacement{}; + write(process, 0, std::uint64_t{0x40}); + write(process, 8, std::uint32_t{0x4942524f}); + write(process, 0x38, libc.data()); + write(libc, 0, std::uint64_t{0x38}); + write(libc, 0x30, replacement.data()); + write(replacement, 0, std::uint64_t{0x78}); + write(replacement, 8, std::uint64_t{2}); + write(replacement, 0x20, &allocate); + write(replacement, 0x28, &release); + write(replacement, 0x30, &allocateZeroed); + write(replacement, 0x38, &reallocate); + write(replacement, 0x40, &align); + write(replacement, 0x48, &realign); + write(replacement, 0x50, &posixAlign); + write(replacement, 0x60, &statsFast); + if (argc > 1 && std::strcmp(argv[1], "default") == 0) { + std::memset(replacement.data() + 0x20, 0, 10 * sizeof(void*)); + ApplicationHeapInitialize_nid_no_patch(process.data()); + require(ApplicationHeapAllocate_nid_no_patch(16) != nullptr); + reject([&] { malloc_stats_fast_nid_postfix(stats.data()); }); + return 0; + } + if (argc > 1 && std::strcmp(argv[1], "missing") == 0) { + write(replacement, 0x60, static_cast(nullptr)); + ApplicationHeapInitialize_nid_no_patch(process.data()); + require(ApplicationHeapAllocate_nid_no_patch(16) == storage.data()); + reject([&] { malloc_stats_fast_nid_postfix(stats.data()); }); + require(statsCalls == 0); + return 0; + } + ApplicationHeapInitialize_nid_no_patch(process.data()); + require(malloc_stats_fast_nid_postfix(stats.data()) == 0x2a && statsCalls == 1 && lastStats == stats.data()); + require(malloc_stats_fast_nid_postfix(nullptr) == 0x2a && statsCalls == 2 && lastStats == nullptr); + statsAllocate = true; + reject([&] { malloc_stats_fast_nid_postfix(stats.data()); }); + statsAllocate = false; + require(statsCalls == 3); + require(malloc_stats_fast_nid_postfix(stats.data()) == 0x2a && statsCalls == 4); + require(ApplicationHeapAllocate_nid_no_patch(16) == storage.data()); +}