#include "stdafx.h" #include #include "Disassembler.h" #include "DisassemblyInfo.h" #include "Cpu.h" #include "Spc.h" #include "NecDsp.h" #include "Sa1.h" #include "Gsu.h" #include "Cx4.h" #include "BsxCart.h" #include "BsxMemoryPack.h" #include "Gameboy.h" #include "GbCpu.h" #include "Debugger.h" #include "MemoryManager.h" #include "LabelManager.h" #include "CpuTypes.h" #include "MemoryDumper.h" #include "Console.h" #include "CodeDataLogger.h" #include "DebugBreakHelper.h" #include "BaseCartridge.h" #include "EmuSettings.h" #include "DebugUtilities.h" #include "../Utilities/FastString.h" #include "../Utilities/HexUtilities.h" #include "../Utilities/StringUtilities.h" Disassembler::Disassembler(shared_ptr console, shared_ptr cdl, Debugger* debugger) { shared_ptr cart = console->GetCartridge(); _cdl = cdl; _debugger = debugger; _labelManager = debugger->GetLabelManager(); _console = console.get(); _cpu = console->GetCpu().get(); _spc = console->GetSpc().get(); _gsu = cart->GetGsu(); _sa1 = cart->GetSa1(); _cx4 = cart->GetCx4(); _necDsp = cart->GetDsp(); _gameboy = cart->GetGameboy(); _settings = console->GetSettings().get(); _memoryDumper = _debugger->GetMemoryDumper().get(); _memoryManager = console->GetMemoryManager().get(); for (int i = 0; i < (int)DebugUtilities::GetLastCpuType(); i++) { _disassemblyResult[i] = vector(); _needDisassemble[i] = true; } for (int i = (int)SnesMemoryType::PrgRom; i < (int)SnesMemoryType::Register; i++) { InitSource((SnesMemoryType)i); } if (_necDsp) { //Build cache for the entire DSP chip (since it only contains instructions) AddressInfo dspStart = {0, SnesMemoryType::DspProgramRom}; BuildCache(dspStart, 0, CpuType::NecDsp); } } void Disassembler::InitSource(SnesMemoryType type) { uint8_t* src = _memoryDumper->GetMemoryBuffer(type); uint32_t size = _memoryDumper->GetMemorySize(type); _disassemblyCache[(int)type] = vector(size); _sources[(int)type] = {src, &_disassemblyCache[(int)type], size}; } DisassemblerSource& Disassembler::GetSource(SnesMemoryType type) { if (_sources[(int)type].Data == nullptr) { throw std::runtime_error("Disassembler::GetSource() invalid memory type"); } return _sources[(int)type]; } uint32_t Disassembler::BuildCache(AddressInfo& addrInfo, uint8_t cpuFlags, CpuType type) { DisassemblerSource& src = GetSource(addrInfo.Type); bool needDisassemble = false; int returnSize = 0; int32_t address = addrInfo.Address; while (address >= 0 && address < (int32_t)src.Cache->size()) { DisassemblyInfo& disInfo = (*src.Cache)[address]; if (!disInfo.IsInitialized() || !disInfo.IsValid(cpuFlags)) { disInfo.Initialize(src.Data + address, cpuFlags, type); for (int i = 1; i < disInfo.GetOpSize(); 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(); } needDisassemble = true; returnSize += disInfo.GetOpSize(); } else { returnSize += disInfo.GetOpSize(); break; } if (disInfo.IsUnconditionalJump()) { //Can't assume what follows is code, stop disassembling break; } disInfo.UpdateCpuFlags(cpuFlags); address += disInfo.GetOpSize(); } if (needDisassemble) { SetDisassembleFlag(type); } return returnSize; } void Disassembler::SetDisassembleFlag(CpuType type) { if (type == CpuType::Cpu || type == CpuType::Sa1 || type == CpuType::Gsu || type == CpuType::Cx4) { _needDisassemble[(int)CpuType::Cpu] = true; _needDisassemble[(int)CpuType::Sa1] = true; _needDisassemble[(int)CpuType::Gsu] = true; _needDisassemble[(int)CpuType::Cx4] = true; } else { _needDisassemble[(int)type] = true; } } void Disassembler::ResetPrgCache() { InitSource(SnesMemoryType::PrgRom); InitSource(SnesMemoryType::GbPrgRom); _needDisassemble[(int)CpuType::Cpu] = true; _needDisassemble[(int)CpuType::Sa1] = true; _needDisassemble[(int)CpuType::Gsu] = true; _needDisassemble[(int)CpuType::Cx4] = true; _needDisassemble[(int)CpuType::Gameboy] = true; } void Disassembler::InvalidateCache(AddressInfo addrInfo, CpuType type) { DisassemblerSource src = GetSource(addrInfo.Type); bool needDisassemble = false; if (addrInfo.Address >= 0) { for (int i = 0; i < 4; i++) { if (addrInfo.Address >= i) { if ((*src.Cache)[addrInfo.Address - i].IsInitialized()) { (*src.Cache)[addrInfo.Address - i].Reset(); needDisassemble = true; } } } } if (needDisassemble) { SetDisassembleFlag(type); } } void Disassembler::Disassemble(CpuType cpuType) { if (!_needDisassemble[(int)cpuType]) { return; } _needDisassemble[(int)cpuType] = false; auto lock = _disassemblyLock.AcquireSafe(); MemoryMappings* mappings = nullptr; int32_t maxAddr = 0xFFFFFF; switch (cpuType) { case CpuType::Cpu: mappings = _memoryManager->GetMemoryMappings(); break; case CpuType::Sa1: if (!_sa1) { return; } mappings = _sa1->GetMemoryMappings(); break; case CpuType::Gsu: if (!_gsu) { return; } mappings = _gsu->GetMemoryMappings(); break; case CpuType::NecDsp: if (!_console->GetCartridge()->GetDsp()) { return; } mappings = nullptr; maxAddr = _necDsp->DebugGetProgramRomSize() - 1; break; case CpuType::Spc: mappings = nullptr; maxAddr = 0xFFFF; break; case CpuType::Gameboy: if (!_gameboy) { return; } mappings = nullptr; maxAddr = 0xFFFF; break; case CpuType::Cx4: if (!_console->GetCartridge()->GetCx4()) { return; } mappings = _console->GetCartridge()->GetCx4()->GetMemoryMappings(); break; default: throw std::runtime_error("Disassemble(): Invalid cpu type"); } vector& results = _disassemblyResult[(int)cpuType]; results.clear(); bool disUnident = _settings->CheckDebuggerFlag(DebuggerFlags::DisassembleUnidentifiedData); bool disData = _settings->CheckDebuggerFlag(DebuggerFlags::DisassembleVerifiedData); bool showUnident = _settings->CheckDebuggerFlag(DebuggerFlags::ShowUnidentifiedData); bool showData = _settings->CheckDebuggerFlag(DebuggerFlags::ShowVerifiedData); bool inUnknownBlock = false; bool inVerifiedBlock = false; LabelInfo labelInfo; AddressInfo addrInfo = {}; AddressInfo prevAddrInfo = {}; int byteCounter = 0; for (int32_t i = 0; i <= maxAddr; i++) { prevAddrInfo = addrInfo; switch (cpuType) { case CpuType::Spc: addrInfo = _spc->GetAbsoluteAddress(i); break; case CpuType::NecDsp: addrInfo = {i, SnesMemoryType::DspProgramRom}; break; case CpuType::Gameboy: addrInfo = _gameboy->GetAbsoluteAddress(i); break; default: addrInfo = mappings->GetAbsoluteAddress(i); break; } if (addrInfo.Address < 0) { continue; } DisassemblerSource src = GetSource(addrInfo.Type); DisassemblyInfo disassemblyInfo = (*src.Cache)[addrInfo.Address]; uint8_t opSize = 0; uint8_t opCode = (src.Data + addrInfo.Address)[0]; bool isCode = addrInfo.Type == SnesMemoryType::PrgRom ? _cdl->IsCode(addrInfo.Address) : false; bool isData = addrInfo.Type == SnesMemoryType::PrgRom ? _cdl->IsData(addrInfo.Address) : false; if (disassemblyInfo.IsInitialized()) { opSize = disassemblyInfo.GetOpSize(); } else if ((isData && disData) || (!isData && !isCode && disUnident)) { opSize = DisassemblyInfo::GetOpSize(opCode, 0, cpuType); } if (opSize > 0) { if (inUnknownBlock || inVerifiedBlock) { int flags = LineFlags::BlockEnd | (inVerifiedBlock ? LineFlags::VerifiedData : 0) | ( ((inVerifiedBlock && showData) || (inUnknownBlock && showUnident)) ? LineFlags::ShowAsData : 0); results.push_back(DisassemblyResult(prevAddrInfo, i - 1, flags)); inUnknownBlock = false; inVerifiedBlock = false; } byteCounter = 0; if (addrInfo.Type == SnesMemoryType::PrgRom && _cdl->IsSubEntryPoint(addrInfo.Address)) { results.push_back(DisassemblyResult(addrInfo, i, LineFlags::SubStart | LineFlags::BlockStart | LineFlags::VerifiedCode)); } 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 { results.push_back(DisassemblyResult(addrInfo, i)); } //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) for (int j = 1, max = (int)(*src.Cache).size(); j < opSize && addrInfo.Address + j < max; j++) { if ((*src.Cache)[addrInfo.Address + j].IsInitialized()) { break; } i++; } if (DisassemblyInfo::IsReturnInstruction(opCode, cpuType)) { //End of function results.push_back(DisassemblyResult(-1, LineFlags::VerifiedCode | LineFlags::BlockEnd)); } } else { if (showData || showUnident) { if ((isData && inUnknownBlock) || (!isData && inVerifiedBlock)) { if (isData && inUnknownBlock) { //In an unknown block and the next byte is data, end the block results.push_back(DisassemblyResult(prevAddrInfo, i - 1, LineFlags::BlockEnd | (showUnident ? LineFlags::ShowAsData : 0))); } else if (!isData && inVerifiedBlock) { //In a verified data block and the next byte is unknown, end the block results.push_back(DisassemblyResult(prevAddrInfo, i - 1, LineFlags::BlockEnd | LineFlags::VerifiedData | (showData ? LineFlags::ShowAsData : 0))); } inUnknownBlock = false; inVerifiedBlock = false; byteCounter = 0; } } if (byteCounter > 0) { //If showing as hex data, add a new data line every 8 bytes byteCounter--; if (byteCounter == 0) { results.push_back(DisassemblyResult(addrInfo, i, LineFlags::ShowAsData | (isData ? LineFlags::VerifiedData : 0))); byteCounter = 8; } } 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 = 8; } else { //If not showing the data at all, display 1 empty line results.push_back(DisassemblyResult(-1, LineFlags::None | (isData ? LineFlags::VerifiedData : 0))); } } } } if (inUnknownBlock || inVerifiedBlock) { int flags = LineFlags::BlockEnd | (inVerifiedBlock ? LineFlags::VerifiedData : 0) | ( ((inVerifiedBlock && showData) || (inUnknownBlock && showUnident)) ? LineFlags::ShowAsData : 0); results.push_back(DisassemblyResult(addrInfo, maxAddr, flags)); } } DisassemblyInfo Disassembler::GetDisassemblyInfo(AddressInfo& info, uint32_t cpuAddress, uint8_t cpuFlags, CpuType type) { DisassemblyInfo disassemblyInfo = (*GetSource(info.Type).Cache)[info.Address]; if (!disassemblyInfo.IsInitialized()) { disassemblyInfo.Initialize(cpuAddress, cpuFlags, type, _memoryDumper); } return disassemblyInfo; } void Disassembler::RefreshDisassembly(CpuType type) { DebugBreakHelper helper(_debugger); _needDisassemble[(int)type] = true; Disassemble(type); } uint32_t Disassembler::GetLineCount(CpuType type) { auto lock = _disassemblyLock.AcquireSafe(); vector& source = _disassemblyResult[(int)type]; return (uint32_t)source.size(); } uint32_t Disassembler::GetLineIndex(CpuType type, uint32_t cpuAddress) { auto lock = _disassemblyLock.AcquireSafe(); vector& source = _disassemblyResult[(int)type]; uint32_t lastAddress = 0; for (size_t i = 1; i < source.size(); i++) { if (source[i].CpuAddress < 0 || (source[i].Flags & LineFlags::SubStart) | (source[i].Flags & LineFlags::Label) || ((source[i].Flags & LineFlags::Comment) && source[i].CommentLine >= 0)) { continue; } if (cpuAddress == (uint32_t)source[i].CpuAddress) { return (uint32_t)i; } else if (cpuAddress >= lastAddress && cpuAddress < (uint32_t)source[i].CpuAddress) { return (uint32_t)i - 1; } lastAddress = source[i].CpuAddress; } return 0; } bool Disassembler::GetLineData(CpuType type, uint32_t lineIndex, CodeLineData& data) { auto lock = _disassemblyLock.AcquireSafe(); vector& source = _disassemblyResult[(int)type]; SnesMemoryType memType = DebugUtilities::GetCpuMemoryType(type); int32_t maxAddr = type == CpuType::Spc ? 0xFFFF : 0xFFFFFF; if (lineIndex < source.size()) { DisassemblyResult result = source[lineIndex]; data.Address = -1; data.AbsoluteAddress = -1; data.EffectiveAddress = -1; data.Flags = result.Flags; switch (result.Address.Type) { default: break; case SnesMemoryType::GbPrgRom: case SnesMemoryType::PrgRom: data.Flags |= (uint8_t)LineFlags::PrgRom; break; case SnesMemoryType::GbWorkRam: case SnesMemoryType::WorkRam: data.Flags |= (uint8_t)LineFlags::WorkRam; break; case SnesMemoryType::GbCartRam: case SnesMemoryType::SaveRam: data.Flags |= (uint8_t)LineFlags::SaveRam; break; } bool isBlockStartEnd = (data.Flags & (LineFlags::BlockStart | LineFlags::BlockEnd)) != 0; if (!isBlockStartEnd && result.Address.Address >= 0) { if ((data.Flags & LineFlags::ShowAsData)) { FastString str(".db", 3); int nextAddr = lineIndex < source.size() - 2 ? (source[lineIndex + 1].CpuAddress + 1) : (maxAddr + 1); for (int i = 0; i < 8 && result.CpuAddress + i < nextAddr; i++) { str.Write(" $", 2); str.Write(HexUtilities::ToHexChar(_memoryDumper->GetMemoryValue(memType, result.CpuAddress + i)), 2); } data.Address = result.CpuAddress; data.AbsoluteAddress = result.Address.Address; memcpy(data.Text, str.ToString(), str.GetSize()); } else if ((data.Flags & LineFlags::Comment) && result.CommentLine >= 0) { string comment = ";" + StringUtilities::Split(_labelManager->GetComment(result.Address), '\n')[result. CommentLine]; data.Flags |= LineFlags::VerifiedCode; memcpy(data.Comment, comment.c_str(), std::min((int)comment.size(), 1000)); } else if (data.Flags & LineFlags::Label) { string label = _labelManager->GetLabel(result.Address) + ":"; data.Flags |= LineFlags::VerifiedCode; memcpy(data.Text, label.c_str(), std::min((int)label.size(), 1000)); } else { DisassemblerSource src = GetSource(result.Address.Type); DisassemblyInfo disInfo = (*src.Cache)[result.Address.Address]; CpuType lineCpuType = disInfo.IsInitialized() ? disInfo.GetCpuType() : type; data.Address = result.CpuAddress; data.AbsoluteAddress = result.Address.Address; switch (lineCpuType) { case CpuType::Cpu: case CpuType::Sa1: { CpuState state = type == CpuType::Sa1 ? _sa1->GetCpuState() : _cpu->GetState(); state.PC = (uint16_t)result.CpuAddress; state.K = (result.CpuAddress >> 16); if (!disInfo.IsInitialized()) { disInfo = DisassemblyInfo(src.Data + result.Address.Address, state.PS, lineCpuType); } else { data.Flags |= (result.Address.Type != SnesMemoryType::PrgRom || _cdl->IsCode(data.AbsoluteAddress)) ? LineFlags::VerifiedCode : LineFlags::UnexecutedCode; } data.OpSize = disInfo.GetOpSize(); data.EffectiveAddress = disInfo.GetEffectiveAddress(_console, &state, lineCpuType); if (data.EffectiveAddress >= 0) { data.Value = disInfo.GetMemoryValue(data.EffectiveAddress, _memoryDumper, memType, data.ValueSize); } else { data.ValueSize = 0; } break; } case CpuType::Spc: { SpcState state = _spc->GetState(); state.PC = (uint16_t)result.CpuAddress; if (!disInfo.IsInitialized()) { disInfo = DisassemblyInfo(src.Data + result.Address.Address, 0, CpuType::Spc); } else { data.Flags |= LineFlags::VerifiedCode; } data.OpSize = disInfo.GetOpSize(); data.EffectiveAddress = disInfo.GetEffectiveAddress(_console, &state, lineCpuType); if (data.EffectiveAddress >= 0) { data.Value = disInfo.GetMemoryValue(data.EffectiveAddress, _memoryDumper, memType, data.ValueSize); data.ValueSize = 1; } else { data.ValueSize = 0; } break; } case CpuType::Gsu: { GsuState state = _gsu->GetState(); if (!disInfo.IsInitialized()) { disInfo = DisassemblyInfo(src.Data + result.Address.Address, 0, CpuType::Gsu); } else { data.Flags |= LineFlags::VerifiedCode; } data.OpSize = disInfo.GetOpSize(); data.EffectiveAddress = disInfo.GetEffectiveAddress(_console, &state, lineCpuType); if (data.EffectiveAddress >= 0) { data.Value = disInfo.GetMemoryValue(data.EffectiveAddress, _memoryDumper, memType, data.ValueSize); data.ValueSize = 2; } else { data.ValueSize = 0; } break; } case CpuType::NecDsp: case CpuType::Cx4: if (!disInfo.IsInitialized()) { disInfo = DisassemblyInfo(src.Data + result.Address.Address, 0, type); } else { data.Flags |= LineFlags::VerifiedCode; } data.OpSize = disInfo.GetOpSize(); data.EffectiveAddress = -1; data.ValueSize = 0; break; case CpuType::Gameboy: { GbCpuState state = _gameboy->GetCpu()->GetState(); if (!disInfo.IsInitialized()) { disInfo = DisassemblyInfo(src.Data + result.Address.Address, 0, CpuType::Gameboy); } else { data.Flags |= LineFlags::VerifiedCode; } data.OpSize = disInfo.GetOpSize(); data.EffectiveAddress = disInfo.GetEffectiveAddress(_console, &state, lineCpuType); data.ValueSize = 0; break; } } string text; disInfo.GetDisassembly(text, result.CpuAddress, _labelManager.get(), _settings); memcpy(data.Text, text.c_str(), std::min((int)text.size(), 1000)); disInfo.GetByteCode(data.ByteCode); if (data.Flags & LineFlags::Comment) { string comment = ";" + _labelManager->GetComment(result.Address); memcpy(data.Comment, comment.c_str(), std::min((int)comment.size(), 1000)); } else { data.Comment[0] = 0; } } } else { if (data.Flags & LineFlags::SubStart) { string label = _labelManager->GetLabel(result.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"; memcpy(data.Text, label.c_str(), label.size() + 1); } if (data.Flags & (LineFlags::BlockStart | LineFlags::BlockEnd)) { if (!(data.Flags & (LineFlags::ShowAsData | LineFlags::SubStart))) { //For hidden blocks, give the start/end lines an address data.Address = result.CpuAddress; data.AbsoluteAddress = result.Address.Address; } } } return true; } return false; } int32_t Disassembler::SearchDisassembly(CpuType type, const char* searchString, int32_t startPosition, int32_t endPosition, bool searchBackwards) { auto lock = _disassemblyLock.AcquireSafe(); vector& source = _disassemblyResult[(int)type]; int step = searchBackwards ? -1 : 1; CodeLineData lineData = {}; for (int i = startPosition; i != endPosition; i += step) { GetLineData(type, i, lineData); string line = lineData.Text; std::transform(line.begin(), line.end(), line.begin(), ::tolower); if (line.find(searchString) != string::npos) { return i; } //Continue search from start/end of document if (!searchBackwards && i == (int)(source.size() - 1)) { i = 0; } else if (searchBackwards && i == 0) { i = (int32_t)(source.size() - 1); } } return -1; }