pcsx2/pcsx2/DebugTools/MIPSAnalyst.cpp

505 lines
14 KiB
C++

/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2014 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "MIPSAnalyst.h"
#include "Debug.h"
#include "DebugInterface.h"
#include "SymbolMap.h"
#include "DebugInterface.h"
#include "R5900.h"
#include "R5900OpcodeTables.h"
static std::vector<MIPSAnalyst::AnalyzedFunction> functions;
#define MIPS_MAKE_J(addr) (0x08000000 | ((addr)>>2))
#define MIPS_MAKE_JAL(addr) (0x0C000000 | ((addr)>>2))
#define MIPS_MAKE_JR_RA() (0x03e00008)
#define MIPS_MAKE_NOP() (0)
#define INVALIDTARGET 0xFFFFFFFF
#define FULLOP_JR_RA 0x03e00008
#define OP_SYSCALL 0x0000000c
#define OP_SYSCALL_MASK 0xFC00003F
#define _RS ((op>>21) & 0x1F)
#define _RT ((op>>16) & 0x1F)
namespace MIPSAnalyst
{
u32 GetJumpTarget(u32 addr)
{
u32 op = r5900Debug.read32(addr);
const R5900::OPCODE& opcode = R5900::GetInstruction(op);
if ((opcode.flags & IS_BRANCH) && (opcode.flags & BRANCHTYPE_MASK) == BRANCHTYPE_JUMP)
{
u32 target = (addr & 0xF0000000) | ((op&0x03FFFFFF) << 2);
return target;
}
else
return INVALIDTARGET;
}
u32 GetBranchTarget(u32 addr)
{
u32 op = r5900Debug.read32(addr);
const R5900::OPCODE& opcode = R5900::GetInstruction(op);
int branchType = (opcode.flags & BRANCHTYPE_MASK);
if ((opcode.flags & IS_BRANCH) && (branchType == BRANCHTYPE_BRANCH || branchType == BRANCHTYPE_BC1))
return addr + 4 + ((signed short)(op&0xFFFF)<<2);
else
return INVALIDTARGET;
}
u32 GetBranchTargetNoRA(u32 addr)
{
u32 op = r5900Debug.read32(addr);
const R5900::OPCODE& opcode = R5900::GetInstruction(op);
int branchType = (opcode.flags & BRANCHTYPE_MASK);
if ((opcode.flags & IS_BRANCH) && (branchType == BRANCHTYPE_BRANCH || branchType == BRANCHTYPE_BC1))
{
if (!(opcode.flags & IS_LINKED))
return addr + 4 + ((signed short)(op&0xFFFF)<<2);
else
return INVALIDTARGET;
}
else
return INVALIDTARGET;
}
u32 GetSureBranchTarget(u32 addr)
{
u32 op = r5900Debug.read32(addr);
const R5900::OPCODE& opcode = R5900::GetInstruction(op);
if ((opcode.flags & IS_BRANCH) && (opcode.flags & BRANCHTYPE_MASK) == BRANCHTYPE_BRANCH)
{
bool sure = false;
bool takeBranch = false;
switch (opcode.flags & CONDTYPE_MASK)
{
case CONDTYPE_EQ:
sure = _RS == _RT;
takeBranch = true;
break;
case CONDTYPE_NE:
sure = _RS == _RT;
takeBranch = false;
break;
case CONDTYPE_LEZ:
case CONDTYPE_GEZ:
sure = _RS == 0;
takeBranch = true;
break;
case CONDTYPE_LTZ:
case CONDTYPE_GTZ:
sure = _RS == 0;
takeBranch = false;
break;
default:
break;
}
if (sure && takeBranch)
return addr + 4 + ((signed short)(op&0xFFFF)<<2);
else if (sure && !takeBranch)
return addr + 8;
else
return INVALIDTARGET;
}
else
return INVALIDTARGET;
}
static const char *DefaultFunctionName(char buffer[256], u32 startAddr) {
sprintf(buffer, "z_un_%08x", startAddr);
return buffer;
}
static u32 ScanAheadForJumpback(u32 fromAddr, u32 knownStart, u32 knownEnd) {
static const u32 MAX_AHEAD_SCAN = 0x1000;
// Maybe a bit high... just to make sure we don't get confused by recursive tail recursion.
static const u32 MAX_FUNC_SIZE = 0x20000;
if (fromAddr > knownEnd + MAX_FUNC_SIZE) {
return INVALIDTARGET;
}
// Code might jump halfway up to before fromAddr, but after knownEnd.
// In that area, there could be another jump up to the valid range.
// So we track that for a second scan.
u32 closestJumpbackAddr = INVALIDTARGET;
u32 closestJumpbackTarget = fromAddr;
// We assume the furthest jumpback is within the func.
u32 furthestJumpbackAddr = INVALIDTARGET;
for (u32 ahead = fromAddr; ahead < fromAddr + MAX_AHEAD_SCAN; ahead += 4) {
u32 aheadOp = r5900Debug.read32(ahead);
u32 target = GetBranchTargetNoRA(ahead);
if (target == INVALIDTARGET && ((aheadOp & 0xFC000000) == 0x08000000)) {
target = GetJumpTarget(ahead);
}
if (target != INVALIDTARGET) {
// Only if it comes back up to known code within this func.
if (target >= knownStart && target <= knownEnd) {
furthestJumpbackAddr = ahead;
}
// But if it jumps above fromAddr, we should scan that area too...
if (target < closestJumpbackTarget && target < fromAddr && target > knownEnd) {
closestJumpbackAddr = ahead;
closestJumpbackTarget = target;
}
}
if (aheadOp == MIPS_MAKE_JR_RA()) {
break;
}
}
if (closestJumpbackAddr != INVALIDTARGET && furthestJumpbackAddr == INVALIDTARGET) {
for (u32 behind = closestJumpbackTarget; behind < fromAddr; behind += 4) {
u32 behindOp = r5900Debug.read32(behind);
u32 target = GetBranchTargetNoRA(behind);
if (target == INVALIDTARGET && ((behindOp & 0xFC000000) == 0x08000000)) {
target = GetJumpTarget(behind);
}
if (target != INVALIDTARGET) {
if (target >= knownStart && target <= knownEnd) {
furthestJumpbackAddr = closestJumpbackAddr;
}
}
}
}
return furthestJumpbackAddr;
}
void ScanForFunctions(SymbolMap& map, u32 startAddr, u32 endAddr, bool insertSymbols) {
AnalyzedFunction currentFunction = {startAddr};
u32 furthestBranch = 0;
bool looking = false;
bool end = false;
bool isStraightLeaf = true;
functions.clear();
u32 addr;
for (addr = startAddr; addr <= endAddr; addr += 4) {
// Use pre-existing symbol map info if available. May be more reliable.
SymbolInfo syminfo;
if (map.GetSymbolInfo(&syminfo, addr, ST_FUNCTION)) {
addr = syminfo.address + syminfo.size - 4;
// We still need to insert the func for hashing purposes.
currentFunction.start = syminfo.address;
currentFunction.end = syminfo.address + syminfo.size - 4;
functions.push_back(currentFunction);
currentFunction.start = addr + 4;
furthestBranch = 0;
looking = false;
end = false;
continue;
}
u32 op = r5900Debug.read32(addr);
u32 target = GetBranchTargetNoRA(addr);
if (target != INVALIDTARGET) {
isStraightLeaf = false;
if (target > furthestBranch) {
furthestBranch = target;
}
} else if ((op & 0xFC000000) == 0x08000000) {
u32 sureTarget = GetJumpTarget(addr);
// Check for a tail call. Might not even have a jr ra.
if (sureTarget != INVALIDTARGET && sureTarget < currentFunction.start) {
if (furthestBranch > addr) {
looking = true;
addr += 4;
} else {
end = true;
}
} else if (sureTarget != INVALIDTARGET && sureTarget > addr && sureTarget > furthestBranch) {
// A jump later. Probably tail, but let's check if it jumps back.
u32 knownEnd = furthestBranch == 0 ? addr : furthestBranch;
u32 jumpback = ScanAheadForJumpback(sureTarget, currentFunction.start, knownEnd);
if (jumpback != INVALIDTARGET && jumpback > addr && jumpback > knownEnd) {
furthestBranch = jumpback;
} else {
if (furthestBranch > addr) {
looking = true;
addr += 4;
} else {
end = true;
}
}
}
}
if (op == MIPS_MAKE_JR_RA()) {
// If a branch goes to the jr ra, it's still ending here.
if (furthestBranch > addr) {
looking = true;
addr += 4;
} else {
end = true;
}
}
if (looking) {
if (addr >= furthestBranch) {
u32 sureTarget = GetSureBranchTarget(addr);
// Regular j only, jals are to new funcs.
if (sureTarget == INVALIDTARGET && ((op & 0xFC000000) == 0x08000000)) {
sureTarget = GetJumpTarget(addr);
}
if (sureTarget != INVALIDTARGET && sureTarget < addr) {
end = true;
} else if (sureTarget != INVALIDTARGET) {
// Okay, we have a downward jump. Might be an else or a tail call...
// If there's a jump back upward in spitting distance of it, it's an else.
u32 knownEnd = furthestBranch == 0 ? addr : furthestBranch;
u32 jumpback = ScanAheadForJumpback(sureTarget, currentFunction.start, knownEnd);
if (jumpback != INVALIDTARGET && jumpback > addr && jumpback > knownEnd) {
furthestBranch = jumpback;
}
}
}
}
if (end) {
// most functions are aligned to 8 or 16 bytes
// add the padding to this one
while (((addr+8) % 16) && r5900Debug.read32(addr+8) == 0)
addr += 4;
currentFunction.end = addr + 4;
currentFunction.isStraightLeaf = isStraightLeaf;
functions.push_back(currentFunction);
furthestBranch = 0;
addr += 4;
looking = false;
end = false;
isStraightLeaf = true;
currentFunction.start = addr+4;
}
}
currentFunction.end = addr + 4;
functions.push_back(currentFunction);
for (auto iter = functions.begin(); iter != functions.end(); iter++) {
iter->size = iter->end - iter->start + 4;
if (insertSymbols) {
char temp[256];
map.AddFunction(DefaultFunctionName(temp, iter->start), iter->start, iter->end - iter->start + 4);
}
}
}
MipsOpcodeInfo GetOpcodeInfo(DebugInterface* cpu, u32 address) {
MipsOpcodeInfo info;
memset(&info, 0, sizeof(info));
if (!cpu->isValidAddress(address))
return info;
info.cpu = cpu;
info.opcodeAddress = address;
info.encodedOpcode = cpu->read32(address);
u32 op = info.encodedOpcode;
const R5900::OPCODE& opcode = R5900::GetInstruction(op);
// extract all the branch related information
info.isBranch = (opcode.flags & IS_BRANCH) != 0;
if (info.isBranch)
{
info.isLinkedBranch = (opcode.flags & IS_LINKED) != 0;
info.isLikelyBranch = (opcode.flags & IS_LIKELY) != 0;
u64 rs,rt;
u32 value;
switch (opcode.flags & BRANCHTYPE_MASK)
{
case BRANCHTYPE_JUMP:
info.isConditional = false;
info.branchTarget = (info.opcodeAddress & 0xF0000000) | ((op&0x03FFFFFF) << 2);
break;
case BRANCHTYPE_BRANCH:
info.isConditional = true;
info.branchTarget = info.opcodeAddress + 4 + ((s16)(op&0xFFFF)<<2);
rs = info.cpu->getRegister(0,MIPS_GET_RS(op))._u64[0];
rt = info.cpu->getRegister(0,MIPS_GET_RT(op))._u64[0];
switch (opcode.flags & CONDTYPE_MASK)
{
case CONDTYPE_EQ:
info.conditionMet = (rt == rs);
if (MIPS_GET_RT(op) == MIPS_GET_RS(op)) // always true
info.isConditional = false;
break;
case CONDTYPE_NE:
info.conditionMet = (rt != rs);
if (MIPS_GET_RT(op) == MIPS_GET_RS(op)) // always false
info.isConditional = false;
break;
case CONDTYPE_LEZ:
info.conditionMet = (((s64)rs) <= 0);
break;
case CONDTYPE_GTZ:
info.conditionMet = (((s64)rs) > 0);
break;
case CONDTYPE_LTZ:
info.conditionMet = (((s64)rs) < 0);
break;
case CONDTYPE_GEZ:
info.conditionMet = (((s64)rs) >= 0);
break;
}
break;
case BRANCHTYPE_REGISTER:
info.isConditional = false;
info.isBranchToRegister = true;
info.branchRegisterNum = (int)MIPS_GET_RS(op);
info.branchTarget = info.cpu->getRegister(0,info.branchRegisterNum)._u32[0];
break;
case BRANCHTYPE_SYSCALL:
info.isConditional = false;
info.isSyscall = true;
info.branchTarget = 0x80000000+0x180;
break;
case BRANCHTYPE_ERET:
info.isConditional = false;
// probably shouldn't be hard coded like this...
if (cpuRegs.CP0.n.Status.b.ERL)
{
info.branchTarget = cpuRegs.CP0.n.ErrorEPC;
} else {
info.branchTarget = cpuRegs.CP0.n.EPC;
}
break;
case BRANCHTYPE_BC1:
info.isConditional = true;
value = info.cpu->getRegister(EECAT_FCR,31)._u32[0] & 0x00800000;
info.branchTarget = info.opcodeAddress + 4 + ((s16)(op&0xFFFF)<<2);
switch (opcode.flags & CONDTYPE_MASK)
{
case CONDTYPE_EQ:
info.conditionMet = value == 0;
break;
case CONDTYPE_NE:
info.conditionMet = value != 0;
break;
}
break;
}
}
// extract the accessed memory address
info.isDataAccess = (opcode.flags & IS_MEMORY) != 0;
if (info.isDataAccess)
{
if (opcode.flags & IS_LEFT)
info.lrType = LOADSTORE_LEFT;
else if (opcode.flags & IS_RIGHT)
info.lrType = LOADSTORE_RIGHT;
u32 rs = info.cpu->getRegister(0, (int)MIPS_GET_RS(op));
s16 imm16 = op & 0xFFFF;
info.dataAddress = rs + imm16;
switch (opcode.flags & MEMTYPE_MASK)
{
case MEMTYPE_BYTE:
info.dataSize = 1;
break;
case MEMTYPE_HALF:
info.dataSize = 2;
break;
case MEMTYPE_WORD:
info.dataSize = 4;
if (info.lrType == LOADSTORE_LEFT)
info.dataAddress -= 3;
break;
case MEMTYPE_DWORD:
info.dataSize = 8;
if (info.lrType == LOADSTORE_LEFT)
info.dataAddress -= 7;
break;
case MEMTYPE_QWORD:
info.dataSize = 16;
break;
}
info.hasRelevantAddress = true;
info.releventAddress = info.dataAddress;
}
// gather relevant address for alu operations
if (opcode.flags & IS_ALU)
{
u64 rs,rt;
rs = info.cpu->getRegister(0,MIPS_GET_RS(op))._u64[0];
rt = info.cpu->getRegister(0,MIPS_GET_RT(op))._u64[0];
switch (opcode.flags & ALUTYPE_MASK)
{
case ALUTYPE_ADDI:
info.hasRelevantAddress = true;
info.releventAddress = rs+((s16)(op & 0xFFFF));
break;
case ALUTYPE_ADD:
info.hasRelevantAddress = true;
info.releventAddress = rs+rt;
break;
case ALUTYPE_SUB:
info.hasRelevantAddress = true;
info.releventAddress = rs-rt;
break;
case ALUTYPE_CONDMOVE:
info.isConditional = true;
switch (opcode.flags & CONDTYPE_MASK)
{
case CONDTYPE_EQ:
info.conditionMet = (rt == 0);
break;
case CONDTYPE_NE:
info.conditionMet = (rt != 0);
break;
}
break;
}
}
return info;
}
}