From 6d952a20b1c5799caaead80404c78e37c040bf21 Mon Sep 17 00:00:00 2001 From: Sinan KARAKAYA Date: Mon, 17 Aug 2026 18:28:58 +0200 Subject: [PATCH] Let an explicit function map override the JAL-target scan On an ELF with no symbols and no DWARF, parse() carves functions from JAL targets. Those carvings end at the next JAL target or, for the last one in a region, at the end of the code section, so on a single-PROGBITS executable they can run straight through interleaved rodata. loadGhidraFunctionMap() appended its rows to the same vector and then purged auto-named entries only where no map row shared the start address. Since both the carvings ("sub_") and the names Ghidra exports by default ("FUN_") count as auto-generated, a carving that shared a start with a map row survived the purge and then won the "larger end" tie-break, so the imprecise bounds replaced the ones the map had just supplied. Collect the map rows into a local vector, drop every auto-named carving once the map has parsed, and append the rows afterwards. Entries named from symbols or DWARF are unaffected. On a 3 MB Metrowerks-built PS2 executable with an 11,491-row map, 5,613 functions (48.8%) had been emitted with inflated bounds; the worst grew from 368 bytes to 0x51 KB and produced 22 MB of C++ decoding string data as instructions. Output for that function is now 19 KB and total output drops from 235 MB to 180 MB. --- ps2xRecomp/src/lib/elf_parser.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/ps2xRecomp/src/lib/elf_parser.cpp b/ps2xRecomp/src/lib/elf_parser.cpp index 91e43ee32..865dd6aad 100644 --- a/ps2xRecomp/src/lib/elf_parser.cpp +++ b/ps2xRecomp/src/lib/elf_parser.cpp @@ -986,6 +986,7 @@ namespace ps2recomp int skippedNonExecutable = 0; int skippedInvalidRange = 0; std::unordered_set mapStarts; + std::vector mapFunctions; while (std::getline(file, line)) { if (line.empty()) @@ -1029,7 +1030,7 @@ namespace ps2recomp func.isStub = false; func.isSkipped = false; - m_extraFunctions.push_back(std::move(func)); + mapFunctions.push_back(std::move(func)); mapStarts.insert(start); count++; } @@ -1062,14 +1063,27 @@ namespace ps2recomp } } + // An explicit function map is authoritative over the internal JAL-target + // scan. Those carvings end at the next JAL target or, for the last one in + // a region, at the end of the code section - which on single-PROGBITS + // executables runs straight through interleaved rodata. Because both the + // carvings ("sub_") and typical map names ("FUN_") count as + // auto-generated, a carving sharing a start with a map row used to + // survive this purge and then win the "larger end" tie-break below, + // replacing precise bounds with runaway ones. Drop every auto-named + // carving instead, then append the map rows. m_extraFunctions.erase( std::remove_if(m_extraFunctions.begin(), m_extraFunctions.end(), [&](const Function &func) { - return IsAutoGeneratedName(func.name) && !mapStarts.contains(func.start); + return IsAutoGeneratedName(func.name); }), m_extraFunctions.end()); + m_extraFunctions.insert(m_extraFunctions.end(), + std::make_move_iterator(mapFunctions.begin()), + std::make_move_iterator(mapFunctions.end())); + std::sort(m_extraFunctions.begin(), m_extraFunctions.end(), [](const Function &a, const Function &b) {