A debugger appeared.

master
Nabile Rahmani 2017-11-16 08:33:04 +01:00
parent 66e5efff59
commit 449b6ebd0c
6 changed files with 321 additions and 0 deletions

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
namespace DotN64.Diagnostics
{
public partial class Debugger
{
private struct Command
{
#region Properties
public IEnumerable<string> Names { get; }
public string Description { get; }
public Action<string[]> Action { get; }
#endregion
#region Constructors
public Command(IEnumerable<string> names, string description, Action<string[]> action)
{
Names = names;
Description = description;
Action = action;
}
#endregion
}
}
}

View File

@ -0,0 +1,12 @@
namespace DotN64.Diagnostics
{
public partial class Debugger
{
public enum Status : byte
{
Stopped,
Running,
Debugging
}
}
}

View File

@ -0,0 +1,262 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace DotN64.Diagnostics
{
using CPU;
public partial class Debugger
{
#region Fields
private readonly Nintendo64 nintendo64;
private readonly IReadOnlyCollection<Command> commands;
private readonly IDictionary<ulong, string> labels = new Dictionary<ulong, string>();
private readonly IList<ulong> breakpoints = new List<ulong>();
#endregion
#region Properties
public Status DebuggerStatus { get; private set; }
#endregion
#region Constructors
public Debugger(Nintendo64 nintendo64)
{
this.nintendo64 = nintendo64;
commands = new[]
{
new Command(new[] { "continue", "c" }, "Continues execution of the CPU.", args => DebuggerStatus = Status.Running),
new Command(new[] { "step", "s" }, "Steps the CPU a specified amount of times.", args =>
{
var count = args.Length > 0 ? int.Parse(args.First()) : 1;
for (var i = 0; i < count; i++)
{
Disassemble();
nintendo64.CPU.Step();
}
}),
new Command(new[] { "goto", "g" }, "Sets the CPU's PC to the specified address.", args => nintendo64.CPU.PC = ulong.Parse(args.First(), NumberStyles.HexNumber)),
new Command(new[] { "disassemble", "d" }, "Disassembles instructions from the current PC.", args =>
{
var count = args.Length > 0 ? ulong.Parse(args.First()) : 1;
for (var i = 0ul; i < count; i++)
{
Disassemble(nintendo64.CPU.PC + i * VR4300.Instruction.Size);
}
}),
new Command(new[] { "label", "labels", "l" }, "Shows, adds or removes a label attached to an address.", args =>
{
var index = 0;
switch (args.Length == 0 ? "show" : args[index++])
{
case "add":
case "a":
labels[args.Length <= index + 1 ? nintendo64.CPU.PC : ulong.Parse(args[index++], NumberStyles.HexNumber)] = args[index++];
break;
case "remove":
case "r":
labels.Remove(args.Length <= index ? nintendo64.CPU.PC : ulong.Parse(args[index++], NumberStyles.HexNumber));
break;
case "clear":
case "c":
labels.Clear();
break;
case "show":
case "s":
{
if (args.Length <= index)
{
foreach (var pair in labels)
{
Console.WriteLine($".{pair.Value}: {pair.Key:X16}");
}
break;
}
var address = ulong.Parse(args[index++], NumberStyles.HexNumber);
Console.WriteLine($".{labels[address]}: {address:X16}");
}
break;
}
}),
new Command(new[] { "breakpoint", "breakpoints", "b" }, "Shows, adds or removes breakpoints.", args =>
{
var index = 0;
switch (args.Length == 0 ? "show" : args[index++])
{
case "add":
case "a":
breakpoints.Add(args.Length <= index ? nintendo64.CPU.PC : ulong.Parse(args[index++], NumberStyles.HexNumber));
break;
case "remove":
case "r":
breakpoints.Remove(args.Length <= index ? nintendo64.CPU.PC : ulong.Parse(args[index++], NumberStyles.HexNumber));
break;
case "clear":
case "c":
breakpoints.Clear();
break;
case "show":
case "s":
foreach (var breakpoint in breakpoints)
{
Console.WriteLine($"● {breakpoint:X16}");
}
break;
}
}),
new Command(new[] { "exit", "quit", "q" }, "Exits the debugger.", args => DebuggerStatus = Status.Stopped),
new Command(new[] { "help", "h" }, "Shows help for commands.", args =>
{
if (args.Length == 0)
{
foreach (var command in commands)
{
Console.WriteLine($"- {string.Join(", ", command.Names)}: {command.Description}");
}
}
else
{
var commandName = args.First();
var command = commands.First(c => c.Names.Contains(commandName));
Console.WriteLine($"{string.Join(", ", command.Names)}: {command.Description}");
}
}),
new Command(new[] { "clear" }, "Clears the terminal.", args => Console.Clear())
};
}
#endregion
#region Methods
private string Disassemble(VR4300.Instruction instruction)
{
switch (instruction.OP)
{
case VR4300.OpCode.SPECIAL:
return ((VR4300.SpecialOpCode)instruction.Funct).ToString();
case VR4300.OpCode.REGIMM:
return ((VR4300.RegImmOpCode)instruction.RT).ToString();
default:
return instruction.OP.ToString();
}
}
private void Disassemble(ulong? address = null)
{
if (!address.HasValue)
address = nintendo64.CPU.PC;
var physicalAddress = address.Value;
var instruction = nintendo64.CPU.CP0.Map(ref physicalAddress).ReadWord(physicalAddress);
if (labels.ContainsKey(address.Value))
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($".{labels[address.Value]}:");
Console.ResetColor();
}
var hasBreakpoint = breakpoints.Contains(address.Value);
if (hasBreakpoint)
{
Console.BackgroundColor = ConsoleColor.DarkRed;
Console.ForegroundColor = ConsoleColor.White;
}
Console.WriteLine($"{(hasBreakpoint ? '●' : ' ')} {address.Value:X16}: {instruction:X8} {Disassemble(instruction)}");
if (hasBreakpoint)
Console.ResetColor();
}
public void Debug()
{
DebuggerStatus = Status.Debugging;
while (DebuggerStatus == Status.Debugging)
{
Console.Write("DotN64-dbg>");
var input = Console.ReadLine().Trim();
if (input.Length == 0)
continue;
var arguments = input.Split();
var commandName = arguments.First();
Command command;
try
{
command = commands.First(c => c.Names.Contains(commandName));
}
catch (InvalidOperationException)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Command not found.");
Console.ResetColor();
continue;
}
try
{
command.Action(arguments.Skip(1).ToArray());
}
catch (Exception e)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Exception: {e.Message}");
Console.ResetColor();
}
}
}
public void Run(bool debug = false)
{
DebuggerStatus = debug ? Status.Debugging : Status.Running;
while (DebuggerStatus != Status.Stopped)
{
switch (DebuggerStatus)
{
case Status.Running:
if (breakpoints.Contains(nintendo64.CPU.PC))
{
Console.WriteLine("Hit a breakpoint; entering debug prompt.");
Debug();
}
try
{
nintendo64.CPU.Step();
}
catch (Exception e)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Exception during CPU execution: {e.Message}");
Console.ResetColor();
Debug();
}
break;
case Status.Debugging:
Debug();
break;
}
}
}
#endregion
}
}

View File

@ -64,6 +64,9 @@
<Compile Include="CPU\VR4300\CP0\VR4300.SystemControlUnit.Register.cs" />
<Compile Include="CPU\VR4300\CP0\VR4300.SystemControlUnit.RegisterIndex.cs" />
<Compile Include="CPU\VR4300\CP0\VR4300.SystemControlUnit.StatusRegister.cs" />
<Compile Include="Diagnostics\Debugger.cs" />
<Compile Include="Diagnostics\Debugger.Status.cs" />
<Compile Include="Diagnostics\Debugger.Command.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="CPU\" />
@ -79,6 +82,7 @@
<Folder Include="RCP\SP\" />
<Folder Include="CPU\VR4300\" />
<Folder Include="CPU\VR4300\CP0\" />
<Folder Include="Diagnostics\" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -99,7 +99,10 @@ namespace DotN64
if (RCP.PI.BootROM == null)
RCP.PI.EmulateBootROM();
}
public void Run()
{
while (true)
{
CPU.Step();

View File

@ -2,11 +2,14 @@
namespace DotN64
{
using Diagnostics;
internal static class Program
{
private static void Main(string[] args)
{
var nintendo64 = new Nintendo64();
var debugger = default(Debugger);
for (int i = 0; i < args.Length; i++)
{
@ -17,6 +20,10 @@ namespace DotN64
case "--pif-rom":
nintendo64.RCP.PI.BootROM = File.ReadAllBytes(args[++i]);
break;
case "--debug":
case "-d":
debugger = new Debugger(nintendo64);
break;
default:
nintendo64.Cartridge = Cartridge.FromFile(new FileInfo(arg));
break;
@ -24,6 +31,11 @@ namespace DotN64
}
nintendo64.PowerOn();
if (debugger == null)
nintendo64.Run();
else
debugger.Run(true);
}
}
}