mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
1182 lines
29 KiB
C++
1182 lines
29 KiB
C++
#include "pch.h"
|
|
#include "GBA/Debugger/GbaDisUtils.h"
|
|
#include "GBA/GbaTypes.h"
|
|
#include "GBA/GbaCpu.h"
|
|
#include "GBA/Debugger/DummyGbaCpu.h"
|
|
#include "GBA/Debugger/GbaCodeDataLogger.h"
|
|
#include "GBA/GbaConsole.h"
|
|
#include "Debugger/DisassemblyInfo.h"
|
|
#include "Debugger/LabelManager.h"
|
|
#include "Shared/EmuSettings.h"
|
|
#include "Utilities/FastString.h"
|
|
#include "Utilities/HexUtilities.h"
|
|
|
|
void GbaDisUtils::GetDisassembly(DisassemblyInfo& info, string& out, uint32_t memoryAddr, LabelManager* labelManager, EmuSettings* settings)
|
|
{
|
|
if(info.GetFlags() & GbaCdlFlags::Thumb) {
|
|
ThumbDisassemble(info, out, memoryAddr, labelManager, settings);
|
|
} else {
|
|
ArmDisassemble(info, out, memoryAddr, labelManager, settings);
|
|
}
|
|
}
|
|
|
|
void GbaDisUtils::ThumbDisassemble(DisassemblyInfo& info, string& out, uint32_t memoryAddr, LabelManager* labelManager, EmuSettings* settings)
|
|
{
|
|
FastString str(settings->GetDebugConfig().UseLowerCaseDisassembly);
|
|
|
|
uint16_t opCode = info.GetByteCode()[0] | (info.GetByteCode()[1] << 8);
|
|
GbaThumbOpCategory category = GbaCpu::GetThumbOpCategory(opCode);
|
|
|
|
auto writeBranchTarget = [&str, labelManager](uint32_t addr) {
|
|
AddressInfo relAddr = { (int32_t)addr, MemoryType::GbaMemory };
|
|
string label = labelManager ? labelManager->GetLabel(relAddr) : "";
|
|
if(label.empty()) {
|
|
str.WriteAll('$', HexUtilities::ToHex(addr));
|
|
} else {
|
|
str.Write(label, true);
|
|
}
|
|
};
|
|
|
|
switch(category) {
|
|
case GbaThumbOpCategory::MoveShiftedRegister:
|
|
{
|
|
uint8_t op = (opCode >> 11) & 0x03;
|
|
uint8_t offset = (opCode >> 6) & 0x1F;
|
|
uint8_t rs = (opCode >> 3) & 0x07;
|
|
uint8_t rd = opCode & 0x07;
|
|
|
|
switch(op) {
|
|
case 0: str.Write("LSL"); break;
|
|
case 1: str.Write("LSR"); break;
|
|
case 2: str.Write("ASR"); break;
|
|
default: str.Write("invalid"); break;
|
|
}
|
|
|
|
str.Write(' ');
|
|
WriteReg(str, rd);
|
|
str.Write(", ");
|
|
WriteReg(str, rs);
|
|
str.WriteAll(", #$", HexUtilities::ToHex(offset));
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::AddSubtract:
|
|
{
|
|
bool sub = opCode & (1 << 9);
|
|
bool immediate = opCode & (1 << 10);
|
|
uint8_t rnOffset = (opCode >> 6) & 0x7;
|
|
uint8_t rs = (opCode >> 3) & 0x07;
|
|
uint8_t rd = opCode & 0x07;
|
|
|
|
str.Write(sub ? "SUB" : "ADD");
|
|
str.Write(' ');
|
|
WriteReg(str, rd);
|
|
str.Write(", ");
|
|
WriteReg(str, rs);
|
|
if(immediate) {
|
|
str.WriteAll(", #$", HexUtilities::ToHex(rnOffset));
|
|
} else {
|
|
str.Write(", ");
|
|
WriteReg(str, rnOffset);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::MoveCmpAddSub:
|
|
{
|
|
uint8_t op = (opCode >> 11) & 0x03;
|
|
uint8_t rd = (opCode >> 8) & 0x07;
|
|
uint8_t offset = opCode & 0xFF;
|
|
|
|
switch(op) {
|
|
case 0: str.Write("MOV"); break;
|
|
case 1: str.Write("CMP"); break;
|
|
case 2: str.Write("ADD"); break;
|
|
case 3: str.Write("SUB"); break;
|
|
}
|
|
str.Write(' ');
|
|
WriteReg(str, rd);
|
|
str.WriteAll(", #$", HexUtilities::ToHex(offset));
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::AluOperation:
|
|
{
|
|
uint8_t op = (opCode >> 6) & 0x0F;
|
|
uint8_t rs = (opCode >> 3) & 0x07;
|
|
uint8_t rd = opCode & 0x07;
|
|
|
|
switch(op) {
|
|
case 0: str.Write("AND"); break;
|
|
case 1: str.Write("EOR"); break;
|
|
case 2: str.Write("LSL"); break;
|
|
case 3: str.Write("LSR"); break;
|
|
case 4: str.Write("ASR"); break;
|
|
case 5: str.Write("ADC"); break;
|
|
case 6: str.Write("SBC"); break;
|
|
case 7: str.Write("ROR"); break;
|
|
case 8: str.Write("TST"); break;
|
|
case 9: str.Write("NEG"); break;
|
|
case 10: str.Write("CMP"); break;
|
|
case 11: str.Write("CMN"); break;
|
|
case 12: str.Write("ORR"); break;
|
|
case 13: str.Write("MUL"); break;
|
|
case 14: str.Write("BIC"); break;
|
|
case 15: str.Write("MVN"); break;
|
|
}
|
|
str.Write(' ');
|
|
WriteReg(str, rd);
|
|
str.Write(", ");
|
|
WriteReg(str, rs);
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::HiRegBranchExch:
|
|
{
|
|
uint8_t op = (opCode >> 8) & 0x03;
|
|
uint8_t rs = ((opCode >> 3) & 0x07) | ((opCode & 0x40) >> 3);
|
|
uint8_t rd = (opCode & 0x07) | ((opCode & 0x80) >> 4);
|
|
|
|
switch(op) {
|
|
case 0: str.Write("ADD"); break;
|
|
case 1: str.Write("CMP"); break;
|
|
case 2: str.Write("MOV"); break;
|
|
case 3: str.Write("BX"); break;
|
|
}
|
|
|
|
str.Write(' ');
|
|
|
|
if(op < 3) {
|
|
WriteReg(str, rd);
|
|
str.Write(", ");
|
|
WriteReg(str, rs);
|
|
} else {
|
|
WriteReg(str, rs);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::PcRelLoad:
|
|
{
|
|
uint8_t rd = (opCode >> 8) & 0x07;
|
|
uint8_t immValue = opCode & 0xFF;
|
|
|
|
str.Write("LDR");
|
|
str.Write(' ');
|
|
WriteReg(str, rd);
|
|
str.WriteAll(", [PC, #$", HexUtilities::ToHex(immValue), ']');
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::LoadStoreRegOffset:
|
|
{
|
|
uint8_t ro = (opCode >> 6) & 0x07;
|
|
uint8_t rb = (opCode >> 3) & 0x07;
|
|
uint8_t rd = opCode & 0x07;
|
|
bool byte = opCode & (1 << 10);
|
|
bool load = opCode & (1 << 11);
|
|
|
|
str.Write(load ? "LDR" : "STR");
|
|
if(byte) {
|
|
str.Write('B');
|
|
}
|
|
|
|
str.Write(' ');
|
|
WriteReg(str, rd);
|
|
str.Write(", [");
|
|
WriteReg(str, rb);
|
|
str.Write(", ");
|
|
WriteReg(str, ro);
|
|
str.Write(']');
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::LoadStoreSignExtended:
|
|
{
|
|
uint8_t ro = (opCode >> 6) & 0x07;
|
|
uint8_t rb = (opCode >> 3) & 0x07;
|
|
uint8_t rd = opCode & 0x07;
|
|
uint8_t op = (opCode >> 10) & 0x03;
|
|
|
|
switch(op) {
|
|
case 0: str.Write("STRH"); break;
|
|
case 1: str.Write("LDRH"); break;
|
|
case 2: str.Write("LDSB"); break;
|
|
case 3: str.Write("LDSH"); break;
|
|
}
|
|
|
|
str.Write(' ');
|
|
WriteReg(str, rd);
|
|
str.Write(", [");
|
|
WriteReg(str, rb);
|
|
str.Write(", ");
|
|
WriteReg(str, ro);
|
|
str.Write(']');
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::LoadStoreImmOffset:
|
|
{
|
|
uint8_t rb = (opCode >> 3) & 0x07;
|
|
uint8_t rd = opCode & 0x07;
|
|
uint8_t offset = (opCode >> 6) & 0x1F;
|
|
bool load = opCode & (1 << 11);
|
|
bool byte = opCode & (1 << 12);
|
|
|
|
str.Write(load ? "LDR" : "STR");
|
|
if(byte) {
|
|
str.Write('B');
|
|
}
|
|
|
|
str.Write(' ');
|
|
WriteReg(str, rd);
|
|
str.Write(", [");
|
|
WriteReg(str, rb);
|
|
if(byte) {
|
|
str.WriteAll(", #$", HexUtilities::ToHex(offset));
|
|
} else {
|
|
str.WriteAll(", #$", HexUtilities::ToHex(offset << 2));
|
|
}
|
|
str.Write(']');
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::LoadStoreHalfWord:
|
|
{
|
|
uint8_t rb = (opCode >> 3) & 0x07;
|
|
uint8_t rd = opCode & 0x07;
|
|
uint8_t offset = (opCode >> 6) & 0x1F;
|
|
bool load = opCode & (1 << 11);
|
|
|
|
str.Write(load ? "LDRH" : "STRH");
|
|
|
|
str.Write(' ');
|
|
WriteReg(str, rd);
|
|
str.Write(", [");
|
|
WriteReg(str, rb);
|
|
str.WriteAll(", #$", HexUtilities::ToHex(offset << 1));
|
|
str.Write(']');
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::SpRelLoadStore:
|
|
{
|
|
uint8_t rd = (opCode >> 8) & 0x07;
|
|
uint8_t immValue = opCode & 0xFF;
|
|
bool load = opCode & (1 << 11);
|
|
|
|
str.Write(load ? "LDR" : "STR");
|
|
|
|
str.Write(' ');
|
|
WriteReg(str, rd);
|
|
str.WriteAll(", [SP, #$", HexUtilities::ToHex(immValue << 2), ']');
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::LoadAddress:
|
|
{
|
|
uint8_t rd = (opCode >> 8) & 0x07;
|
|
uint8_t immValue = opCode & 0xFF;
|
|
bool useSp = opCode & (1 << 11);
|
|
|
|
str.Write("ADD");
|
|
|
|
str.Write(' ');
|
|
WriteReg(str, rd);
|
|
str.Write(", ");
|
|
str.Write(useSp ? "SP" : "PC");
|
|
str.WriteAll(", #$", HexUtilities::ToHex(immValue << 2));
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::AddOffsetToSp:
|
|
{
|
|
uint16_t immValue = (opCode & 0x7F) << 2;
|
|
bool sign = opCode & (1 << 7);
|
|
|
|
str.Write("ADD SP, #");
|
|
str.WriteAll(sign ? "-" : "", "$", HexUtilities::ToHex(immValue));
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::PushPopReg:
|
|
{
|
|
uint8_t regMask = opCode & 0xFF;
|
|
bool storeLrLoadPc = opCode & (1 << 8);
|
|
bool load = opCode & (1 << 11);
|
|
|
|
str.Write(load ? "POP" : "PUSH");
|
|
str.Write(" {");
|
|
|
|
WriteRegList(str, regMask, 8);
|
|
|
|
if(storeLrLoadPc) {
|
|
if(regMask) {
|
|
str.Write(',');
|
|
}
|
|
str.Write(load ? "PC" : "LR");
|
|
}
|
|
str.Write("}");
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::MultipleLoadStore:
|
|
{
|
|
uint8_t regMask = opCode & 0xFF;
|
|
uint8_t rb = (opCode >> 8) & 0x07;
|
|
bool load = opCode & (1 << 11);
|
|
|
|
str.Write(load ? "LDMIA" : "STMIA");
|
|
str.Write(' ');
|
|
WriteReg(str, rb);
|
|
str.Write("!, {");
|
|
WriteRegList(str, regMask, 8);
|
|
str.Write('}');
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::ConditionalBranch:
|
|
{
|
|
int16_t offset = (((int16_t)(int8_t)(opCode & 0xFF)) << 1) + 4;
|
|
uint8_t cond = (opCode >> 8) & 0x0F;
|
|
|
|
switch(cond) {
|
|
case 0: str.Write("BEQ"); break;
|
|
case 1: str.Write("BNE"); break;
|
|
case 2: str.Write("BCS"); break;
|
|
case 3: str.Write("BCC"); break;
|
|
case 4: str.Write("BMI"); break;
|
|
case 5: str.Write("BPL"); break;
|
|
case 6: str.Write("BVS"); break;
|
|
case 7: str.Write("BVC"); break;
|
|
case 8: str.Write("BHI"); break;
|
|
case 9: str.Write("BLS"); break;
|
|
case 10: str.Write("BGE"); break;
|
|
case 11: str.Write("BLT"); break;
|
|
case 12: str.Write("BGT"); break;
|
|
case 13: str.Write("BLE"); break;
|
|
default: str.Write("invalid"); break;
|
|
}
|
|
|
|
str.Write(' ');
|
|
writeBranchTarget(memoryAddr + offset);
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::SoftwareInterrupt:
|
|
{
|
|
uint8_t value = opCode & 0xFF;
|
|
|
|
str.Write("SWI");
|
|
str.WriteAll(" #$", HexUtilities::ToHex(value));
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::UnconditionalBranch:
|
|
{
|
|
int16_t offset = ((int16_t)((opCode & 0x7FF) << 5)) >> 4;
|
|
str.Write("BAL ");
|
|
writeBranchTarget(memoryAddr + offset + 4);
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::LongBranchLink:
|
|
{
|
|
uint16_t offset = opCode & 0x7FF;
|
|
bool high = opCode & (1 << 11);
|
|
str.WriteAll("BL", (high ? "H" : "L"), " #$", HexUtilities::ToHex(offset));
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::InvalidOp:
|
|
{
|
|
str.Write("INVALID");
|
|
break;
|
|
}
|
|
}
|
|
|
|
out += str.ToString();
|
|
}
|
|
|
|
void GbaDisUtils::ArmDisassemble(DisassemblyInfo& info, string& out, uint32_t memoryAddr, LabelManager* labelManager, EmuSettings* settings)
|
|
{
|
|
FastString str(settings->GetDebugConfig().UseLowerCaseDisassembly);
|
|
uint32_t opCode = info.GetByteCode()[0] | (info.GetByteCode()[1] << 8) | (info.GetByteCode()[2] << 16) | (info.GetByteCode()[3] << 24);
|
|
|
|
ArmOpCategory category = GbaCpu::GetArmOpCategory(opCode);
|
|
|
|
auto writeBranchTarget = [&str, labelManager](uint32_t addr) {
|
|
AddressInfo relAddr = { (int32_t)addr, MemoryType::GbaMemory };
|
|
string label = labelManager ? labelManager->GetLabel(relAddr) : "";
|
|
if(label.empty()) {
|
|
str.WriteAll('$', HexUtilities::ToHex(addr));
|
|
} else {
|
|
str.Write(label, true);
|
|
}
|
|
};
|
|
|
|
switch(category) {
|
|
case ArmOpCategory::Branch:
|
|
{
|
|
str.Write('B');
|
|
if(opCode & (1 << 24)) {
|
|
str.Write('L');
|
|
}
|
|
WriteCond(str, opCode);
|
|
str.Write(' ');
|
|
|
|
int32_t offset = (((int32_t)opCode << 8) >> 6);
|
|
writeBranchTarget(memoryAddr + offset + 8);
|
|
break;
|
|
}
|
|
|
|
case ArmOpCategory::DataProcessing:
|
|
{
|
|
bool immediate = (opCode & (1 << 25)) != 0;
|
|
uint8_t rn = (opCode >> 16) & 0x0F;
|
|
uint8_t rd = (opCode >> 12) & 0x0F;
|
|
bool updateFlags = (opCode & (1 << 20)) != 0;
|
|
|
|
ArmAluOperation aluOp = (ArmAluOperation)((opCode >> 21) & 0x0F);
|
|
switch(aluOp) {
|
|
case ArmAluOperation::And: str.Write("AND"); break;
|
|
case ArmAluOperation::Eor: str.Write("EOR"); break;
|
|
case ArmAluOperation::Sub: str.Write("SUB"); break;
|
|
case ArmAluOperation::Rsb: str.Write("RSB"); break;
|
|
case ArmAluOperation::Add: str.Write("ADD"); break;
|
|
case ArmAluOperation::Adc: str.Write("ADC"); break;
|
|
case ArmAluOperation::Sbc: str.Write("SBC"); break;
|
|
case ArmAluOperation::Rsc: str.Write("RSC"); break;
|
|
case ArmAluOperation::Tst: str.Write("TST"); break;
|
|
case ArmAluOperation::Teq: str.Write("TEQ"); break;
|
|
case ArmAluOperation::Cmp: str.Write("CMP"); break;
|
|
case ArmAluOperation::Cmn: str.Write("CMN"); break;
|
|
case ArmAluOperation::Orr: str.Write("ORR"); break;
|
|
case ArmAluOperation::Mov: str.Write("MOV"); break;
|
|
case ArmAluOperation::Bic: str.Write("BIC"); break;
|
|
case ArmAluOperation::Mvn: str.Write("MVN"); break;
|
|
}
|
|
|
|
WriteCond(str, opCode);
|
|
bool isCmp = aluOp >= ArmAluOperation::Tst && aluOp <= ArmAluOperation::Cmn;
|
|
if(updateFlags && !isCmp) {
|
|
str.Write('S');
|
|
}
|
|
|
|
str.Write(' ');
|
|
|
|
if(aluOp == ArmAluOperation::Mov || aluOp == ArmAluOperation::Mvn) {
|
|
WriteReg(str, rd);
|
|
} else if(isCmp) {
|
|
WriteReg(str, rn);
|
|
} else {
|
|
WriteReg(str, rd);
|
|
str.Write(", ");
|
|
WriteReg(str, rn);
|
|
}
|
|
|
|
if(immediate) {
|
|
uint8_t shift = ((opCode >> 8) & 0x0F) * 2;
|
|
uint32_t val = (opCode & 0xFF);
|
|
if(shift) {
|
|
val = (val >> shift) | (val << (32 - shift));
|
|
}
|
|
str.WriteAll(", #$", HexUtilities::ToHex(val));
|
|
} else {
|
|
str.Write(", ");
|
|
uint8_t rm = opCode & 0x0F;
|
|
WriteReg(str, rm);
|
|
uint8_t shiftType = (opCode >> 5) & 0x03;
|
|
|
|
bool useReg = opCode & (1 << 4);
|
|
uint8_t shift = ((opCode >> 7) & 0x1F);
|
|
|
|
if(useReg || shift || shiftType != 0) {
|
|
switch(shiftType) {
|
|
case 0: str.Write(", LSL"); break;
|
|
case 1: str.Write(", LSR"); break;
|
|
case 2: str.Write(", ASR"); break;
|
|
case 3: str.Write(shift == 0 ? ", RRX" : ", ROR"); break;
|
|
}
|
|
}
|
|
|
|
if(useReg) {
|
|
//Shift amount in register
|
|
uint8_t rs = (opCode >> 8) & 0x0F;
|
|
str.Write(' ');
|
|
WriteReg(str, rs);
|
|
} else {
|
|
if(shift == 0 && shiftType != 0) {
|
|
if(shiftType != 3) {
|
|
str.WriteAll(" #", std::to_string(32));
|
|
}
|
|
} else {
|
|
if(shift) {
|
|
str.WriteAll(" #", std::to_string(shift));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case ArmOpCategory::SingleDataTransfer:
|
|
{
|
|
bool immediate = (opCode & (1 << 25)) == 0;
|
|
bool pre = (opCode & (1 << 24)) != 0;
|
|
bool up = (opCode & (1 << 23)) != 0;
|
|
bool byte = (opCode & (1 << 22)) != 0;
|
|
bool writeBack = (opCode & (1 << 21)) != 0;
|
|
bool load = (opCode & (1 << 20)) != 0;
|
|
uint8_t rn = (opCode >> 16) & 0x0F;
|
|
uint8_t rd = (opCode >> 12) & 0x0F;
|
|
|
|
str.Write(load ? "LDR" : "STR");
|
|
WriteCond(str, opCode);
|
|
if(byte) {
|
|
str.Write('B');
|
|
}
|
|
|
|
str.Write(' ');
|
|
WriteReg(str, rd);
|
|
str.Write(", ");
|
|
|
|
if(immediate) {
|
|
uint16_t offset = opCode & 0xFFF;
|
|
str.Write('[');
|
|
if(offset == 0) {
|
|
WriteReg(str, rn);
|
|
str.Write(']');
|
|
} else if(pre) {
|
|
WriteReg(str, rn);
|
|
str.WriteAll(", ", up ? '+' : '-', "#$", HexUtilities::ToHex(offset), ']');
|
|
if(writeBack) {
|
|
str.Write('!');
|
|
}
|
|
} else {
|
|
WriteReg(str, rn);
|
|
str.WriteAll("], ", up ? '+' : '-', "#$", HexUtilities::ToHex(offset));
|
|
}
|
|
} else {
|
|
str.Write('[');
|
|
WriteReg(str, rn);
|
|
|
|
if(!pre) {
|
|
str.Write(']');
|
|
}
|
|
|
|
uint8_t rm = opCode & 0x0F;
|
|
str.WriteAll(", ", up ? '+' : '-', 'R', std::to_string(rm));
|
|
|
|
uint8_t shiftType = (opCode >> 5) & 0x03;
|
|
uint8_t shift = (opCode >> 7) & 0x1F;
|
|
|
|
if(shift) {
|
|
switch(shiftType) {
|
|
case 0: str.Write(", LSL"); break;
|
|
case 1: str.Write(", LSR"); break;
|
|
case 2: str.Write(", ASR"); break;
|
|
case 3: str.Write(", ROR"); break;
|
|
}
|
|
|
|
str.WriteAll(" #", std::to_string(shift));
|
|
}
|
|
|
|
if(pre) {
|
|
str.Write(']');
|
|
if(writeBack) {
|
|
str.Write('!');
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ArmOpCategory::Mrs:
|
|
{
|
|
uint8_t rd = (opCode >> 12) & 0x0F;
|
|
bool psrSrc = opCode & (1 << 22);
|
|
|
|
str.Write("MRS");
|
|
WriteCond(str, opCode);
|
|
str.Write(' ');
|
|
WriteReg(str, rd);
|
|
str.WriteAll(", ", psrSrc ? "SPSR" : "CPSR");
|
|
break;
|
|
}
|
|
|
|
case ArmOpCategory::Msr:
|
|
{
|
|
bool immediate = opCode & (1 << 25);
|
|
bool psrDst = opCode & (1 << 22);
|
|
uint8_t affectedBytes = (opCode >> 16) & 0x0F;
|
|
|
|
str.Write("MSR");
|
|
WriteCond(str, opCode);
|
|
str.Write(' ');
|
|
|
|
str.WriteAll(psrDst ? "SPSR_" : "CPSR_");
|
|
if(affectedBytes & 0x08) {
|
|
str.Write('F');
|
|
}
|
|
if(affectedBytes & 0x04) {
|
|
str.Write('S');
|
|
}
|
|
if(affectedBytes & 0x02) {
|
|
str.Write('X');
|
|
}
|
|
if(affectedBytes & 0x01) {
|
|
str.Write('C');
|
|
}
|
|
|
|
if(immediate) {
|
|
uint32_t val = (opCode & 0xFF);
|
|
uint8_t shift = ((opCode >> 8) & 0x0F) * 2;
|
|
if(shift) {
|
|
val = (val >> shift) | (val << (32 - shift));
|
|
}
|
|
str.WriteAll(", #$", HexUtilities::ToHex(val));
|
|
} else {
|
|
uint8_t rm = opCode & 0x0F;
|
|
str.Write(", ");
|
|
WriteReg(str, rm);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case ArmOpCategory::SingleDataSwap:
|
|
{
|
|
str.Write("SWP");
|
|
WriteCond(str, opCode);
|
|
|
|
if(opCode & (1 << 22)) {
|
|
str.Write('B');
|
|
}
|
|
|
|
uint8_t rm = opCode & 0x0F;
|
|
uint8_t rd = (opCode >> 12) & 0x0F;
|
|
uint8_t rn = (opCode >> 16) & 0x0F;
|
|
|
|
str.Write(' ');
|
|
WriteReg(str, rd);
|
|
str.Write(", ");
|
|
WriteReg(str, rm);
|
|
str.Write(", [");
|
|
WriteReg(str, rn);
|
|
str.Write(']');
|
|
break;
|
|
}
|
|
|
|
case ArmOpCategory::BranchExchangeRegister:
|
|
{
|
|
str.Write("BX");
|
|
WriteCond(str, opCode);
|
|
str.Write(' ');
|
|
uint8_t rn = opCode & 0x0F;
|
|
WriteReg(str, rn);
|
|
break;
|
|
}
|
|
|
|
case ArmOpCategory::BlockDataTransfer:
|
|
{
|
|
bool pre = (opCode & (1 << 24)) != 0;
|
|
bool up = (opCode & (1 << 23)) != 0;
|
|
bool psrForceUser = (opCode & (1 << 22)) != 0;
|
|
bool writeBack = (opCode & (1 << 21)) != 0;
|
|
bool load = (opCode & (1 << 20)) != 0;
|
|
uint8_t rn = (opCode >> 16) & 0x0F;
|
|
uint16_t regMask = (uint16_t)opCode;
|
|
|
|
if(load) {
|
|
str.Write("LDM");
|
|
if(up) {
|
|
str.Write(pre ? "IB" : "IA");
|
|
} else {
|
|
str.Write(pre ? "DB" : "DA");
|
|
}
|
|
} else {
|
|
str.Write("STM");
|
|
if(up) {
|
|
str.Write(pre ? "IB" : "IA");
|
|
} else {
|
|
str.Write(pre ? "DB" : "DA");
|
|
}
|
|
}
|
|
|
|
str.Write(' ');
|
|
WriteReg(str, rn);
|
|
if(writeBack) {
|
|
str.Write('!');
|
|
}
|
|
str.Write(", {");
|
|
WriteRegList(str, regMask, 16);
|
|
str.Write('}');
|
|
|
|
if(psrForceUser) {
|
|
str.Write('^');
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ArmOpCategory::SignedHalfDataTransfer:
|
|
{
|
|
bool pre = (opCode & (1 << 24)) != 0;
|
|
bool up = (opCode & (1 << 23)) != 0;
|
|
bool immediate = (opCode & (1 << 22)) != 0;
|
|
bool writeBack = (opCode & (1 << 21)) != 0;
|
|
bool load = (opCode & (1 << 20)) != 0;
|
|
uint8_t rn = (opCode >> 16) & 0x0F;
|
|
uint8_t rd = (opCode >> 12) & 0x0F;
|
|
|
|
bool sign = (opCode & (1 << 6)) != 0;
|
|
bool half = (opCode & (1 << 5)) != 0;
|
|
|
|
str.Write(load ? "LDR" : "STR");
|
|
WriteCond(str, opCode);
|
|
|
|
if(sign) {
|
|
str.Write('S');
|
|
}
|
|
if(half) {
|
|
str.Write('H');
|
|
} else {
|
|
str.Write('B');
|
|
}
|
|
|
|
str.Write(' ');
|
|
WriteReg(str, rd);
|
|
str.Write(", ");
|
|
|
|
if(immediate) {
|
|
uint8_t offset = ((opCode >> 4) & 0xF0) | (opCode & 0x0F);
|
|
str.Write('[');
|
|
if(offset == 0) {
|
|
WriteReg(str, rn);
|
|
str.Write(']');
|
|
} else if(pre) {
|
|
WriteReg(str, rn);
|
|
str.WriteAll(", ", up ? '+' : '-', "#$", HexUtilities::ToHex(offset), ']');
|
|
if(writeBack) {
|
|
str.Write('!');
|
|
}
|
|
} else {
|
|
WriteReg(str, rn);
|
|
str.WriteAll("], ", up ? '+' : '-', "#$", HexUtilities::ToHex(offset));
|
|
}
|
|
} else {
|
|
str.Write('[');
|
|
WriteReg(str, rn);
|
|
|
|
if(!pre) {
|
|
str.Write(']');
|
|
}
|
|
|
|
uint8_t rm = opCode & 0x0F;
|
|
str.WriteAll(", ", up ? '+' : '-', 'R', std::to_string(rm));
|
|
|
|
if(pre) {
|
|
str.Write(']');
|
|
if(writeBack) {
|
|
str.Write('!');
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ArmOpCategory::Multiply:
|
|
{
|
|
uint8_t rd = (opCode >> 16) & 0x0F;
|
|
uint8_t rn = (opCode >> 12) & 0x0F;
|
|
uint8_t rs = (opCode >> 8) & 0x0F;
|
|
uint8_t rm = opCode & 0x0F;
|
|
bool updateFlags = (opCode & (1 << 20)) != 0;
|
|
bool multAndAcc = (opCode & (1 << 21)) != 0;
|
|
|
|
str.Write(multAndAcc ? "MLA" : "MUL");
|
|
WriteCond(str, opCode);
|
|
if(updateFlags) {
|
|
str.Write('S');
|
|
}
|
|
|
|
str.Write(' ');
|
|
WriteReg(str, rd);
|
|
str.Write(", ");
|
|
WriteReg(str, rm);
|
|
str.Write(", ");
|
|
WriteReg(str, rs);
|
|
|
|
if(multAndAcc) {
|
|
str.Write(", ");
|
|
WriteReg(str, rn);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ArmOpCategory::MultiplyLong:
|
|
{
|
|
uint8_t rh = (opCode >> 16) & 0x0F;
|
|
uint8_t rl = (opCode >> 12) & 0x0F;
|
|
uint8_t rs = (opCode >> 8) & 0x0F;
|
|
uint8_t rm = opCode & 0x0F;
|
|
bool updateFlags = (opCode & (1 << 20)) != 0;
|
|
bool multAndAcc = (opCode & (1 << 21)) != 0;
|
|
bool sign = (opCode & (1 << 22)) != 0;
|
|
|
|
str.Write(sign ? 'S' : 'U');
|
|
str.Write(multAndAcc ? "MLAL" : "MULL");
|
|
WriteCond(str, opCode);
|
|
if(updateFlags) {
|
|
str.Write('S');
|
|
}
|
|
|
|
str.Write(' ');
|
|
WriteReg(str, rl);
|
|
str.Write(", ");
|
|
WriteReg(str, rh);
|
|
str.Write(", ");
|
|
WriteReg(str, rm);
|
|
str.Write(", ");
|
|
WriteReg(str, rs);
|
|
break;
|
|
}
|
|
|
|
case ArmOpCategory::SoftwareInterrupt:
|
|
{
|
|
uint32_t value = opCode & 0xFFFFFF;
|
|
str.Write("SWI");
|
|
str.WriteAll(" #$", HexUtilities::ToHex24(value));
|
|
break;
|
|
}
|
|
|
|
default:
|
|
str.Write("INVALID");
|
|
break;
|
|
}
|
|
|
|
out += str.ToString();
|
|
}
|
|
|
|
void GbaDisUtils::WriteRegList(FastString& str, uint16_t regMask, uint8_t size)
|
|
{
|
|
bool firstReg = true;
|
|
int8_t rangeStart = -1;
|
|
for(int i = 0; i < size + 1; i++) {
|
|
if(regMask & (1 << i)) {
|
|
if(rangeStart == -1) {
|
|
if(!firstReg) {
|
|
str.Write(',');
|
|
}
|
|
firstReg = false;
|
|
WriteReg(str, i);
|
|
rangeStart = i;
|
|
}
|
|
} else {
|
|
if(rangeStart >= 0) {
|
|
if(i - rangeStart > 1) {
|
|
str.Write('-');
|
|
WriteReg(str, i - 1);
|
|
}
|
|
}
|
|
rangeStart = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GbaDisUtils::WriteReg(FastString& str, uint8_t reg)
|
|
{
|
|
switch(reg) {
|
|
case 13: str.Write("SP"); break;
|
|
case 14: str.Write("LR"); break;
|
|
case 15: str.Write("PC"); break;
|
|
default: str.WriteAll('R', std::to_string(reg)); break;
|
|
}
|
|
}
|
|
|
|
void GbaDisUtils::WriteCond(FastString& str, uint32_t opCode)
|
|
{
|
|
switch(opCode >> 28) {
|
|
default: case 0: str.Write("EQ"); break;
|
|
case 1: str.Write("NE"); break;
|
|
case 2: str.Write("CS"); break;
|
|
case 3: str.Write("CC"); break;
|
|
case 4: str.Write("MI"); break;
|
|
case 5: str.Write("PL"); break;
|
|
case 6: str.Write("VS"); break;
|
|
case 7: str.Write("VC"); break;
|
|
case 8: str.Write("HI"); break;
|
|
case 9: str.Write("LS"); break;
|
|
case 10: str.Write("GE"); break;
|
|
case 11: str.Write("LT"); break;
|
|
case 12: str.Write("GT"); break;
|
|
case 13: str.Write("LE"); break;
|
|
case 14: str.Write(""); break;
|
|
case 15: str.Write("--"); break;
|
|
}
|
|
}
|
|
|
|
EffectiveAddressInfo GbaDisUtils::GetEffectiveAddress(DisassemblyInfo& info, GbaConsole* console, GbaCpuState& state)
|
|
{
|
|
bool thumb = state.CPSR.Thumb;
|
|
uint32_t opCode = console->GetMemoryManager()->DebugCpuRead(thumb ? GbaAccessMode::HalfWord : GbaAccessMode::Word, state.Pipeline.Execute.Address);
|
|
state.Pipeline.Execute.OpCode = opCode;
|
|
|
|
if(thumb) {
|
|
GbaThumbOpCategory category = GbaCpu::GetThumbOpCategory(opCode);
|
|
switch(category) {
|
|
case GbaThumbOpCategory::HiRegBranchExch: {
|
|
uint8_t op = (opCode >> 8) & 0x03;
|
|
if(op == 3) {
|
|
uint8_t rs = ((opCode >> 3) & 0x07) | ((opCode & 0x40) >> 3);
|
|
return { (int)state.R[rs], 0, true, MemoryType::GbaMemory };
|
|
}
|
|
break;
|
|
}
|
|
|
|
case GbaThumbOpCategory::LongBranchLink: {
|
|
bool high = opCode & (1 << 11);
|
|
if(high) {
|
|
uint32_t opCodeLow = console->GetMemoryManager()->DebugCpuRead(GbaAccessMode::HalfWord, state.Pipeline.Execute.Address - 2);
|
|
uint16_t offsetLow = opCodeLow & 0x7FF;
|
|
uint16_t offsetHigh = opCode & 0x7FF;
|
|
int32_t relOffset = ((int32_t)offsetLow << 21) >> 9;
|
|
uint32_t r14 = state.R[15] - 2 + relOffset;
|
|
uint32_t addr = r14 + (offsetHigh << 1);
|
|
return { (int)addr, 0, true, MemoryType::GbaMemory };
|
|
}
|
|
return {};
|
|
}
|
|
|
|
case GbaThumbOpCategory::InvalidOp:
|
|
case GbaThumbOpCategory::MultipleLoadStore:
|
|
case GbaThumbOpCategory::PushPopReg:
|
|
return {};
|
|
}
|
|
} else {
|
|
ArmOpCategory category = GbaCpu::GetArmOpCategory(opCode);
|
|
switch(category) {
|
|
case ArmOpCategory::BranchExchangeRegister: {
|
|
uint8_t rs = opCode & 0x0F;
|
|
return { (int)state.R[rs], 0, true, MemoryType::GbaMemory };
|
|
}
|
|
|
|
case ArmOpCategory::InvalidOp:
|
|
case ArmOpCategory::BlockDataTransfer:
|
|
return {};
|
|
}
|
|
}
|
|
|
|
DummyGbaCpu dummyCpu;
|
|
dummyCpu.Init(console->GetEmulator(), console->GetMemoryManager(), nullptr);
|
|
dummyCpu.SetDummyState(state);
|
|
dummyCpu.Exec<false, false>();
|
|
|
|
uint32_t count = dummyCpu.GetOperationCount();
|
|
for(int i = count - 1; i >= 0; i--) {
|
|
MemoryOperationInfo opInfo = dummyCpu.GetOperationInfo(i);
|
|
if(opInfo.Type != MemoryOperationType::ExecOperand) {
|
|
EffectiveAddressInfo result;
|
|
result.Address = opInfo.Address;
|
|
result.Type = opInfo.MemType;
|
|
result.ValueSize = 1;
|
|
|
|
switch(dummyCpu.GetOperationMode(i) & (GbaAccessMode::Byte | GbaAccessMode::HalfWord | GbaAccessMode::Word)) {
|
|
case GbaAccessMode::Byte: result.ValueSize = 1; break;
|
|
case GbaAccessMode::HalfWord: result.ValueSize = 2; break;
|
|
case GbaAccessMode::Word: result.ValueSize = 4; break;
|
|
}
|
|
|
|
result.ShowAddress = true;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
uint8_t GbaDisUtils::GetOpSize(uint32_t opCode, uint8_t flags)
|
|
{
|
|
return IsThumbMode(flags) ? 2 : 4;
|
|
}
|
|
|
|
bool GbaDisUtils::IsJumpToSub(uint32_t opCode, uint8_t flags)
|
|
{
|
|
if(IsThumbMode(flags)) {
|
|
GbaThumbOpCategory category = GbaCpu::GetThumbOpCategory(opCode);
|
|
if(category == GbaThumbOpCategory::LongBranchLink) {
|
|
bool high = opCode & (1 << 11);
|
|
return high;
|
|
}
|
|
return false;
|
|
} else {
|
|
ArmOpCategory category = GbaCpu::GetArmOpCategory(opCode);
|
|
if(category == ArmOpCategory::Branch) {
|
|
bool withLink = opCode & (1 << 24);
|
|
return withLink;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool GbaDisUtils::IsReturnInstruction(uint32_t opCode, uint8_t flags)
|
|
{
|
|
//For GBA, this is only used to show markers at the end of functions
|
|
//Callstack uses a different logic in GbaDebugger itself
|
|
if(IsThumbMode(flags)) {
|
|
GbaThumbOpCategory category = GbaCpu::GetThumbOpCategory(opCode);
|
|
if(category == GbaThumbOpCategory::HiRegBranchExch) {
|
|
uint8_t op = (opCode >> 8) & 0x03;
|
|
if(op == 3) {
|
|
uint8_t rs = ((opCode >> 3) & 0x07) | ((opCode & 0x40) >> 3);
|
|
//BX 14
|
|
return rs == 14;
|
|
}
|
|
}
|
|
return false;
|
|
} else {
|
|
ArmOpCategory category = GbaCpu::GetArmOpCategory(opCode);
|
|
if(category == ArmOpCategory::BranchExchangeRegister) {
|
|
//BX 14
|
|
uint8_t rs = opCode & 0x0F;
|
|
return rs == 14;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool GbaDisUtils::IsUnconditionalJump(uint32_t opCode, uint8_t flags)
|
|
{
|
|
if(IsThumbMode(flags)) {
|
|
GbaThumbOpCategory category = GbaCpu::GetThumbOpCategory(opCode);
|
|
switch(category) {
|
|
case GbaThumbOpCategory::UnconditionalBranch:
|
|
case GbaThumbOpCategory::SoftwareInterrupt:
|
|
return true;
|
|
|
|
case GbaThumbOpCategory::PushPopReg: {
|
|
bool storeLrLoadPc = opCode & (1 << 8);
|
|
bool load = opCode & (1 << 11);
|
|
return storeLrLoadPc && load;
|
|
}
|
|
|
|
case GbaThumbOpCategory::MultipleLoadStore: {
|
|
uint16_t regMask = (uint8_t)opCode;
|
|
if(regMask == 0) {
|
|
bool load = opCode & (1 << 11);
|
|
return load;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
case GbaThumbOpCategory::HiRegBranchExch: {
|
|
uint8_t op = (opCode >> 8) & 0x03;
|
|
uint8_t rd = (opCode & 0x07) | ((opCode & 0x80) >> 4);
|
|
return op == 3 || ((op == 0 || op == 2) && rd == 15);
|
|
}
|
|
|
|
case GbaThumbOpCategory::LongBranchLink: {
|
|
bool high = opCode & (1 << 11);
|
|
return high;
|
|
}
|
|
}
|
|
return false;
|
|
} else {
|
|
bool isUnconditional = ((opCode >> 28) & 0x0F) == 0x0E;
|
|
if(!isUnconditional) {
|
|
return false;
|
|
}
|
|
return IsArmBranch(opCode);
|
|
}
|
|
}
|
|
|
|
bool GbaDisUtils::IsArmBranch(uint32_t opCode)
|
|
{
|
|
ArmOpCategory category = GbaCpu::GetArmOpCategory(opCode);
|
|
switch(category) {
|
|
case ArmOpCategory::Branch: return true;
|
|
case ArmOpCategory::BranchExchangeRegister: return true;
|
|
case ArmOpCategory::SoftwareInterrupt: return true;
|
|
|
|
case ArmOpCategory::Msr: return false;
|
|
|
|
case ArmOpCategory::Mrs:
|
|
case ArmOpCategory::DataProcessing:
|
|
case ArmOpCategory::Multiply:
|
|
case ArmOpCategory::SingleDataSwap: {
|
|
uint8_t rd = (opCode >> 12) & 0x0F;
|
|
return rd == 15;
|
|
}
|
|
|
|
case ArmOpCategory::SingleDataTransfer:
|
|
case ArmOpCategory::SignedHalfDataTransfer: {
|
|
uint8_t rd = (opCode >> 12) & 0x0F;
|
|
if(rd == 15) {
|
|
bool pre = (opCode & (1 << 24));
|
|
bool writeBack = (opCode & (1 << 21));
|
|
bool load = (opCode & (1 << 20));
|
|
uint8_t rn = (opCode >> 16) & 0x0F;
|
|
if((rd != rn || !load) && (writeBack || !pre)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
case ArmOpCategory::MultiplyLong: {
|
|
uint8_t rl = (opCode >> 12) & 0x0F;
|
|
uint8_t rh = (opCode >> 16) & 0x0F;
|
|
return rl == 15 || rh == 15;
|
|
}
|
|
|
|
case ArmOpCategory::BlockDataTransfer: {
|
|
uint8_t rn = (opCode >> 16) & 0x0F;
|
|
bool load = (opCode & (1 << 20));
|
|
if(rn == 15) {
|
|
bool writeBack = (opCode & (1 << 21));
|
|
if(writeBack || load) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
uint16_t regMask = (uint16_t)opCode;
|
|
if((regMask & 0x8000) || regMask == 0) {
|
|
return load;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GbaDisUtils::IsConditionalJump(uint32_t opCode, uint8_t flags)
|
|
{
|
|
if(IsThumbMode(flags)) {
|
|
GbaThumbOpCategory category = GbaCpu::GetThumbOpCategory(opCode);
|
|
return category == GbaThumbOpCategory::ConditionalBranch;
|
|
} else {
|
|
bool isConditional = ((opCode >> 28) & 0x0F) <= 0x0D;
|
|
return isConditional && IsArmBranch(opCode);
|
|
}
|
|
}
|
|
|
|
CdlFlags::CdlFlags GbaDisUtils::GetOpFlags(uint32_t opCode, uint8_t flags, uint32_t pc, uint32_t prevPc)
|
|
{
|
|
if(pc == prevPc + GbaDisUtils::GetOpSize(opCode, flags)) {
|
|
return CdlFlags::None;
|
|
}
|
|
|
|
if(IsJumpToSub(opCode, flags)) {
|
|
return CdlFlags::SubEntryPoint;
|
|
} else {
|
|
return CdlFlags::JumpTarget;
|
|
}
|
|
}
|
|
|
|
bool GbaDisUtils::IsThumbMode(uint8_t flags)
|
|
{
|
|
return flags & GbaCdlFlags::Thumb;
|
|
}
|