Skip to content

Commit

Permalink
CFG_CPU: CFG Graph based CPU emulation passing a few bin tests (#494)
Browse files Browse the repository at this point in the history
* Disable execution via CFG CPU for now

* New CPU that parses instructions the first time they are encountered and puts the parsing result in a cache. Instructions are chained in a ICFG graph.

* CfgCpu: ModRm support

* Tests for modrm parser and executor

* First try at a CPU CFG UI (#634)

Signed-off-by: Maximilien Noal <noal.maximilien@gmail.com>

* refactor: internal debugger on CfgCpu...

...and visits the State, the successors for the graph, and tries to
disasm the current instructions.

Also the graph is not rendered if the emulator is not paused.

And it should work with loops.

Finally, it displays some meta informations about the graph.

Signed-off-by: Maximilien Noal <noal.maximilien@gmail.com>

* Added all the instructions needed for CFG CPU to execute add.bin and sub.bin (#636)

* Update DebugWindow.axaml

* UI: Show Debug on error possible

Signed-off-by: Maximilien Noal <noal.maximilien@gmail.com>

* Templatize instructions definition and move execution to their respective classes thanks to code generator

* Move register indexes to their own enum

---------

Signed-off-by: Maximilien Noal <noal.maximilien@gmail.com>
Co-authored-by: Maximilien Noal <noal.maximilien@gmail.com>
  • Loading branch information
kevinferrare and maximilien-noal authored Jun 20, 2024
1 parent 55d13fa commit a98047c
Show file tree
Hide file tree
Showing 180 changed files with 6,721 additions and 148 deletions.
12 changes: 6 additions & 6 deletions src/Spice86.Core/Emulator/CPU/CPU.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1357,27 +1357,27 @@ private bool ProcessPrefix(int opcode) {
switch (opcode) {
case 0x26:
// ES:
State.SegmentOverrideIndex = SegmentRegisters.EsIndex;
State.SegmentOverrideIndex = (uint)SegmentRegisterIndex.EsIndex;
break;
case 0x2E:
// CS:
State.SegmentOverrideIndex = SegmentRegisters.CsIndex;
State.SegmentOverrideIndex = (uint)SegmentRegisterIndex.CsIndex;
break;
case 0x36:
// SS:
State.SegmentOverrideIndex = SegmentRegisters.SsIndex;
State.SegmentOverrideIndex = (uint)SegmentRegisterIndex.SsIndex;
break;
case 0x3E:
// DS:
State.SegmentOverrideIndex = SegmentRegisters.DsIndex;
State.SegmentOverrideIndex = (uint)SegmentRegisterIndex.DsIndex;
break;
case 0x64:
// FS:
State.SegmentOverrideIndex = SegmentRegisters.FsIndex;
State.SegmentOverrideIndex = (uint)SegmentRegisterIndex.FsIndex;
break;
case 0x65:
// GS
State.SegmentOverrideIndex = SegmentRegisters.GsIndex;
State.SegmentOverrideIndex = (uint)SegmentRegisterIndex.GsIndex;
break;
case 0x66:
_instructions16Or32 = _instructions32;
Expand Down
49 changes: 49 additions & 0 deletions src/Spice86.Core/Emulator/CPU/CfgCpu/CfgCpu.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
namespace Spice86.Core.Emulator.CPU.CfgCpu;

using Spice86.Core.Emulator.CPU.CfgCpu.ControlFlowGraph;
using Spice86.Core.Emulator.CPU.CfgCpu.Feeder;
using Spice86.Core.Emulator.CPU.CfgCpu.InstructionExecutor;
using Spice86.Core.Emulator.CPU.CfgCpu.Linker;
using Spice86.Core.Emulator.Devices.ExternalInput;
using Spice86.Core.Emulator.InternalDebugger;
using Spice86.Core.Emulator.InterruptHandlers.Common.Callback;
using Spice86.Core.Emulator.IOPorts;
using Spice86.Core.Emulator.Memory;
using Spice86.Core.Emulator.VM;

public class CfgCpu : IDebuggableComponent {
private readonly InstructionExecutionHelper _instructionExecutionHelper;
private readonly State _state;
private readonly DualPic _dualPic;

private readonly CfgNodeFeeder _cfgNodeFeeder;

public CfgCpu(IMemory memory, State state, IOPortDispatcher? ioPortDispatcher, CallbackHandler callbackHandler,
DualPic dualPic, MachineBreakpoints machineBreakpoints) {
_instructionExecutionHelper = new(state, memory, ioPortDispatcher, callbackHandler);
_state = state;
_dualPic = dualPic;
_cfgNodeFeeder = new(memory, state, machineBreakpoints);
}

private ExecutionContext CurrentExecutionContext { get; } = new();

public void Accept<T>(T emulatorDebugger) where T : IInternalDebugger {
emulatorDebugger.Visit(this);
_state.Accept(emulatorDebugger);
CurrentExecutionContext.Accept(emulatorDebugger);
}

public void ExecuteNext() {
ICfgNode toExecute = _cfgNodeFeeder.GetLinkedCfgNodeToExecute(CurrentExecutionContext);

// Execute the node
toExecute.Execute(_instructionExecutionHelper);
ICfgNode? nextToExecute = _instructionExecutionHelper.NextNode;
_state.IncCycles();

// Register what was executed and what is next node according to the graph in the execution context for next pass
CurrentExecutionContext.LastExecuted = toExecute;
CurrentExecutionContext.NodeToExecuteNextAccordingToGraph = nextToExecute;
}
}
20 changes: 20 additions & 0 deletions src/Spice86.Core/Emulator/CPU/CfgCpu/ControlFlowGraph/CfgNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace Spice86.Core.Emulator.CPU.CfgCpu.ControlFlowGraph;

using Spice86.Core.Emulator.CPU.CfgCpu.InstructionExecutor;
using Spice86.Shared.Emulator.Memory;

public abstract class CfgNode : ICfgNode {
public CfgNode(SegmentedAddress address) {
Address = address;
}

public HashSet<ICfgNode> Predecessors { get; } = new();
public HashSet<ICfgNode> Successors { get; } = new();
public SegmentedAddress Address { get; }

public abstract bool IsAssembly { get; }

public abstract void UpdateSuccessorCache();

public abstract void Execute(InstructionExecutionHelper helper);
}
41 changes: 41 additions & 0 deletions src/Spice86.Core/Emulator/CPU/CfgCpu/ControlFlowGraph/ICfgNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace Spice86.Core.Emulator.CPU.CfgCpu.ControlFlowGraph;

using Spice86.Core.Emulator.CPU.CfgCpu.InstructionExecutor;
using Spice86.Shared.Emulator.Memory;

/// <summary>
/// Represents a node in the CFG graph.
/// </summary>
public interface ICfgNode {
/// <summary>
/// Nodes that were executed before this node
/// </summary>
HashSet<ICfgNode> Predecessors { get; }

/// <summary>
/// Nodes that were executed after this node
/// </summary>
HashSet<ICfgNode> Successors { get; }

/// <summary>
/// Address of the node in memory
/// </summary>
public SegmentedAddress Address { get; }

/// <summary>
/// True when the node represents an assembly instruction that exists or has existed in memory
/// </summary>
public bool IsAssembly { get; }

/// <summary>
/// Needs to be called each time a successor is added
/// </summary>
public void UpdateSuccessorCache();

/// <summary>
/// Execute this node
/// </summary>
/// <param name="context">InstructionExecutionContext instance providing access to the outside</param>
public void Execute(InstructionExecutionHelper helper);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Spice86.Core.Emulator.CPU.CfgCpu.Exceptions;

public class UnhandledCfgDiscrepancyException : Exception {
public UnhandledCfgDiscrepancyException(string message) : base(message) {
}
}
86 changes: 86 additions & 0 deletions src/Spice86.Core/Emulator/CPU/CfgCpu/Feeder/CfgNodeFeeder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
namespace Spice86.Core.Emulator.CPU.CfgCpu.Feeder;

using Spice86.Core.Emulator.CPU.CfgCpu.ControlFlowGraph;
using Spice86.Core.Emulator.CPU.CfgCpu.Linker;
using Spice86.Core.Emulator.CPU.CfgCpu.ParsedInstruction;
using Spice86.Core.Emulator.CPU.CfgCpu.ParsedInstruction.SelfModifying;
using Spice86.Core.Emulator.Memory;
using Spice86.Core.Emulator.VM;

using System.Linq;

/// <summary>
/// Handles coherency between the memory and the graph of instructions executed by the CPU.
/// Next node to execute is normally the next node from the graph but several checks are done to make sure it is really it:
/// - The node is not null (otherwise it is taken from memory)
/// - If the node is an assembly node, it is the same as what is currently in memory, otherwise it means self modifying code is beeing detected
/// - If self modifying code is beeing detected, Discriminator node is beeing injected instead.
/// Once the node to execute is determined, it is linked to the previously executed node in the execution context if possible.
/// </summary>
public class CfgNodeFeeder {
private readonly State _state;
private readonly InstructionsFeeder _instructionsFeeder;
private readonly NodeLinker _nodeLinker = new();
private readonly DiscriminatorReducer _discriminatorReducer;

public CfgNodeFeeder(IMemory memory, State state, MachineBreakpoints machineBreakpoints) {
_state = state;
_instructionsFeeder = new(machineBreakpoints, memory, state);
_discriminatorReducer = new(new List<IInstructionReplacer<CfgInstruction>>()
{ _nodeLinker, _instructionsFeeder });
}

private CfgInstruction CurrentNodeFromInstructionFeeder =>
_instructionsFeeder.GetInstructionFromMemory(_state.IpSegmentedAddress);

public ICfgNode GetLinkedCfgNodeToExecute(ExecutionContext executionContext) {
// Determine actual node to execute. Graph may not represent what is actually in memory if graph is not complete or if self modifying code
ICfgNode toExecute = DetermineToExecute(executionContext.NodeToExecuteNextAccordingToGraph);
if (executionContext.LastExecuted != null) {
// Register what we found in the graph
_nodeLinker.Link(executionContext.LastExecuted, toExecute);
}

return toExecute;
}

private ICfgNode DetermineToExecute(ICfgNode? currentFromGraph) {
if (currentFromGraph == null) {
// Need to fetch from feeder
return CurrentNodeFromInstructionFeeder;
}

if (!currentFromGraph.IsAssembly) {
// Cannot check if match with memory. Just execute it.
return currentFromGraph;
}

CfgInstruction fromMemory = CurrentNodeFromInstructionFeeder;
if (ReferenceEquals(fromMemory, currentFromGraph)) {
// Instruction is assembly and Graph agrees with memory. Nominal case.
return currentFromGraph;
}

// Graph and memory are not aligned ... Need to inject Node with discriminator
// If previous was Discriminated and current was not in its successors we would not be there because
// currentFromGraph would have been null and the linker would then link it to DiscriminatedNode
return CreateDiscriminatedNode(fromMemory, (CfgInstruction)currentFromGraph);
}

private ICfgNode CreateDiscriminatedNode(CfgInstruction instruction1, CfgInstruction instruction2) {
IList<CfgInstruction> reducedInstructions =
_discriminatorReducer.ReduceAll(new List<CfgInstruction>() { instruction1, instruction2 });
if (reducedInstructions.Count == 1) {
return reducedInstructions.First();
}

DiscriminatedNode res = new DiscriminatedNode(instruction1.Address);
foreach (CfgInstruction reducedInstruction in reducedInstructions) {
// Make predecessors of instructions point to res instead of instruction1/2
_nodeLinker.InsertIntermediatePredecessor(reducedInstruction, res);
}

res.UpdateSuccessorCache();
return res;
}
}
100 changes: 100 additions & 0 deletions src/Spice86.Core/Emulator/CPU/CfgCpu/Feeder/CurrentInstructions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
namespace Spice86.Core.Emulator.CPU.CfgCpu.Feeder;

using Spice86.Core.Emulator.CPU.CfgCpu.ParsedInstruction;
using Spice86.Core.Emulator.Memory;
using Spice86.Core.Emulator.VM;
using Spice86.Core.Emulator.VM.Breakpoint;
using Spice86.Shared.Emulator.Memory;

/// <summary>
/// Cache of current instructions in memory.
/// Cache coherency is managed by breakpoints, as soon as an instruction is written in memory it is evicted.
/// </summary>
public class CurrentInstructions : IInstructionReplacer<CfgInstruction> {
private readonly IMemory _memory;
private readonly MachineBreakpoints _machineBreakpoints;

/// <summary>
/// Instruction currently known to be in memory at a given address.
/// Memory write breakpoints invalidate this cache when CPU writes there.
/// </summary>
private readonly Dictionary<SegmentedAddress, CfgInstruction> _currentInstructionAtAddress =
new Dictionary<SegmentedAddress, CfgInstruction>();


/// <summary>
/// Breakpoints that have been installed to monitor instruction at a given address. So that we can reset them when we want.
/// </summary>
private readonly Dictionary<SegmentedAddress, List<AddressBreakPoint>> _breakpointsForInstruction =
new Dictionary<SegmentedAddress, List<AddressBreakPoint>>();

public CurrentInstructions(IMemory memory, MachineBreakpoints machineBreakpoints) {
_memory = memory;
_machineBreakpoints = machineBreakpoints;
}

public CfgInstruction? GetAtAddress(SegmentedAddress address) {
_currentInstructionAtAddress.TryGetValue(address, out CfgInstruction? res);
return res;
}

public void ReplaceInstruction(CfgInstruction old, CfgInstruction instruction) {
SegmentedAddress instructionAddress = instruction.Address;
if (_currentInstructionAtAddress.ContainsKey(instructionAddress)) {
ClearCurrentInstruction(old);
SetAsCurrent(instruction);
}
}


public void SetAsCurrent(CfgInstruction instruction) {
// Set breakpoints so that we are notified if instruction changes in memory
CreateBreakpointsForInstruction(instruction);
// Add instruction in current cache
AddInstructionInCurrentCache(instruction);
}

private void CreateBreakpointsForInstruction(CfgInstruction instruction) {
SegmentedAddress instructionAddress = instruction.Address;
List<AddressBreakPoint> breakpoints = new();
_breakpointsForInstruction.Add(instructionAddress, breakpoints);
uint instructionPhysicalAddress = instructionAddress.ToPhysical();
for (uint byteAddress = instructionPhysicalAddress;
byteAddress < instructionPhysicalAddress + instruction.Length;
byteAddress++) {
// When reached the breakpoint will clear the cache and the other breakpoints for the instruction
AddressBreakPoint breakPoint = new AddressBreakPoint(BreakPointType.WRITE, byteAddress,
b => { OnBreakPointReached(b, instruction); }, false);
breakpoints.Add(breakPoint);
_machineBreakpoints.ToggleBreakPoint(breakPoint, true);
}
}

private void OnBreakPointReached(BreakPoint breakPoint, CfgInstruction instruction) {
if (breakPoint is AddressBreakPoint addressBreakPoint) {
// Check that value is effectively beeing modified
byte current = _memory.UInt8[addressBreakPoint.Address];
byte newValue = _memory.CurrentlyWritingByte;
if (current == newValue) {
return;
}
}
ClearCurrentInstruction(instruction);
}

private void AddInstructionInCurrentCache(CfgInstruction instruction) {
SegmentedAddress instructionAddress = instruction.Address;
_currentInstructionAtAddress.Add(instructionAddress, instruction);
}

private void ClearCurrentInstruction(CfgInstruction instruction) {
SegmentedAddress instructionAddress = instruction.Address;
IList<AddressBreakPoint> breakpoints = _breakpointsForInstruction[instructionAddress];
_breakpointsForInstruction.Remove(instructionAddress);
foreach (AddressBreakPoint breakPoint in breakpoints) {
_machineBreakpoints.ToggleBreakPoint(breakPoint, false);
}

_currentInstructionAtAddress.Remove(instruction.Address);
}
}
Loading

0 comments on commit a98047c

Please sign in to comment.