feat(analysis): add execution trace recorder
This commit is contained in:
1 parent
210cf5c6b1
commit
6ff06cfe6e
2 files changed
+154
No files matched your search
@@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
using Chip8.Core;
|
||||||
|
|
||||||
|
namespace Chip8.Analysis;
|
||||||
|
|
||||||
|
public sealed class TraceEntry
|
||||||
|
{
|
||||||
|
public TraceEntry(int step, ushort address, ushort opcode, ushort? extraWord, string mnemonic, byte[] registers, ushort index, byte stackPointer, byte delayTimer, byte soundTimer)
|
||||||
|
{
|
||||||
|
this.step = step;
|
||||||
|
this.address = address;
|
||||||
|
this.opcode = opcode;
|
||||||
|
this.extraWord = extraWord;
|
||||||
|
this.mnemonic = mnemonic;
|
||||||
|
this.registers = registers;
|
||||||
|
this.index = index;
|
||||||
|
this.stackPointer = stackPointer;
|
||||||
|
this.delayTimer = delayTimer;
|
||||||
|
this.soundTimer = soundTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int step { get; }
|
||||||
|
public ushort address { get; }
|
||||||
|
public ushort opcode { get; }
|
||||||
|
public ushort? extraWord { get; }
|
||||||
|
public string mnemonic { get; }
|
||||||
|
public byte[] registers { get; }
|
||||||
|
public ushort index { get; }
|
||||||
|
public byte stackPointer { get; }
|
||||||
|
public byte delayTimer { get; }
|
||||||
|
public byte soundTimer { get; }
|
||||||
|
|
||||||
|
public byte register(int slot) => registers[slot & 0xF];
|
||||||
|
|
||||||
|
public string registerDump()
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
for (int slot = 0; slot < registers.Length; slot++)
|
||||||
|
{
|
||||||
|
if (slot > 0)
|
||||||
|
{
|
||||||
|
builder.Append(' ');
|
||||||
|
}
|
||||||
|
builder.Append('V').Append(slot.ToString("X1", CultureInfo.InvariantCulture));
|
||||||
|
builder.Append('=').Append(registers[slot].ToString("X2", CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return "#" + step.ToString(CultureInfo.InvariantCulture).PadLeft(5)
|
||||||
|
+ " 0x" + address.ToString("X4", CultureInfo.InvariantCulture)
|
||||||
|
+ " " + opcode.ToString("X4", CultureInfo.InvariantCulture)
|
||||||
|
+ " " + mnemonic.PadRight(18)
|
||||||
|
+ " I=0x" + index.ToString("X3", CultureInfo.InvariantCulture)
|
||||||
|
+ " SP=" + stackPointer.ToString(CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Chip8.Core;
|
||||||
|
|
||||||
|
namespace Chip8.Analysis;
|
||||||
|
|
||||||
|
public sealed class TraceRecorder
|
||||||
|
{
|
||||||
|
readonly List<TraceEntry> entryList = new();
|
||||||
|
|
||||||
|
public IReadOnlyList<TraceEntry> entries => entryList;
|
||||||
|
|
||||||
|
public int count => entryList.Count;
|
||||||
|
|
||||||
|
public bool haltedOnSpin { get; private set; }
|
||||||
|
|
||||||
|
public TraceEntry this[int index] => entryList[index];
|
||||||
|
|
||||||
|
public static TraceRecorder record(byte[] program, int maxSteps, Platform platform = Platform.Chip8, Quirks? quirks = null, int seed = 1, bool stopOnSpin = true)
|
||||||
|
{
|
||||||
|
var machine = new Machine(quirks, seed, platform);
|
||||||
|
machine.loadRom(program);
|
||||||
|
var recorder = new TraceRecorder();
|
||||||
|
recorder.capture(machine, maxSteps, stopOnSpin);
|
||||||
|
return recorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void capture(Machine machine, int maxSteps, bool stopOnSpin = true)
|
||||||
|
{
|
||||||
|
int spinRun = 0;
|
||||||
|
|
||||||
|
for (int step = 0; step < maxSteps; step++)
|
||||||
|
{
|
||||||
|
if (machine.exited)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ushort address = machine.pc;
|
||||||
|
ushort opcode = (ushort)((machine.memory[address] << 8) | machine.memory[address + 1]);
|
||||||
|
ushort? extraWord = null;
|
||||||
|
if (opcode == 0xF000)
|
||||||
|
{
|
||||||
|
extraWord = (ushort)((machine.memory[address + 2] << 8) | machine.memory[address + 3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
string mnemonic = Disassembler.describe(opcode, extraWord ?? 0);
|
||||||
|
var registers = (byte[])machine.v.Clone();
|
||||||
|
entryList.Add(new TraceEntry(step, address, opcode, extraWord, mnemonic, registers, machine.i, machine.sp, machine.delayTimer, machine.soundTimer));
|
||||||
|
|
||||||
|
machine.step();
|
||||||
|
|
||||||
|
if (stopOnSpin && machine.pc == address)
|
||||||
|
{
|
||||||
|
spinRun++;
|
||||||
|
if (spinRun >= 2)
|
||||||
|
{
|
||||||
|
haltedOnSpin = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
spinRun = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<ushort> visitedAddresses()
|
||||||
|
{
|
||||||
|
var visited = new List<ushort>();
|
||||||
|
var seen = new HashSet<ushort>();
|
||||||
|
foreach (var entry in entryList)
|
||||||
|
{
|
||||||
|
if (seen.Add(entry.address))
|
||||||
|
{
|
||||||
|
visited.Add(entry.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return visited;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string render()
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
foreach (var entry in entryList)
|
||||||
|
{
|
||||||
|
builder.Append(entry).Append('\n');
|
||||||
|
}
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user