recompiler: filter auto-generated map symbols to reduce oversized outputs (#95)

This commit is contained in:
Antonio Guastella
2026-03-05 15:14:41 +01:00
committed by GitHub
parent da10073cb9
commit 493522a769
3 changed files with 159 additions and 3 deletions
+4 -1
View File
@@ -5,6 +5,7 @@
#include <string>
#include <vector>
#include <memory>
#include <unordered_set>
namespace ps2recomp
{
@@ -45,6 +46,8 @@ namespace ps2recomp
std::vector<Symbol> m_symbols;
std::vector<Relocation> m_relocations;
std::vector<Function> m_extraFunctions;
bool m_hasLoadedGhidraMap = false;
std::unordered_set<uint32_t> m_ghidraMapStarts;
void loadSections();
void loadSymbols();
@@ -56,4 +59,4 @@ namespace ps2recomp
} // namespace ps2recomp
#endif // PS2RECOMP_ELF_PARSER_H
#endif // PS2RECOMP_ELF_PARSER_H
+48 -2
View File
@@ -26,7 +26,10 @@ namespace
{
bool IsAutoGeneratedName(const std::string &name)
{
return name.rfind("sub_", 0) == 0;
return name.rfind("sub_", 0) == 0 ||
name.rfind("FUN_", 0) == 0 ||
name.rfind("LAB_", 0) == 0 ||
name.rfind("DAT_", 0) == 0;
}
void AppendLoadSegmentsAsSections(const ELFIO::elfio &elf, std::vector<ps2recomp::Section> &sections)
@@ -566,6 +569,13 @@ namespace ps2recomp
continue;
}
if (m_hasLoadedGhidraMap &&
IsAutoGeneratedName(symbol.name) &&
!m_ghidraMapStarts.contains(symbol.address))
{
continue;
}
const Section *functionSection = FindFunctionSectionByAddress(m_sections, symbol.address);
if (!functionSection)
{
@@ -947,6 +957,9 @@ namespace ps2recomp
return false;
}
m_hasLoadedGhidraMap = false;
m_ghidraMapStarts.clear();
std::ifstream file(mapPath);
if (!file.is_open())
{
@@ -963,6 +976,7 @@ namespace ps2recomp
int count = 0;
int skippedNonExecutable = 0;
int skippedInvalidRange = 0;
std::unordered_set<uint32_t> mapStarts;
while (std::getline(file, line))
{
if (line.empty())
@@ -1007,6 +1021,7 @@ namespace ps2recomp
func.isSkipped = false;
m_extraFunctions.push_back(std::move(func));
mapStarts.insert(start);
count++;
}
catch (...)
@@ -1017,6 +1032,8 @@ namespace ps2recomp
if (count > 0)
{
m_hasLoadedGhidraMap = true;
m_ghidraMapStarts = mapStarts;
std::cout << "Loaded " << count << " functions from Ghidra map" << std::endl;
if (skippedNonExecutable > 0)
{
@@ -1029,9 +1046,36 @@ namespace ps2recomp
<< " Ghidra function(s) with invalid ranges after section clamping." << std::endl;
}
m_extraFunctions.erase(
std::remove_if(m_extraFunctions.begin(), m_extraFunctions.end(),
[&](const Function &func)
{
return IsAutoGeneratedName(func.name) && !mapStarts.contains(func.start);
}),
m_extraFunctions.end());
std::sort(m_extraFunctions.begin(), m_extraFunctions.end(),
[](const Function &a, const Function &b)
{ return a.start < b.start; });
{
if (a.start != b.start)
{
return a.start < b.start;
}
const bool aAuto = IsAutoGeneratedName(a.name);
const bool bAuto = IsAutoGeneratedName(b.name);
if (aAuto != bAuto)
{
return !aAuto;
}
if (a.end != b.end)
{
return a.end > b.end;
}
return a.name < b.name;
});
m_extraFunctions.erase(
std::unique(m_extraFunctions.begin(), m_extraFunctions.end(),
@@ -1267,6 +1311,8 @@ namespace ps2recomp
void ElfParser::loadDebugFunctions()
{
m_extraFunctions.clear();
m_hasLoadedGhidraMap = false;
m_ghidraMapStarts.clear();
if (HasDwarfSections(*m_elf))
{
+107
View File
@@ -6,6 +6,7 @@
#include "ps2recomp/types.h"
#include <elfio/elfio.hpp>
#include <algorithm>
#include <array>
#include <chrono>
#include <filesystem>
#include <fstream>
@@ -118,6 +119,41 @@ static bool writeMinimalMipsElfWithCodeAndDataFunctionSymbols(const std::filesys
return writer.save(elfPath.string());
}
static bool writeMinimalMipsElfWithJalFallbackTarget(const std::filesystem::path &elfPath)
{
ELFIO::elfio writer;
writer.create(ELFIO::ELFCLASS32, ELFIO::ELFDATA2LSB);
writer.set_os_abi(ELFIO::ELFOSABI_NONE);
writer.set_type(ELFIO::ET_EXEC);
writer.set_machine(ELFIO::EM_MIPS);
writer.set_entry(0x00100000u);
ELFIO::section *text = writer.sections.add(".text");
text->set_type(ELFIO::SHT_PROGBITS);
text->set_flags(ELFIO::SHF_ALLOC | ELFIO::SHF_EXECINSTR);
text->set_addr_align(4);
text->set_address(0x00100000u);
const std::array<uint32_t, 6> textWords = {
0x0C040004u, // jal 0x00100010
0x00000000u, // nop
0x03E00008u, // jr $ra
0x00000000u, // nop
0x03E00008u, // jr $ra
0x00000000u // nop
};
text->set_data(reinterpret_cast<const char *>(textWords.data()),
static_cast<ELFIO::Elf_Word>(textWords.size() * sizeof(uint32_t)));
ELFIO::segment *textSegment = writer.segments.add();
textSegment->set_type(ELFIO::PT_LOAD);
textSegment->set_flags(ELFIO::PF_R | ELFIO::PF_X);
textSegment->set_align(0x1000);
textSegment->add_section_index(text->get_index(), text->get_addr_align());
return writer.save(elfPath.string());
}
void register_ps2_recompiler_tests()
{
MiniTest::Case("PS2Recompiler", [](TestCase &tc)
@@ -515,6 +551,77 @@ void register_ps2_recompiler_tests()
std::filesystem::remove(elfPath, removeError);
});
tc.Run("ghidra map replaces JAL fallback-only auto starts", [](TestCase &t) {
const auto uniqueSuffix = std::to_string(
static_cast<unsigned long long>(std::chrono::steady_clock::now().time_since_epoch().count()));
const std::filesystem::path elfPath =
std::filesystem::temp_directory_path() / ("ps2recomp-ghidra-merge-" + uniqueSuffix + ".elf");
const std::filesystem::path mapPath =
std::filesystem::temp_directory_path() / ("ps2recomp-ghidra-merge-" + 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;
}
const auto fallbackExtras = parser.extractExtraFunctions();
const bool hasFallbackStart = std::any_of(
fallbackExtras.begin(), fallbackExtras.end(),
[](const Function &fn)
{ return fn.start == 0x00100010u; });
t.IsTrue(hasFallbackStart, "JAL fallback should discover secondary start before map load");
std::ofstream mapFile(mapPath);
t.IsTrue(static_cast<bool>(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_00100000,0x00100000,0x00100010,0x10\n";
mapFile.close();
const bool mapLoaded = parser.loadGhidraFunctionMap(mapPath.string());
t.IsTrue(mapLoaded, "ghidra map should load");
const auto functions = parser.extractFunctions();
const auto entryIt = std::find_if(
functions.begin(), functions.end(),
[](const Function &fn)
{ return fn.start == 0x00100000u; });
t.IsTrue(entryIt != functions.end(), "ghidra entry should exist");
if (entryIt != functions.end())
{
t.Equals(entryIt->name, std::string("FUN_00100000"),
"ghidra name should win over fallback auto-name");
}
const bool stillHasFallbackOnlyStart = std::any_of(
functions.begin(), functions.end(),
[](const Function &fn)
{ return fn.start == 0x00100010u; });
t.IsFalse(stillHasFallbackOnlyStart,
"fallback-only function starts should be removed once ghidra map is loaded");
std::error_code removeError;
std::filesystem::remove(elfPath, removeError);
std::filesystem::remove(mapPath, removeError);
});
tc.Run("respect max length for .cpp filenames", [](TestCase& t) {
t.IsTrue(PS2Recompiler::ClampFilenameLength("ReallyLongFunctionNameReallyLongFunctionNameReallyLongFunctionName_0x12345678",".cpp",50).length() <= 50,"Function name must be max 50 characters");