From 28320acf2c07319d88d61551394576d2b1a6f29f Mon Sep 17 00:00:00 2001 From: gookie <36005381+gookie-dev@users.noreply.github.com> Date: Sun, 11 Oct 2026 13:59:58 +0200 Subject: [PATCH] perf(libkernel): read a loaded image's unwind tables once in sceKernelGetModuleInfoFromAddr sceKernelGetModuleInfoFromAddr found the size of the image's .eh_frame on every call by walking all of its CIE and FDE records, checking each against every PT_LOAD header. The guest libc's unwinder calls it for each frame it unwinds, so a title that unwinds often walked its whole .eh_frame hundreds of times per second. The tables read from a .eh_frame_hdr are now kept per header address and reused while the image stays loaded. The cache is dropped whenever dl_iterate_phdr reports that an object was unloaded (dlpi_subs), so an address reused by another image is read again. guest_module_info queries a module twice and expects the same tables, then unloads it, loads it again and expects tables of the same size at the same offset in the new image. PPSA16827 (Harmony's Odyssey), title screen, over 500 calls per second counted under gdb, which slows the title: the process used 9269 CPU samples per 10 s, 53.7% of them in this walk, and 3306 with this change, 3.2% in the lookup. The title still runs at 27 FPS; its frame rate is limited elsewhere. --- .../prx/libkernel/Module/src/ModuleInfo.cpp | 23 +++++++++++++++++-- core/libs/tests/GuestModuleInfo.cpp | 11 +++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libkernel/Module/src/ModuleInfo.cpp b/core/libs/prx/libkernel/Module/src/ModuleInfo.cpp index 66dc83653..b83f9a11f 100644 --- a/core/libs/prx/libkernel/Module/src/ModuleInfo.cpp +++ b/core/libs/prx/libkernel/Module/src/ModuleInfo.cpp @@ -22,7 +22,9 @@ #else #include #include +#include #include +#include #endif extern "C" std::int32_t ModuleIdForImage_nid_no_patch(const void* native); @@ -81,6 +83,24 @@ bool Contains(const dl_phdr_info& image, std::uintptr_t begin, std::uint64_t siz return false; } +struct FrameTableCache { + std::mutex mutex; + unsigned long long unloads = 0; + std::unordered_map tables; +}; + +EhFrame::Tables FrameTables(const dl_phdr_info& image, std::uintptr_t header) { + static auto& cache = *new FrameTableCache(); + std::lock_guard lock(cache.mutex); + if (cache.unloads != image.dlpi_subs) { + cache.tables.clear(); + cache.unloads = image.dlpi_subs; + } + if (const auto found = cache.tables.find(header); found != cache.tables.end()) return found->second; + const auto contains = [&](std::uintptr_t begin, std::uint64_t size) { return Contains(image, begin, size); }; + return cache.tables.emplace(header, EhFrame::ReadTables(contains, header, Caller)).first->second; +} + std::string ImageName(const dl_phdr_info& image) { std::string path = image.dlpi_name ? image.dlpi_name : ""; if (path.empty()) { @@ -111,8 +131,7 @@ void Fill(const dl_phdr_info& image, TInfo& info) { info.tls_size = ToU32(header.p_memsz, "TLS size"); info.tls_align = ToU32(header.p_align, "TLS alignment"); } else if (header.p_type == PT_GNU_EH_FRAME) { - const auto contains = [&](std::uintptr_t begin, std::uint64_t size) { return Contains(image, begin, size); }; - const auto tables = EhFrame::ReadTables(contains, address, Caller); + const auto tables = FrameTables(image, address); info.eh_frame_hdr_addr = address; info.eh_frame_hdr_size = ToU32(header.p_memsz, "eh_frame_hdr size"); info.eh_frame_addr = tables.frames; diff --git a/core/libs/tests/GuestModuleInfo.cpp b/core/libs/tests/GuestModuleInfo.cpp index b63e20a04..40eacdf81 100644 --- a/core/libs/tests/GuestModuleInfo.cpp +++ b/core/libs/tests/GuestModuleInfo.cpp @@ -114,5 +114,16 @@ int main(int argc, char** argv) { ModuleInfoEx unset{}; unset.st_size = 0xf0000; Require(sceKernelGetModuleInfoFromAddr(reinterpret_cast(add), 2, &unset) == 0 && unset.st_size == sizeof(ModuleInfoEx)); + const auto again = Query(add, 0); + Require(again.eh_frame_hdr_addr == info.eh_frame_hdr_addr && again.eh_frame_hdr_size == info.eh_frame_hdr_size); + Require(again.eh_frame_addr == info.eh_frame_addr && again.eh_frame_size == info.eh_frame_size); Require(dlclose_nid_postfix(module) == 0); + void* reloaded = dlopen_nid_postfix(argv[1], 2); + Require(reloaded != nullptr); + const void* reloadedAdd = dlsym_nid_postfix(reloaded, "GuestModuleAdd"); + Require(reloadedAdd != nullptr); + const auto fresh = Query(reloadedAdd, 0); + Require(fresh.eh_frame_size == info.eh_frame_size && fresh.eh_frame_hdr_size == info.eh_frame_hdr_size); + Require(fresh.eh_frame_addr - fresh.segments[0].address == info.eh_frame_addr - info.segments[0].address); + Require(dlclose_nid_postfix(reloaded) == 0); }