-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CFG_CPU: CFG Graph based CPU emulation passing a few bin tests (#494)
* 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
1 parent
55d13fa
commit a98047c
Showing
180 changed files
with
6,721 additions
and
148 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
20
src/Spice86.Core/Emulator/CPU/CfgCpu/ControlFlowGraph/CfgNode.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
41
src/Spice86.Core/Emulator/CPU/CfgCpu/ControlFlowGraph/ICfgNode.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
|
||
} |
6 changes: 6 additions & 0 deletions
6
src/Spice86.Core/Emulator/CPU/CfgCpu/Exceptions/UnhandledCfgDiscrepancyException.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
86
src/Spice86.Core/Emulator/CPU/CfgCpu/Feeder/CfgNodeFeeder.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
100
src/Spice86.Core/Emulator/CPU/CfgCpu/Feeder/CurrentInstructions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.