feat(libc): implement malloc_usable_size

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Adrià Franch 2026-10-11 04:03:40 +02:00
parent aac0f1b810
commit 859f2f3b92
7 changed files with 54 additions and 1 deletions

View File

@ -16,6 +16,7 @@ void* ApplicationHeapRealign_nid_no_patch(void* pointer, std::size_t bytes, std:
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);
std::size_t ApplicationHeapUsableSize_nid_no_patch(void* pointer);
}

View File

@ -7,6 +7,7 @@ namespace GuestHeap {
extern "C" {
std::size_t GuestHeapUsableSize_nid_postfix(const void* pointer);
void* GuestHeapAllocate_nid_postfix(std::size_t bytes);
void GuestHeapFree_nid_postfix(void* pointer);
void* GuestHeapReallocate_nid_postfix(void* pointer, std::size_t bytes);

View File

@ -23,6 +23,7 @@ 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*);
using UsableSize = std::size_t (APS5_VABI *)(void*);
std::mutex heapMutex;
std::array<void*, 10> heapApi{};
@ -249,3 +250,20 @@ int ApplicationHeapStatsFast_nid_no_patch(void* stats) {
CallbackScope scope;
return statsFast(stats);
}
std::size_t ApplicationHeapUsableSize_nid_no_patch(void* pointer) {
UsableSize usableSize = nullptr;
{
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 != defaultApi()) {
if (heapApi[9] == nullptr) throw std::runtime_error("malloc_usable_size: implemented only for an application allocator that provides it");
std::memcpy(&usableSize, &heapApi[9], sizeof(usableSize));
}
}
if (usableSize == nullptr) return GuestHeap::GuestHeapUsableSize_nid_postfix(pointer);
CallbackScope scope;
return usableSize(pointer);
}

View File

@ -268,6 +268,10 @@ int APS5_VABI malloc_stats_fast_nid_postfix(void* stats) {
return ApplicationHeapStatsFast_nid_no_patch(stats);
}
size_t APS5_VABI malloc_usable_size_nid_postfix(void* pointer) {
return ApplicationHeapUsableSize_nid_no_patch(pointer);
}
void* APS5_VABI malloc_nid_postfix(size_t size) {
return ApplicationHeapAllocate_nid_no_patch(size);
}

View File

@ -263,6 +263,12 @@ void* GuestHeapRealign_nid_postfix(void* pointer, std::size_t bytes, std::size_t
return result;
}
std::size_t GuestHeapUsableSize_nid_postfix(const void* pointer) {
if (pointer == nullptr) return 0;
GuestAllocations::Mutation mutation;
return mutation.Find(pointer).bytes;
}
void* GuestHeapAlign_nid_postfix(std::size_t alignment, std::size_t bytes) {
GuestAllocations::Mutation mutation;
return allocate(mutation, alignment, bytes);

View File

@ -8,6 +8,7 @@
extern "C" {
int APS5_VABI malloc_stats_fast_nid_postfix(void*);
std::size_t APS5_VABI malloc_usable_size_nid_postfix(void*);
}
namespace {
@ -46,6 +47,15 @@ int APS5_VABI statsFast(void* stats) {
return 0x2a;
}
unsigned usableCalls = 0;
void* lastUsable = nullptr;
std::size_t APS5_VABI usableSize(void* pointer) {
++usableCalls;
lastUsable = pointer;
return 0x55;
}
template<typename TValue, std::size_t TSize>
void write(std::array<std::byte, TSize>& data, std::size_t offset, TValue value) {
require(offset <= data.size() && sizeof(value) <= data.size() - offset);
@ -75,19 +85,29 @@ int main(int argc, char** argv) {
write(replacement, 0x48, &realign);
write(replacement, 0x50, &posixAlign);
write(replacement, 0x60, &statsFast);
write(replacement, 0x68, &usableSize);
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()); });
auto* small = ApplicationHeapAllocate_nid_no_patch(16);
auto* large = ApplicationHeapAllocate_nid_no_patch(100000);
require(malloc_usable_size_nid_postfix(small) == 16 && malloc_usable_size_nid_postfix(large) == 100000);
require(malloc_usable_size_nid_postfix(nullptr) == 0 && usableCalls == 0);
ApplicationHeapFree_nid_no_patch(small);
ApplicationHeapFree_nid_no_patch(large);
return 0;
}
if (argc > 1 && std::strcmp(argv[1], "missing") == 0) {
write(replacement, 0x60, static_cast<void*>(nullptr));
write(replacement, 0x68, static_cast<void*>(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);
reject([&] { malloc_usable_size_nid_postfix(storage.data()); });
reject([&] { malloc_usable_size_nid_postfix(nullptr); });
require(statsCalls == 0 && usableCalls == 0);
return 0;
}
ApplicationHeapInitialize_nid_no_patch(process.data());
@ -99,4 +119,6 @@ int main(int argc, char** argv) {
require(statsCalls == 3);
require(malloc_stats_fast_nid_postfix(stats.data()) == 0x2a && statsCalls == 4);
require(ApplicationHeapAllocate_nid_no_patch(16) == storage.data());
require(malloc_usable_size_nid_postfix(storage.data()) == 0x55 && usableCalls == 1 && lastUsable == storage.data());
require(malloc_usable_size_nid_postfix(nullptr) == 0x55 && usableCalls == 2 && lastUsable == nullptr);
}

View File

@ -162,6 +162,7 @@ Throughout the project, every function at every stage either **does exactly what
- [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
- [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`
- [_sceLibcInternalThreadAtexit, _sceLibcInternalThreadDtors](../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - FreeBSD `__cxa_thread_atexit` semantics assumed; a null or non-image destructor and an allocation failure throw
- [_sceKernelRtldThreadAtexitIncrement, _sceKernelRtldThreadAtexitDecrement](../../core/libs/prx/libkernel/System/src/Rtld.cpp) (libkernel) - the argument is the `dso` address a title's `libc.prx` passes to `__cxa_thread_atexit` (PPSA14632), which fails when Increment returns nonzero. As in the [fpPS4](https://github.com/red-prig/fpPS4) reimplementation, Increment takes a reference on the loaded image containing the address and Decrement releases one; the variants without the leading underscore are assumed to match
- [sceImeGetPanelPositionAndForm](../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) - the output is taken to be the `PositionAndForm` that `sceImeDialogGetPanelPositionAndForm` fills; it is never written, since no panel can be open. That a closed panel is reported before a null output is assumed by analogy with `sceImeSetCaret`, not confirmed on a PS5 title