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:
Unknown W. Brackets 2014-05-30 22:45:06 -07:00
parent 72eb15f282
commit f489694515
6 changed files with 93 additions and 44 deletions

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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];
}
}

View File

@ -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(&currentMIPS->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(&currentMIPS->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(&currentMIPS->r[MIPS_REG_RA]));
SUB(32, M(&currentMIPS->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(&currentMIPS->r[MIPS_REG_RA]));
SUB(32, M(&currentMIPS->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);
}