#include "pch.h" #include #include "Debugger/Disassembler.h" #include "Debugger/DisassemblyInfo.h" #include "Debugger/CdlManager.h" #include "Debugger/Debugger.h" #include "Debugger/LabelManager.h" #include "Debugger/MemoryDumper.h" #include "Debugger/CodeDataLogger.h" #include "Debugger/DebugBreakHelper.h" #include "Debugger/DebugUtilities.h" #include "SNES/SnesCpuTypes.h" #include "SNES/SpcTypes.h" #include "SNES/Coprocessors/GSU/GsuTypes.h" #include "SNES/Coprocessors/CX4/Cx4Types.h" #include "Gameboy/GbTypes.h" #include "NES/NesTypes.h" #include "PCE/PceTypes.h" #include "Shared/EmuSettings.h" #include "Utilities/FastString.h" #include "Utilities/HexUtilities.h" #include "Utilities/StringUtilities.h" Disassembler::Disassembler(IConsole* console, Debugger* debugger) { _debugger = debugger; _labelManager = debugger->GetLabelManager(); _console = console; _settings = debugger->GetEmulator()->GetSettings(); _memoryDumper = _debugger->GetMemoryDumper(); for(int i = (int)MemoryType::SnesPrgRom; i < DebugUtilities::GetMemoryTypeCount(); i++) { InitSource((MemoryType)i); } } void Disassembler::InitSource(MemoryType type) { uint32_t size = _memoryDumper->GetMemorySize(type); _sources[(int)type] = { vector(size), size }; } DisassemblerSource& Disassembler::GetSource(MemoryType type) { return _sources[(int)type]; } uint32_t Disassembler::BuildCache(AddressInfo &addrInfo, uint8_t cpuFlags, CpuType type) { DisassemblerSource& src = GetSource(addrInfo.Type); int returnSize = 0; int32_t address = addrInfo.Address; do { DisassemblyInfo &disInfo = src.Cache[address]; if(!disInfo.IsInitialized() || !disInfo.IsValid(cpuFlags)) { disInfo.Initialize(address, cpuFlags, type, addrInfo.Type, _memoryDumper); for(int i = 1; i < disInfo.GetOpSize() && address + i < src.Cache.size() ; i++) { //Clear any instructions that start in the middle of this one //(can happen when resizing an instruction after X/M updates) src.Cache[address + i] = DisassemblyInfo(); } returnSize += disInfo.GetOpSize(); } else { returnSize += disInfo.GetOpSize(); break; } if(!disInfo.CanDisassembleNextOp()) { //Can't assume what follows is code, stop disassembling break; } disInfo.UpdateCpuFlags(cpuFlags); address += disInfo.GetOpSize(); } while(address >= 0 && address < (int32_t)src.Cache.size()); return returnSize; } void Disassembler::ResetPrgCache() { InitSource(MemoryType::SnesPrgRom); InitSource(MemoryType::GbPrgRom); InitSource(MemoryType::NesPrgRom); InitSource(MemoryType::PcePrgRom); } void Disassembler::InvalidateCache(AddressInfo addrInfo, CpuType type) { if(addrInfo.Address >= 0) { DisassemblerSource& src = GetSource(addrInfo.Type); for(int i = 0; i < 4; i++) { if(addrInfo.Address >= i) { if(src.Cache[addrInfo.Address - i].IsInitialized()) { src.Cache[addrInfo.Address - i].Reset(); } } } } } vector Disassembler::Disassemble(CpuType cpuType, uint16_t bank) { constexpr int bytesPerRow = 8; vector results; results.reserve(20000); DebugConfig& cfg = _settings->GetDebugConfig(); bool disUnident = cfg.DisassembleUnidentifiedData; bool disData = cfg.DisassembleVerifiedData; bool showUnident = cfg.ShowUnidentifiedData; bool showData = cfg.ShowVerifiedData; bool showJumpLabels = cfg.ShowJumpLabels; bool inUnknownBlock = false; bool inVerifiedBlock = false; bool inUnmappedBlock = false; LabelInfo labelInfo; AddressInfo prevAddrInfo = {}; int byteCounter = 0; AddressInfo relAddress = {}; relAddress.Type = DebugUtilities::GetCpuMemoryType(cpuType); if(bank > GetMaxBank(cpuType)) { return results; } int32_t bankStart = bank << 16; int32_t bankEnd = (bank + 1) << 16; bankEnd = std::min(bankEnd, (int32_t)_memoryDumper->GetMemorySize(relAddress.Type)); AddressInfo addrInfo = {}; auto pushEndBlock = [&]() { if(inUnknownBlock || inVerifiedBlock) { int flags = LineFlags::BlockEnd; if(inVerifiedBlock) { flags |= LineFlags::VerifiedData; } if((inVerifiedBlock && showData) || (inUnknownBlock && showUnident)) { flags |= LineFlags::ShowAsData; } results[results.size() - 1].SetByteCount(bytesPerRow - byteCounter + 1); results.push_back(DisassemblyResult(prevAddrInfo, relAddress.Address - 1, flags)); inUnknownBlock = false; inVerifiedBlock = false; byteCounter = 0; } }; auto pushUnmappedBlock = [&]() { int32_t prevAddress = results.size() > 0 ? results[results.size() - 1].CpuAddress + 1 : bankStart; results.push_back(DisassemblyResult(prevAddress, LineFlags::BlockStart | LineFlags::UnmappedMemory)); results.push_back(DisassemblyResult(prevAddress, LineFlags::UnmappedMemory | LineFlags::Empty)); results.push_back(DisassemblyResult(relAddress.Address - 1, LineFlags::BlockEnd | LineFlags::UnmappedMemory)); inUnmappedBlock = false; }; for(int32_t i = bankStart; i < bankEnd; i++) { relAddress.Address = i; addrInfo = _console->GetAbsoluteAddress(relAddress); if(addrInfo.Address < 0 || addrInfo.Type == MemoryType::SnesRegister) { pushEndBlock(); inUnmappedBlock = true; continue; } if(inUnmappedBlock) { pushUnmappedBlock(); } DisassemblerSource& src = GetSource(addrInfo.Type); DisassemblyInfo disassemblyInfo = src.Cache[addrInfo.Address]; CodeDataLogger* cdl = _debugger->GetCdlManager()->GetCodeDataLogger(addrInfo.Type); uint8_t opSize = 0; bool isCode = cdl ? cdl->IsCode(addrInfo.Address) : false; bool isData = cdl ? cdl->IsData(addrInfo.Address) : false; if(disassemblyInfo.IsInitialized()) { opSize = disassemblyInfo.GetOpSize(); } else if((isData && disData) || (!isData && !isCode && disUnident)) { disassemblyInfo.Initialize(i, 0, cpuType, relAddress.Type, _memoryDumper); opSize = disassemblyInfo.GetOpSize(); } if(opSize > 0) { pushEndBlock(); if(cdl && cdl->IsSubEntryPoint(addrInfo.Address)) { results.push_back(DisassemblyResult(addrInfo, i, LineFlags::SubStart | LineFlags::BlockStart | LineFlags::VerifiedCode | LineFlags::Empty)); } if(_labelManager->GetLabelAndComment(addrInfo, labelInfo)) { bool hasMultipleComment = labelInfo.Comment.find_first_of('\n') != string::npos; if(hasMultipleComment) { int16_t lineCount = 0; for(char c : labelInfo.Comment) { if(c == '\n') { results.push_back(DisassemblyResult(addrInfo, i, LineFlags::Comment, lineCount)); lineCount++; } } results.push_back(DisassemblyResult(addrInfo, i, LineFlags::Comment, lineCount)); } if(labelInfo.Label.size()) { results.push_back(DisassemblyResult(addrInfo, i, LineFlags::Label)); } if(!hasMultipleComment && labelInfo.Comment.size()) { results.push_back(DisassemblyResult(addrInfo, i, LineFlags::Comment)); } else { results.push_back(DisassemblyResult(addrInfo, i)); } } else if(showJumpLabels && cdl && (cdl->IsJumpTarget(addrInfo.Address) || cdl->IsSubEntryPoint(addrInfo.Address))) { results.push_back(DisassemblyResult(addrInfo, i, LineFlags::Label)); results.push_back(DisassemblyResult(addrInfo, i)); } else { results.push_back(DisassemblyResult(addrInfo, i)); } if(disassemblyInfo.IsReturnInstruction()) { //End of function results.push_back(DisassemblyResult(i, LineFlags::VerifiedCode | LineFlags::BlockEnd | LineFlags::Empty)); } //Move to the end of the instruction (but realign disassembly if another valid instruction is found) //This can sometimes happen if the 2nd byte of BRK/COP is reused as the first byte of the next instruction //Also required when disassembling unvalidated data as code (to realign once we find verified code) MemoryType prevMemType = addrInfo.Type; for(int j = 1; j < opSize && i + j < bankEnd; j++) { relAddress.Address = i + 1; addrInfo = _console->GetAbsoluteAddress(relAddress); if(addrInfo.Type != prevMemType || src.Cache[addrInfo.Address].IsInitialized()) { break; } i++; } } else { if(showData || showUnident) { if((isData && inUnknownBlock) || (!isData && inVerifiedBlock)) { pushEndBlock(); } } if(byteCounter > 0) { //If showing as hex data, add a new data line every 8 bytes byteCounter--; if(byteCounter == 0) { results[results.size() - 1].SetByteCount(bytesPerRow); results.push_back(DisassemblyResult(addrInfo, i, LineFlags::ShowAsData | (isData ? LineFlags::VerifiedData : 0), bytesPerRow)); byteCounter = bytesPerRow; } } else if(!inUnknownBlock && !inVerifiedBlock) { //If not in a block, start a new block based on the current byte's type (data vs unidentified) bool showAsData = (isData && showData) || ((!isData && !isCode) && showUnident); if(isData) { inVerifiedBlock = true; results.push_back(DisassemblyResult(addrInfo, i, LineFlags::BlockStart | LineFlags::VerifiedData | (showAsData ? LineFlags::ShowAsData : 0))); } else { inUnknownBlock = true; results.push_back(DisassemblyResult(addrInfo, i, LineFlags::BlockStart | (showAsData ? LineFlags::ShowAsData : 0))); } if(showAsData) { //If showing data as hex, add the first row of the block results.push_back(DisassemblyResult(addrInfo, i, LineFlags::ShowAsData | (isData ? LineFlags::VerifiedData : 0))); byteCounter = bytesPerRow; } else { //If not showing the data at all, display 1 empty line results.push_back(DisassemblyResult(addrInfo, i, LineFlags::Empty | (isData ? LineFlags::VerifiedData : 0))); } } prevAddrInfo = addrInfo; } } relAddress.Address = bankEnd; pushEndBlock(); if(inUnmappedBlock) { pushUnmappedBlock(); } return results; } void Disassembler::GetLineData(DisassemblyResult& row, CpuType type, MemoryType memType, CodeLineData& data) { data.Address = row.CpuAddress; data.AbsoluteAddress = row.Address; data.EffectiveAddress = {}; data.Value = 0; data.Flags = row.Flags; data.LineCpuType = type; switch(row.Address.Type) { default: break; case MemoryType::GbPrgRom: case MemoryType::SnesPrgRom: case MemoryType::NesPrgRom: case MemoryType::PcePrgRom: data.Flags |= (uint8_t)LineFlags::PrgRom; break; case MemoryType::GbWorkRam: case MemoryType::SnesWorkRam: case MemoryType::NesWorkRam: case MemoryType::PceWorkRam: data.Flags |= (uint8_t)LineFlags::WorkRam; break; case MemoryType::GbCartRam: case MemoryType::SnesSaveRam: case MemoryType::NesSaveRam: case MemoryType::PceSaveRam: data.Flags |= (uint8_t)LineFlags::SaveRam; break; } bool isBlockStartEnd = (data.Flags & (LineFlags::BlockStart | LineFlags::BlockEnd | LineFlags::Empty)) != 0; if(!isBlockStartEnd && row.Address.Address >= 0) { if((data.Flags & LineFlags::ShowAsData)) { FastString str(".db", 3); for(int i = 0; i < row.GetByteCount(); i++) { str.Write(" $", 2); str.Write(HexUtilities::ToHexChar(_memoryDumper->GetMemoryValue(memType, row.CpuAddress + i)), 2); } data.OpSize = row.GetByteCount(); memcpy(data.Text, str.ToString(), str.GetSize() + 1); } else if((data.Flags & LineFlags::Comment) && row.CommentLine >= 0) { string comment = ";" + StringUtilities::Split(_labelManager->GetComment(row.Address), '\n')[row.CommentLine]; data.Flags |= LineFlags::VerifiedCode; memcpy(data.Comment, comment.c_str(), std::min((int)comment.size() + 1, 1000)); } else if(data.Flags & LineFlags::Label) { string label = _labelManager->GetLabel(row.Address); if(label.empty()) { //Use address as the label label = "$" + DebugUtilities::AddressToHex(type, row.CpuAddress); if(_settings->GetDebugConfig().UseLowerCaseDisassembly) { std::transform(label.begin(), label.end(), label.begin(), ::tolower); } } label += ":"; data.Flags |= LineFlags::VerifiedCode; memcpy(data.Text, label.c_str(), std::min((int)label.size() + 1, 1000)); } else { DisassemblerSource& src = GetSource(row.Address.Type); DisassemblyInfo disInfo = src.Cache[row.Address.Address]; //Always use Sa1 as the cpu type when disassembling Sa1 address space CpuType lineCpuType = type != CpuType::Sa1 && disInfo.IsInitialized() ? disInfo.GetCpuType() : type; data.LineCpuType = lineCpuType; CdlManager* cdlManager = _debugger->GetCdlManager(); switch(lineCpuType) { case CpuType::Snes: case CpuType::Sa1: { SnesCpuState state = (SnesCpuState&)_debugger->GetCpuStateRef(lineCpuType); state.PC = (uint16_t)row.CpuAddress; state.K = (row.CpuAddress >> 16); CodeDataLogger* cdl = cdlManager->GetCodeDataLogger(row.Address.Type); if(!disInfo.IsInitialized()) { disInfo = DisassemblyInfo(row.Address.Address, state.PS, lineCpuType, row.Address.Type, _memoryDumper); } else { data.Flags |= (!cdl || cdl->IsCode(data.AbsoluteAddress.Address)) ? LineFlags::VerifiedCode : LineFlags::UnexecutedCode; } data.OpSize = disInfo.GetOpSize(); data.EffectiveAddress = disInfo.GetEffectiveAddress(_debugger, &state, lineCpuType); if(data.EffectiveAddress.Address >= 0) { data.Value = disInfo.GetMemoryValue(data.EffectiveAddress, _memoryDumper, memType); } break; } case CpuType::Spc: { SpcState state = (SpcState&)_debugger->GetCpuStateRef(lineCpuType); state.PC = (uint16_t)row.CpuAddress; if(!disInfo.IsInitialized()) { disInfo = DisassemblyInfo(row.Address.Address, 0, CpuType::Spc, row.Address.Type, _memoryDumper); } else { data.Flags |= LineFlags::VerifiedCode; } data.OpSize = disInfo.GetOpSize(); data.EffectiveAddress = disInfo.GetEffectiveAddress(_debugger, &state, lineCpuType); if(data.EffectiveAddress.Address >= 0) { data.Value = disInfo.GetMemoryValue(data.EffectiveAddress, _memoryDumper, memType); } break; } case CpuType::Gsu: { GsuState state = (GsuState&)_debugger->GetCpuStateRef(lineCpuType); state.R[15] = (uint16_t)row.CpuAddress; state.ProgramBank = (row.CpuAddress >> 16); if(!disInfo.IsInitialized()) { disInfo = DisassemblyInfo(row.Address.Address, 0, CpuType::Gsu, row.Address.Type, _memoryDumper); } else { data.Flags |= LineFlags::VerifiedCode; } data.OpSize = disInfo.GetOpSize(); data.EffectiveAddress = disInfo.GetEffectiveAddress(_debugger, &state, lineCpuType); if(data.EffectiveAddress.Address >= 0) { data.Value = disInfo.GetMemoryValue(data.EffectiveAddress, _memoryDumper, memType); } break; } case CpuType::NecDsp: if(!disInfo.IsInitialized()) { disInfo = DisassemblyInfo(row.Address.Address, 0, type, row.Address.Type, _memoryDumper); } else { data.Flags |= LineFlags::VerifiedCode; } data.OpSize = disInfo.GetOpSize(); break; case CpuType::Cx4: { Cx4State state = (Cx4State&)_debugger->GetCpuStateRef(lineCpuType); if(!disInfo.IsInitialized()) { disInfo = DisassemblyInfo(row.Address.Address, 0, type, row.Address.Type, _memoryDumper); } else { data.Flags |= LineFlags::VerifiedCode; } data.OpSize = disInfo.GetOpSize(); data.EffectiveAddress = disInfo.GetEffectiveAddress(_debugger, &state, lineCpuType); if(data.EffectiveAddress.Address >= 0) { data.Value = disInfo.GetMemoryValue(data.EffectiveAddress, _memoryDumper, memType); } break; } case CpuType::Gameboy: { GbCpuState state = (GbCpuState&)_debugger->GetCpuStateRef(lineCpuType); state.PC = (uint16_t)row.CpuAddress; CodeDataLogger* cdl = cdlManager->GetCodeDataLogger(row.Address.Type); if(!disInfo.IsInitialized()) { disInfo = DisassemblyInfo(row.Address.Address, 0, CpuType::Gameboy, row.Address.Type, _memoryDumper); } else { data.Flags |= (!cdl || cdl->IsCode(data.AbsoluteAddress.Address)) ? LineFlags::VerifiedCode : LineFlags::UnexecutedCode; } data.OpSize = disInfo.GetOpSize(); data.EffectiveAddress = disInfo.GetEffectiveAddress(_debugger, &state, lineCpuType); if(data.EffectiveAddress.Address >= 0) { data.Value = disInfo.GetMemoryValue(data.EffectiveAddress, _memoryDumper, memType); } break; } case CpuType::Nes: { NesCpuState state = (NesCpuState&)_debugger->GetCpuStateRef(lineCpuType); state.PC = (uint16_t)row.CpuAddress; CodeDataLogger* cdl = cdlManager->GetCodeDataLogger(row.Address.Type); if(!disInfo.IsInitialized()) { disInfo = DisassemblyInfo(row.Address.Address, 0, CpuType::Nes, row.Address.Type, _memoryDumper); } else { data.Flags |= (!cdl || cdl->IsCode(data.AbsoluteAddress.Address)) ? LineFlags::VerifiedCode : LineFlags::UnexecutedCode; } data.OpSize = disInfo.GetOpSize(); data.EffectiveAddress = disInfo.GetEffectiveAddress(_debugger, &state, lineCpuType); if(data.EffectiveAddress.Address >= 0) { data.Value = disInfo.GetMemoryValue(data.EffectiveAddress, _memoryDumper, memType); } break; } case CpuType::Pce: { PceCpuState state = (PceCpuState&)_debugger->GetCpuStateRef(lineCpuType); state.PC = (uint16_t)row.CpuAddress; CodeDataLogger* cdl = cdlManager->GetCodeDataLogger(row.Address.Type); if(!disInfo.IsInitialized()) { disInfo = DisassemblyInfo(row.Address.Address, 0, CpuType::Pce, row.Address.Type, _memoryDumper); } else { data.Flags |= (!cdl || cdl->IsCode(data.AbsoluteAddress.Address)) ? LineFlags::VerifiedCode : LineFlags::UnexecutedCode; } data.OpSize = disInfo.GetOpSize(); data.EffectiveAddress = disInfo.GetEffectiveAddress(_debugger, &state, lineCpuType); if(data.EffectiveAddress.Address >= 0) { data.Value = disInfo.GetMemoryValue(data.EffectiveAddress, _memoryDumper, memType); } break; } } string text; disInfo.GetDisassembly(text, row.CpuAddress, _labelManager, _settings); memcpy(data.Text, text.c_str(), std::min((int)text.size() + 1, 1000)); disInfo.GetByteCode(data.ByteCode); if(data.Flags & LineFlags::Comment) { string comment = ";" + _labelManager->GetComment(row.Address); memcpy(data.Comment, comment.c_str(), std::min((int)comment.size() + 1, 1000)); } else { data.Comment[0] = 0; } } } else { if(data.Flags & LineFlags::SubStart) { string label = _labelManager->GetLabel(row.Address); if(label.empty()) { label = "sub start"; } memcpy(data.Text, label.c_str(), label.size() + 1); } else if(data.Flags & LineFlags::BlockStart) { string label = (data.Flags & LineFlags::VerifiedData) ? "data" : "unidentified"; if(data.Flags & LineFlags::UnmappedMemory) { label = "unmapped"; } memcpy(data.Text, label.c_str(), label.size() + 1); } } } int32_t Disassembler::GetMatchingRow(vector& rows, uint32_t address, bool returnFirstRow) { int32_t i; for(i = 0; i < (int32_t)rows.size(); i++) { if(rows[i].CpuAddress == (int32_t)address) { if(i + 1 >= rows.size() || rows[i + 1].CpuAddress != (int32_t)address || address == 0 || returnFirstRow) { //Keep going down until the last instance of the matching address is found //Except for address 0, to ensure scrolling to the very top is allowed break; } } else if(rows[i].CpuAddress > (int32_t)address) { while(i > 0 && (rows[i].CpuAddress > (int32_t)address || rows[i].CpuAddress < 0)) { i--; } break; } } return std::max(0, i); } uint32_t Disassembler::GetDisassemblyOutput(CpuType type, uint32_t address, CodeLineData output[], uint32_t rowCount) { uint16_t bank = address >> 16; Timer timer; vector rows = Disassemble(type, bank); int32_t i = GetMatchingRow(rows, address, true); if(i >= (int32_t)rows.size()) { return 0; } MemoryType memType = DebugUtilities::GetCpuMemoryType(type); uint32_t maxBank = (_memoryDumper->GetMemorySize(memType) - 1) >> 16; int32_t row; for(row = 0; row < (int32_t)rowCount; row++){ if(row + i >= rows.size()) { if(bank < maxBank) { bank++; rows = Disassemble(type, bank); if(rows.size() == 0) { break; } i = -row; } else { break; } } GetLineData(rows[row + i], type, memType, output[row]); } return row; } uint16_t Disassembler::GetMaxBank(CpuType cpuType) { AddressInfo relAddress = {}; relAddress.Type = DebugUtilities::GetCpuMemoryType(cpuType); return (_memoryDumper->GetMemorySize(relAddress.Type) - 1) >> 16; } int32_t Disassembler::GetDisassemblyRowAddress(CpuType cpuType, uint32_t address, int32_t rowOffset) { uint16_t bank = address >> 16; vector rows = Disassemble(cpuType, bank); int32_t len = (int32_t)rows.size(); if(len == 0) { return address; } uint16_t maxBank = GetMaxBank(cpuType); int32_t i = GetMatchingRow(rows, address, false); if(rowOffset > 0) { while(len > 0) { for(; i < len; i++) { if(rowOffset <= 0 && rows[i].CpuAddress >= 0 && rows[i].CpuAddress != (int32_t)address) { return rows[i].CpuAddress; } rowOffset--; } //End of bank, didn't find an appropriate row to jump to, try the next bank if(bank == maxBank) { //Reached bottom of last bank, return the bottom row return rows[len - 1].CpuAddress >= 0 ? rows[len - 1].CpuAddress : address; } bank++; rows = Disassemble(cpuType, bank); len = (int32_t)rows.size(); i = 0; } } else if(rowOffset < 0) { while(len > 0) { for(; i >= 0; i--) { if(rowOffset >= 0 && rows[i].CpuAddress >= 0 && rows[i].CpuAddress != (int32_t)address) { return rows[i].CpuAddress; } rowOffset++; } //Start of bank, didn't find an appropriate row to jump to, try the previous bank if(bank == 0) { //Reached top of first bank, return the top row return rows[0].CpuAddress >= 0 ? rows[0].CpuAddress : address; } bank--; rows = Disassemble(cpuType, bank); len = (int32_t)rows.size(); i = len - 1; } } return address; }