Files
PS2Recomp/ps2xRecomp/src/lib/elf_parser.cpp
T
Ranieri 5196a6672a Refactor runtime for move speed and better code style (#140)
* feat: added guestBranchKind enum to categorize branch types
feat: added missingFunctionPolicy enum to define behaviors for missing function scenarios
refactor: added handle guest branches and report missing functions
feat lookupFunction to utilize new dispatch logic and improve error handling for unregistered functions

* fix: fix test conflict

* feat: added debug sound driver logs

* feat: emmiter for return

* feat: added recompiler reporter
feat: added strict diagnostics flag for heavy debug calls

* feat: staticc table insted of hashmap for runtime

* feat: back file to ignore

* feat: explode code across helpers and classes

* feat: update codegen test
feat: better guest nop check

* feat: fix link problem on linux

* feat: fix Segmentation fault
2026-07-04 00:43:22 -03:00

1437 lines
45 KiB
C++

#include "ps2recomp/elf_parser.h"
#include "ps2recomp/recompiler_reporter.h"
#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_private.h"
#include <libdwarf.h>
#include <dwarf.h>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cstring>
namespace
{
bool IsAutoGeneratedName(const std::string &name)
{
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)
{
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;
}
bool HasAnyExecutableSection(const std::vector<ps2recomp::Section> &sections)
{
for (const auto &section : sections)
{
if (section.isCode)
{
return true;
}
}
return false;
}
const ps2recomp::Section *FindFunctionSectionByAddress(const std::vector<ps2recomp::Section> &sections, uint32_t address)
{
const ps2recomp::Section *codeSection = FindCodeSectionByAddress(sections, address);
if (codeSection)
{
return codeSection;
}
// Some malformed/stripped ELFs may not carry executable section flags.
if (!HasAnyExecutableSection(sections))
{
return FindSectionByAddress(sections, address);
}
return nullptr;
}
uint32_t ClampFunctionEndToSection(const ps2recomp::Section *section, uint32_t start, uint32_t requestedEnd)
{
if (!section)
{
return requestedEnd;
}
const uint64_t sectionEnd64 = static_cast<uint64_t>(section->address) + static_cast<uint64_t>(section->size);
const uint32_t sectionEnd = (sectionEnd64 > 0xFFFFFFFFull)
? 0xFFFFFFFFu
: static_cast<uint32_t>(sectionEnd64);
uint32_t end = requestedEnd;
if (end == 0 || end > sectionEnd)
{
end = sectionEnd;
}
if (end <= start)
{
const uint64_t minimumEnd64 = static_cast<uint64_t>(start) + 4ull;
if (minimumEnd64 <= sectionEnd64)
{
end = static_cast<uint32_t>(minimumEnd64);
}
else
{
end = sectionEnd;
}
}
return end;
}
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;
func.isSkipped = 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;
func.isSkipped = false;
outFunctions.push_back(std::move(func));
}
}
}
namespace ps2recomp
{
ElfParser::ElfParser(const std::string &filePath)
: m_filePath(filePath), m_elf(new ELFIO::elfio())
{
}
bool ElfParser::isExecutableSection(const ELFIO::section *section) const
{
return (section->get_flags() & ELFIO::SHF_EXECINSTR) != 0;
}
bool ElfParser::isDataSection(const ELFIO::section *section) const
{
return (section->get_flags() & ELFIO::SHF_ALLOC) != 0 &&
!(section->get_flags() & ELFIO::SHF_EXECINSTR);
}
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());
// Symbol table sizes are authoritative wwhen exist
std::unordered_map<uint32_t, uint32_t> authoritativeEndByStart;
authoritativeEndByStart.reserve(m_symbols.size());
for (const auto &symbol : m_symbols)
{
if (!symbol.isFunction || symbol.isImported || symbol.size == 0)
{
continue;
}
if (m_hasLoadedGhidraMap &&
IsAutoGeneratedName(symbol.name) &&
!m_ghidraMapStarts.contains(symbol.address))
{
continue;
}
const Section *functionSection = FindFunctionSectionByAddress(m_sections, symbol.address);
if (!functionSection)
{
continue;
}
const uint64_t symbolEnd64 = static_cast<uint64_t>(symbol.address) + static_cast<uint64_t>(symbol.size);
uint32_t symbolEnd = (symbolEnd64 > 0xFFFFFFFFull)
? 0xFFFFFFFFu
: static_cast<uint32_t>(symbolEnd64);
symbolEnd = ClampFunctionEndToSection(functionSection, symbol.address, symbolEnd);
if (symbolEnd <= symbol.address)
{
continue;
}
auto inserted = authoritativeEndByStart.emplace(symbol.address, symbolEnd);
if (!inserted.second && symbolEnd > inserted.first->second)
{
inserted.first->second = symbolEnd;
}
}
// 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))
{
continue;
}
const Section *functionSection = FindFunctionSectionByAddress(m_sections, extra.start);
if (!functionSection)
{
continue;
}
const uint32_t clampedEnd = ClampFunctionEndToSection(functionSection, extra.start, extra.end);
if (clampedEnd <= extra.start)
{
continue;
}
auto inserted = authoritativeEndByStart.emplace(extra.start, clampedEnd);
if (!inserted.second && clampedEnd > inserted.first->second)
{
inserted.first->second = clampedEnd;
}
}
std::vector<std::pair<uint32_t, uint32_t>> authoritativeRanges;
authoritativeRanges.reserve(authoritativeEndByStart.size());
for (const auto &entry : authoritativeEndByStart)
{
authoritativeRanges.emplace_back(entry.first, entry.second);
}
std::sort(authoritativeRanges.begin(), authoritativeRanges.end(),
[](const std::pair<uint32_t, uint32_t> &a, const std::pair<uint32_t, uint32_t> &b)
{ return a.first < b.first; });
auto isInsideAuthoritativeRange = [&](uint32_t startAddress)
{
if (authoritativeRanges.empty())
{
return false;
}
auto it = std::upper_bound(
authoritativeRanges.begin(),
authoritativeRanges.end(),
startAddress,
[](uint32_t value, const std::pair<uint32_t, uint32_t> &range)
{ return value < range.first; });
if (it == authoritativeRanges.begin())
{
return false;
}
--it;
return startAddress > it->first && startAddress < it->second;
};
auto addOrMerge = [&](const Function &newFunction)
{
if (newFunction.start == 0)
{
return;
}
if (!FindFunctionSectionByAddress(m_sections, newFunction.start))
{
return;
}
const bool insideAuthoritativeRange = isInsideAuthoritativeRange(newFunction.start);
const bool hasOwnAuthoritativeRange = authoritativeEndByStart.contains(newFunction.start);
const bool hasAutoName = newFunction.name.empty() || IsAutoGeneratedName(newFunction.name);
if (insideAuthoritativeRange && (!hasOwnAuthoritativeRange || hasAutoName))
{
return;
}
auto it = indexByStart.find(newFunction.start);
if (it == indexByStart.end())
{
indexByStart.emplace(newFunction.start, functions.size());
functions.push_back(newFunction);
Function &insertedFunction = functions.back();
auto authoritativeIt = authoritativeEndByStart.find(insertedFunction.start);
if (authoritativeIt != authoritativeEndByStart.end())
{
insertedFunction.end = ClampFunctionEndToSection(
FindFunctionSectionByAddress(m_sections, insertedFunction.start),
insertedFunction.start,
authoritativeIt->second);
}
return;
}
Function &existing = functions[it->second];
if (!newFunction.name.empty())
{
if (existing.name.empty() || (IsAutoGeneratedName(existing.name) && !IsAutoGeneratedName(newFunction.name)))
{
existing.name = newFunction.name;
}
}
auto authoritativeIt = authoritativeEndByStart.find(existing.start);
if (authoritativeIt != authoritativeEndByStart.end())
{
existing.end = ClampFunctionEndToSection(
FindFunctionSectionByAddress(m_sections, existing.start),
existing.start,
authoritativeIt->second);
}
else if (newFunction.end > existing.end)
{
existing.end = newFunction.end;
}
existing.isStub = existing.isStub || newFunction.isStub;
existing.isSkipped = existing.isSkipped || newFunction.isSkipped;
};
for (const auto &symbol : m_symbols)
{
if (!symbol.isFunction || symbol.isImported)
{
continue;
}
if (!FindFunctionSectionByAddress(m_sections, symbol.address))
{
continue;
}
Function func;
func.name = symbol.name;
func.start = symbol.address;
if (symbol.size > 0)
{
const uint64_t end64 = static_cast<uint64_t>(symbol.address) + static_cast<uint64_t>(symbol.size);
func.end = (end64 > 0xFFFFFFFFull) ? 0xFFFFFFFFu : static_cast<uint32_t>(end64);
}
else
{
func.end = 0;
}
func.isRecompiled = false;
func.isStub = false;
func.isSkipped = 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];
auto authoritativeIt = authoritativeEndByStart.find(func.start);
if (authoritativeIt != authoritativeEndByStart.end())
{
func.end = authoritativeIt->second;
continue;
}
if (func.end > func.start)
{
continue;
}
const Section *section = FindFunctionSectionByAddress(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;
}
std::vector<Symbol> ElfParser::extractSymbols()
{
return m_symbols;
}
std::vector<Section> ElfParser::getSections()
{
return m_sections;
}
std::vector<Relocation> ElfParser::getRelocations()
{
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)
{
if (address >= section.address && address < (section.address + section.size))
{
return true;
}
}
return false;
}
uint32_t ElfParser::readWord(uint32_t address) const
{
for (const auto &section : m_sections)
{
if (address < section.address || section.size < sizeof(uint32_t))
{
continue;
}
const uint32_t offset = address - section.address;
if (offset > section.size - static_cast<uint32_t>(sizeof(uint32_t)))
{
continue;
}
if (section.data)
{
uint32_t word = 0;
std::memcpy(&word, section.data + offset, sizeof(word));
return word;
}
}
throw std::runtime_error("Invalid address for readWord: " + std::to_string(address));
}
uint8_t *ElfParser::getSectionData(const std::string &sectionName) const
{
for (const auto &section : m_sections)
{
if (section.name == sectionName)
{
return section.data;
}
}
return nullptr;
}
uint32_t ElfParser::getSectionAddress(const std::string &sectionName) const
{
for (const auto &section : m_sections)
{
if (section.name == sectionName)
{
return section.address;
}
}
return 0;
}
uint32_t ElfParser::getSectionSize(const std::string &sectionName) const
{
for (const auto &section : m_sections)
{
if (section.name == sectionName)
{
return section.size;
}
}
return 0;
}
void ElfParser::debugAddress(uint32_t address) const
{
for (const auto &section : m_sections)
{
if (address < section.address || address >= (section.address + section.size))
{
continue;
}
const uint32_t offset = address - section.address;
std::printf(
"Address 0x%08X -> section '%s'\n"
" section.address=0x%08X section.size=0x%08X section.offset=0x%08X\n"
" isCode=%d isData=%d isBSS=%d isReadOnly=%d data=%p\n"
" offsetInSection=0x%08X\n",
address,
section.name.c_str(),
section.address, section.size, section.offset,
section.isCode ? 1 : 0,
section.isData ? 1 : 0,
section.isBSS ? 1 : 0,
section.isReadOnly ? 1 : 0,
(void *)section.data,
offset);
if (!section.data)
{
std::printf(" section.data == nullptr (possible SHT_NOBITS/BSS)\n");
return;
}
const uint32_t dumpStart = (offset >= 16) ? (offset - 16) : 0;
const uint32_t dumpEnd = std::min(section.size, offset + 32);
std::printf(" bytes around address:\n ");
for (uint32_t dumpOffset = dumpStart; dumpOffset < dumpEnd; ++dumpOffset)
{
std::printf("%02X ", section.data[dumpOffset]);
}
std::printf("\n");
return;
}
std::printf("Address 0x%08X not covered by any section in m_sections\n", address);
}
uint32_t ElfParser::getEntryPoint() const
{
return static_cast<uint32_t>(m_elf->get_entry());
}
void ElfParser::setReporter(RecompilerReporter *reporter)
{
m_reporter = reporter;
}
bool ElfParser::loadGhidraFunctionMap(const std::string &mapPath)
{
if (mapPath.empty())
{
return false;
}
m_hasLoadedGhidraMap = false;
m_ghidraMapStarts.clear();
std::ifstream file(mapPath);
if (!file.is_open())
{
if (m_reporter)
{
m_reporter->warning("ghidra-map", "Could not open Ghidra function map: " + mapPath);
}
return false;
}
std::string line;
if (!std::getline(file, line))
{
return false;
}
int count = 0;
int skippedNonExecutable = 0;
int skippedInvalidRange = 0;
std::unordered_set<uint32_t> mapStarts;
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);
const Section *section = FindFunctionSectionByAddress(m_sections, start);
if (!section)
{
++skippedNonExecutable;
continue;
}
end = ClampFunctionEndToSection(section, start, end);
if (end <= start)
{
++skippedInvalidRange;
continue;
}
Function func{};
func.name = name;
func.start = start;
func.end = end;
func.isRecompiled = false;
func.isStub = false;
func.isSkipped = false;
m_extraFunctions.push_back(std::move(func));
mapStarts.insert(start);
count++;
}
catch (...)
{
continue;
}
}
if (count > 0)
{
m_hasLoadedGhidraMap = true;
m_ghidraMapStarts = mapStarts;
if (m_reporter)
{
m_reporter->info("ghidra-map", "Loaded " + std::to_string(count) + " functions from Ghidra map");
}
if (skippedNonExecutable > 0)
{
if (m_reporter)
{
m_reporter->warning("ghidra-map", "Ignored " + std::to_string(skippedNonExecutable) + " Ghidra function(s) outside executable sections.");
}
}
if (skippedInvalidRange > 0)
{
if (m_reporter)
{
m_reporter->warning("ghidra-map", "Ignored " + std::to_string(skippedInvalidRange) + " Ghidra function(s) with invalid ranges after section clamping.");
}
}
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)
{
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(),
[](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;
}
if (skippedNonExecutable > 0 || skippedInvalidRange > 0)
{
if (m_reporter)
{
m_reporter->warning("ghidra-map", "Loaded 0 functions from Ghidra map after filtering (" +
std::to_string(skippedNonExecutable) + " non-executable, " +
std::to_string(skippedInvalidRange) + " invalid range).");
}
}
return false;
}
ElfParser::~ElfParser() = default;
bool ElfParser::parse()
{
if (!m_elf->load(m_filePath))
{
if (m_reporter)
{
m_reporter->error("elf", "Could not load ELF file: " + m_filePath);
}
return false;
}
// Check if this is a PS2 ELF (MIPS R5900)
if (m_elf->get_machine() != ELFIO::EM_MIPS)
{
if (m_reporter)
{
m_reporter->error("elf", "Not a MIPS ELF file");
}
return false;
}
loadSections();
loadSymbols();
loadRelocations();
loadDebugFunctions();
return true;
}
void ElfParser::loadSections()
{
m_sections.clear();
ELFIO::Elf_Half sec_num = m_elf->sections.size();
for (ELFIO::Elf_Half i = 0; i < sec_num; ++i)
{
ELFIO::section *psec = m_elf->sections[i];
Section section;
section.name = psec->get_name();
section.address = psec->get_address();
section.size = psec->get_size();
section.offset = psec->get_offset();
section.isCode = isExecutableSection(psec);
section.isData = isDataSection(psec);
section.isBSS = (psec->get_type() == ELFIO::SHT_NOBITS);
section.isReadOnly = !(psec->get_flags() & ELFIO::SHF_WRITE);
if (psec->get_size() > 0 && psec->get_type() != ELFIO::SHT_NOBITS)
{
section.data = (uint8_t *)psec->get_data();
}
else
{
section.data = nullptr;
}
m_sections.push_back(section);
}
if (m_sections.empty())
{
AppendLoadSegmentsAsSections(*m_elf, m_sections);
if (!m_sections.empty())
{
if (m_reporter)
{
m_reporter->info("elf", "ELF has no section headers; using loadable segments as sections (" +
std::to_string(m_sections.size()) + " entries).");
}
}
}
}
void ElfParser::loadSymbols()
{
m_symbols.clear();
for (ELFIO::Elf_Half i = 0; i < m_elf->sections.size(); ++i)
{
ELFIO::section *psec = m_elf->sections[i];
if (psec->get_type() == ELFIO::SHT_SYMTAB || psec->get_type() == ELFIO::SHT_DYNSYM)
{
if (psec->get_link() >= m_elf->sections.size())
{
if (m_reporter)
{
m_reporter->warning("elf", "Symbol section link out of bounds: " + std::to_string(psec->get_link()));
}
continue;
}
ELFIO::symbol_section_accessor symbols(*m_elf, psec);
ELFIO::Elf_Xword sym_num = symbols.get_symbols_num();
ELFIO::section *pstrSec = m_elf->sections[psec->get_link()];
ELFIO::string_section_accessor strings(pstrSec);
for (ELFIO::Elf_Xword j = 0; j < sym_num; ++j)
{
std::string name;
ELFIO::Elf64_Addr value;
ELFIO::Elf_Xword size;
unsigned char bind;
unsigned char type;
ELFIO::Elf_Half section_index;
unsigned char other;
symbols.get_symbol(j, name, value, size, bind, type, section_index, other);
if (name.empty())
{
continue;
}
Symbol symbol;
symbol.name = name;
symbol.address = static_cast<uint32_t>(value);
symbol.size = static_cast<uint32_t>(size);
symbol.isFunction = (type == ELFIO::STT_FUNC);
symbol.isImported = section_index == ELFIO::SHN_UNDEF;
symbol.isExported = (!symbol.isImported && bind == ELFIO::STB_GLOBAL);
m_symbols.push_back(symbol);
}
}
}
}
void ElfParser::loadRelocations()
{
m_relocations.clear();
for (ELFIO::Elf_Half i = 0; i < m_elf->sections.size(); ++i)
{
ELFIO::section *psec = m_elf->sections[i];
if (psec->get_type() == ELFIO::SHT_REL || psec->get_type() == ELFIO::SHT_RELA)
{
if (psec->get_link() >= m_elf->sections.size())
{
if (m_reporter)
{
m_reporter->warning("elf", "Relocation section link out of bounds: " + std::to_string(psec->get_link()));
}
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())
{
if (m_reporter)
{
m_reporter->warning("elf", "Symbol section link out of bounds (in relocation): " + std::to_string(symSec->get_link()));
}
continue;
}
ELFIO::symbol_section_accessor symbols(*m_elf, symSec);
ELFIO::section *strSec = m_elf->sections[symSec->get_link()];
ELFIO::string_section_accessor strings(strSec);
for (ELFIO::Elf_Xword j = 0; j < relocs.get_entries_num(); ++j)
{
ELFIO::Elf64_Addr offset;
ELFIO::Elf_Word symbol;
ELFIO::Elf_Word type;
ELFIO::Elf_Sxword addend;
// Always use the 5-parameter version
if (psec->get_type() == ELFIO::SHT_REL)
{
// Pass addend even for REL sections
relocs.get_entry(j, offset, symbol, type, addend);
// Reset addend for REL sections since it's not part of the section
addend = 0;
}
else
{
relocs.get_entry(j, offset, symbol, type, addend);
}
Relocation reloc;
reloc.offset = static_cast<uint32_t>(offset);
reloc.info = (symbol << 8) | (type & 0xFF);
reloc.symbol = symbol;
reloc.symbolName.clear();
reloc.type = type;
reloc.addend = static_cast<int32_t>(addend);
if (symbol < symbols.get_symbols_num())
{
std::string symName;
ELFIO::Elf64_Addr symValue = 0;
ELFIO::Elf_Xword symSize = 0;
unsigned char symBind = 0;
unsigned char symType = 0;
ELFIO::Elf_Half symSectionIndex = 0;
unsigned char symOther = 0;
if (symbols.get_symbol(symbol, symName, symValue, symSize,
symBind, symType, symSectionIndex, symOther))
{
reloc.symbolName = symName;
}
}
m_relocations.push_back(reloc);
}
}
}
}
void ElfParser::loadDebugFunctions()
{
m_extraFunctions.clear();
m_hasLoadedGhidraMap = false;
m_ghidraMapStarts.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());
}
}