diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 49a57bbf9..84ba3bd15 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -348,6 +348,17 @@ if(BUILD_TESTING) set_tests_properties(application_heap_replacement application_heap_invalid_metadata application_heap_default application_heap_exit_order 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") add_test_executable(cpu_topology_tests ${LIBS_INCLUDE_DIR}/tests/CpuTopology.cpp) target_include_directories(cpu_topology_tests PRIVATE ${LIBS_INCLUDE_DIR}) 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); } 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()); +}