diff --git a/sources/Debugger/CPUDebug.cpp b/sources/Debugger/CPUDebug.cpp index a642956..50a7d01 100644 --- a/sources/Debugger/CPUDebug.cpp +++ b/sources/Debugger/CPUDebug.cpp @@ -5,6 +5,7 @@ #include "CPUDebug.hpp" #include "../Utility/Utility.hpp" #include "../Exceptions/InvalidOpcode.hpp" +#include "../CPU/CPU.hpp" #include #include #include @@ -75,7 +76,9 @@ namespace ComSquare::Debugger this->_isPaused = true; } uint24_t pc = (this->_registers.pbr << 16u) | (this->_registers.pc - 1u); - this->_ui.logger->append((this->_parseInstruction(pc).toString() + " - " + Utility::to_hex(opcode)).c_str()); + DisassemblyContext ctx = {this->_registers.p.m, this->_registers.p.x_b}; + DisassembledInstruction instruction = this->_parseInstruction(pc, ctx); + this->_ui.logger->append((instruction.toString() + " - " + Utility::to_hex(opcode)).c_str()); unsigned ret = CPU::_executeInstruction(opcode); this->_updateRegistersPanel(); return ret; @@ -135,53 +138,104 @@ namespace ComSquare::Debugger return str; } - std::vector CPUDebug::_disassemble(uint24_t pc, uint24_t length) - { - std::vector map; - uint24_t endAddr = pc + length; - - while (pc < endAddr) { - DisassembledInstruction instruction = this->_parseInstruction(pc); - map.push_back(instruction); - pc += instruction.size; - } - return map; - } - void CPUDebug::clearHistory() { this->_ui.logger->clear(); } - std::string CPUDebug::_getImmediateValueForA(uint24_t pc) + std::vector CPUDebug::_disassemble(uint24_t pc, uint24_t length) + { + std::vector map; + uint24_t endAddr = pc + length; + DisassemblyContext ctx; + + while (pc < endAddr) { + DisassembledInstruction instruction = this->_parseInstruction(pc, ctx); + map.push_back(instruction); + pc += instruction.size; + if (instruction.addressingMode == ImmediateForA && !ctx.mFlag) + pc++; + if (instruction.addressingMode == ImmediateForX && !ctx.xFlag) + pc++; + + if (instruction.opcode == 0x40 && ctx.isEmulationMode) { // RTI + ctx.mFlag = true; + ctx.xFlag = true; + } + if (instruction.opcode == 0xC2) { // REP + if (ctx.isEmulationMode) { + ctx.mFlag = true; + ctx.xFlag = true; + } else { + uint8_t m = this->_bus->read(pc - 1); + ctx.mFlag &= ~m & 0b00100000u; + ctx.xFlag &= ~m & 0b00010000u; + } + } + if (instruction.opcode == 0xE2) { // SEP + uint8_t m = this->_bus->read(pc - 1); + ctx.mFlag |= m & 0b00100000u; + ctx.xFlag |= m & 0b00010000u; + } + if (instruction.opcode == 0x28) { // PLP + if (ctx.isEmulationMode) { + ctx.mFlag = true; + ctx.xFlag = true; + } else + ctx.compromised = true; + } + if (instruction.opcode == 0xFB) {// XCE + ctx.compromised = true; + ctx.isEmulationMode = false; // The most common use of the XCE is to enable native mode at the start of the ROM so we guess that it has done that. + } + } + return map; + } + + DisassembledInstruction CPUDebug::_parseInstruction(uint24_t pc, DisassemblyContext &ctx) + { + uint24_t opcode = this->_bus->read(pc, true); + Instruction instruction = this->_instructions[opcode]; + std::string argument = this->_getInstructionParameter(instruction, pc + 1, ctx); + return DisassembledInstruction(instruction, pc, argument, opcode); + } + + std::string CPUDebug::_getInstructionParameter(Instruction &instruction, uint24_t pc, DisassemblyContext &ctx) + { + switch (instruction.addressingMode) { + case Implied: + return ""; + case ImmediateForA: + return this->_getImmediateValue(pc, !ctx.mFlag); + case ImmediateForX: + return this->_getImmediateValue(pc, !ctx.xFlag); + case Immediate8bits: + return this->_getImmediateValue(pc, false); + case Absolute: + return this->_getAbsoluteValue(pc); + case AbsoluteLong: + return this->_getAbsoluteLongValue(pc); + case DirectPage: + return this->_getDirectValue(pc); + case DirectPageIndexedByX: + return this->_getDirectIndexedByXValue(pc); + + default: + return "???"; + } + } + + std::string CPUDebug::_getImmediateValue(uint24_t pc, bool dual) { unsigned value = this->_bus->read(pc, true); - if (!this->_registers.p.m) + if (dual) value += this->_bus->read(pc + 1, true) << 8u; std::stringstream ss; ss << "#$" << std::hex << value; return ss.str(); } - std::string CPUDebug::_getImmediateValueForX(uint24_t pc) - { - unsigned value = this->_bus->read(pc, true); - - if (!this->_registers.p.x_b) - value += this->_bus->read(pc + 1, true) << 8u; - std::stringstream ss; - ss << "#$" << std::hex << value; - return ss.str(); - } - - std::string CPUDebug::_getImmediateValue8Bits(uint24_t pc) - { - std::stringstream ss; - ss << "#$" << std::hex << static_cast(this->_bus->read(pc, true)); - return ss.str(); - } - std::string CPUDebug::_getImmediateValue16Bits(uint24_t pc) { unsigned value = this->_bus->read(pc, true); @@ -226,39 +280,6 @@ namespace ComSquare::Debugger return ss.str(); } - DisassembledInstruction CPUDebug::_parseInstruction(uint24_t pc) - { - uint24_t opcode = this->_bus->read(pc, true); - Instruction instruction = this->_instructions[opcode]; - std::string argument = this->_getInstructionParameter(instruction, pc + 1); - return DisassembledInstruction(instruction, pc, argument, opcode); - } - - std::string CPUDebug::_getInstructionParameter(Instruction &instruction, uint24_t pc) - { - switch (instruction.addressingMode) { - case Implied: - return ""; - case ImmediateForA: - return this->_getImmediateValueForA(pc); - case ImmediateForX: - return this->_getImmediateValueForX(pc); - case Immediate8bits: - return this->_getImmediateValue8Bits(pc); - case Absolute: - return this->_getAbsoluteValue(pc); - case AbsoluteLong: - return this->_getAbsoluteLongValue(pc); - case DirectPage: - return this->_getDirectValue(pc); - case DirectPageIndexedByX: - return this->_getDirectIndexedByXValue(pc); - - default: - return "???"; - } - } - int CPUDebug::RESB(uint24_t) { CPU::RESB(); diff --git a/sources/Debugger/CPUDebug.hpp b/sources/Debugger/CPUDebug.hpp index b9474ad..d6f05c3 100644 --- a/sources/Debugger/CPUDebug.hpp +++ b/sources/Debugger/CPUDebug.hpp @@ -38,6 +38,21 @@ public: namespace ComSquare::Debugger { + //! @brief Struct used to emulate the state of the processor during the disassembly since instructions take a different amount of space depending on some flags. + struct DisassemblyContext { + //! @brief The accumulator and Memory width flag (in native mode only) - 0 = 16 bits mode, 1 = 8 bits mode. + //! @info If this flag is set to false, instructions that have the ImmediateByA addressing mode take 1 more bit of space. + bool mFlag = true; + //! @brief The indeX register width flag (in native mode only) - 0 = 16 bits mode, 1 = 8 bits mode OR the Break flag (in emulation mode only) + //! @info If this flag is set to false, instructions that have the ImmediateByX addressing mode take 1 more bit of space. + bool xFlag = true; + //! @brief Is the CPU emulating a 6502? If yes, some instructions don't change flags the same way. + bool isEmulationMode = true; + //! @brief Sometimes, the flags can't be tracked correctly after an instruction so the next instructions may not be correctly disassembled. + bool compromised = false; + }; + + //! @brief Struct representing an instruction in an human readable way (created by disassembling the rom). struct DisassembledInstruction : public CPU::Instruction { uint24_t address; std::string argument; @@ -68,23 +83,20 @@ namespace ComSquare::Debugger SNES &_snes; //! @brief Reimplement the basic instruction execution method to log instructions inside the logger view. unsigned _executeInstruction(uint8_t opcode) override; - //! @brief Parse the instruction at the program counter given to have human readable information. - DisassembledInstruction _parseInstruction(uint24_t pc); - //! @brief Get the parameter of the instruction as an hexadecimal string. - std::string _getInstructionParameter(ComSquare::CPU::Instruction &instruction, uint24_t pc); - //! @brief Get a printable string representing the flags. - std::string _getFlagsString(); //! @brief Disassemble part of the memory (using the bus) and parse it to a map of address and disassembled instruction. std::vector _disassemble(uint24_t startAddr, uint24_t size); + //! @brief Parse the instruction at the program counter given to have human readable information. + DisassembledInstruction _parseInstruction(uint24_t pc, DisassemblyContext &ctx); + //! @brief Get the parameter of the instruction as an hexadecimal string. + std::string _getInstructionParameter(ComSquare::CPU::Instruction &instruction, uint24_t pc, DisassemblyContext &ctx); + //! @brief Get a printable string representing the flags. + std::string _getFlagsString(); //! @brief Update the register's panel (accumulator, stack pointer...) void _updateRegistersPanel(); - //! @brief Return a printable string corresponding to the value of an immediate addressing mode for a. - std::string _getImmediateValueForA(uint24_t pc); - //! @brief Return a printable string corresponding to the value of an immediate addressing mode for x. - std::string _getImmediateValueForX(uint24_t pc); - //! @brief Return a printable string corresponding to the value of a 8bits immediate addressing mode (used only with SEP and REP). - std::string _getImmediateValue8Bits(uint24_t pc); + //! @brief Return a printable string corresponding to the value of a 8 or 16 bits immediate addressing mode. + //! @param dual Set this to true if the instruction take 16bits and not 8. (used for the immediate by a when the flag m is not set or the immediate by x if the flag x is not set). + std::string _getImmediateValue(uint24_t pc, bool dual); //! @brief Return a printable string corresponding to the value of a 16bits immediate addressing mode. std::string _getImmediateValue16Bits(uint24_t pc); //! @brief Return a printable string corresponding to the value of a direct addressing mode.