Feature/better analyzer v1 (#39)

* feat: pin dependencies

* feat: dwarf parse

* fix duplicated functions

* feat: small code cleanup

* feat: crash prevent

* feat: modernize code

* feat: split m_libFunctions from m_knownLibNames

* feat: added ghidra loader

* feat: auto bootstrap name for recompiler
This commit is contained in:
Ranieri
2026-02-02 01:49:51 -03:00
committed by GitHub
parent b7086d89de
commit d7306d2095
15 changed files with 1205 additions and 204 deletions
+753 -18
View File
@@ -2,6 +2,466 @@
#include "ps2recomp/types.h"
#include <iostream>
#include <stdexcept>
#include <unordered_set>
#define NOMINMAX
#include <fcntl.h>
#if defined(_WIN32)
#include <io.h>
#include <direct.h>
#include <windows.h>
#else
#include <unistd.h>
#endif
#include <libdwarf.h>
#include <dwarf.h>
#include <fstream>
#include <sstream>
#include <algorithm>
namespace
{
bool IsAutoGeneratedName(const std::string &name)
{
return name.rfind("sub_", 0) == 0;
}
void AppendLoadSegmentsAsSections(const ELFIO::elfio &elf, std::vector<ps2recomp::Section> &sections)
{
const ELFIO::Elf_Half segCount = elf.segments.size();
if (segCount == 0)
{
return;
}
for (ELFIO::Elf_Half i = 0; i < segCount; ++i)
{
ELFIO::segment *segment = elf.segments[i];
if (!segment || segment->get_type() != ELFIO::PT_LOAD)
{
continue;
}
const ELFIO::Elf64_Addr vaddr = segment->get_virtual_address();
const ELFIO::Elf_Xword fileSize = segment->get_file_size();
const ELFIO::Elf_Xword memSize = segment->get_memory_size();
const ELFIO::Elf_Word flags = segment->get_flags();
if (vaddr > 0xFFFFFFFFu || fileSize > 0xFFFFFFFFu || memSize > 0xFFFFFFFFu)
{
continue;
}
if (fileSize > 0)
{
ps2recomp::Section load{};
load.name = "LOAD" + std::to_string(i);
load.address = static_cast<uint32_t>(vaddr);
load.size = static_cast<uint32_t>(fileSize);
load.offset = static_cast<uint32_t>(segment->get_offset());
load.isCode = (flags & ELFIO::PF_X) != 0;
load.isData = (flags & ELFIO::PF_W) != 0 || (flags & ELFIO::PF_R) != 0;
load.isBSS = false;
load.isReadOnly = (flags & ELFIO::PF_W) == 0;
load.data = const_cast<uint8_t *>(
reinterpret_cast<const uint8_t *>(segment->get_data()));
sections.push_back(load);
}
if (memSize > fileSize)
{
ps2recomp::Section bss{};
bss.name = "LOAD" + std::to_string(i) + ".bss";
bss.address = static_cast<uint32_t>(vaddr + fileSize);
bss.size = static_cast<uint32_t>(memSize - fileSize);
bss.offset = static_cast<uint32_t>(segment->get_offset() + fileSize);
bss.isCode = false;
bss.isData = true;
bss.isBSS = true;
bss.isReadOnly = false;
bss.data = nullptr;
sections.push_back(bss);
}
}
if (!sections.empty())
{
std::sort(sections.begin(), sections.end(),
[](const ps2recomp::Section &a, const ps2recomp::Section &b)
{ return a.address < b.address; });
}
}
const ps2recomp::Section *FindSectionByAddress(const std::vector<ps2recomp::Section> &sections, uint32_t address)
{
for (const auto &section : sections)
{
if (address >= section.address && address < (section.address + section.size))
{
return &section;
}
}
return nullptr;
}
}
namespace
{
bool HasDwarfSections(const ELFIO::elfio &elf)
{
for (ELFIO::Elf_Half i = 0; i < elf.sections.size(); ++i)
{
const ELFIO::section *section = elf.sections[i];
const std::string &name = section->get_name();
if (name.rfind(".debug_", 0) == 0 || name.rfind(".zdebug_", 0) == 0)
{
return true;
}
}
return false;
}
const ps2recomp::Section *FindCodeSectionByAddress(const std::vector<ps2recomp::Section> &sections, uint32_t address)
{
for (const auto &section : sections)
{
if (!section.isCode)
{
continue;
}
if (address >= section.address && address < (section.address + section.size))
{
return &section;
}
}
return nullptr;
}
std::string MakeAutoFunctionName(uint32_t address)
{
char buffer[32]{};
std::snprintf(buffer, sizeof(buffer), "sub_%08X", address);
return std::string(buffer);
}
std::string ReadDieName(Dwarf_Debug dbg, Dwarf_Die die, Dwarf_Error *error)
{
const int kAttrsToTry[] =
{
#ifdef DW_AT_linkage_name
DW_AT_linkage_name,
#endif
#ifdef DW_AT_MIPS_linkage_name
DW_AT_MIPS_linkage_name,
#endif
};
for (int attrNum : kAttrsToTry)
{
Dwarf_Attribute attr = nullptr;
if (dwarf_attr(die, attrNum, &attr, error) == DW_DLV_OK)
{
char *attrString = nullptr;
if (dwarf_formstring(attr, &attrString, error) == DW_DLV_OK && attrString)
{
std::string result(attrString);
dwarf_dealloc(dbg, attrString, DW_DLA_STRING);
dwarf_dealloc(dbg, attr, DW_DLA_ATTR);
return result;
}
dwarf_dealloc(dbg, attr, DW_DLA_ATTR);
}
}
// Fallback: DW_AT_name
char *dieName = nullptr;
if (dwarf_diename(die, &dieName, error) == DW_DLV_OK && dieName)
{
std::string result(dieName);
dwarf_dealloc(dbg, dieName, DW_DLA_STRING);
return result;
}
return {};
}
bool TryReadDieRange(
Dwarf_Debug dbg,
Dwarf_Die die,
uint32_t &outLowPc,
uint32_t &outHighPc,
Dwarf_Error *error)
{
outLowPc = 0;
outHighPc = 0;
Dwarf_Addr lowPc = 0;
if (dwarf_lowpc(die, &lowPc, error) != DW_DLV_OK)
{
return false;
}
// high_pc can be absolute address (DWARF2/3) or offset from low_pc (DWARF4+)
Dwarf_Addr highPc = 0;
Dwarf_Half highPcForm = 0;
Dwarf_Form_Class highPcClass = DW_FORM_CLASS_UNKNOWN;
if (dwarf_highpc_b(die, &highPc, &highPcForm, &highPcClass, error) == DW_DLV_OK)
{
if (highPcClass == DW_FORM_CLASS_CONSTANT)
{
highPc = lowPc + highPc;
}
if (lowPc <= 0xFFFFFFFFu && highPc <= 0xFFFFFFFFu && highPc > lowPc)
{
outLowPc = static_cast<uint32_t>(lowPc);
outHighPc = static_cast<uint32_t>(highPc);
return true;
}
return false;
}
// If no high_pc, try DW_AT_ranges
Dwarf_Attribute rangesAttr = nullptr;
if (dwarf_attr(die, DW_AT_ranges, &rangesAttr, error) != DW_DLV_OK)
{
if (lowPc <= 0xFFFFFFFFu)
{
outLowPc = static_cast<uint32_t>(lowPc);
outHighPc = static_cast<uint32_t>(lowPc + 4);
return true;
}
return false;
}
Dwarf_Off rangesOffset = 0;
if (dwarf_global_formref(rangesAttr, &rangesOffset, error) != DW_DLV_OK)
{
dwarf_dealloc(dbg, rangesAttr, DW_DLA_ATTR);
return false;
}
Dwarf_Ranges *ranges = nullptr;
Dwarf_Signed rangesCount = 0;
Dwarf_Unsigned byteCount = 0;
Dwarf_Off realOffset = 0;
if (dwarf_get_ranges_b(dbg, rangesOffset, die, &realOffset, &ranges, &rangesCount, &byteCount, error) != DW_DLV_OK)
{
dwarf_dealloc(dbg, rangesAttr, DW_DLA_ATTR);
return false;
}
Dwarf_Addr baseAddr = lowPc;
Dwarf_Addr minPc = 0;
Dwarf_Addr maxPc = 0;
bool hasAny = false;
for (Dwarf_Signed i = 0; i < rangesCount; ++i)
{
const Dwarf_Ranges &entry = ranges[i];
if (entry.dwr_type == DW_RANGES_END)
{
break;
}
if (entry.dwr_type == DW_RANGES_ADDRESS_SELECTION)
{
baseAddr = entry.dwr_addr2;
continue;
}
if (entry.dwr_type != DW_RANGES_ENTRY)
{
continue;
}
const Dwarf_Addr start = baseAddr + entry.dwr_addr1;
const Dwarf_Addr end = baseAddr + entry.dwr_addr2;
if (end <= start)
{
continue;
}
if (!hasAny)
{
minPc = start;
maxPc = end;
hasAny = true;
}
else
{
minPc = std::min(minPc, start);
maxPc = std::max(maxPc, end);
}
}
dwarf_dealloc_ranges(dbg, ranges, rangesCount);
dwarf_dealloc(dbg, rangesAttr, DW_DLA_ATTR);
if (!hasAny)
{
return false;
}
if (minPc <= 0xFFFFFFFFu && maxPc <= 0xFFFFFFFFu && maxPc > minPc)
{
outLowPc = static_cast<uint32_t>(minPc);
outHighPc = static_cast<uint32_t>(maxPc);
return true;
}
return false;
}
void VisitDieTreeAndCollectFunctions(
Dwarf_Debug dbg,
Dwarf_Die rootDie,
ps2recomp::ElfParser *parser,
std::vector<ps2recomp::Function> &outFunctions)
{
Dwarf_Error error = nullptr;
Dwarf_Die current = rootDie;
while (current)
{
Dwarf_Half tag = 0;
if (dwarf_tag(current, &tag, &error) == DW_DLV_OK)
{
if (tag == DW_TAG_subprogram)
{
uint32_t lowPc = 0;
uint32_t highPc = 0;
if (TryReadDieRange(dbg, current, lowPc, highPc, &error))
{
if (FindCodeSectionByAddress(parser->getSections(), lowPc))
{
ps2recomp::Function func{};
func.name = ReadDieName(dbg, current, &error);
func.start = lowPc;
func.end = highPc;
func.isRecompiled = false;
func.isStub = false;
if (func.name.empty())
{
func.name = MakeAutoFunctionName(func.start);
}
outFunctions.push_back(std::move(func));
}
}
}
}
// Depth-first: child first
Dwarf_Die child = nullptr;
if (dwarf_child(current, &child, &error) == DW_DLV_OK)
{
VisitDieTreeAndCollectFunctions(dbg, child, parser, outFunctions);
}
// Next sibling
Dwarf_Die sibling = nullptr;
const int siblingResult = dwarf_siblingof_b(dbg, current, TRUE, &sibling, &error);
dwarf_dealloc(dbg, current, DW_DLA_DIE);
if (siblingResult != DW_DLV_OK)
{
break;
}
current = sibling;
}
}
void ScanJalTargetsFallback(ps2recomp::ElfParser *parser, std::vector<ps2recomp::Function> &outFunctions)
{
std::unordered_set<uint32_t> starts;
starts.reserve(4096);
const uint32_t entry = parser->getEntryPoint();
if (FindCodeSectionByAddress(parser->getSections(), entry))
{
starts.insert(entry);
}
const auto &sections = parser->getSections();
for (const auto &section : sections)
{
if (!section.isCode || !section.data || section.size < 4)
{
continue;
}
for (uint32_t offset = 0; offset + 4 <= section.size; offset += 4)
{
const uint32_t pc = section.address + offset;
uint32_t raw = 0;
std::memcpy(&raw, section.data + offset, sizeof(uint32_t));
const uint32_t op = (raw >> 26) & 0x3F;
if (op != 0x03) // JAL
{
continue;
}
const uint32_t index = raw & 0x03FFFFFF;
const uint32_t target = ((pc + 4) & 0xF0000000u) | (index << 2);
if (FindCodeSectionByAddress(sections, target))
{
starts.insert(target);
}
}
}
std::vector<uint32_t> sortedStarts(starts.begin(), starts.end());
std::sort(sortedStarts.begin(), sortedStarts.end());
for (size_t i = 0; i < sortedStarts.size(); ++i)
{
const uint32_t start = sortedStarts[i];
const ps2recomp::Section *sec = FindCodeSectionByAddress(sections, start);
if (!sec)
{
continue;
}
const uint32_t secEnd = sec->address + sec->size;
uint32_t end = secEnd;
if (i + 1 < sortedStarts.size())
{
const uint32_t next = sortedStarts[i + 1];
if (next > start && next < secEnd)
{
end = next;
}
}
ps2recomp::Function func{};
func.name = MakeAutoFunctionName(start);
func.start = start;
func.end = (end > start) ? end : (start + 4);
func.isRecompiled = false;
func.isStub = false;
outFunctions.push_back(std::move(func));
}
}
}
namespace ps2recomp
{
@@ -23,28 +483,96 @@ namespace ps2recomp
}
std::vector<Function> ElfParser::extractFunctions() const
{
{
std::vector<Function> functions;
functions.reserve(m_symbols.size() + m_extraFunctions.size());
std::unordered_map<uint32_t, size_t> indexByStart;
indexByStart.reserve(functions.capacity());
auto addOrMerge = [&](const Function &newFunction)
{
if (newFunction.start == 0)
{
return;
}
auto it = indexByStart.find(newFunction.start);
if (it == indexByStart.end())
{
indexByStart.emplace(newFunction.start, functions.size());
functions.push_back(newFunction);
return;
}
Function &existing = functions[it->second];
if (!newFunction.name.empty())
{
if (existing.name.empty() || (IsAutoGeneratedName(existing.name) && !IsAutoGeneratedName(newFunction.name)))
{
existing.name = newFunction.name;
}
}
if (newFunction.end > existing.end)
{
existing.end = newFunction.end;
}
existing.isStub = existing.isStub || newFunction.isStub;
};
for (const auto &symbol : m_symbols)
{
if (symbol.isFunction && symbol.size > 0)
if (!symbol.isFunction || symbol.isImported)
{
Function func;
func.name = symbol.name;
func.start = symbol.address;
func.end = symbol.address + symbol.size;
func.isRecompiled = false;
func.isStub = false;
functions.push_back(func);
continue;
}
Function func;
func.name = symbol.name;
func.start = symbol.address;
func.end = (symbol.size > 0) ? (symbol.address + symbol.size) : 0;
func.isRecompiled = false;
func.isStub = false;
addOrMerge(func);
}
for (const auto &func : m_extraFunctions)
{
addOrMerge(func);
}
std::sort(functions.begin(), functions.end(),
[](const Function &a, const Function &b)
{ return a.start < b.start; });
for (size_t index = 0; index < functions.size(); ++index)
{
Function &func = functions[index];
if (func.end > func.start)
{
continue;
}
const Section *section = FindSectionByAddress(m_sections, func.start);
uint32_t sectionEnd = section ? (section->address + section->size) : (func.start + 4);
uint32_t nextStart = sectionEnd;
if (index + 1 < functions.size())
{
const uint32_t candidate = functions[index + 1].start;
if (candidate > func.start && section && candidate < sectionEnd)
{
nextStart = candidate;
}
}
func.end = (nextStart > func.start) ? nextStart : (func.start + 4);
}
return functions;
}
@@ -63,6 +591,11 @@ namespace ps2recomp
return m_relocations;
}
std::vector<Function> ElfParser::extractExtraFunctions() const
{
return m_extraFunctions;
}
bool ElfParser::isValidAddress(uint32_t address) const
{
for (const auto &section : m_sections)
@@ -94,7 +627,7 @@ namespace ps2recomp
}
uint8_t *ElfParser::getSectionData(const std::string &sectionName) const
{
{
for (const auto &section : m_sections)
{
if (section.name == sectionName)
@@ -107,7 +640,7 @@ namespace ps2recomp
}
uint32_t ElfParser::getSectionAddress(const std::string &sectionName) const
{
{
for (const auto &section : m_sections)
{
if (section.name == sectionName)
@@ -120,7 +653,7 @@ namespace ps2recomp
}
uint32_t ElfParser::getSectionSize(const std::string &sectionName) const
{
{
for (const auto &section : m_sections)
{
if (section.name == sectionName)
@@ -137,6 +670,91 @@ namespace ps2recomp
return static_cast<uint32_t>(m_elf->get_entry());
}
bool ElfParser::loadGhidraFunctionMap(const std::string &mapPath)
{
if (mapPath.empty())
{
return false;
}
std::ifstream file(mapPath);
if (!file.is_open())
{
std::cerr << "Warning: Could not open Ghidra function map: " << mapPath << std::endl;
return false;
}
std::string line;
if (!std::getline(file, line))
{
return false;
}
int count = 0;
while (std::getline(file, line))
{
if (line.empty())
continue;
std::stringstream ss(line);
std::string name, startStr, endStr, sizeStr;
if (!std::getline(ss, name, ',') ||
!std::getline(ss, startStr, ',') ||
!std::getline(ss, endStr, ',') ||
!std::getline(ss, sizeStr, ','))
{
continue;
}
try
{
uint32_t start = std::stoul(startStr, nullptr, 0);
uint32_t end = std::stoul(endStr, nullptr, 0);
Function func{};
func.name = name;
func.start = start;
func.end = end;
func.isRecompiled = false;
func.isStub = false;
m_extraFunctions.push_back(std::move(func));
count++;
}
catch (...)
{
continue;
}
}
if (count > 0)
{
std::cout << "Loaded " << count << " functions from Ghidra map" << std::endl;
std::sort(m_extraFunctions.begin(), m_extraFunctions.end(),
[](const Function &a, const Function &b)
{ return a.start < b.start; });
m_extraFunctions.erase(
std::unique(m_extraFunctions.begin(), m_extraFunctions.end(),
[](const Function &a, const Function &b)
{
if (a.start == b.start)
{
// pick the function with real name and not auto generated
return true;
}
return false;
}),
m_extraFunctions.end());
return true;
}
return false;
}
ElfParser::~ElfParser() = default;
bool ElfParser::parse()
@@ -157,6 +775,7 @@ namespace ps2recomp
loadSections();
loadSymbols();
loadRelocations();
loadDebugFunctions();
return true;
}
@@ -192,6 +811,16 @@ namespace ps2recomp
m_sections.push_back(section);
}
if (m_sections.empty())
{
AppendLoadSegmentsAsSections(*m_elf, m_sections);
if (!m_sections.empty())
{
std::cout << "Info: ELF has no section headers; using loadable segments as sections ("
<< m_sections.size() << " entries)." << std::endl;
}
}
}
void ElfParser::loadSymbols()
@@ -204,6 +833,12 @@ namespace ps2recomp
if (psec->get_type() == ELFIO::SHT_SYMTAB || psec->get_type() == ELFIO::SHT_DYNSYM)
{
if (psec->get_link() >= m_elf->sections.size())
{
std::cerr << "Warning: Symbol section link out of bounds: " << psec->get_link() << std::endl;
continue;
}
ELFIO::symbol_section_accessor symbols(*m_elf, psec);
ELFIO::Elf_Xword sym_num = symbols.get_symbols_num();
@@ -223,8 +858,7 @@ namespace ps2recomp
symbols.get_symbol(j, name, value, size, bind, type, section_index, other);
// Skip empty symbols or those with invalid section index
if (name.empty() || section_index == ELFIO::SHN_UNDEF)
if (name.empty())
{
continue;
}
@@ -234,8 +868,9 @@ namespace ps2recomp
symbol.address = static_cast<uint32_t>(value);
symbol.size = static_cast<uint32_t>(size);
symbol.isFunction = (type == ELFIO::STT_FUNC);
symbol.isImported = (bind == ELFIO::STB_GLOBAL && section_index == ELFIO::SHN_UNDEF);
symbol.isExported = (bind == ELFIO::STB_GLOBAL && section_index != ELFIO::SHN_UNDEF);
symbol.isImported = section_index == ELFIO::SHN_UNDEF;
symbol.isExported = (!symbol.isImported && bind == ELFIO::STB_GLOBAL);
m_symbols.push_back(symbol);
}
@@ -253,9 +888,22 @@ namespace ps2recomp
if (psec->get_type() == ELFIO::SHT_REL || psec->get_type() == ELFIO::SHT_RELA)
{
if (psec->get_link() >= m_elf->sections.size())
{
std::cout << "Warning: Relocation section link out of bounds: " << psec->get_link() << std::endl;
continue;
}
ELFIO::relocation_section_accessor relocs(*m_elf, psec);
ELFIO::section *symSec = m_elf->sections[psec->get_link()];
if (symSec->get_link() >= m_elf->sections.size())
{
std::cout << "Warning: Symbol section link out of bounds (in relocation): " << symSec->get_link() << std::endl;
continue;
}
ELFIO::symbol_section_accessor symbols(*m_elf, symSec);
ELFIO::section *strSec = m_elf->sections[symSec->get_link()];
@@ -294,4 +942,91 @@ namespace ps2recomp
}
}
}
}
void ElfParser::loadDebugFunctions()
{
m_extraFunctions.clear();
if (HasDwarfSections(*m_elf))
{
#if defined(_WIN32)
const int fileDescriptor = _open(m_filePath.c_str(), _O_RDONLY | _O_BINARY);
#else
const int fileDescriptor = ::open(m_filePath.c_str(), O_RDONLY);
#endif
if (fileDescriptor >= 0)
{
Dwarf_Debug dbg = nullptr;
Dwarf_Error error = nullptr;
const int initResult = dwarf_init_b(fileDescriptor, DW_GROUPNUMBER_BASE, nullptr, nullptr, &dbg, &error);
if (initResult == DW_DLV_OK)
{
for (;;)
{
Dwarf_Unsigned cuHeaderLength = 0;
Dwarf_Half versionStamp = 0;
Dwarf_Unsigned abbrevOffset = 0;
Dwarf_Half addressSize = 0;
Dwarf_Half lengthSize = 0;
Dwarf_Half extensionSize = 0;
Dwarf_Sig8 typeSignature = {0};
Dwarf_Unsigned typeOffset = 0;
Dwarf_Unsigned nextCuHeader = 0;
Dwarf_Half headerCuType = 0;
Dwarf_Die cuDie = nullptr;
const int cuResult = dwarf_next_cu_header_e(
dbg,
TRUE,
&cuDie,
&cuHeaderLength,
&versionStamp,
&abbrevOffset,
&addressSize,
&lengthSize,
&extensionSize,
&typeSignature,
&typeOffset,
&nextCuHeader,
&headerCuType,
&error);
if (cuResult != DW_DLV_OK)
{
break;
}
if (cuDie != nullptr)
{
VisitDieTreeAndCollectFunctions(dbg, cuDie, this, m_extraFunctions);
}
}
dwarf_finish(dbg);
}
#if defined(_WIN32)
_close(fileDescriptor);
#else
::close(fileDescriptor);
#endif
}
}
if (m_extraFunctions.empty())
{
ScanJalTargetsFallback(this, m_extraFunctions);
}
std::sort(m_extraFunctions.begin(), m_extraFunctions.end(),
[](const Function &a, const Function &b)
{ return a.start < b.start; });
m_extraFunctions.erase(
std::unique(m_extraFunctions.begin(), m_extraFunctions.end(),
[](const Function &a, const Function &b)
{ return a.start == b.start; }),
m_extraFunctions.end());
}
}