mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-25 23:25:40 +00:00
554 lines
14 KiB
C++
554 lines
14 KiB
C++
/**
|
|
** Supermodel
|
|
** A Sega Model 3 Arcade Emulator.
|
|
** Copyright 2011 Bart Trzynadlowski, Nik Henson
|
|
**
|
|
** This file is part of Supermodel.
|
|
**
|
|
** Supermodel is free software: you can redistribute it and/or modify it under
|
|
** the terms of the GNU General Public License as published by the Free
|
|
** Software Foundation, either version 3 of the License, or (at your option)
|
|
** any later version.
|
|
**
|
|
** Supermodel 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 Supermodel. If not, see <http://www.gnu.org/licenses/>.
|
|
**/
|
|
|
|
/*
|
|
* PPCDebug.cpp
|
|
*/
|
|
|
|
#ifdef SUPERMODEL_DEBUGGER
|
|
|
|
#include "PPCDebug.h"
|
|
#include "CPU/PowerPC/ppc.h"
|
|
#include "CPU/PowerPC/PPCDisasm.h"
|
|
|
|
#include <cctype>
|
|
#include <string>
|
|
|
|
#define M_AA 0x00000002
|
|
#define M_LK 0x00000001
|
|
#define M_BO 0x03E00000
|
|
#define M_BD 0x0000FFFC
|
|
#define M_LI 0x03FFFFFC
|
|
|
|
#define MSR_IP 0x00000040
|
|
|
|
namespace Debugger
|
|
{
|
|
static UINT32 GetSpecialReg(CCPUDebug *cpu, unsigned id)
|
|
{
|
|
switch (id)
|
|
{
|
|
case PPCSPECIAL_LR: return ::ppc_get_lr();
|
|
case PPCSPECIAL_FPSCR: return 0; // TODO
|
|
case PPCSPECIAL_MSR: return ::ppc_read_msr();
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
static bool SetSpecialReg(CCPUDebug *cpu, unsigned id, UINT32 data)
|
|
{
|
|
switch (id)
|
|
{
|
|
case PPCSPECIAL_LR: /* TODO */ return false;
|
|
case PPCSPECIAL_FPSCR: /* TODO */ return false;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
static UINT8 GetCR(CCPUDebug *cpu, unsigned id)
|
|
{
|
|
return ::ppc_get_cr(id);
|
|
}
|
|
|
|
static bool SetCR(CCPUDebug *cpu, unsigned id, UINT8 data)
|
|
{
|
|
::ppc_set_cr(id, data);
|
|
return true;
|
|
}
|
|
|
|
static UINT32 GetSPR(CCPUDebug *cpu, unsigned id)
|
|
{
|
|
return ::ppc_read_spr(id);
|
|
}
|
|
|
|
static bool SetSPR(CCPUDebug *cpu, unsigned id, UINT32 data)
|
|
{
|
|
::ppc_write_spr(id, data);
|
|
return true;
|
|
}
|
|
|
|
static UINT32 GetGPR(CCPUDebug *cpu, unsigned id)
|
|
{
|
|
return ::ppc_get_gpr(id);
|
|
}
|
|
|
|
static bool SetGPR(CCPUDebug *cpu, unsigned id, UINT32 data)
|
|
{
|
|
::ppc_set_gpr(id, data);
|
|
return true;
|
|
}
|
|
|
|
static double GetFPR(CCPUDebug *cpu, unsigned id)
|
|
{
|
|
return ::ppc_get_fpr(id);
|
|
}
|
|
|
|
static bool SetFPR(CCPUDebug *cpu, unsigned id, double data)
|
|
{
|
|
::ppc_set_fpr(id, data);
|
|
return true;
|
|
}
|
|
|
|
static const char *srGroup = "Special Registers";
|
|
static const char *crGroup = "Condition Registers";
|
|
static const char *grGroup = "GPR Registers";
|
|
static const char *frGroup = "FPR Registers";
|
|
|
|
CPPCDebug::CPPCDebug(const char *name) : CCPUDebug("PPC", name, 4, 4, true, 32, 7), m_irqState(0)
|
|
{
|
|
// PC & Link registers
|
|
AddPCRegister ("pc", srGroup);
|
|
AddAddrRegister("lr", srGroup, PPCSPECIAL_LR, GetSpecialReg, SetSpecialReg);
|
|
|
|
// SPR registers
|
|
AddInt32Register ("ctr", srGroup, SPR_LR, GetSPR, SetSPR);
|
|
AddInt32Register ("xer", srGroup, SPR_XER, GetSPR, SetSPR);
|
|
//AddStatus32Register("xer", srGroup, SPR_XER, "SOC", GetSPR, SetSPR); //TODO: bit mapping is wrong
|
|
AddInt32Register ("srr0", srGroup, SPR_SRR0, GetSPR, SetSPR);
|
|
AddInt32Register ("srr1", srGroup, SPR_SRR1, GetSPR, SetSPR);
|
|
AddInt32Register ("msr", srGroup, PPCSPECIAL_MSR, GetSpecialReg, SetSpecialReg);
|
|
AddInt32Register ("sdr1", srGroup, SPR603E_SDR1, GetSPR, SetSPR);
|
|
AddInt32Register ("imiss",srGroup, SPR603E_IMISS, GetSPR, SetSPR);
|
|
AddInt32Register ("dmiss",srGroup, SPR603E_DMISS, GetSPR, SetSPR);
|
|
AddInt32Register ("hid0", srGroup, SPR603E_HID0, GetSPR, SetSPR);
|
|
AddInt32Register ("hid1", srGroup, SPR603E_HID1, GetSPR, SetSPR);
|
|
|
|
// etc...
|
|
|
|
// Condition registers
|
|
for (unsigned id = 0; id < 8; id++)
|
|
{
|
|
sprintf(m_crNames[id], "cr%u", id);
|
|
AddStatus8Register(m_crNames[id], crGroup, id, "<>=O", GetCR, SetCR);
|
|
}
|
|
//AddStatus16Register("fpscr", "Condition Registers", PPCSPECIAL_FPSCR, "FEVOUZX789ABCRI 0123", GetSpecial, SetSpecial);
|
|
|
|
// GPR registers
|
|
for (unsigned id = 0; id < 32; id++)
|
|
{
|
|
sprintf(m_gprNames[id], "r%u", id);
|
|
AddInt32Register(m_gprNames[id], grGroup, id, GetGPR, SetGPR);
|
|
}
|
|
|
|
// FPR registers
|
|
for (unsigned id = 0; id < 32; id++)
|
|
{
|
|
sprintf(m_fprNames[id], "f%u", id);
|
|
AddFPointRegister(m_fprNames[id], frGroup, id, GetFPR, SetFPR);
|
|
}
|
|
|
|
// Exceptions
|
|
AddException("IRQ", EXCEPTION_IRQ, "External Interrupt");
|
|
AddException("DEC", EXCEPTION_DECREMENTER, "Decrement Overflow");
|
|
AddException("TRAP", EXCEPTION_TRAP, "Program Exception/Trap");
|
|
AddException("SYSCALL", EXCEPTION_SYSTEM_CALL, "System Call");
|
|
AddException("SMI", EXCEPTION_SMI, "SMI");
|
|
AddException("DSI", EXCEPTION_DSI, "DSI");
|
|
AddException("ISI", EXCEPTION_ISI, "ISI");
|
|
}
|
|
|
|
CPPCDebug::~CPPCDebug()
|
|
{
|
|
DetachFromCPU();
|
|
}
|
|
|
|
void CPPCDebug::AttachToCPU()
|
|
{
|
|
::ppc_attach_debugger(this);
|
|
}
|
|
|
|
::IBus *CPPCDebug::AttachBus(::IBus *bus)
|
|
{
|
|
m_bus = bus;
|
|
return this;
|
|
}
|
|
|
|
void CPPCDebug::DetachFromCPU()
|
|
{
|
|
::ppc_detach_debugger();
|
|
}
|
|
|
|
::IBus *CPPCDebug::DetachBus()
|
|
{
|
|
::IBus *bus = m_bus;
|
|
m_bus = NULL;
|
|
return bus;
|
|
}
|
|
|
|
UINT32 CPPCDebug::GetResetAddr()
|
|
{
|
|
// Reset address appears to be hardcoded to 0xFFF00100
|
|
return 0xFFF00100;
|
|
}
|
|
|
|
bool CPPCDebug::UpdatePC(UINT32 pc)
|
|
{
|
|
::ppc_set_pc(pc);
|
|
return true;
|
|
}
|
|
|
|
bool CPPCDebug::ForceException(CException *ex)
|
|
{
|
|
// TODO - no way to force exceptions
|
|
return false;
|
|
}
|
|
|
|
bool CPPCDebug::ForceInterrupt(CInterrupt *in)
|
|
{
|
|
if (in->code > 7)
|
|
return false;
|
|
UINT8 irqState = m_bus->Read8(0xF0100018) | 1<<in->code;
|
|
m_bus->Write8(0xF0100018, irqState);
|
|
::ppc_set_irq_line(1); // TODO - what is irqline arg for? not actually used?
|
|
return true;
|
|
}
|
|
|
|
UINT64 CPPCDebug::ReadMem(UINT32 addr, unsigned dataSize)
|
|
{
|
|
switch (dataSize)
|
|
{
|
|
case 1: return (UINT64)m_bus->Read8(addr);
|
|
case 2: return (UINT64)m_bus->Read16(addr);
|
|
case 4: return (UINT64)m_bus->Read32(addr);
|
|
case 8: return m_bus->Read64(addr);
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
bool CPPCDebug::WriteMem(UINT32 addr, unsigned dataSize, UINT64 data)
|
|
{
|
|
switch (dataSize)
|
|
{
|
|
case 1: m_bus->Write8(addr, (UINT8)data); return true;
|
|
case 2: m_bus->Write16(addr, (UINT16)data); return true;
|
|
case 4: m_bus->Write32(addr, (UINT32)data); return true;
|
|
case 8: m_bus->Write64(addr, data); return true;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
void CPPCDebug::CheckException(UINT16 exCode)
|
|
{
|
|
CCPUDebug::CPUException(exCode);
|
|
|
|
if (exCode == EXCEPTION_IRQ)
|
|
{
|
|
UINT8 irqState = m_bus->Read8(0xF0100018); // TODO - replace this with function pointer
|
|
|
|
UINT8 newIRQs = (irqState^m_irqState)&irqState;
|
|
for (int intCode = 0; newIRQs && intCode < 8; intCode++)
|
|
{
|
|
if (newIRQs&0x01)
|
|
CPUInterrupt(intCode);
|
|
newIRQs >>= 1;
|
|
}
|
|
m_irqState = irqState;
|
|
}
|
|
}
|
|
|
|
int CPPCDebug::Disassemble(UINT32 addr, char *mnemonic, char *operands)
|
|
{
|
|
char opStr[255];
|
|
char valStr[40];
|
|
UINT32 opcode = m_bus->Read32(addr);
|
|
operands[0] = '\0';
|
|
if (!::DisassemblePowerPC(opcode, addr, mnemonic, opStr, true))
|
|
{
|
|
char *o = opStr;
|
|
char *s = strstr(o, "0x");
|
|
while (s)
|
|
{
|
|
strncpy(operands, o, s - o);
|
|
operands[s - o] = '\0';
|
|
s += 2;
|
|
|
|
char *p = s;
|
|
unsigned len = 0;
|
|
UINT64 data = 0;
|
|
while (p)
|
|
{
|
|
char c = toupper(*(p++));
|
|
if (c >= '0' && c <= '9')
|
|
{
|
|
data <<= 4;
|
|
data += (UINT64)(c - '0');
|
|
}
|
|
else if (c >= 'A' && c <= 'F')
|
|
{
|
|
data <<= 4;
|
|
data += (UINT64)(10 + c - 'A');
|
|
}
|
|
else
|
|
break;
|
|
len++;
|
|
}
|
|
|
|
unsigned dataSize = (p - s) / 2;
|
|
if (dataSize == (unsigned)(memBusWidth / 8))
|
|
{
|
|
EOpFlags opFlags = GetOpFlags(addr, opcode);
|
|
FormatJumpAddress(valStr, (UINT32)data, opFlags);
|
|
}
|
|
else
|
|
FormatData(valStr, dataSize, data);
|
|
|
|
strcat(operands, valStr);
|
|
operands += strlen(operands);
|
|
o = p - 1;
|
|
s = strstr(o, "0x");
|
|
}
|
|
strcat(operands, o);
|
|
return 4;
|
|
}
|
|
else
|
|
return -4;
|
|
}
|
|
|
|
EOpFlags CPPCDebug::GetOpFlags(UINT32 addr, UINT32 opcode)
|
|
{
|
|
EOpFlags opFlags;
|
|
|
|
UINT32 op = opcode>>26;
|
|
if (op == 0x10)
|
|
{
|
|
// Instruction is branch conditional: bc, bca, bcl or bcla
|
|
UINT32 bo = (opcode&M_BO)>>21;
|
|
if (opcode&M_LK)
|
|
{
|
|
if (opcode&M_AA)
|
|
opFlags = JumpSub; // bcla
|
|
else if (bo&0x04)
|
|
opFlags = (EOpFlags)(JumpSub | Relative); // bcl without counter
|
|
else
|
|
opFlags = (EOpFlags)(JumpSub | Relative); // bcl with counter
|
|
}
|
|
else
|
|
{
|
|
if (opcode&M_AA)
|
|
opFlags = JumpSimple; // bca
|
|
else if (bo&0x04)
|
|
opFlags = (EOpFlags)(JumpSimple | Relative); // bc without counter
|
|
else
|
|
opFlags = (EOpFlags)(JumpLoop | Relative); // bc with counter
|
|
}
|
|
// Check BO is not just branch always
|
|
return ((bo&0x14) == 0x14 ? opFlags : (EOpFlags)(opFlags | Conditional));
|
|
}
|
|
else if (op == 0x12)
|
|
{
|
|
// Instruction is branch: b, ba, bl or bla
|
|
if (opcode&M_LK)
|
|
{
|
|
if (opcode&M_AA)
|
|
return JumpSub; // bla
|
|
else
|
|
return (EOpFlags)(JumpSub | Relative); // bl
|
|
}
|
|
else
|
|
{
|
|
if (opcode&M_AA)
|
|
return JumpSimple; // ba
|
|
else
|
|
return (EOpFlags)(JumpSimple | Relative); // b
|
|
}
|
|
}
|
|
else if (op == 0x13)
|
|
{
|
|
UINT32 exOp = (opcode>>1)&0x3ff;
|
|
UINT32 bo = (opcode&M_BO)>>21;
|
|
if (exOp == 0x0210)
|
|
{
|
|
// Instruction is branch conditional to count register: bcctr or bcctrl
|
|
if (opcode&M_LK)
|
|
opFlags = (EOpFlags)(JumpSub | Relative); // bcctrl
|
|
else if (bo&0x04)
|
|
opFlags = (EOpFlags)(JumpSimple | Relative); // bcctr without counter
|
|
else
|
|
opFlags = (EOpFlags)(JumpLoop | Relative); // bcctr with counter
|
|
// Check BO is not just branch always
|
|
return ((bo&0x14) == 0x14 ? opFlags : (EOpFlags)(opFlags | Conditional));
|
|
}
|
|
else if (exOp == 0x0010)
|
|
{
|
|
// Instruction is branch conditional to link register: bclr or bclrl
|
|
if (opcode&M_LK)
|
|
opFlags = (EOpFlags)(JumpSub | ReturnSub); // bclrl
|
|
else
|
|
opFlags = ReturnSub; // bclr
|
|
// Check BO is not just branch always
|
|
return ((bo&0x14) == 0x14 ? opFlags : (EOpFlags)(opFlags | Conditional));
|
|
}
|
|
else if (exOp == 0x0032)
|
|
{
|
|
// Instruction is return from interrupt: rfi
|
|
return ReturnEx;
|
|
}
|
|
// TODO - traps etc
|
|
}
|
|
return NormalOp;
|
|
}
|
|
|
|
bool CPPCDebug::GetJumpAddr(UINT32 addr, UINT32 opcode, UINT32 &jumpAddr)
|
|
{
|
|
// Check instruction is one of following branches: b, ba, bl, bla, bc, bca, bcl or bcla
|
|
UINT32 disp;
|
|
UINT32 op = opcode>>26;
|
|
if (op == 0x10)
|
|
{
|
|
// Instruction is b, ba, bl or bla, so calculate branch displacement
|
|
disp = ((opcode&M_BD)>>2) * 4;
|
|
if (disp & 0x00008000)
|
|
disp |= 0xFFFF0000; // Sign extended
|
|
if (opcode&M_AA)
|
|
jumpAddr = disp; // ba or bla
|
|
else
|
|
jumpAddr = addr + disp; // b or bl
|
|
return true;
|
|
}
|
|
else if (op == 0x12)
|
|
{
|
|
// Instruction is bc, bca, bcl or bcla, so calculate branch displacement
|
|
disp = ((opcode&M_LI) >> 2) * 4;
|
|
if (disp & 0x02000000)
|
|
disp |= 0xFC000000; // Sign extended
|
|
if (opcode&M_AA)
|
|
jumpAddr = disp; // bca or bcla
|
|
else
|
|
jumpAddr = addr + disp; // bc or bcl
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CPPCDebug::GetJumpRetAddr(UINT32 addr, UINT32 opcode, UINT32 &retAddr)
|
|
{
|
|
UINT32 op = opcode>>26;
|
|
if ((op == 0x10 || op == 0x12) && (opcode&M_LK))
|
|
{
|
|
// Instruction is bl, bla, bcl or bcla (TODO - add bclrl?)
|
|
retAddr = addr + 4;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CPPCDebug::GetReturnAddr(UINT32 addr, UINT32 opcode, UINT32 &retAddr)
|
|
{
|
|
// Check instruction is one of following: bclr, bclrl or rfi
|
|
if ((opcode>>26) != 0x13)
|
|
return false;
|
|
|
|
UINT32 exOp = (opcode>>1)&0x3ff;
|
|
if (exOp == 0x0010)
|
|
{
|
|
// For bclr and blclr, return address is in link register
|
|
retAddr = ::ppc_get_lr();
|
|
return true;
|
|
}
|
|
else if (exOp == 0x0032)
|
|
{
|
|
// For rfi, return address is in SRR0
|
|
retAddr = ::ppc_read_spr(SPR_SRR0);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CPPCDebug::GetHandlerAddr(CException *ex, UINT32 &handlerAddr)
|
|
{
|
|
UINT32 msr = ::ppc_read_msr();
|
|
UINT32 base = (msr&MSR_IP ? 0xFFF00000 : 0x00000000);
|
|
switch (ex->code)
|
|
{
|
|
case EXCEPTION_DSI: handlerAddr = base + 0x0300; return true;
|
|
case EXCEPTION_ISI: handlerAddr = base + 0x0400; return true;
|
|
case EXCEPTION_IRQ: handlerAddr = base + 0x0500; return true;
|
|
case EXCEPTION_TRAP: handlerAddr = base + 0x0700; return true;
|
|
case EXCEPTION_DECREMENTER: handlerAddr = base + 0x0900; return true;
|
|
case EXCEPTION_SYSTEM_CALL: handlerAddr = base + 0x0C00; return true;
|
|
case EXCEPTION_SMI: handlerAddr = base + 0x1400; return true;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
bool CPPCDebug::GetHandlerAddr(CInterrupt *in, UINT32 &handlerAddr)
|
|
{
|
|
UINT32 msr = ::ppc_read_msr();
|
|
handlerAddr = (msr&MSR_IP ? 0xFFF00500 : 0x00000500);
|
|
return true;
|
|
}
|
|
|
|
// IBus methods
|
|
|
|
UINT8 CPPCDebug::Read8(UINT32 addr)
|
|
{
|
|
UINT8 data = m_bus->Read8(addr);
|
|
CheckRead8(addr, data);
|
|
return data;
|
|
}
|
|
|
|
UINT16 CPPCDebug::Read16(UINT32 addr)
|
|
{
|
|
UINT16 data = m_bus->Read16(addr);
|
|
CheckRead16(addr, data);
|
|
return data;
|
|
}
|
|
|
|
UINT32 CPPCDebug::Read32(UINT32 addr)
|
|
{
|
|
UINT32 data = m_bus->Read32(addr);
|
|
CheckRead32(addr, data);
|
|
return data;
|
|
}
|
|
|
|
UINT64 CPPCDebug::Read64(UINT32 addr)
|
|
{
|
|
UINT64 data = m_bus->Read64(addr);
|
|
CheckRead64(addr, data);
|
|
return data;
|
|
}
|
|
|
|
void CPPCDebug::Write8(UINT32 addr, UINT8 data)
|
|
{
|
|
m_bus->Write8(addr, data);
|
|
CheckWrite8(addr, data);
|
|
}
|
|
|
|
void CPPCDebug::Write16(UINT32 addr, UINT16 data)
|
|
{
|
|
m_bus->Write16(addr, data);
|
|
CheckWrite16(addr, data);
|
|
}
|
|
|
|
void CPPCDebug::Write32(UINT32 addr, UINT32 data)
|
|
{
|
|
m_bus->Write32(addr, data);
|
|
CheckWrite32(addr, data);
|
|
}
|
|
|
|
void CPPCDebug::Write64(UINT32 addr, UINT64 data)
|
|
{
|
|
m_bus->Write64(addr, data);
|
|
CheckWrite64(addr, data);
|
|
}
|
|
}
|
|
|
|
#endif // SUPERMODEL_DEBUGGER
|