From 9db9a9ec6b20ba53cc168a98ca9214eb86767cde Mon Sep 17 00:00:00 2001 From: hedgeg0d Date: Sat, 29 Aug 2026 22:06:43 +0300 Subject: [PATCH] fix(recomp): clamp same-start functions to Ghidra map ends --- ps2xRecomp/include/ps2recomp/elf_parser.h | 1 + ps2xRecomp/src/lib/elf_parser.cpp | 13 ++++- ps2xTest/src/ps2_recompiler_tests.cpp | 58 +++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/ps2xRecomp/include/ps2recomp/elf_parser.h b/ps2xRecomp/include/ps2recomp/elf_parser.h index 10f9e4ce8..ba2bb6c59 100644 --- a/ps2xRecomp/include/ps2recomp/elf_parser.h +++ b/ps2xRecomp/include/ps2recomp/elf_parser.h @@ -52,6 +52,7 @@ namespace ps2recomp bool m_hasLoadedGhidraMap = false; RecompilerReporter *m_reporter = nullptr; std::unordered_set m_ghidraMapStarts; + std::unordered_map m_ghidraMapEnds; void loadSections(); void loadSymbols(); diff --git a/ps2xRecomp/src/lib/elf_parser.cpp b/ps2xRecomp/src/lib/elf_parser.cpp index 91e43ee32..b845d57e8 100644 --- a/ps2xRecomp/src/lib/elf_parser.cpp +++ b/ps2xRecomp/src/lib/elf_parser.cpp @@ -603,7 +603,8 @@ namespace ps2recomp // Named debug/map functions with explicit bounds are authoritative too. for (const auto &extra : m_extraFunctions) { - if (extra.start == 0 || extra.end <= extra.start || extra.name.empty() || IsAutoGeneratedName(extra.name)) + if (extra.start == 0 || extra.end <= extra.start || extra.name.empty() || + (IsAutoGeneratedName(extra.name) && !m_ghidraMapStarts.contains(extra.start))) { continue; } @@ -614,7 +615,12 @@ namespace ps2recomp continue; } - const uint32_t clampedEnd = ClampFunctionEndToSection(functionSection, extra.start, extra.end); + uint32_t clampedEnd = ClampFunctionEndToSection(functionSection, extra.start, extra.end); + auto ghidraEndIt = m_ghidraMapEnds.find(extra.start); + if (ghidraEndIt != m_ghidraMapEnds.end()) + { + clampedEnd = ghidraEndIt->second; + } if (clampedEnd <= extra.start) { continue; @@ -965,6 +971,7 @@ namespace ps2recomp m_hasLoadedGhidraMap = false; m_ghidraMapStarts.clear(); + m_ghidraMapEnds.clear(); std::ifstream file(mapPath); if (!file.is_open()) @@ -1031,6 +1038,7 @@ namespace ps2recomp m_extraFunctions.push_back(std::move(func)); mapStarts.insert(start); + m_ghidraMapEnds[start] = end; count++; } catch (...) @@ -1350,6 +1358,7 @@ namespace ps2recomp m_extraFunctions.clear(); m_hasLoadedGhidraMap = false; m_ghidraMapStarts.clear(); + m_ghidraMapEnds.clear(); if (HasDwarfSections(*m_elf)) { diff --git a/ps2xTest/src/ps2_recompiler_tests.cpp b/ps2xTest/src/ps2_recompiler_tests.cpp index 4dce6227f..14a9b6fbd 100644 --- a/ps2xTest/src/ps2_recompiler_tests.cpp +++ b/ps2xTest/src/ps2_recompiler_tests.cpp @@ -947,6 +947,64 @@ void register_ps2_recompiler_tests() std::filesystem::remove(mapPath, removeError); }); + tc.Run("ghidra map end clamps a same-start auto function", [](TestCase &t) { + const auto uniqueSuffix = std::to_string( + static_cast(std::chrono::steady_clock::now().time_since_epoch().count())); + const std::filesystem::path elfPath = + std::filesystem::temp_directory_path() / ("ps2recomp-ghidra-clamp-" + uniqueSuffix + ".elf"); + const std::filesystem::path mapPath = + std::filesystem::temp_directory_path() / ("ps2recomp-ghidra-clamp-" + uniqueSuffix + ".csv"); + + const bool writeOk = writeMinimalMipsElfWithJalFallbackTarget(elfPath); + t.IsTrue(writeOk, "temporary ELF should be generated"); + if (!writeOk) + { + return; + } + + ElfParser parser(elfPath.string()); + const bool parseOk = parser.parse(); + t.IsTrue(parseOk, "generated ELF should parse"); + if (!parseOk) + { + std::error_code removeError; + std::filesystem::remove(elfPath, removeError); + return; + } + + std::ofstream mapFile(mapPath); + t.IsTrue(static_cast(mapFile), "ghidra map file should be writable"); + if (!mapFile) + { + std::error_code removeError; + std::filesystem::remove(elfPath, removeError); + return; + } + mapFile << "name,start,end,size\n"; + mapFile << "FUN_00100010,0x00100010,0x00100014,0x4\n"; + mapFile.close(); + + const bool mapLoaded = parser.loadGhidraFunctionMap(mapPath.string()); + t.IsTrue(mapLoaded, "ghidra map should load"); + + const auto functions = parser.extractFunctions(); + const auto clampedIt = std::find_if( + functions.begin(), functions.end(), + [](const Function &fn) + { return fn.start == 0x00100010u; }); + t.IsTrue(clampedIt != functions.end(), + "same-start function should survive the ghidra map merge"); + if (clampedIt != functions.end()) + { + t.Equals(clampedIt->end, 0x00100014u, + "ghidra end should clamp the analyzer's wider span"); + } + + std::error_code removeError; + std::filesystem::remove(elfPath, removeError); + std::filesystem::remove(mapPath, removeError); + }); + tc.Run("runtime call resolution includes Veronica compatibility aliases", [](TestCase &t) { t.Equals(ps2_runtime_calls::resolveSyscallName("ReleaseAlarm"), std::string_view{"ReleaseAlarm"}, "ReleaseAlarm should resolve as a syscall name");