Skip to content

Commit

Permalink
Debugger: Added basic support for SDCC symbol files (.cdb), including…
Browse files Browse the repository at this point in the history
… source mappings
  • Loading branch information
SourMesen committed Dec 12, 2024
1 parent b30bb12 commit 0ff6e6a
Show file tree
Hide file tree
Showing 7 changed files with 360 additions and 3 deletions.
1 change: 1 addition & 0 deletions UI/Config/IntegrationConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class IntegrationConfig : BaseConfig<IntegrationConfig>
[Reactive] public bool AutoLoadMlbFiles { get; set; } = true;
[Reactive] public bool AutoLoadCdlFiles { get; set; } = false;
[Reactive] public bool AutoLoadSymFiles { get; set; } = true;
[Reactive] public bool AutoLoadCdbFiles { get; set; } = true;
[Reactive] public bool AutoLoadElfFiles { get; set; } = true;
[Reactive] public bool AutoLoadFnsFiles { get; set; } = true;

Expand Down
2 changes: 1 addition & 1 deletion UI/Debugger/Disassembly/SourceViewStyleProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public override List<CodeColor> GetCodeColors(CodeLineData lineData, bool highli

private static Regex _space = new Regex("^[ \t]+", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static Regex _comment = new Regex("^//.*", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static Regex _keywords = new Regex("^(if|else|static|void|int|short|long|char|unsigned|signed|break|return|continue|switch|case|const|while|do|#define|#pragma|#include){1}([^a-z0-9_-]+|$)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static Regex _keywords = new Regex("^(if|else|static|extern|inline|void|int|short|long|char|unsigned|signed|uint8_t|int8_t|uint16_t|int16_t|uint32_t|int32_t|uint64_t|int64_t|struct|break|return|continue|switch|case|const|while|do|for|default|#define|#pragma|#include){1}([^a-z0-9_-]+|$)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static Regex _text = new Regex("^([a-z0-9_]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static Regex _syntax = new Regex("^[]([)!+,.|:<>?&^;{}\"'/*%=#-]{1}", RegexOptions.Compiled);
private static Regex _number = new Regex("^(0x[0-9a-f]+|0b[01]+|[0-9]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
Expand Down
334 changes: 334 additions & 0 deletions UI/Debugger/Integration/SdccSymbolImporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
using Mesen.Config;
using Mesen.Debugger.Labels;
using Mesen.Interop;
using Mesen.Utilities;
using Mesen.Windows;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

namespace Mesen.Debugger.Integration;

public abstract class SdccSymbolImporter : ISymbolProvider
{
private static Regex _globalSymbolRegex = new Regex(@"^L:G\$([^$]+)\$([0-9_]+)\$(\d+):([0-9A-Fa-f]+)$", RegexOptions.Compiled);
private static Regex _srcMappingRegex = new Regex(@"^L:C\$([^$]+)\$(\d+)\$([0-9_]+)\$(\d+):([0-9A-Fa-f]+)$", RegexOptions.Compiled);

private Dictionary<string, SourceFileInfo> _sourceFiles = new();
private Dictionary<string, AddressInfo> _addressByLine = new();
private Dictionary<string, SourceCodeLocation> _linesByAddress = new();
private Dictionary<string, SymbolInfo> _symbols = new();

public DateTime SymbolFileStamp { get; private set; }
public string SymbolPath { get; private set; } = "";

public List<SourceFileInfo> SourceFiles { get { return _sourceFiles.Values.ToList(); } }

protected SdccSymbolImporter()
{
}

public static SdccSymbolImporter? Import(RomFormat romFormat, string path, bool showResult)
{
SdccSymbolImporter? importer = romFormat switch {
RomFormat.iNes or RomFormat.Nsf or RomFormat.VsSystem or RomFormat.VsDualSystem => new NesSdccSymbolImporter(),
RomFormat.Gb => new GbSdccSymbolImporter(),
RomFormat.Sms or RomFormat.GameGear => new SmsSdccSymbolImporter(),
_ => null
};

if(importer == null) {
EmuApi.WriteLogEntry("[Debugger] Error: Attempted to load .cdb file for an unsupported file format: " + romFormat);
}

importer?.Import(path, showResult);

return importer;
}

public AddressInfo? GetLineAddress(SourceFileInfo file, int lineIndex)
{
AddressInfo address;
if(_addressByLine.TryGetValue(file.Name.ToString() + "_" + lineIndex.ToString(), out address)) {
return address;
}
return null;
}

public AddressInfo? GetLineEndAddress(SourceFileInfo file, int lineIndex)
{
return null;
}

public string GetSourceCodeLine(int prgRomAddress)
{
throw new NotImplementedException();
}

public SourceCodeLocation? GetSourceCodeLineInfo(AddressInfo address)
{
string key = address.Type.ToString() + address.Address.ToString();
SourceCodeLocation location;
if(_linesByAddress.TryGetValue(key, out location)) {
return location;
}
return null;
}

public SourceSymbol? GetSymbol(string word, int scopeStart, int scopeEnd)
{
foreach(SymbolInfo symbol in _symbols.Values) {
if(symbol.Name == word) {
//TODOv2 SCOPE
return symbol.SourceSymbol;
}
}

return null;
}

public AddressInfo? GetSymbolAddressInfo(SourceSymbol symbol)
{
if(symbol.InternalSymbol is SymbolInfo dbgSymbol) {
return dbgSymbol.Address;
}
return null;
}

public SourceCodeLocation? GetSymbolDefinition(SourceSymbol symbol)
{
return null;
}

public List<SourceSymbol> GetSymbols()
{
return _symbols.Values.Select(s => s.SourceSymbol).ToList();
}

public int GetSymbolSize(SourceSymbol srcSymbol)
{
return 1;
}

protected abstract bool TryDecodeAddress(int addr, out AddressInfo absAddr);

private string? FindInFolder(string basePath, string folder, string filename)
{
if(!Directory.Exists(Path.Combine(basePath, folder))) {
return null;
}

string ? foundPath = Directory.EnumerateFiles(Path.Combine(basePath, folder), filename, SearchOption.AllDirectories).FirstOrDefault();
if(File.Exists(foundPath)) {
return foundPath;
}
return null;
}

private bool FindFile(string path, string filename, [MaybeNullWhen(false)] out string result)
{
string? searchPath = Path.GetDirectoryName(path) ?? "";
string sourceFile = Path.Combine(searchPath, filename);
result = null;

while(!File.Exists(sourceFile)) {
//Go back up folder structure to attempt to find the file
result = FindInFolder(searchPath, "src", filename) ?? FindInFolder(searchPath, "include", filename);
if(result != null) {
return true;
}

string oldPath = searchPath;
searchPath = Path.GetDirectoryName(searchPath);
if(searchPath == null || searchPath == oldPath) {
return false;
}
sourceFile = Path.Combine(searchPath, filename);
}

return false;
}

public void Import(string path, bool showResult)
{
SymbolFileStamp = File.GetLastWriteTime(path);

string basePath = Path.GetDirectoryName(path) ?? "";
SymbolPath = basePath;

string[] lines = File.ReadAllLines(path);

int errorCount = 0;

string prevFilename = "";
int prevLineAddr = -1;
SourceCodeLocation prevLoc = new();

Dictionary<string, CodeLabel> labels = new();

for(int i = 0; i < lines.Length; i++) {
string str = lines[i].Trim();
Match m;
if((m = _srcMappingRegex.Match(str)).Success) {
string filename = m.Groups[1].Value;
SourceFileInfo? srcFile;
if(!_sourceFiles.TryGetValue(filename, out srcFile)) {
if(!FindFile(path, filename, out string? sourceFile)) {
continue;
}

string[] data = File.ReadAllLines(sourceFile);
srcFile = new SourceFileInfo(filename, false, new SdccSourceFile() { Data = data });
_sourceFiles[filename] = srcFile;
}

int lineNumber = int.Parse(m.Groups[2].Value) - 1;
int baseAddr = int.Parse(m.Groups[5].Value, System.Globalization.NumberStyles.HexNumber);

if(!TryDecodeAddress(baseAddr, out AddressInfo absAddr)) {
continue;
}

int length = filename == prevFilename && prevLineAddr >= 0 && prevLineAddr < absAddr.Address ? absAddr.Address - prevLineAddr : 1;

for(int j = 0; j < length; j++) {
_linesByAddress[absAddr.Type.ToString() + (prevLineAddr + j).ToString()] = prevLoc;
}

_addressByLine[filename + "_" + lineNumber] = absAddr;

prevLineAddr = absAddr.Address;
prevFilename = filename;

SourceCodeLocation loc = new SourceCodeLocation(srcFile, lineNumber);
prevLoc = loc;
_linesByAddress[absAddr.Type.ToString() + absAddr.Address.ToString()] = loc;
} else if((m = _globalSymbolRegex.Match(str)).Success) {
string name = m.Groups[1].Value;
int baseAddr = int.Parse(m.Groups[4].Value, System.Globalization.NumberStyles.HexNumber);

if(!TryDecodeAddress(baseAddr, out AddressInfo absAddr)) {
continue;
}

string label = LabelManager.InvalidLabelRegex.Replace(name, "_");
if(ConfigManager.Config.Debug.Integration.IsMemoryTypeImportEnabled(absAddr.Type)) {
labels[label] = new CodeLabel() {
Label = label,
Address = (UInt32)absAddr.Address,
MemoryType = absAddr.Type,
Comment = "",
Flags = CodeLabelFlags.None,
Length = 1
};
}

_symbols[name] = new SymbolInfo(name, absAddr);
}
}

LabelManager.SetLabels(labels.Values, true);

if(showResult) {
if(errorCount > 0) {
MesenMsgBox.Show(null, "ImportLabelsWithErrors", MessageBoxButtons.OK, MessageBoxIcon.Warning, labels.Count.ToString(), errorCount.ToString());
} else {
MesenMsgBox.Show(null, "ImportLabels", MessageBoxButtons.OK, MessageBoxIcon.Info, labels.Count.ToString());
}
}
}

class SdccSourceFile : IFileDataProvider
{
public string[] Data { get; init; } = Array.Empty<string>();
}

private readonly struct SymbolInfo
{
public string Name { get; }
public int? Value { get; } = null;
public AddressInfo? Address { get; } = null;
public SourceSymbol SourceSymbol { get => new SourceSymbol(Name, Address?.Address ?? Value, this); }

public SymbolInfo(string name, AddressInfo address)
{
Name = name;
Address = address;
}

public SymbolInfo(string name, int value)
{
Name = name;
Value = value;
}
}
}

public class NesSdccSymbolImporter : SdccSymbolImporter
{
private int _romSize;

public NesSdccSymbolImporter()
{
_romSize = DebugApi.GetMemorySize(MemoryType.NesPrgRom);
}

protected override bool TryDecodeAddress(int addr, out AddressInfo absAddr)
{
int bank = addr >> 16;
addr &= 0xFFFF;

if(addr >= 0xC000) {
absAddr = new AddressInfo() { Address = _romSize - 0x4000 + (addr & 0x3FFF), Type = MemoryType.NesPrgRom };
} else if(addr >= 0x8000) {
absAddr = new AddressInfo() { Address = bank * 0x4000 + (addr & 0x3FFF), Type = MemoryType.NesPrgRom };
} else if(addr < 0x2000) {
absAddr = new AddressInfo() { Address = addr & 0x7FF, Type = MemoryType.NesInternalRam };
} else {
absAddr = new();
return false;
}
return true;
}
}

public class SmsSdccSymbolImporter : SdccSymbolImporter
{
protected override bool TryDecodeAddress(int addr, out AddressInfo absAddr)
{
int bank = addr >> 16;
addr &= 0xFFFF;

if(addr >= 0xC000) {
absAddr = new AddressInfo() { Address = (addr & 0x1FFF), Type = MemoryType.SmsWorkRam };
} else if(addr < 0xC000) {
absAddr = new AddressInfo() { Address = bank * 0x4000 + (addr & 0x3FFF), Type = MemoryType.SmsPrgRom };
} else {
absAddr = new();
return false;
}
return true;
}
}

public class GbSdccSymbolImporter : SdccSymbolImporter
{
protected override bool TryDecodeAddress(int addr, out AddressInfo absAddr)
{
int bank = addr >> 16;
addr &= 0xFFFF;

if(addr >= 0xC000) {
absAddr = new AddressInfo() { Address = (addr & 0x1FFF), Type = MemoryType.GbWorkRam };
} else if(addr < 0xC000) {
absAddr = new AddressInfo() { Address = bank * 0x4000 + (addr & 0x3FFF), Type = MemoryType.GbPrgRom };
} else {
absAddr = new();
return false;
}
return true;
}
}
16 changes: 16 additions & 0 deletions UI/Debugger/Utilities/DebugWorkspaceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ public static void Load()
}
}

if(SymbolProvider == null && ConfigManager.Config.Debug.Integration.AutoLoadCdbFiles) {
string? symPath = GetMatchingFile(FileDialogHelper.CdbFileExt);
if(symPath != null) {
LoadCdbFile(symPath, false);
}
}

if(SymbolProvider == null && ConfigManager.Config.Debug.Integration.AutoLoadElfFiles) {
string? symPath = GetMatchingFile(FileDialogHelper.ElfFileExt);
if(symPath != null) {
Expand Down Expand Up @@ -132,6 +139,14 @@ public static void LoadElfFile(string path, bool showResult)
}
}

public static void LoadCdbFile(string path, bool showResult)
{
if(File.Exists(path) && Path.GetExtension(path).ToLower() == "." + FileDialogHelper.CdbFileExt) {
ResetLabels();
SymbolProvider = SdccSymbolImporter.Import(_romInfo.Format, path, showResult);
}
}

public static void LoadSymFile(string path, bool showResult)
{
if(File.Exists(path) && Path.GetExtension(path).ToLower() == "." + FileDialogHelper.SymFileExt) {
Expand Down Expand Up @@ -211,6 +226,7 @@ public static void LoadSupportedFile(string filename, bool showResult)
switch(Path.GetExtension(filename).ToLower().Substring(1)) {
case FileDialogHelper.DbgFileExt: LoadDbgSymbolFile(filename, showResult); break;
case FileDialogHelper.SymFileExt: LoadSymFile(filename, showResult); break;
case FileDialogHelper.CdbFileExt: LoadCdbFile(filename, showResult); break;
case FileDialogHelper.ElfFileExt: LoadElfFile(filename, showResult); break;
case FileDialogHelper.MesenLabelExt: LoadMesenLabelFile(filename, showResult); break;
case FileDialogHelper.NesAsmLabelExt: LoadNesAsmLabelFile(filename, showResult); break;
Expand Down
4 changes: 4 additions & 0 deletions UI/Debugger/Windows/DebuggerConfigWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@
IsChecked="{Binding Integration.AutoLoadElfFiles}"
Content="{l:Translate chkAutoLoadElfFiles}"
/>
<CheckBox
IsChecked="{Binding Integration.AutoLoadCdbFiles}"
Content="{l:Translate chkAutoLoadCdbFiles}"
/>
<CheckBox
IsChecked="{Binding Integration.AutoLoadMlbFiles}"
Content="{l:Translate chkAutoLoadMlbFiles}"
Expand Down
Loading

0 comments on commit 0ff6e6a

Please sign in to comment.