mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-10-01 10:26:07 -04:00
recompiler: filter auto-generated map symbols to reduce oversized outputs (#95)
This commit is contained in:
committed by
GitHub
parent
da10073cb9
commit
493522a769
@@ -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
|
||||
|
||||
@@ -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> §ions)
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user