mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-23 13:30:02 +00:00
Add the option to hook, rather than replace, funcs.
This can be useful for debugging or developing translations/game hacks, and also gives us options when dealing with GLES incompatibilities.
This commit is contained in:
parent
72eb15f282
commit
f489694515
@ -23,6 +23,7 @@
|
||||
#include "Core/Debugger/Breakpoints.h"
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/JitCommon/JitCommon.h"
|
||||
#include "Core/MIPS/MIPSCodeUtils.h"
|
||||
#include "Core/MIPS/MIPSAnalyst.h"
|
||||
#include "Core/HLE/ReplaceTables.h"
|
||||
#include "Core/HLE/FunctionWrappers.h"
|
||||
@ -515,20 +516,35 @@ const ReplacementTableEntry *GetReplacementFunc(int i) {
|
||||
return &entries[i];
|
||||
}
|
||||
|
||||
void WriteReplaceInstruction(u32 address, u64 hash, int size) {
|
||||
static void WriteReplaceInstruction(u32 address, int index) {
|
||||
const u32 prevInstr = Memory::Read_U32(address);
|
||||
if (MIPS_IS_REPLACEMENT(prevInstr)) {
|
||||
return;
|
||||
}
|
||||
if (MIPS_IS_RUNBLOCK(prevInstr)) {
|
||||
// Likely already both replaced and jitted. Ignore.
|
||||
return;
|
||||
}
|
||||
replacedInstructions[address] = prevInstr;
|
||||
Memory::Write_U32(MIPS_EMUHACK_CALL_REPLACEMENT | index, address);
|
||||
}
|
||||
|
||||
void WriteReplaceInstructions(u32 address, u64 hash, int size) {
|
||||
int index = GetReplacementFuncIndex(hash, size);
|
||||
if (index >= 0) {
|
||||
u32 prevInstr = Memory::Read_U32(address);
|
||||
if (MIPS_IS_REPLACEMENT(prevInstr)) {
|
||||
return;
|
||||
auto entry = GetReplacementFunc(index);
|
||||
if (entry->flags & REPFLAG_HOOKEXIT) {
|
||||
// When hooking func exit, we search for jr ra, and replace those.
|
||||
for (u32 offset = 0; offset < (u32)size; offset += 4) {
|
||||
const u32 op = Memory::Read_U32(address + offset);
|
||||
if (op == MIPS_MAKE_JR_RA()) {
|
||||
WriteReplaceInstruction(address, index);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
WriteReplaceInstruction(address, index);
|
||||
}
|
||||
if (MIPS_IS_RUNBLOCK(prevInstr)) {
|
||||
// Likely already both replaced and jitted. Ignore.
|
||||
return;
|
||||
}
|
||||
replacedInstructions[address] = prevInstr;
|
||||
INFO_LOG(HLE, "Replaced %s at %08x with hash %016llx", entries[index].name, address, hash);
|
||||
Memory::Write_U32(MIPS_EMUHACK_CALL_REPLACEMENT | (int)index, address);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,10 @@ typedef int (* ReplaceFunc)();
|
||||
|
||||
enum {
|
||||
REPFLAG_ALLOWINLINE = 1,
|
||||
// Note that this will re-execute in a funciton that loops at start.
|
||||
REPFLAG_HOOKENTER = 2,
|
||||
// Only hooks jr ra, so only use on funcs that have that.
|
||||
REPFLAG_HOOKEXIT = 4,
|
||||
};
|
||||
|
||||
// Kind of similar to HLE functions but with different data.
|
||||
@ -57,7 +61,7 @@ int GetNumReplacementFuncs();
|
||||
int GetReplacementFuncIndex(u64 hash, int funcSize);
|
||||
const ReplacementTableEntry *GetReplacementFunc(int index);
|
||||
|
||||
void WriteReplaceInstruction(u32 address, u64 hash, int size);
|
||||
void WriteReplaceInstructions(u32 address, u64 hash, int size);
|
||||
void RestoreReplacedInstruction(u32 address);
|
||||
void RestoreReplacedInstructions(u32 startAddr, u32 endAddr);
|
||||
bool GetReplacedOpAt(u32 address, u32 *op);
|
||||
|
@ -357,6 +357,11 @@ bool Jit::ReplaceJalTo(u32 dest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
|
||||
// If it's a hook, we can't replace the jal, we have to go inside the func.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Warning - this might be bad if the code at the destination changes...
|
||||
if (entry->flags & REPFLAG_ALLOWINLINE) {
|
||||
// Jackpot! Just do it, no flushing. The code will be entirely inlined.
|
||||
@ -397,11 +402,18 @@ void Jit::Comp_ReplacementFunc(MIPSOpcode op)
|
||||
if (entry->jitReplaceFunc) {
|
||||
MIPSReplaceFunc repl = entry->jitReplaceFunc;
|
||||
int cycles = (this->*repl)();
|
||||
FlushAll();
|
||||
// Flushed, so R1 is safe.
|
||||
LDR(R1, CTXREG, MIPS_REG_RA * 4);
|
||||
js.downcountAmount = cycles;
|
||||
WriteExitDestInR(R1);
|
||||
|
||||
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
|
||||
// Compile the original instruction at this address. We ignore cycles for hooks.
|
||||
MIPSCompileOp(Memory::Read_Instruction(js.compilerPC, true));
|
||||
} else {
|
||||
FlushAll();
|
||||
// Flushed, so R1 is safe.
|
||||
LDR(R1, CTXREG, MIPS_REG_RA * 4);
|
||||
js.downcountAmount = cycles;
|
||||
WriteExitDestInR(R1);
|
||||
js.compiling = false;
|
||||
}
|
||||
} else if (entry->replaceFunc) {
|
||||
FlushAll();
|
||||
// Standard function call, nothing fancy.
|
||||
@ -412,21 +424,20 @@ void Jit::Comp_ReplacementFunc(MIPSOpcode op)
|
||||
MOVI2R(R0, (u32)entry->replaceFunc);
|
||||
BL(R0);
|
||||
}
|
||||
// Alternatively, we could inline it here, instead of calling out, if it's a function
|
||||
// we can emit.
|
||||
|
||||
LDR(R1, CTXREG, MIPS_REG_RA * 4);
|
||||
WriteDownCountR(R0);
|
||||
js.downcountAmount = 0; // we just subtracted most of it
|
||||
WriteExitDestInR(R1);
|
||||
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
|
||||
// Compile the original instruction at this address. We ignore cycles for hooks.
|
||||
MIPSCompileOp(Memory::Read_Instruction(js.compilerPC, true));
|
||||
} else {
|
||||
LDR(R1, CTXREG, MIPS_REG_RA * 4);
|
||||
WriteDownCountR(R0);
|
||||
js.downcountAmount = 0; // we just subtracted most of it
|
||||
WriteExitDestInR(R1);
|
||||
js.compiling = false;
|
||||
}
|
||||
} else {
|
||||
ERROR_LOG(HLE, "Replacement function %s has neither jit nor regular impl", entry->name);
|
||||
}
|
||||
|
||||
js.compiling = false;
|
||||
|
||||
// We could even do this in the jal that is branching to the function
|
||||
// but having the op is necessary for the interpreter anyway.
|
||||
}
|
||||
|
||||
void Jit::Comp_Generic(MIPSOpcode op)
|
||||
|
@ -953,7 +953,7 @@ skip:
|
||||
lock_guard guard(functions_lock);
|
||||
|
||||
for (size_t i = 0; i < functions.size(); i++) {
|
||||
WriteReplaceInstruction(functions[i].start, functions[i].hash, functions[i].size);
|
||||
WriteReplaceInstructions(functions[i].start, functions[i].hash, functions[i].size);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1022,11 +1022,16 @@ namespace MIPSInt
|
||||
const ReplacementTableEntry *entry = GetReplacementFunc(index);
|
||||
if (entry && entry->replaceFunc) {
|
||||
entry->replaceFunc();
|
||||
|
||||
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
|
||||
// Interpret the original instruction under the hook.
|
||||
MIPSInterpret(Memory::Read_Instruction(PC, true));
|
||||
} else {
|
||||
PC = currentMIPS->r[MIPS_REG_RA];
|
||||
}
|
||||
} else {
|
||||
ERROR_LOG(CPU, "Bad replacement function index %i", index);
|
||||
}
|
||||
|
||||
PC = currentMIPS->r[MIPS_REG_RA];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -423,6 +423,11 @@ bool Jit::ReplaceJalTo(u32 dest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
|
||||
// If it's a hook, we can't replace the jal, we have to go inside the func.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Warning - this might be bad if the code at the destination changes...
|
||||
if (entry->flags & REPFLAG_ALLOWINLINE) {
|
||||
// Jackpot! Just do it, no flushing. The code will be entirely inlined.
|
||||
@ -470,26 +475,34 @@ void Jit::Comp_ReplacementFunc(MIPSOpcode op)
|
||||
if (entry->jitReplaceFunc) {
|
||||
MIPSReplaceFunc repl = entry->jitReplaceFunc;
|
||||
int cycles = (this->*repl)();
|
||||
FlushAll();
|
||||
MOV(32, R(ECX), M(¤tMIPS->r[MIPS_REG_RA]));
|
||||
js.downcountAmount = cycles;
|
||||
WriteExitDestInReg(ECX);
|
||||
js.compiling = false;
|
||||
|
||||
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
|
||||
// Compile the original instruction at this address. We ignore cycles for hooks.
|
||||
MIPSCompileOp(Memory::Read_Instruction(js.compilerPC, true));
|
||||
} else {
|
||||
FlushAll();
|
||||
MOV(32, R(ECX), M(¤tMIPS->r[MIPS_REG_RA]));
|
||||
js.downcountAmount = cycles;
|
||||
WriteExitDestInReg(ECX);
|
||||
js.compiling = false;
|
||||
}
|
||||
} else if (entry->replaceFunc) {
|
||||
FlushAll();
|
||||
|
||||
// Standard function call, nothing fancy.
|
||||
// The function returns the number of cycles it took in EAX.
|
||||
ABI_CallFunction(entry->replaceFunc);
|
||||
// Alternatively, we could inline it here, instead of calling out, if it's a function
|
||||
// we can emit.
|
||||
|
||||
MOV(32, R(ECX), M(¤tMIPS->r[MIPS_REG_RA]));
|
||||
SUB(32, M(¤tMIPS->downcount), R(EAX));
|
||||
js.downcountAmount = 0; // we just subtracted most of it
|
||||
WriteExitDestInReg(ECX);
|
||||
|
||||
js.compiling = false;
|
||||
if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {
|
||||
// Compile the original instruction at this address. We ignore cycles for hooks.
|
||||
MIPSCompileOp(Memory::Read_Instruction(js.compilerPC, true));
|
||||
} else {
|
||||
MOV(32, R(ECX), M(¤tMIPS->r[MIPS_REG_RA]));
|
||||
SUB(32, M(¤tMIPS->downcount), R(EAX));
|
||||
js.downcountAmount = 0; // we just subtracted most of it
|
||||
WriteExitDestInReg(ECX);
|
||||
js.compiling = false;
|
||||
}
|
||||
} else {
|
||||
ERROR_LOG(HLE, "Replacement function %s has neither jit nor regular impl", entry->name);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user