feat(analysis): add execution trace recorder

This commit is contained in:
dualfroz committed 2026-08-25 17:03:20 +02:00
1 parent 210cf5c6b1
commit 6ff06cfe6e
2 files changed
+154

No files matched your search

+61
View File
@@ -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);
}
}
+93
View File
@@ -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();
}
}