From 2c9d6aabd7ec9c414bc642c11ef6642974571cdf Mon Sep 17 00:00:00 2001 From: Sour Date: Wed, 2 Jan 2019 22:40:21 -0500 Subject: [PATCH] Debugger: Improved CA/CC65 integration -Auto-detect all function entry points -Auto-detect all jump targets -Fixed a bug where jump targets were being disassembled when loading a CDL file, potentially causing data to be shown as code -Fixed some valid data block being shown as unidentified data --- Core/CodeDataLogger.cpp | 5 + Core/CodeDataLogger.h | 10 +- Core/CodeRunner.cpp | 2 +- Core/Debugger.cpp | 11 +- Core/Debugger.h | 4 - Core/Disassembler.cpp | 53 +++---- Core/Disassembler.h | 8 +- Core/DisassemblyInfo.cpp | 33 ++-- Core/DisassemblyInfo.h | 9 +- GUI.NET/Debugger/DbgImporter.cs | 248 +++++++++++++++++++++++-------- GUI.NET/Debugger/LabelManager.cs | 6 +- GUI.NET/Debugger/frmGoToAll.cs | 6 +- GUI.NET/InteropEmu.cs | 30 ++-- InteropDLL/DebugWrapper.cpp | 2 - 14 files changed, 277 insertions(+), 150 deletions(-) diff --git a/Core/CodeDataLogger.cpp b/Core/CodeDataLogger.cpp index bf43a637..19d823c1 100644 --- a/Core/CodeDataLogger.cpp +++ b/Core/CodeDataLogger.cpp @@ -151,6 +151,11 @@ bool CodeDataLogger::IsCode(uint32_t absoluteAddr) return (_cdlData[absoluteAddr] & (uint8_t)CdlPrgFlags::Code) == (uint8_t)CdlPrgFlags::Code; } +bool CodeDataLogger::IsJumpTarget(uint32_t absoluteAddr) +{ + return (_cdlData[absoluteAddr] & (uint8_t)CdlPrgFlags::JumpTarget) == (uint8_t)CdlPrgFlags::JumpTarget; +} + bool CodeDataLogger::IsSubEntryPoint(uint32_t absoluteAddr) { return (_cdlData[absoluteAddr] & (uint8_t)CdlPrgFlags::SubEntryPoint) == (uint8_t)CdlPrgFlags::SubEntryPoint; diff --git a/Core/CodeDataLogger.h b/Core/CodeDataLogger.h index 22bdef1f..3a317951 100644 --- a/Core/CodeDataLogger.h +++ b/Core/CodeDataLogger.h @@ -11,9 +11,16 @@ enum class CdlPrgFlags None = 0x00, Code = 0x01, Data = 0x02, - IndirectCode = 0x10, + + //Bit 0x10 is used for "indirectly accessed as code" in FCEUX + //Repurposed to mean the address is the target of a jump instruction + JumpTarget = 0x10, + IndirectData = 0x20, PcmData = 0x40, + + //Unused bit in original CDL spec + //Used to denote that the byte is the start of function (sub) SubEntryPoint = 0x80 }; @@ -74,6 +81,7 @@ public: CdlRatios GetRatios(); bool IsCode(uint32_t absoluteAddr); + bool IsJumpTarget(uint32_t absoluteAddr); bool IsSubEntryPoint(uint32_t absoluteAddr); bool IsData(uint32_t absoluteAddr); bool IsRead(uint32_t absoluteAddr); diff --git a/Core/CodeRunner.cpp b/Core/CodeRunner.cpp index efecf71b..d4ae7cb3 100644 --- a/Core/CodeRunner.cpp +++ b/Core/CodeRunner.cpp @@ -44,5 +44,5 @@ void CodeRunner::WriteRAM(uint16_t addr, uint8_t value) DisassemblyInfo CodeRunner::GetDisassemblyInfo(uint16_t cpuAddress) { - return DisassemblyInfo(_byteCode.data() + cpuAddress - CodeRunner::BaseAddress, false, false); + return DisassemblyInfo(_byteCode.data() + cpuAddress - CodeRunner::BaseAddress, false); } \ No newline at end of file diff --git a/Core/Debugger.cpp b/Core/Debugger.cpp index acaa5492..21b3f6a8 100644 --- a/Core/Debugger.cpp +++ b/Core/Debugger.cpp @@ -765,14 +765,18 @@ bool Debugger::ProcessRamOperation(MemoryOperationType type, uint16_t &addr, uin bool isSubEntryPoint = _lastInstruction == 0x20; //Previous instruction was a JSR if(absoluteAddr >= 0) { + bool isJumpTarget = _disassembler->IsJump(_lastInstruction); _codeDataLogger->SetFlag(absoluteAddr, CdlPrgFlags::Code); if(isSubEntryPoint) { _codeDataLogger->SetFlag(absoluteAddr, CdlPrgFlags::SubEntryPoint); _functionEntryPoints.emplace(absoluteAddr); + } else if(isJumpTarget) { + //Only mark as jump target if not marked as sub entry point + _codeDataLogger->SetFlag(absoluteAddr, CdlPrgFlags::JumpTarget); } } - _disassembler->BuildCache(addressInfo, addr, isSubEntryPoint, false); + _disassembler->BuildCache(addressInfo, addr, isSubEntryPoint, true); ProcessStepConditions(addr); @@ -1142,11 +1146,6 @@ const char* Debugger::GetCode(uint32_t &length) } } -void Debugger::GetJumpTargets(bool* jumpTargets) -{ - _disassembler->GetJumpTargets(jumpTargets); -} - int32_t Debugger::GetRelativeAddress(uint32_t addr, AddressType type) { switch(type) { diff --git a/Core/Debugger.h b/Core/Debugger.h index e705e103..7f959378 100644 --- a/Core/Debugger.h +++ b/Core/Debugger.h @@ -170,8 +170,6 @@ public: void SetBreakpoints(Breakpoint breakpoints[], uint32_t length); - void ProcessMarkedBreakpoints(BreakpointType type, OperationInfo &operationInfo); - shared_ptr GetLabelManager(); void GetFunctionEntryPoints(int32_t* entryPoints, int32_t maxCount); @@ -222,8 +220,6 @@ public: void GenerateCodeOutput(); const char* GetCode(uint32_t &length); - void GetJumpTargets(bool* jumpTargets); - int32_t GetRelativeAddress(uint32_t addr, AddressType type); int32_t GetAbsoluteAddress(uint32_t addr); int32_t GetAbsoluteChrAddress(uint32_t addr); diff --git a/Core/Disassembler.cpp b/Core/Disassembler.cpp index 121c1b83..686ede16 100644 --- a/Core/Disassembler.cpp +++ b/Core/Disassembler.cpp @@ -146,12 +146,30 @@ bool Disassembler::IsUnofficialOpCode(uint8_t opCode) bool Disassembler::IsJump(uint8_t opCode) { - return opCode == 0x10 || opCode == 0x30|| opCode == 0x50 || opCode == 0x70 || opCode == 0x90 || opCode == 0xB0 || opCode == 0xD0 || opCode == 0xF0 || opCode == 0x4C || opCode == 0x20; + return ( + opCode == 0x10 || //BPL + opCode == 0x30 || //BMI + opCode == 0x50 || //BVC + opCode == 0x70 || //BVS + opCode == 0x90 || //BCC + opCode == 0xB0 || //BCS + opCode == 0xD0 || //BNE + opCode == 0xF0 || //BEQ + opCode == 0x4C || //JMP (Absolute) + opCode == 0x6C || //JMP (Indirect) + opCode == 0x20 //JSR + ); } bool Disassembler::IsUnconditionalJump(uint8_t opCode) { - return opCode == 0x40 || opCode == 0x60 || opCode == 0x6C || opCode == 0x4C || opCode == 0x20; + return ( + opCode == 0x40 || //RTI + opCode == 0x60 || //RTS + opCode == 0x6C || //JMP (Indirect) + opCode == 0x4C || //JMP (Absolute) + opCode == 0x20 //JSR + ); } void Disassembler::GetInfo(AddressTypeInfo &info, uint8_t** source, uint32_t &size, vector> **cache) @@ -188,7 +206,7 @@ void Disassembler::GetInfo(AddressTypeInfo &info, uint8_t** source, uint32_t &si } -uint32_t Disassembler::BuildCache(AddressTypeInfo &info, uint16_t cpuAddress, bool isSubEntryPoint, bool isJumpTarget) +uint32_t Disassembler::BuildCache(AddressTypeInfo &info, uint16_t cpuAddress, bool isSubEntryPoint, bool processJumps) { uint32_t mask = info.Type == AddressType::InternalRam ? 0x7FF : 0xFFFFFFFF; @@ -203,9 +221,8 @@ uint32_t Disassembler::BuildCache(AddressTypeInfo &info, uint16_t cpuAddress, bo if(!disInfo) { while(absoluteAddr < (int32_t)size && !(*cache)[absoluteAddr]) { bool isJump = IsUnconditionalJump(source[absoluteAddr]); - disInfo = new DisassemblyInfo(source+absoluteAddr, isSubEntryPoint, isJumpTarget); + disInfo = new DisassemblyInfo(source+absoluteAddr, isSubEntryPoint); isSubEntryPoint = false; - isJumpTarget = false; (*cache)[absoluteAddr] = shared_ptr(disInfo); @@ -219,22 +236,17 @@ uint32_t Disassembler::BuildCache(AddressTypeInfo &info, uint16_t cpuAddress, bo if(isSubEntryPoint) { disInfo->SetSubEntryPoint(); } - if(isJumpTarget) { - disInfo->SetJumpTarget(); - } uint8_t opCode = source[absoluteAddr]; - if(IsJump(opCode)) { - uint16_t jumpDest = disInfo->GetOpAddr(cpuAddress); + if(processJumps && IsJump(opCode)) { + uint16_t jumpDest = disInfo->GetJumpDestination(cpuAddress, _memoryManager); if(jumpDest != cpuAddress) { AddressTypeInfo addressInfo; _debugger->GetAbsoluteAddressAndType(jumpDest, &addressInfo); - const uint8_t jsrCode = 0x20; + constexpr uint8_t jsrCode = 0x20; if(addressInfo.Address >= 0) { - BuildCache(addressInfo, jumpDest, opCode == jsrCode, true); - } else { - disInfo->SetJumpTarget(); + BuildCache(addressInfo, jumpDest, opCode == jsrCode, false); } } } @@ -539,7 +551,7 @@ string Disassembler::GetCode(AddressTypeInfo &addressInfo, uint32_t endAddr, uin isVerifiedData = addressInfo.Type == AddressType::PrgRom && cdl->IsData(addr&mask); if(!info && ((disassembleUnidentifiedData && !isVerifiedData) || (disassembleVerifiedData && isVerifiedData))) { dataType = isVerifiedData ? DataType::VerifiedData : DataType::UnidentifiedData; - tmpInfo->Initialize(source + (addr & mask), false, false); + tmpInfo->Initialize(source + (addr & mask), false); info = tmpInfo; } else if(info) { dataType = DataType::VerifiedCode; @@ -686,14 +698,3 @@ DisassemblyInfo Disassembler::GetDisassemblyInfo(AddressTypeInfo &info) return DisassemblyInfo(); } } - -void Disassembler::GetJumpTargets(bool* jumpTargets) -{ - memset(jumpTargets, 0, _disassembleCache.size()); - for(size_t i = 0, len = _disassembleCache.size(); i < len; i++) { - shared_ptr disInfo = _disassembleCache[i]; - if(disInfo && disInfo->IsJumpTarget()) { - jumpTargets[i] = true; - } - } -} \ No newline at end of file diff --git a/Core/Disassembler.h b/Core/Disassembler.h index 737c27ab..0544589d 100644 --- a/Core/Disassembler.h +++ b/Core/Disassembler.h @@ -28,8 +28,6 @@ private: vector> _disassembleSaveRamCache; vector> _disassembleMemoryCache; - bool IsJump(uint8_t opCode); - bool IsUnconditionalJump(uint8_t opCode); void GetLine(string &out, string code = "", string comment = string(), int32_t cpuAddress = -1, int32_t absoluteAddress = -1, DataType dataType = DataType::VerifiedCode, char memoryType = ' '); void GetCodeLine(string &out, string &code, string &comment, int32_t cpuAddress, int32_t absoluteAddress, string &byteCode, string &addressing, DataType dataType, bool isIndented, char memoryType = ' '); void GetSubHeader(string &out, DisassemblyInfo *info, string &label, uint16_t relativeAddr, uint16_t resetVector, uint16_t nmiVector, uint16_t irqVector); @@ -43,16 +41,16 @@ public: void BuildOpCodeTables(bool useLowerCase); void Reset(); - uint32_t BuildCache(AddressTypeInfo &info, uint16_t memoryAddr, bool isSubEntryPoint, bool isJumpTarget); + uint32_t BuildCache(AddressTypeInfo &info, uint16_t memoryAddr, bool isSubEntryPoint, bool processJumps); void InvalidateCache(AddressTypeInfo &info); bool IsUnofficialOpCode(uint8_t opCode); + bool IsJump(uint8_t opCode); + bool IsUnconditionalJump(uint8_t opCode); string GetCode(AddressTypeInfo &addressInfo, uint32_t endAddr, uint16_t memoryAddr, State& cpuState, shared_ptr memoryManager, shared_ptr labelManager); DisassemblyInfo GetDisassemblyInfo(AddressTypeInfo &info); - void GetJumpTargets(bool* jumpTargets); - void RebuildPrgRomCache(uint32_t absoluteAddr, int32_t length); }; diff --git a/Core/DisassemblyInfo.cpp b/Core/DisassemblyInfo.cpp index 81f8baa5..61a66c3f 100644 --- a/Core/DisassemblyInfo.cpp +++ b/Core/DisassemblyInfo.cpp @@ -33,9 +33,9 @@ DisassemblyInfo::DisassemblyInfo() { } -DisassemblyInfo::DisassemblyInfo(uint8_t* opPointer, bool isSubEntryPoint, bool isJumpTarget) +DisassemblyInfo::DisassemblyInfo(uint8_t* opPointer, bool isSubEntryPoint) { - Initialize(opPointer, isSubEntryPoint, isJumpTarget); + Initialize(opPointer, isSubEntryPoint); } void DisassemblyInfo::ToString(string &out, uint32_t memoryAddr, MemoryManager* memoryManager, LabelManager* labelManager, bool extendZeroPage) @@ -148,10 +148,9 @@ uint16_t DisassemblyInfo::GetOpAddr(uint16_t memoryAddr) return opAddr; } -void DisassemblyInfo::Initialize(uint8_t* opPointer, bool isSubEntryPoint, bool isJumpTarget) +void DisassemblyInfo::Initialize(uint8_t* opPointer, bool isSubEntryPoint) { _isSubEntryPoint = isSubEntryPoint; - _isJumpTarget = isJumpTarget; uint8_t opCode = *opPointer; _opSize = DisassemblyInfo::OPSize[opCode]; @@ -169,11 +168,6 @@ void DisassemblyInfo::SetSubEntryPoint() _isSubEntryPoint = true; } -void DisassemblyInfo::SetJumpTarget() -{ - _isJumpTarget = true; -} - int32_t DisassemblyInfo::GetMemoryValue(State& cpuState, MemoryManager* memoryManager) { int32_t address = -1; @@ -192,6 +186,22 @@ int32_t DisassemblyInfo::GetMemoryValue(State& cpuState, MemoryManager* memoryMa } } +int32_t DisassemblyInfo::GetJumpDestination(uint16_t pc, MemoryManager* memoryManager) +{ + int32_t address = -1; + if(_opMode == AddrMode::Rel || _opMode == AddrMode::Abs) { + address = GetOpAddr(pc); + } else if(_opMode == AddrMode::Ind) { + address = GetIndirectJumpDestination(memoryManager); + } + + if(address >= 0 && address <= 0xFFFF) { + return memoryManager->DebugRead(address); + } else { + return -1; + } +} + uint16_t DisassemblyInfo::GetIndirectJumpDestination(MemoryManager* memoryManager) { uint16_t addr = _byteCode[1] | (_byteCode[2] << 8); @@ -319,8 +329,3 @@ bool DisassemblyInfo::IsSubExitPoint() { return _isSubExitPoint; } - -bool DisassemblyInfo::IsJumpTarget() -{ - return _isJumpTarget; -} diff --git a/Core/DisassemblyInfo.h b/Core/DisassemblyInfo.h index a6179903..c8ed0c67 100644 --- a/Core/DisassemblyInfo.h +++ b/Core/DisassemblyInfo.h @@ -17,7 +17,6 @@ public: private: uint8_t _byteCode[3]; - bool _isJumpTarget = false; bool _isSubEntryPoint = false; bool _isSubExitPoint = false; uint32_t _opSize = 0; @@ -25,18 +24,17 @@ private: public: DisassemblyInfo(); - DisassemblyInfo(uint8_t* opPointer, bool isSubEntryPoint, bool isJumpTarget); + DisassemblyInfo(uint8_t* opPointer, bool isSubEntryPoint); - void Initialize(uint8_t * opPointer, bool isSubEntryPoint, bool isJumpTarget); + void Initialize(uint8_t * opPointer, bool isSubEntryPoint); void SetSubEntryPoint(); - void SetJumpTarget(); - int32_t GetEffectiveAddress(State& cpuState, MemoryManager* memoryManager); void GetEffectiveAddressString(string &out, State& cpuState, MemoryManager* memoryManager, LabelManager* labelManager); int32_t GetMemoryValue(State& cpuState, MemoryManager* memoryManager); + int32_t GetJumpDestination(uint16_t pc, MemoryManager* memoryManager); uint16_t GetIndirectJumpDestination(MemoryManager* memoryManager); void ToString(string &out, uint32_t memoryAddr, MemoryManager* memoryManager, LabelManager* labelManager, bool extendZeroPage); void GetByteCode(string &out); @@ -45,6 +43,5 @@ public: bool IsSubEntryPoint(); bool IsSubExitPoint(); - bool IsJumpTarget(); }; diff --git a/GUI.NET/Debugger/DbgImporter.cs b/GUI.NET/Debugger/DbgImporter.cs index 6ffd7547..1ab6eca7 100644 --- a/GUI.NET/Debugger/DbgImporter.cs +++ b/GUI.NET/Debugger/DbgImporter.cs @@ -39,7 +39,7 @@ namespace Mesen.GUI.Debugger private static Regex _segmentRegex = new Regex("^seg\tid=([0-9]+),.*start=0x([0-9a-fA-F]+),.*size=0x([0-9A-Fa-f]+)", RegexOptions.Compiled); private static Regex _segmentPrgRomRegex = new Regex("^seg\tid=([0-9]+),.*start=0x([0-9a-fA-F]+),.*size=0x([0-9A-Fa-f]+),.*ooffs=([0-9]+)", RegexOptions.Compiled); - private static Regex _lineRegex = new Regex("^line\tid=([0-9]+),.*file=([0-9]+),.*line=([0-9]+)(,.*type=([0-9]+)){0,1}(,.*span=([0-9]+)){0,1}", RegexOptions.Compiled); + private static Regex _lineRegex = new Regex("^line\tid=([0-9]+),.*file=([0-9]+),.*line=([0-9]+)(,.*type=([0-9]+)){0,1}(,.*span=([0-9+]+)){0,1}", RegexOptions.Compiled); private static Regex _fileRegex = new Regex("^file\tid=([0-9]+),.*name=\"([^\"]+)\"", RegexOptions.Compiled); private static Regex _spanRegex = new Regex("^span\tid=([0-9]+),.*seg=([0-9]+),.*start=([0-9]+),.*size=([0-9]+)(,.*type=([0-9]+)){0,1}", RegexOptions.Compiled); private static Regex _symbolRegex = new Regex("^sym\tid=([0-9]+).*name=\"([0-9a-zA-Z@_-]+)\"(,.*def=([0-9+]+)){0,1}(,.*ref=([0-9+]+)){0,1}(,.*val=0x([0-9a-fA-F]+)){0,1}(,.*seg=([0-9]+)){0,1}(,.*exp=([0-9]+)){0,1}", RegexOptions.Compiled); @@ -51,6 +51,7 @@ namespace Mesen.GUI.Debugger private static Regex _cPreviousLinesRegex = new Regex("^\\s*//(.*)", RegexOptions.Compiled); private Dictionary _linesByPrgAddress = new Dictionary(); + private Dictionary _linesByFile = new Dictionary(); private Dictionary _prgAddressByLine = new Dictionary(); public Dictionary Files { get { return _files; } } @@ -66,6 +67,17 @@ namespace Mesen.GUI.Debugger return -1; } + private int GetPrgAddress(SpanInfo span) + { + SegmentInfo segment; + if(_segments.TryGetValue(span.SegmentID, out segment)) { + if(!segment.IsRam && span.Size != segment.Size) { + return span.Offset + segment.FileOffset - _headerSize; + } + } + return -1; + } + public LineInfo GetSourceCodeLineInfo(int prgRomAddress) { LineInfo line; @@ -110,24 +122,49 @@ namespace Mesen.GUI.Debugger return null; } + private SpanInfo GetSymbolDefinitionSpan(SymbolInfo symbol) + { + foreach(int definition in symbol.Definitions) { + LineInfo definitionLine; + FileInfo file; + if(_lines.TryGetValue(definition, out definitionLine)) { + if(_files.TryGetValue(definitionLine.FileID, out file)) { + int lineNumber = definitionLine.LineNumber; + while(!(definitionLine?.SpanIDs.Count > 0) && lineNumber < file.Data.Length - 1) { + //Definition line contains no code, try the next line + lineNumber++; + definitionLine = _linesByFile[file.ID][lineNumber]; + } + + if(definitionLine != null && definitionLine.SpanIDs.Count > 0) { + SpanInfo span; + if(_spans.TryGetValue(definitionLine.SpanIDs[0], out span)) { + return span; + } + } + } + } + } + return null; + } + private SymbolInfo GetMatchingSymbol(SymbolInfo symbol, int rangeStart, int rangeEnd) { foreach(int reference in symbol.References) { LineInfo line = _lines[reference]; - if(line.SpanID == null) { - continue; - } - SpanInfo span = _spans[line.SpanID.Value]; - SegmentInfo seg = _segments[span.SegmentID]; + foreach(int spanID in line.SpanIDs) { + SpanInfo span = _spans[spanID]; + SegmentInfo seg = _segments[span.SegmentID]; - if(!seg.IsRam) { - int spanPrgOffset = seg.FileOffset - _headerSize + span.Offset; - if(rangeStart < spanPrgOffset + span.Size && rangeEnd >= spanPrgOffset) { - if(symbol.ExportSymbolID != null && symbol.Address == null) { - return _symbols[symbol.ExportSymbolID.Value]; - } else { - return symbol; + if(!seg.IsRam) { + int spanPrgOffset = seg.FileOffset - _headerSize + span.Offset; + if(rangeStart < spanPrgOffset + span.Size && rangeEnd >= spanPrgOffset) { + if(symbol.ExportSymbolID != null && symbol.Address == null) { + return _symbols[symbol.ExportSymbolID.Value]; + } else { + return symbol; + } } } } @@ -246,9 +283,23 @@ namespace Mesen.GUI.Debugger FileID = Int32.Parse(match.Groups[2].Value), LineNumber = Int32.Parse(match.Groups[3].Value) - 1, Type = match.Groups[5].Success ? (LineType)Int32.Parse(match.Groups[5].Value) : LineType.Assembly, - SpanID = match.Groups[7].Success ? (int?)Int32.Parse(match.Groups[7].Value) : null }; + if(line.LineNumber < 0) { + line.LineNumber = 0; + } + + if(match.Groups[7].Success) { + string[] spanIDs = match.Groups[7].Value.Split('+'); + line.SpanIDs = new List(spanIDs.Length); + for(int i = spanIDs.Length - 1; i >= 0; i--) { + //Read them backwards to get them in order + line.SpanIDs.Add(Int32.Parse(spanIDs[i])); + } + } else { + line.SpanIDs = new List(); + } + _usedFileIds.Add(line.FileID); _lines.Add(line.ID, line); return true; @@ -402,10 +453,13 @@ namespace Mesen.GUI.Debugger count++; } - int address = GetSymbolAddressInfo(symbol).Address; - CodeLabel label = this.CreateLabel(address, segment.IsRam); - if(label != null) { - label.Label = newName; + AddressTypeInfo addressInfo = GetSymbolAddressInfo(symbol); + if(addressInfo != null) { + int address = addressInfo.Address; + CodeLabel label = this.CreateLabel(address, segment.IsRam); + if(label != null) { + label.Label = newName; + } } } } catch { @@ -419,11 +473,11 @@ namespace Mesen.GUI.Debugger foreach(KeyValuePair kvp in _lines) { try { LineInfo line = kvp.Value; - if(line.SpanID == null) { + if(line.SpanIDs.Count == 0) { continue; } - SpanInfo span = _spans[line.SpanID.Value]; + SpanInfo span = _spans[line.SpanIDs[0]]; SegmentInfo segment = _segments[span.SegmentID]; if(_files[line.FileID].Data == null) { @@ -479,6 +533,19 @@ namespace Mesen.GUI.Debugger private void LoadFileData(string path) { + Dictionary maxLineCountByFile = new Dictionary(); + + foreach(LineInfo line in _lines.Values) { + int currentMax; + if(maxLineCountByFile.TryGetValue(line.FileID, out currentMax)) { + if(currentMax < line.LineNumber) { + maxLineCountByFile[line.FileID] = line.LineNumber; + } + } else { + maxLineCountByFile[line.FileID] = line.LineNumber; + } + } + foreach(FileInfo file in _files.Values) { if(_usedFileIds.Contains(file.ID)) { try { @@ -497,6 +564,14 @@ namespace Mesen.GUI.Debugger if(File.Exists(sourceFile)) { file.Data = File.ReadAllLines(sourceFile); } + + LineInfo[] fileInfos = new LineInfo[maxLineCountByFile[file.ID] + 1]; + foreach(LineInfo line in _lines.Values) { + if(line.FileID == file.ID) { + fileInfos[line.LineNumber] = line; + } + } + _linesByFile[file.ID] = fileInfos; } catch { _errorCount++; } @@ -504,6 +579,74 @@ namespace Mesen.GUI.Debugger } } + private void BuildCdlData() + { + int prgSize = InteropEmu.DebugGetMemorySize(DebugMemoryType.PrgRom); + if(prgSize <= 0) { + return; + } + + byte[] cdlFile = new byte[prgSize]; + + //Mark data/code regions + foreach(SpanInfo span in _spans.Values) { + int prgAddress = GetPrgAddress(span); + if(prgAddress >= 0 && prgAddress < prgSize) { + for(int i = 0; i < span.Size; i++) { + if(cdlFile[prgAddress + i] != (byte)CdlPrgFlags.Data && !span.IsData && span.Size <= 3) { + cdlFile[prgAddress + i] = (byte)CdlPrgFlags.Code; + } else if(span.IsData) { + cdlFile[prgAddress + i] = (byte)CdlPrgFlags.Data; + } else if(cdlFile[prgAddress + i] == 0) { + //Mark bytes as tentative data, until we know that the bytes are actually code + cdlFile[prgAddress + i] = 0x04; + } + } + } + } + for(int i = 0; i < cdlFile.Length; i++) { + if(cdlFile[i] == 0x04) { + //Mark all bytes marked as tentative data as data + cdlFile[i] = (byte)CdlPrgFlags.Data; + } + } + + //Find/identify functions and jump targets + byte[] prgRomContent = InteropEmu.DebugGetMemoryState(DebugMemoryType.PrgRom); + foreach(SymbolInfo symbol in _symbols.Values) { + LineInfo line; + if(!symbol.SegmentID.HasValue) { + //This is a constant, ignore it + continue; + } + + foreach(int reference in symbol.References) { + if(_lines.TryGetValue(reference, out line) && line.SpanIDs.Count > 0) { + SpanInfo span; + if(_spans.TryGetValue(line.SpanIDs[0], out span) && !span.IsData && span.Size <= 3) { + int referencePrgAddr = GetPrgAddress(span); + if(referencePrgAddr >= 0 && referencePrgAddr < prgRomContent.Length) { + byte opCode = prgRomContent[referencePrgAddr]; + if(opCode == 0x20 || opCode == 0x10 || opCode == 0x30 || opCode == 0x50 || opCode == 0x70 || opCode == 0x90 || opCode == 0xB0 || opCode == 0xD0 || opCode == 0xF0 || opCode == 0x4C || opCode == 0x20 || opCode == 0x4C || opCode == 0x6C) { + //This symbol is used with a JSR/jump instruction, so it's either a function or jump target + bool isJsr = opCode == 0x20; + SpanInfo definitionSpan = GetSymbolDefinitionSpan(symbol); + if(definitionSpan != null) { + int definitionPrgAddr = GetPrgAddress(definitionSpan); + if(definitionPrgAddr >= 0 && definitionPrgAddr < prgRomContent.Length) { + cdlFile[definitionPrgAddr] |= (byte)(isJsr ? CdlPrgFlags.SubEntryPoint : CdlPrgFlags.JumpTarget); + break; + } + } + } + } + } + } + } + } + InteropEmu.DebugSetCdlData(cdlFile); + } + public void Import(string path, bool silent = false) { RomInfo romInfo = InteropEmu.GetRomInfo(); @@ -537,51 +680,30 @@ namespace Mesen.GUI.Debugger LoadFileData(basePath); - int prgSize = InteropEmu.DebugGetMemorySize(DebugMemoryType.PrgRom); - if(prgSize > 0) { - byte[] cdlFile = new byte[prgSize]; - foreach(KeyValuePair kvp in _spans) { - SegmentInfo segment; - if(_segments.TryGetValue(kvp.Value.SegmentID, out segment)) { - if(!segment.IsRam && kvp.Value.Size != segment.Size) { - int prgAddress = kvp.Value.Offset + segment.FileOffset - _headerSize; - - if(prgAddress >= 0 && prgAddress < prgSize) { - for(int i = 0; i < kvp.Value.Size; i++) { - if(cdlFile[prgAddress + i] == 0 && !kvp.Value.IsData && kvp.Value.Size <= 3) { - cdlFile[prgAddress + i] = (byte)0x01; - } else if(kvp.Value.IsData) { - cdlFile[prgAddress + i] = (byte)0x02; - } - } - } - } - } - } - InteropEmu.DebugSetCdlData(cdlFile); - } + BuildCdlData(); foreach(LineInfo line in _lines.Values) { - if(line.SpanID == null) { - continue; - } + foreach(int spanID in line.SpanIDs) { + SpanInfo span; + if(_spans.TryGetValue(spanID, out span)) { + SegmentInfo segment; + if(_segments.TryGetValue(span.SegmentID, out segment) && !segment.IsRam) { + for(int i = 0; i < span.Size; i++) { + int prgAddress = segment.FileOffset - _headerSize + span.Offset + i; - FileInfo file = _files[line.FileID]; - SpanInfo span = _spans[line.SpanID.Value]; - SegmentInfo segment = _segments[span.SegmentID]; - if(!segment.IsRam) { - for(int i = 0; i < span.Size; i++) { - int prgAddress = segment.FileOffset - _headerSize + span.Offset + i; + LineInfo existingLine; + if(_linesByPrgAddress.TryGetValue(prgAddress, out existingLine) && existingLine.Type == LineType.External) { + //Give priority to lines that come from C files + continue; + } - LineInfo existingLine; - if(_linesByPrgAddress.TryGetValue(prgAddress, out existingLine) && existingLine.Type == LineType.External) { - //Give priority to lines that come from C files - continue; - } - - _linesByPrgAddress[prgAddress] = line; - if(i == 0) { - _prgAddressByLine[file.ID.ToString() + "_" + line.LineNumber.ToString()] = prgAddress; + _linesByPrgAddress[prgAddress] = line; + if(i == 0 && spanID == line.SpanIDs[0]) { + //Mark the first byte of the first span representing this line as the PRG address for this line of code + FileInfo file = _files[line.FileID]; + _prgAddressByLine[file.ID.ToString() + "_" + line.LineNumber.ToString()] = prgAddress; + } + } } } } @@ -606,7 +728,7 @@ namespace Mesen.GUI.Debugger labels.AddRange(_saveRamLabels.Values); labelCount += _ramLabels.Count + _workRamLabels.Count + _saveRamLabels.Count; } - LabelManager.SetLabels(labels, !silent); + LabelManager.SetLabels(labels, true); if(!silent) { if(_errorCount > 0) { @@ -660,7 +782,7 @@ namespace Mesen.GUI.Debugger { public int ID; public int FileID; - public int? SpanID; + public List SpanIDs; public LineType Type; public int LineNumber; diff --git a/GUI.NET/Debugger/LabelManager.cs b/GUI.NET/Debugger/LabelManager.cs index c2aba0eb..90ce5c8d 100644 --- a/GUI.NET/Debugger/LabelManager.cs +++ b/GUI.NET/Debugger/LabelManager.cs @@ -145,10 +145,10 @@ namespace Mesen.GUI.Debugger public static void CreateAutomaticJumpLabels() { - bool[] jumpTargets = InteropEmu.DebugGetJumpTargets(); + byte[] cdlData = InteropEmu.DebugGetPrgCdlData(); List labelsToAdd = new List(); - for(int i = 0; i < jumpTargets.Length; i++) { - if(jumpTargets[i] && LabelManager.GetLabel((uint)i, AddressType.PrgRom) == null) { + for(int i = 0; i < cdlData.Length; i++) { + if((cdlData[i] & (byte)CdlPrgFlags.JumpTarget) != 0 && LabelManager.GetLabel((uint)i, AddressType.PrgRom) == null) { labelsToAdd.Add(new CodeLabel() { Flags = CodeLabelFlags.AutoJumpLabel, Address = (uint)i, AddressType = AddressType.PrgRom, Label = "L" + i.ToString("X4"), Comment = "" }); } } diff --git a/GUI.NET/Debugger/frmGoToAll.cs b/GUI.NET/Debugger/frmGoToAll.cs index 2daf997d..39da6070 100644 --- a/GUI.NET/Debugger/frmGoToAll.cs +++ b/GUI.NET/Debugger/frmGoToAll.cs @@ -156,7 +156,7 @@ namespace Mesen.GUI.Debugger _resultCount = 0; HashSet entryPoints = new HashSet(InteropEmu.DebugGetFunctionEntryPoints()); - bool[] isJumpTargets = InteropEmu.DebugGetJumpTargets(); + byte[] cdlData = InteropEmu.DebugGetPrgCdlData(); List searchResults = new List(); @@ -201,7 +201,7 @@ namespace Mesen.GUI.Debugger SearchResultType resultType = SearchResultType.Data; if(addressInfo?.Type == AddressType.PrgRom && entryPoints.Contains(addressInfo.Address)) { resultType = SearchResultType.Function; - } else if(addressInfo?.Type == AddressType.PrgRom && addressInfo.Address < isJumpTargets.Length && isJumpTargets[addressInfo.Address]) { + } else if(addressInfo?.Type == AddressType.PrgRom && addressInfo.Address < cdlData.Length && (cdlData[addressInfo.Address] & (byte)CdlPrgFlags.JumpTarget) != 0) { resultType = SearchResultType.JumpTarget; } else if(isConstant) { resultType = SearchResultType.Constant; @@ -226,7 +226,7 @@ namespace Mesen.GUI.Debugger SearchResultType resultType = SearchResultType.Data; if(label.AddressType == AddressType.PrgRom && entryPoints.Contains((int)label.Address)) { resultType = SearchResultType.Function; - } else if(label.AddressType == AddressType.PrgRom && label.Address < isJumpTargets.Length && isJumpTargets[label.Address]) { + } else if(label.AddressType == AddressType.PrgRom && label.Address < cdlData.Length && (cdlData[label.Address] & (byte)CdlPrgFlags.JumpTarget) != 0) { resultType = SearchResultType.JumpTarget; } diff --git a/GUI.NET/InteropEmu.cs b/GUI.NET/InteropEmu.cs index 0c12af23..028437ee 100644 --- a/GUI.NET/InteropEmu.cs +++ b/GUI.NET/InteropEmu.cs @@ -368,21 +368,6 @@ namespace Mesen.GUI return assembledCode; } - [DllImport(DLLPath, EntryPoint = "DebugGetJumpTargets")] private static extern void DebugGetJumpTargetsWrapper(IntPtr isJumpTargets); - public static bool[] DebugGetJumpTargets() - { - bool[] isJumpTarget = new bool[InteropEmu.DebugGetMemorySize(DebugMemoryType.PrgRom)]; - - GCHandle hJumpTarget = GCHandle.Alloc(isJumpTarget, GCHandleType.Pinned); - try { - InteropEmu.DebugGetJumpTargetsWrapper(hJumpTarget.AddrOfPinnedObject()); - } finally { - hJumpTarget.Free(); - } - - return isJumpTarget; - } - [DllImport(DLLPath, EntryPoint = "DebugGetMemoryState")] private static extern UInt32 DebugGetMemoryStateWrapper(DebugMemoryType type, IntPtr buffer); public static byte[] DebugGetMemoryState(DebugMemoryType type) { @@ -608,6 +593,12 @@ namespace Mesen.GUI } [DllImport(DLLPath, EntryPoint = "DebugGetCdlData")] private static extern void DebugGetCdlDataWrapper(UInt32 offset, UInt32 length, DebugMemoryType type, IntPtr counts); + + public static byte[] DebugGetPrgCdlData() + { + return DebugGetCdlData(0, (uint)InteropEmu.DebugGetMemorySize(DebugMemoryType.PrgRom), DebugMemoryType.PrgRom); + } + public static byte[] DebugGetCdlData(UInt32 offset, UInt32 length, DebugMemoryType type) { byte[] cdlData = new byte[length]; @@ -1180,9 +1171,16 @@ namespace Mesen.GUI None = 0x00, Code = 0x01, Data = 0x02, - IndirectCode = 0x10, + + //Bit 0x10 is used for "indirectly accessed as code" in FCEUX + //Repurposed to mean the address is the target of a jump instruction + JumpTarget = 0x10, + IndirectData = 0x20, PcmData = 0x40, + + //Unused bit in original CDL spec + //Used to denote that the byte is the start of function (sub) SubEntryPoint = 0x80 } diff --git a/InteropDLL/DebugWrapper.cpp b/InteropDLL/DebugWrapper.cpp index bb02e45c..3e15ae4c 100644 --- a/InteropDLL/DebugWrapper.cpp +++ b/InteropDLL/DebugWrapper.cpp @@ -67,8 +67,6 @@ extern "C" DllExport void __stdcall DebugBreakOnScanline(int32_t scanline) { GetDebugger()->BreakOnScanline(scanline); } DllExport const char* __stdcall DebugGetCode(uint32_t &length) { return GetDebugger()->GetCode(length); } - DllExport void __stdcall DebugGetJumpTargets(bool* isJumpTarget) { GetDebugger()->GetJumpTargets(isJumpTarget); } - DllExport void __stdcall DebugSetPpuViewerScanlineCycle(int32_t ppuViewerId, int32_t scanline, int32_t cycle) { return GetDebugger()->SetPpuViewerScanlineCycle(ppuViewerId, scanline, cycle); } DllExport void __stdcall DebugClearPpuViewerSettings(int32_t ppuViewerId) { return GetDebugger()->ClearPpuViewerSettings(ppuViewerId); }