mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-29 17:58:24 -04:00
feat: IOP emulator
refactor: codegen to catch callbacks on mips code feat: added a lot of entries or IOP emulator
This commit is contained in:
@@ -5,6 +5,48 @@
|
||||
|
||||
namespace ps2recomp
|
||||
{
|
||||
inline constexpr uint32_t MIPS_INSTRUCTION_SIZE = sizeof(uint32_t);
|
||||
inline constexpr uint16_t MIPS_IMMEDIATE_SIGN_BIT = 0x8000u;
|
||||
inline constexpr uint32_t MIPS_JUMP_TARGET_SHIFT = 2u;
|
||||
inline constexpr uint32_t MIPS_JUMP_REGION_MASK = 0xF0000000u;
|
||||
|
||||
// R5900 general-purpose register indices used by the encoded RS/RT/RD fields.
|
||||
enum GprRegisters : uint32_t
|
||||
{
|
||||
GPR_ZERO = 0,
|
||||
GPR_AT = 1,
|
||||
GPR_V0 = 2,
|
||||
GPR_V1 = 3,
|
||||
GPR_A0 = 4,
|
||||
GPR_A1 = 5,
|
||||
GPR_A2 = 6,
|
||||
GPR_A3 = 7,
|
||||
GPR_T0 = 8,
|
||||
GPR_T1 = 9,
|
||||
GPR_T2 = 10,
|
||||
GPR_T3 = 11,
|
||||
GPR_T4 = 12,
|
||||
GPR_T5 = 13,
|
||||
GPR_T6 = 14,
|
||||
GPR_T7 = 15,
|
||||
GPR_S0 = 16,
|
||||
GPR_S1 = 17,
|
||||
GPR_S2 = 18,
|
||||
GPR_S3 = 19,
|
||||
GPR_S4 = 20,
|
||||
GPR_S5 = 21,
|
||||
GPR_S6 = 22,
|
||||
GPR_S7 = 23,
|
||||
GPR_T8 = 24,
|
||||
GPR_T9 = 25,
|
||||
GPR_K0 = 26,
|
||||
GPR_K1 = 27,
|
||||
GPR_GP = 28,
|
||||
GPR_SP = 29,
|
||||
GPR_FP = 30,
|
||||
GPR_RA = 31,
|
||||
};
|
||||
|
||||
// Basic MIPS opcodes (shared with R4300i)
|
||||
enum MipsOpcodes
|
||||
{
|
||||
|
||||
@@ -42,9 +42,12 @@ namespace ps2recomp
|
||||
std::vector<Function> &functions,
|
||||
std::unordered_map<uint32_t, std::vector<Instruction>> &decodedFunctions,
|
||||
const std::vector<Section> §ions);
|
||||
static size_t ResliceEntryFunctions(
|
||||
std::vector<Function> &functions,
|
||||
std::unordered_map<uint32_t, std::vector<Instruction>> &decodedFunctions);
|
||||
static size_t ResliceEntryFunctions(std::vector<Function> &functions, std::unordered_map<uint32_t, std::vector<Instruction>> &decodedFunctions);
|
||||
static size_t CollectInternalEntryTargets(
|
||||
const std::vector<Function> &functions,
|
||||
const std::unordered_map<uint32_t, std::vector<Instruction>> &decodedFunctions,
|
||||
const std::unordered_set<uint32_t> &entryAddresses,
|
||||
std::unordered_map<uint32_t, std::vector<uint32_t>> &targetsByOwner);
|
||||
|
||||
static std::string ClampFilenameLength(const std::string& baseName, const std::string& extension, std::size_t maxLength);
|
||||
|
||||
@@ -67,6 +70,7 @@ namespace ps2recomp
|
||||
std::unordered_set<std::string> m_stubFunctions;
|
||||
std::unordered_set<uint32_t> m_stubFunctionStarts;
|
||||
std::unordered_map<uint32_t, std::string> m_stubHandlerBindingsByStart;
|
||||
std::unordered_set<uint32_t> m_entryPointHintStarts;
|
||||
std::unordered_set<uint32_t> m_correctnessCriticalFunctionStarts;
|
||||
std::map<uint32_t, std::string> m_generatedStubs;
|
||||
std::unordered_map<uint32_t, std::string> m_functionRenames;
|
||||
|
||||
@@ -187,6 +187,7 @@ namespace ps2recomp
|
||||
std::vector<std::string> skipFunctions;
|
||||
std::unordered_map<uint32_t, std::string> patches;
|
||||
std::vector<std::string> stubImplementations;
|
||||
std::vector<std::string> entryPointHints;
|
||||
std::unordered_map<uint32_t, uint32_t> mmioByInstructionAddress;
|
||||
std::vector<JumpTable> jumpTables;
|
||||
};
|
||||
|
||||
@@ -74,6 +74,27 @@ namespace ps2recomp
|
||||
config.stubImplementations = toml::find<std::vector<std::string>>(data, "stubs");
|
||||
}
|
||||
|
||||
auto appendEntryPointHints = [&](const toml::value &table, const char *key)
|
||||
{
|
||||
if (!table.contains(key) || !table.at(key).is_array())
|
||||
{
|
||||
return;
|
||||
}
|
||||
const auto values = toml::find<std::vector<std::string>>(table, key);
|
||||
config.entryPointHints.insert(
|
||||
config.entryPointHints.end(), values.begin(), values.end());
|
||||
};
|
||||
appendEntryPointHints(general, "entry_points");
|
||||
appendEntryPointHints(data, "entry_points");
|
||||
// Backward compatibility
|
||||
appendEntryPointHints(general, "untracked_stubs");
|
||||
appendEntryPointHints(data, "untracked_stubs");
|
||||
|
||||
std::sort(config.entryPointHints.begin(), config.entryPointHints.end());
|
||||
config.entryPointHints.erase(
|
||||
std::unique(config.entryPointHints.begin(), config.entryPointHints.end()),
|
||||
config.entryPointHints.end());
|
||||
|
||||
if (general.contains("skip") && general.at("skip").is_array())
|
||||
{
|
||||
config.skipFunctions = toml::find<std::vector<std::string>>(general, "skip");
|
||||
@@ -276,6 +297,7 @@ namespace ps2recomp
|
||||
general["patch_cache"] = config.patchCache;
|
||||
general["skip"] = config.skipFunctions;
|
||||
general["stubs"] = config.stubImplementations;
|
||||
general["entry_points"] = config.entryPointHints;
|
||||
data["general"] = general;
|
||||
|
||||
if (!config.mmioByInstructionAddress.empty())
|
||||
|
||||
@@ -135,6 +135,11 @@ namespace ps2recomp
|
||||
|
||||
for (const auto &inst : instructions)
|
||||
{
|
||||
if (inst.opcode == OPCODE_SPECIAL && inst.function == SPECIAL_SYSCALL)
|
||||
{
|
||||
queueResumeEntryTarget(inst.address + 4u);
|
||||
}
|
||||
|
||||
bool isStaticJump = (inst.opcode == OPCODE_J || inst.opcode == OPCODE_JAL);
|
||||
if (inst.isBranch && inst.opcode != OPCODE_J && inst.opcode != OPCODE_JAL)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "ps2recomp/elf_parser.h"
|
||||
#include "ps2recomp/instructions.h"
|
||||
#include "ps2recomp/recompiler_reporter.h"
|
||||
#include "ps2recomp/types.h"
|
||||
#include <iostream>
|
||||
@@ -116,6 +117,8 @@ namespace
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace ps2recomp;
|
||||
|
||||
bool HasDwarfSections(const ELFIO::elfio &elf)
|
||||
{
|
||||
for (ELFIO::Elf_Half i = 0; i < elf.sections.size(); ++i)
|
||||
@@ -453,7 +456,328 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
void ScanJalTargetsFallback(ps2recomp::ElfParser *parser, std::vector<ps2recomp::Function> &outFunctions)
|
||||
bool ReadSectionWord(const ps2recomp::Section §ion, uint32_t offset, uint32_t &outWord)
|
||||
{
|
||||
if (!section.data || offset > section.size || section.size - offset < sizeof(uint32_t))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(&outWord, section.data + offset, sizeof(uint32_t));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LooksLikeCallableEntry(const std::vector<ps2recomp::Section> §ions, uint32_t address, bool allowLeafThunk)
|
||||
{
|
||||
if ((address % MIPS_INSTRUCTION_SIZE) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const ps2recomp::Section *section = FindCodeSectionByAddress(sections, address);
|
||||
if (!section || !section->data)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t startOffset = address - section->address;
|
||||
constexpr uint32_t kProbeWords = 8;
|
||||
|
||||
for (uint32_t index = 0; index < kProbeWords; ++index)
|
||||
{
|
||||
uint32_t raw = 0;
|
||||
if (!ReadSectionWord(*section, startOffset + (index * MIPS_INSTRUCTION_SIZE), raw))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const uint32_t opcode = OPCODE(raw);
|
||||
const uint32_t rs = RS(raw);
|
||||
const uint32_t rt = RT(raw);
|
||||
const uint16_t immediate = static_cast<uint16_t>(IMMEDIATE(raw));
|
||||
|
||||
// Non-leaf functions normally allocate their stack frame immediately.
|
||||
// Accept ADDIU/DADDIU $sp,$sp,-N in the first few instructions.
|
||||
if (index < 4 &&
|
||||
(opcode == OPCODE_ADDIU || opcode == OPCODE_DADDIU) &&
|
||||
rs == GPR_SP && rt == GPR_SP &&
|
||||
(immediate & MIPS_IMMEDIATE_SIGN_BIT) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Some prologues set up GP before saving RA, so also recognize the
|
||||
// common SW/SD/SQ $ra,offset($sp) forms in the entry window.
|
||||
if ((opcode == OPCODE_SW || opcode == OPCODE_SD || opcode == OPCODE_SQ) &&
|
||||
rs == GPR_SP && rt == GPR_RA)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Leaf callbacks and vtable thunks often have no stack frame at all.
|
||||
if (allowLeafThunk &&
|
||||
opcode == OPCODE_SPECIAL && FUNCTION(raw) == SPECIAL_JR && rs == GPR_RA)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WritesGpr(uint32_t raw, uint32_t reg)
|
||||
{
|
||||
if (reg == GPR_ZERO)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t opcode = OPCODE(raw);
|
||||
const uint32_t rt = RT(raw);
|
||||
const uint32_t rd = RD(raw);
|
||||
|
||||
if (opcode == OPCODE_SPECIAL || opcode == OPCODE_MMI)
|
||||
{
|
||||
return rd == reg;
|
||||
}
|
||||
|
||||
if (opcode == OPCODE_JAL)
|
||||
{
|
||||
return reg == GPR_RA;
|
||||
}
|
||||
|
||||
bool writesRt = false;
|
||||
switch (opcode)
|
||||
{
|
||||
case OPCODE_ADDI:
|
||||
case OPCODE_ADDIU:
|
||||
case OPCODE_SLTI:
|
||||
case OPCODE_SLTIU:
|
||||
case OPCODE_ANDI:
|
||||
case OPCODE_ORI:
|
||||
case OPCODE_XORI:
|
||||
case OPCODE_LUI:
|
||||
case OPCODE_DADDI:
|
||||
case OPCODE_DADDIU:
|
||||
case OPCODE_LDL:
|
||||
case OPCODE_LDR:
|
||||
case OPCODE_LQ:
|
||||
case OPCODE_LB:
|
||||
case OPCODE_LH:
|
||||
case OPCODE_LWL:
|
||||
case OPCODE_LW:
|
||||
case OPCODE_LBU:
|
||||
case OPCODE_LHU:
|
||||
case OPCODE_LWR:
|
||||
case OPCODE_LWU:
|
||||
case OPCODE_LL:
|
||||
case OPCODE_LLD:
|
||||
case OPCODE_LD:
|
||||
writesRt = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return writesRt && rt == reg;
|
||||
}
|
||||
|
||||
bool IsControlTransfer(uint32_t raw)
|
||||
{
|
||||
const uint32_t opcode = OPCODE(raw);
|
||||
switch (opcode)
|
||||
{
|
||||
case OPCODE_REGIMM:
|
||||
case OPCODE_J:
|
||||
case OPCODE_JAL:
|
||||
case OPCODE_BEQ:
|
||||
case OPCODE_BNE:
|
||||
case OPCODE_BLEZ:
|
||||
case OPCODE_BGTZ:
|
||||
case OPCODE_BEQL:
|
||||
case OPCODE_BNEL:
|
||||
case OPCODE_BLEZL:
|
||||
case OPCODE_BGTZL:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (opcode != OPCODE_SPECIAL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t function = FUNCTION(raw);
|
||||
return function == SPECIAL_JR || function == SPECIAL_JALR;
|
||||
}
|
||||
|
||||
bool IsCallInstruction(uint32_t raw)
|
||||
{
|
||||
const uint32_t opcode = OPCODE(raw);
|
||||
return opcode == OPCODE_JAL ||
|
||||
(opcode == OPCODE_SPECIAL && FUNCTION(raw) == SPECIAL_JALR);
|
||||
}
|
||||
|
||||
void ScanMaterializedCodeAddresses(const std::vector<ps2recomp::Section> §ions,
|
||||
std::unordered_set<uint32_t> &starts)
|
||||
{
|
||||
constexpr uint32_t kMaxLookaheadWords = 4;
|
||||
|
||||
for (const auto §ion : sections)
|
||||
{
|
||||
if (!section.isCode || !section.data || section.size < (2u * MIPS_INSTRUCTION_SIZE))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (uint32_t offset = 0; offset + MIPS_INSTRUCTION_SIZE <= section.size;
|
||||
offset += MIPS_INSTRUCTION_SIZE)
|
||||
{
|
||||
uint32_t upperRaw = 0;
|
||||
if (!ReadSectionWord(section, offset, upperRaw) ||
|
||||
OPCODE(upperRaw) != OPCODE_LUI)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint32_t upperReg = RT(upperRaw);
|
||||
if (upperReg == GPR_ZERO)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint32_t upperValue = IMMEDIATE(upperRaw) << 16;
|
||||
bool sawControlTransfer = false;
|
||||
bool sawCallTransfer = false;
|
||||
|
||||
for (uint32_t lookahead = 1; lookahead <= kMaxLookaheadWords; ++lookahead)
|
||||
{
|
||||
uint32_t lowRaw = 0;
|
||||
if (!ReadSectionWord(section, offset + (lookahead * MIPS_INSTRUCTION_SIZE), lowRaw))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const uint32_t opcode = OPCODE(lowRaw);
|
||||
const uint32_t rs = RS(lowRaw);
|
||||
const uint32_t rt = RT(lowRaw);
|
||||
|
||||
if ((opcode == OPCODE_ADDIU || opcode == OPCODE_ORI || opcode == OPCODE_DADDIU) &&
|
||||
rs == upperReg)
|
||||
{
|
||||
const uint16_t immediate = static_cast<uint16_t>(IMMEDIATE(lowRaw));
|
||||
uint32_t target = 0;
|
||||
if (opcode == OPCODE_ORI)
|
||||
{
|
||||
target = upperValue | static_cast<uint32_t>(immediate);
|
||||
}
|
||||
else // ADDIU/DADDIU use a signed low half
|
||||
{
|
||||
target = upperValue + static_cast<uint32_t>(
|
||||
static_cast<int32_t>(static_cast<int16_t>(immediate)));
|
||||
}
|
||||
|
||||
uint32_t nextRaw = 0;
|
||||
const bool followedByCall =
|
||||
ReadSectionWord(section,
|
||||
offset + ((lookahead + 1u) * MIPS_INSTRUCTION_SIZE),
|
||||
nextRaw) &&
|
||||
IsCallInstruction(nextRaw);
|
||||
const bool materializedAsCallArgument =
|
||||
rt >= GPR_A0 && rt <= GPR_A3 && (sawCallTransfer || followedByCall);
|
||||
|
||||
if (LooksLikeCallableEntry(sections, target, materializedAsCallArgument))
|
||||
{
|
||||
starts.insert(target);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// The instruction immediately after a branch/call is its
|
||||
// delay slot. It may complete a callback address, but no
|
||||
// later instruction is in the same straight-line state.
|
||||
if (sawControlTransfer)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (WritesGpr(lowRaw, upperReg))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (IsControlTransfer(lowRaw))
|
||||
{
|
||||
sawControlTransfer = true;
|
||||
sawCallTransfer = IsCallInstruction(lowRaw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsDedicatedFunctionPointerSection(const std::string &name)
|
||||
{
|
||||
return name == ".ctors" || name == ".dtors" ||
|
||||
name == ".init_array" || name == ".fini_array";
|
||||
}
|
||||
|
||||
void ScanDataFunctionPointerTables(const std::vector<ps2recomp::Section> §ions,
|
||||
std::unordered_set<uint32_t> &starts)
|
||||
{
|
||||
struct PointerCandidate
|
||||
{
|
||||
uint32_t sourceOffset;
|
||||
uint32_t target;
|
||||
};
|
||||
|
||||
constexpr uint32_t kClusterDistanceBytes = 32;
|
||||
|
||||
for (const auto §ion : sections)
|
||||
{
|
||||
if (!section.isData || section.isCode || section.isBSS ||
|
||||
!section.data || section.size < MIPS_INSTRUCTION_SIZE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<PointerCandidate> candidates;
|
||||
for (uint32_t offset = 0; offset + MIPS_INSTRUCTION_SIZE <= section.size;
|
||||
offset += MIPS_INSTRUCTION_SIZE)
|
||||
{
|
||||
uint32_t target = 0;
|
||||
if (ReadSectionWord(section, offset, target) &&
|
||||
LooksLikeCallableEntry(sections, target, true))
|
||||
{
|
||||
candidates.push_back({offset, target});
|
||||
}
|
||||
}
|
||||
|
||||
const bool dedicatedPointerSection = IsDedicatedFunctionPointerSection(section.name);
|
||||
for (size_t index = 0; index < candidates.size(); ++index)
|
||||
{
|
||||
bool clustered = dedicatedPointerSection;
|
||||
if (index > 0 &&
|
||||
candidates[index].sourceOffset - candidates[index - 1].sourceOffset <= kClusterDistanceBytes)
|
||||
{
|
||||
clustered = true;
|
||||
}
|
||||
if (index + 1 < candidates.size() &&
|
||||
candidates[index + 1].sourceOffset - candidates[index].sourceOffset <= kClusterDistanceBytes)
|
||||
{
|
||||
clustered = true;
|
||||
}
|
||||
|
||||
if (clustered)
|
||||
{
|
||||
starts.insert(candidates[index].target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScanFunctionStartsFallback(ps2recomp::ElfParser *parser, std::vector<ps2recomp::Function> &outFunctions)
|
||||
{
|
||||
std::unordered_set<uint32_t> starts;
|
||||
starts.reserve(4096);
|
||||
@@ -467,26 +791,29 @@ namespace
|
||||
const auto §ions = parser->getSections();
|
||||
for (const auto §ion : sections)
|
||||
{
|
||||
if (!section.isCode || !section.data || section.size < 4)
|
||||
if (!section.isCode || !section.data || section.size < MIPS_INSTRUCTION_SIZE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (uint32_t offset = 0; offset + 4 <= section.size; offset += 4)
|
||||
for (uint32_t offset = 0; offset + MIPS_INSTRUCTION_SIZE <= section.size;
|
||||
offset += MIPS_INSTRUCTION_SIZE)
|
||||
{
|
||||
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
|
||||
const uint32_t op = OPCODE(raw);
|
||||
if (op != OPCODE_JAL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint32_t index = raw & 0x03FFFFFF;
|
||||
const uint32_t target = ((pc + 4) & 0xF0000000u) | (index << 2);
|
||||
const uint32_t index = TARGET(raw);
|
||||
const uint32_t target =
|
||||
((pc + MIPS_INSTRUCTION_SIZE) & MIPS_JUMP_REGION_MASK) |
|
||||
(index << MIPS_JUMP_TARGET_SHIFT);
|
||||
|
||||
if (FindCodeSectionByAddress(sections, target))
|
||||
{
|
||||
@@ -494,6 +821,9 @@ namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScanMaterializedCodeAddresses(sections, starts);
|
||||
ScanDataFunctionPointerTables(sections, starts);
|
||||
|
||||
std::vector<uint32_t> sortedStarts(starts.begin(), starts.end());
|
||||
std::sort(sortedStarts.begin(), sortedStarts.end());
|
||||
@@ -523,7 +853,7 @@ namespace
|
||||
ps2recomp::Function func{};
|
||||
func.name = MakeAutoFunctionName(start);
|
||||
func.start = start;
|
||||
func.end = (end > start) ? end : (start + 4);
|
||||
func.end = (end > start) ? end : (start + MIPS_INSTRUCTION_SIZE);
|
||||
func.isRecompiled = false;
|
||||
func.isStub = false;
|
||||
func.isSkipped = false;
|
||||
@@ -1420,7 +1750,7 @@ namespace ps2recomp
|
||||
|
||||
if (m_extraFunctions.empty())
|
||||
{
|
||||
ScanJalTargetsFallback(this, m_extraFunctions);
|
||||
ScanFunctionStartsFallback(this, m_extraFunctions);
|
||||
}
|
||||
|
||||
std::sort(m_extraFunctions.begin(), m_extraFunctions.end(),
|
||||
|
||||
@@ -45,8 +45,8 @@ namespace ps2recomp
|
||||
ss << "#include <stdexcept>\n";
|
||||
ss << "#include \"ps2_runtime_macros.h\"\n";
|
||||
ss << "#include \"ps2_runtime.h\"\n";
|
||||
ss << "#include \"ps2_recompiled_functions.h\"\n";
|
||||
ss << "#include \"ps2_recompiled_stubs.h\"\n\n";
|
||||
ss << "#include <ps2_recompiled_functions.h>\n";
|
||||
ss << "#include <ps2_recompiled_stubs.h>\n\n";
|
||||
ss << "#include \"ps2_syscalls.h\"\n";
|
||||
ss << "#include \"ps2_stubs.h\"\n\n";
|
||||
ss << "#ifdef PS2_FUNCTION_LOG_TRACKER\n";
|
||||
|
||||
@@ -102,10 +102,10 @@ namespace ps2recomp
|
||||
void writeCombinedOutputPreamble(std::ostream &output)
|
||||
{
|
||||
output << "#include <stdexcept>\n";
|
||||
output << "#include \"ps2_recompiled_functions.h\"\n\n";
|
||||
output << "#include <ps2_recompiled_functions.h>\n\n";
|
||||
output << "#include \"ps2_runtime_macros.h\"\n";
|
||||
output << "#include \"ps2_runtime.h\"\n";
|
||||
output << "#include \"ps2_recompiled_stubs.h\"\n";
|
||||
output << "#include <ps2_recompiled_stubs.h>\n";
|
||||
output << "#include \"ps2_syscalls.h\"\n";
|
||||
output << "#include \"ps2_stubs.h\"\n";
|
||||
output << "#ifdef _DEBUG\n";
|
||||
@@ -725,6 +725,71 @@ namespace ps2recomp
|
||||
|
||||
return reslicedCount;
|
||||
}
|
||||
|
||||
size_t collectInternalEntryTargetsImpl(
|
||||
const std::vector<Function> &functions,
|
||||
const std::unordered_map<uint32_t, std::vector<Instruction>> &decodedFunctions,
|
||||
const std::unordered_set<uint32_t> &entryAddresses,
|
||||
std::unordered_map<uint32_t, std::vector<uint32_t>> &targetsByOwner)
|
||||
{
|
||||
std::unordered_set<uint32_t> functionStarts;
|
||||
functionStarts.reserve(functions.size());
|
||||
for (const auto &function : functions)
|
||||
{
|
||||
functionStarts.insert(function.start);
|
||||
}
|
||||
|
||||
size_t addedCount = 0u;
|
||||
for (uint32_t entryAddress : entryAddresses)
|
||||
{
|
||||
if (functionStarts.contains(entryAddress))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const Function *owner = nullptr;
|
||||
for (const auto &function : functions)
|
||||
{
|
||||
if (!function.isRecompiled || function.isStub || function.isSkipped ||
|
||||
entryAddress <= function.start || entryAddress >= function.end)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto decodedIt = decodedFunctions.find(function.start);
|
||||
if (decodedIt == decodedFunctions.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool containsInstruction = std::any_of(decodedIt->second.begin(), decodedIt->second.end(), [entryAddress](const Instruction &instruction)
|
||||
{ return instruction.address == entryAddress; });
|
||||
if (!containsInstruction)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!owner || function.start > owner->start)
|
||||
{
|
||||
owner = &function;
|
||||
}
|
||||
}
|
||||
|
||||
if (!owner)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &targets = targetsByOwner[owner->start];
|
||||
if (std::find(targets.begin(), targets.end(), entryAddress) == targets.end())
|
||||
{
|
||||
targets.push_back(entryAddress);
|
||||
++addedCount;
|
||||
}
|
||||
}
|
||||
|
||||
return addedCount;
|
||||
}
|
||||
}
|
||||
|
||||
PS2Recompiler::PS2Recompiler(const std::string &configPath)
|
||||
@@ -751,6 +816,7 @@ namespace ps2recomp
|
||||
m_stubFunctions.clear();
|
||||
m_stubFunctionStarts.clear();
|
||||
m_stubHandlerBindingsByStart.clear();
|
||||
m_entryPointHintStarts.clear();
|
||||
m_correctnessCriticalFunctionStarts.clear();
|
||||
|
||||
for (const auto &name : m_config.skipFunctions)
|
||||
@@ -792,6 +858,14 @@ namespace ps2recomp
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto &hint : m_config.entryPointHints)
|
||||
{
|
||||
const FunctionSelector selector = parseFunctionSelector(hint);
|
||||
if (selector.start.has_value())
|
||||
{
|
||||
m_entryPointHintStarts.insert(*selector.start);
|
||||
}
|
||||
}
|
||||
|
||||
m_reporter.progress("parsing ELF");
|
||||
m_elfParser = std::make_unique<ElfParser>(m_config.inputPath);
|
||||
@@ -983,7 +1057,7 @@ namespace ps2recomp
|
||||
|
||||
if (isStubFunction(function))
|
||||
{
|
||||
if (!correctnessCritical || hasResolvedStubHandler(function))
|
||||
if (hasResolvedStubHandler(function))
|
||||
{
|
||||
function.isStub = true;
|
||||
function.isSkipped = false;
|
||||
@@ -991,12 +1065,15 @@ namespace ps2recomp
|
||||
continue;
|
||||
}
|
||||
|
||||
m_reporter.recordCorrectnessCriticalGuestFallback();
|
||||
if (correctnessCritical)
|
||||
{
|
||||
m_reporter.recordCorrectnessCriticalGuestFallback();
|
||||
}
|
||||
m_reporter.warningAt(
|
||||
"correctness-critical",
|
||||
"stub",
|
||||
function.name,
|
||||
function.start,
|
||||
"Unresolved initializer stub ignored; recompiling the original guest function");
|
||||
"Configured stub has no runtime handler; recompiling the original guest function");
|
||||
}
|
||||
|
||||
if (shouldSkipFunction(function))
|
||||
@@ -1882,6 +1959,22 @@ namespace ps2recomp
|
||||
targets.push_back(target);
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<uint32_t> guestFallbackEntryAddresses = m_entryPointHintStarts;
|
||||
for (uint32_t address : m_stubFunctionStarts)
|
||||
{
|
||||
const auto bindingIt = m_stubHandlerBindingsByStart.find(address);
|
||||
if (bindingIt == m_stubHandlerBindingsByStart.end() ||
|
||||
resolveStubTarget(bindingIt->second) == StubTarget::Unknown)
|
||||
{
|
||||
guestFallbackEntryAddresses.insert(address);
|
||||
}
|
||||
}
|
||||
collectInternalEntryTargetsImpl(
|
||||
m_functions,
|
||||
m_decodedFunctions,
|
||||
guestFallbackEntryAddresses,
|
||||
m_resumeEntryTargetsByOwner);
|
||||
|
||||
size_t totalTargets = 0u;
|
||||
for (auto it = m_resumeEntryTargetsByOwner.begin(); it != m_resumeEntryTargetsByOwner.end();)
|
||||
@@ -2152,12 +2245,12 @@ namespace ps2recomp
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
std::string PS2Recompiler::clampFilenameLength(const std::string& baseName, const std::string& extension, std::size_t maxLength)
|
||||
std::string PS2Recompiler::clampFilenameLength(const std::string &baseName, const std::string &extension, std::size_t maxLength)
|
||||
{
|
||||
if (maxLength == 0)
|
||||
{
|
||||
// Keep this static helper side-effect free; callers validate arguments.
|
||||
//Better go over the limit than create files with an empty path
|
||||
// Better go over the limit than create files with an empty path
|
||||
return baseName + extension;
|
||||
}
|
||||
|
||||
@@ -2224,13 +2317,20 @@ namespace ps2recomp
|
||||
return stats.discoveredCount;
|
||||
}
|
||||
|
||||
size_t PS2Recompiler::ResliceEntryFunctions(
|
||||
std::vector<Function> &functions,
|
||||
std::unordered_map<uint32_t, std::vector<Instruction>> &decodedFunctions)
|
||||
size_t PS2Recompiler::ResliceEntryFunctions(std::vector<Function> &functions, std::unordered_map<uint32_t, std::vector<Instruction>> &decodedFunctions)
|
||||
{
|
||||
return resliceEntryFunctionsImpl(functions, decodedFunctions);
|
||||
}
|
||||
|
||||
size_t PS2Recompiler::CollectInternalEntryTargets(
|
||||
const std::vector<Function> &functions,
|
||||
const std::unordered_map<uint32_t, std::vector<Instruction>> &decodedFunctions,
|
||||
const std::unordered_set<uint32_t> &entryAddresses,
|
||||
std::unordered_map<uint32_t, std::vector<uint32_t>> &targetsByOwner)
|
||||
{
|
||||
return collectInternalEntryTargetsImpl(functions, decodedFunctions, entryAddresses, targetsByOwner);
|
||||
}
|
||||
|
||||
StubTarget PS2Recompiler::resolveStubTarget(const std::string &name)
|
||||
{
|
||||
if (!ps2_runtime_calls::resolveSyscallName(name).empty())
|
||||
@@ -2244,7 +2344,7 @@ namespace ps2recomp
|
||||
return StubTarget::Unknown;
|
||||
}
|
||||
|
||||
std::string PS2Recompiler::ClampFilenameLength(const std::string& baseName, const std::string& extension, std::size_t maxLength)
|
||||
std::string PS2Recompiler::ClampFilenameLength(const std::string &baseName, const std::string &extension, std::size_t maxLength)
|
||||
{
|
||||
return clampFilenameLength(baseName, extension, maxLength);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,70 @@ import java.util.regex.Pattern;
|
||||
|
||||
public class ExportPS2Functions extends GhidraScript {
|
||||
|
||||
// Names and values mirror ps2recomp::MipsOpcodes/SpecialFunctions/GprRegisters.
|
||||
// would be amazing cmake create this script coping the register from the header file
|
||||
private static final int MIPS_INSTRUCTION_SIZE = 4;
|
||||
private static final int MIPS_IMMEDIATE_BITS = 16;
|
||||
private static final int MIPS_IMMEDIATE_SIGN_BIT = 0x8000;
|
||||
private static final long UINT32_MASK = 0xFFFFFFFFL;
|
||||
private static final long OPCODE_MASK = 0x3FL;
|
||||
private static final long REGISTER_MASK = 0x1FL;
|
||||
private static final long IMMEDIATE_MASK = 0xFFFFL;
|
||||
private static final int OPCODE_SHIFT = 26;
|
||||
private static final int RS_SHIFT = 21;
|
||||
private static final int RT_SHIFT = 16;
|
||||
private static final int RD_SHIFT = 11;
|
||||
|
||||
private static final int GPR_ZERO = 0;
|
||||
private static final int GPR_A0 = 4;
|
||||
private static final int GPR_A3 = 7;
|
||||
private static final int GPR_SP = 29;
|
||||
private static final int GPR_RA = 31;
|
||||
|
||||
private static final int OPCODE_SPECIAL = 0x00;
|
||||
private static final int OPCODE_REGIMM = 0x01;
|
||||
private static final int OPCODE_J = 0x02;
|
||||
private static final int OPCODE_JAL = 0x03;
|
||||
private static final int OPCODE_BEQ = 0x04;
|
||||
private static final int OPCODE_BNE = 0x05;
|
||||
private static final int OPCODE_BLEZ = 0x06;
|
||||
private static final int OPCODE_BGTZ = 0x07;
|
||||
private static final int OPCODE_ADDI = 0x08;
|
||||
private static final int OPCODE_ADDIU = 0x09;
|
||||
private static final int OPCODE_SLTI = 0x0A;
|
||||
private static final int OPCODE_SLTIU = 0x0B;
|
||||
private static final int OPCODE_ANDI = 0x0C;
|
||||
private static final int OPCODE_ORI = 0x0D;
|
||||
private static final int OPCODE_XORI = 0x0E;
|
||||
private static final int OPCODE_LUI = 0x0F;
|
||||
private static final int OPCODE_BEQL = 0x14;
|
||||
private static final int OPCODE_BNEL = 0x15;
|
||||
private static final int OPCODE_BLEZL = 0x16;
|
||||
private static final int OPCODE_BGTZL = 0x17;
|
||||
private static final int OPCODE_DADDI = 0x18;
|
||||
private static final int OPCODE_DADDIU = 0x19;
|
||||
private static final int OPCODE_LDL = 0x1A;
|
||||
private static final int OPCODE_LDR = 0x1B;
|
||||
private static final int OPCODE_MMI = 0x1C;
|
||||
private static final int OPCODE_LQ = 0x1E;
|
||||
private static final int OPCODE_SQ = 0x1F;
|
||||
private static final int OPCODE_LB = 0x20;
|
||||
private static final int OPCODE_LH = 0x21;
|
||||
private static final int OPCODE_LWL = 0x22;
|
||||
private static final int OPCODE_LW = 0x23;
|
||||
private static final int OPCODE_LBU = 0x24;
|
||||
private static final int OPCODE_LHU = 0x25;
|
||||
private static final int OPCODE_LWR = 0x26;
|
||||
private static final int OPCODE_LWU = 0x27;
|
||||
private static final int OPCODE_SW = 0x2B;
|
||||
private static final int OPCODE_LL = 0x30;
|
||||
private static final int OPCODE_LLD = 0x34;
|
||||
private static final int OPCODE_LD = 0x37;
|
||||
private static final int OPCODE_SD = 0x3F;
|
||||
|
||||
private static final int SPECIAL_JR = 0x08;
|
||||
private static final int SPECIAL_JALR = 0x09;
|
||||
|
||||
// For now I have to copy all functions from the runtime handler list
|
||||
private static final Set<String> RUNTIME_HANDLER_NAMES = new HashSet<>(Arrays.asList(
|
||||
"FlushCache", "iFlushCache", "ResetEE", "SetMemoryMode", "InitThread", "CreateThread",
|
||||
@@ -207,6 +271,16 @@ public class ExportPS2Functions extends GhidraScript {
|
||||
boolean syntheticEntry = false;
|
||||
}
|
||||
|
||||
private static final class AddressTakenCandidate {
|
||||
long sourceOffset;
|
||||
long target;
|
||||
|
||||
AddressTakenCandidate(long sourceOffset, long target) {
|
||||
this.sourceOffset = sourceOffset;
|
||||
this.target = target;
|
||||
}
|
||||
}
|
||||
|
||||
private enum ClassificationKind {
|
||||
STUB,
|
||||
UNTRACKED_STUB,
|
||||
@@ -224,7 +298,31 @@ public class ExportPS2Functions extends GhidraScript {
|
||||
}
|
||||
|
||||
private static String hex(long value) {
|
||||
return String.format("0x%08X", value & 0xFFFFFFFFL);
|
||||
return String.format("0x%08X", value & UINT32_MASK);
|
||||
}
|
||||
|
||||
private static int opcode(long raw) {
|
||||
return (int) ((raw >>> OPCODE_SHIFT) & OPCODE_MASK);
|
||||
}
|
||||
|
||||
private static int rs(long raw) {
|
||||
return (int) ((raw >>> RS_SHIFT) & REGISTER_MASK);
|
||||
}
|
||||
|
||||
private static int rt(long raw) {
|
||||
return (int) ((raw >>> RT_SHIFT) & REGISTER_MASK);
|
||||
}
|
||||
|
||||
private static int rd(long raw) {
|
||||
return (int) ((raw >>> RD_SHIFT) & REGISTER_MASK);
|
||||
}
|
||||
|
||||
private static int function(long raw) {
|
||||
return (int) (raw & OPCODE_MASK);
|
||||
}
|
||||
|
||||
private static int immediate(long raw) {
|
||||
return (int) (raw & IMMEDIATE_MASK);
|
||||
}
|
||||
|
||||
private static String tomlString(String value) {
|
||||
@@ -466,8 +564,8 @@ public class ExportPS2Functions extends GhidraScript {
|
||||
}
|
||||
|
||||
MemoryBlock fromBlock = currentProgram.getMemory().getBlock(from);
|
||||
if (fromBlock == null || !fromBlock.isExecute()) {
|
||||
continue; // lets ignore DATA/non-code refs
|
||||
if (fromBlock != null && fromBlock.isExecute()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,7 +573,311 @@ public class ExportPS2Functions extends GhidraScript {
|
||||
}
|
||||
|
||||
private static String makeAnonymousEntryName(long start) {
|
||||
return String.format("entry_%08x", start & 0xFFFFFFFFL);
|
||||
return String.format("entry_%08x", start & UINT32_MASK);
|
||||
}
|
||||
|
||||
private Long readWord(Address address) {
|
||||
if (address == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return ((long) currentProgram.getMemory().getInt(address)) & UINT32_MASK;
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Address addressFromOffset(long offset) {
|
||||
try {
|
||||
return currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(offset & UINT32_MASK);
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean looksLikeCallableEntry(long target, boolean allowLeafThunk) {
|
||||
if ((target % MIPS_INSTRUCTION_SIZE) != 0L) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Address address = addressFromOffset(target);
|
||||
if (!isExecutableAddress(address) || currentProgram.getListing().getInstructionAt(address) == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int index = 0; index < 8; ++index) {
|
||||
Address probe;
|
||||
try {
|
||||
probe = address.add(index * (long) MIPS_INSTRUCTION_SIZE);
|
||||
} catch (Exception ignored) {
|
||||
break;
|
||||
}
|
||||
|
||||
Long rawValue = readWord(probe);
|
||||
if (rawValue == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
long raw = rawValue;
|
||||
int opcode = opcode(raw);
|
||||
int rs = rs(raw);
|
||||
int rt = rt(raw);
|
||||
int immediate = immediate(raw);
|
||||
|
||||
if (index < 4 && (opcode == OPCODE_ADDIU || opcode == OPCODE_DADDIU) && rs == GPR_SP && rt == GPR_SP && (immediate & MIPS_IMMEDIATE_SIGN_BIT) != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((opcode == OPCODE_SW || opcode == OPCODE_SD || opcode == OPCODE_SQ) &&
|
||||
rs == GPR_SP && rt == GPR_RA) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (allowLeafThunk && opcode == OPCODE_SPECIAL &&
|
||||
function(raw) == SPECIAL_JR && rs == GPR_RA) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean writesGpr(long raw, int register) {
|
||||
if (register == GPR_ZERO) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int opcode = opcode(raw);
|
||||
int rt = rt(raw);
|
||||
int rd = rd(raw);
|
||||
|
||||
if (opcode == OPCODE_SPECIAL || opcode == OPCODE_MMI) {
|
||||
return rd == register;
|
||||
}
|
||||
if (opcode == OPCODE_JAL) {
|
||||
return register == GPR_RA;
|
||||
}
|
||||
|
||||
boolean writesRt;
|
||||
switch (opcode) {
|
||||
case OPCODE_ADDI:
|
||||
case OPCODE_ADDIU:
|
||||
case OPCODE_SLTI:
|
||||
case OPCODE_SLTIU:
|
||||
case OPCODE_ANDI:
|
||||
case OPCODE_ORI:
|
||||
case OPCODE_XORI:
|
||||
case OPCODE_LUI:
|
||||
case OPCODE_DADDI:
|
||||
case OPCODE_DADDIU:
|
||||
case OPCODE_LDL:
|
||||
case OPCODE_LDR:
|
||||
case OPCODE_LQ:
|
||||
case OPCODE_LB:
|
||||
case OPCODE_LH:
|
||||
case OPCODE_LWL:
|
||||
case OPCODE_LW:
|
||||
case OPCODE_LBU:
|
||||
case OPCODE_LHU:
|
||||
case OPCODE_LWR:
|
||||
case OPCODE_LWU:
|
||||
case OPCODE_LL:
|
||||
case OPCODE_LLD:
|
||||
case OPCODE_LD:
|
||||
writesRt = true;
|
||||
break;
|
||||
default:
|
||||
writesRt = false;
|
||||
break;
|
||||
}
|
||||
return writesRt && rt == register;
|
||||
}
|
||||
|
||||
private static boolean isControlTransfer(long raw) {
|
||||
int opcode = opcode(raw);
|
||||
switch (opcode) {
|
||||
case OPCODE_REGIMM:
|
||||
case OPCODE_J:
|
||||
case OPCODE_JAL:
|
||||
case OPCODE_BEQ:
|
||||
case OPCODE_BNE:
|
||||
case OPCODE_BLEZ:
|
||||
case OPCODE_BGTZ:
|
||||
case OPCODE_BEQL:
|
||||
case OPCODE_BNEL:
|
||||
case OPCODE_BLEZL:
|
||||
case OPCODE_BGTZL:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (opcode != OPCODE_SPECIAL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int function = function(raw);
|
||||
return function == SPECIAL_JR || function == SPECIAL_JALR;
|
||||
}
|
||||
|
||||
private static boolean isCallInstruction(long raw) {
|
||||
int opcode = opcode(raw);
|
||||
return opcode == OPCODE_JAL ||
|
||||
(opcode == OPCODE_SPECIAL && function(raw) == SPECIAL_JALR);
|
||||
}
|
||||
|
||||
private void addSyntheticEntry(List<FunctionRecord> records, Set<Long> existingStarts, long target) {
|
||||
target &= UINT32_MASK;
|
||||
if (!existingStarts.add(target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
FunctionRecord record = new FunctionRecord();
|
||||
record.name = makeAnonymousEntryName(target);
|
||||
record.start = target;
|
||||
record.syntheticEntry = true;
|
||||
records.add(record);
|
||||
}
|
||||
|
||||
private void collectMaterializedCodeEntries(List<FunctionRecord> records, Set<Long> existingStarts) {
|
||||
AddressSet executableAddresses = new AddressSet();
|
||||
for (MemoryBlock block : currentProgram.getMemory().getBlocks()) {
|
||||
if (block != null && block.isExecute()) {
|
||||
executableAddresses.addRange(block.getStart(), block.getEnd());
|
||||
}
|
||||
}
|
||||
|
||||
InstructionIterator instructions = currentProgram.getListing().getInstructions(executableAddresses, true);
|
||||
while (instructions.hasNext() && !monitor.isCancelled()) {
|
||||
Instruction instruction = instructions.next();
|
||||
if (instruction == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Long upperRawValue = readWord(instruction.getAddress());
|
||||
if (upperRawValue == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long upperRaw = upperRawValue;
|
||||
if (opcode(upperRaw) != OPCODE_LUI) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int upperRegister = rt(upperRaw);
|
||||
if (upperRegister == GPR_ZERO) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long upperValue = ((long) immediate(upperRaw)) << MIPS_IMMEDIATE_BITS;
|
||||
boolean sawControlTransfer = false;
|
||||
boolean sawCallTransfer = false;
|
||||
|
||||
for (int lookahead = 1; lookahead <= 4; ++lookahead) {
|
||||
Address lowAddress;
|
||||
try {
|
||||
lowAddress = instruction.getAddress().add(lookahead * (long) MIPS_INSTRUCTION_SIZE);
|
||||
} catch (Exception ignored) {
|
||||
break;
|
||||
}
|
||||
|
||||
Long lowRawValue = readWord(lowAddress);
|
||||
if (lowRawValue == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
long lowRaw = lowRawValue;
|
||||
int opcode = opcode(lowRaw);
|
||||
int rs = rs(lowRaw);
|
||||
int rt = rt(lowRaw);
|
||||
|
||||
if ((opcode == OPCODE_ADDIU || opcode == OPCODE_ORI || opcode == OPCODE_DADDIU) &&
|
||||
rs == upperRegister) {
|
||||
int immediate = immediate(lowRaw);
|
||||
long target;
|
||||
if (opcode == OPCODE_ORI) {
|
||||
target = upperValue | immediate;
|
||||
} else {
|
||||
target = (upperValue + (short) immediate) & UINT32_MASK;
|
||||
}
|
||||
|
||||
Long nextRaw = readWord(addressFromOffset(
|
||||
lowAddress.getOffset() + MIPS_INSTRUCTION_SIZE));
|
||||
boolean followedByCall = nextRaw != null && isCallInstruction(nextRaw);
|
||||
boolean materializedAsCallArgument =
|
||||
rt >= GPR_A0 && rt <= GPR_A3 && (sawCallTransfer || followedByCall);
|
||||
|
||||
if (looksLikeCallableEntry(target, materializedAsCallArgument)) {
|
||||
addSyntheticEntry(records, existingStarts, target);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (sawControlTransfer) {
|
||||
break;
|
||||
}
|
||||
if (writesGpr(lowRaw, upperRegister)) {
|
||||
break;
|
||||
}
|
||||
if (isControlTransfer(lowRaw)) {
|
||||
sawControlTransfer = true;
|
||||
sawCallTransfer = isCallInstruction(lowRaw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDedicatedFunctionPointerBlock(String name) {
|
||||
return ".ctors".equals(name) || ".dtors".equals(name) ||
|
||||
".init_array".equals(name) || ".fini_array".equals(name);
|
||||
}
|
||||
|
||||
private void collectDataFunctionPointerEntries(List<FunctionRecord> records, Set<Long> existingStarts) {
|
||||
final long clusterDistanceBytes = 32L;
|
||||
|
||||
for (MemoryBlock block : currentProgram.getMemory().getBlocks()) {
|
||||
if (block == null || block.isExecute() || !block.isInitialized() ||
|
||||
block.getSize() < MIPS_INSTRUCTION_SIZE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<AddressTakenCandidate> candidates = new ArrayList<>();
|
||||
for (long offset = 0;
|
||||
offset + MIPS_INSTRUCTION_SIZE <= block.getSize() && !monitor.isCancelled();
|
||||
offset += MIPS_INSTRUCTION_SIZE) {
|
||||
Address source;
|
||||
try {
|
||||
source = block.getStart().add(offset);
|
||||
} catch (Exception ignored) {
|
||||
break;
|
||||
}
|
||||
|
||||
Long target = readWord(source);
|
||||
if (target != null && looksLikeCallableEntry(target, true)) {
|
||||
candidates.add(new AddressTakenCandidate(offset, target));
|
||||
}
|
||||
}
|
||||
|
||||
boolean dedicatedPointerBlock = isDedicatedFunctionPointerBlock(block.getName());
|
||||
for (int index = 0; index < candidates.size(); ++index) {
|
||||
AddressTakenCandidate candidate = candidates.get(index);
|
||||
boolean clustered = dedicatedPointerBlock;
|
||||
|
||||
if (index > 0 &&
|
||||
candidate.sourceOffset - candidates.get(index - 1).sourceOffset <= clusterDistanceBytes) {
|
||||
clustered = true;
|
||||
}
|
||||
if (index + 1 < candidates.size() &&
|
||||
candidates.get(index + 1).sourceOffset - candidate.sourceOffset <= clusterDistanceBytes) {
|
||||
clustered = true;
|
||||
}
|
||||
|
||||
if (clustered) {
|
||||
addSyntheticEntry(records, existingStarts, candidate.target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<FunctionRecord> collectExecutableLabelRecords(List<FunctionRecord> functionRecords) {
|
||||
@@ -555,6 +957,13 @@ public class ExportPS2Functions extends GhidraScript {
|
||||
existingStarts.add(start);
|
||||
}
|
||||
|
||||
// Ghidra does not always promote function pointers to CALL references,
|
||||
// especially when the low half is produced in a MIPS delay slot. Mirror
|
||||
// the stripped-ELF fallback used by ElfParser so the CSV still contains
|
||||
// callback and vtable entries that are only address-taken.
|
||||
collectMaterializedCodeEntries(labelRecords, existingStarts);
|
||||
collectDataFunctionPointerEntries(labelRecords, existingStarts);
|
||||
|
||||
if (labelRecords.isEmpty()) {
|
||||
return labelRecords;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user