Skip to content

Commit

Permalink
Misc small changes accrued over time while debugging Spice86 and reve…
Browse files Browse the repository at this point in the history
…rsing Krondor.
  • Loading branch information
JorisVanEijden committed Jun 28, 2024
1 parent 4beb477 commit c88027c
Show file tree
Hide file tree
Showing 20 changed files with 342 additions and 121 deletions.
134 changes: 70 additions & 64 deletions src/Spice86.Core/Emulator/CPU/CPU.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public class Cpu : IDebuggableComponent {
/// CPU uses this internally and adjusts IP after instruction execution is done.
/// </remarks>
private ushort _internalIp;
private readonly CircularBuffer<string> _lastAddresses = new(20);

private readonly IOPortDispatcher _ioPortDispatcher;

Expand All @@ -100,7 +101,7 @@ public Cpu(IMemory memory, State state, DualPic dualPic, IOPortDispatcher ioPort
FunctionHandlerInExternalInterrupt = new FunctionHandler(_memory, state, ExecutionFlowRecorder, _loggerService, recordData);
FunctionHandlerInUse = FunctionHandler;
_modRM = new ModRM(_memory, this, state);
_instructions8 = new Instructions8( this, _memory, _modRM);
_instructions8 = new Instructions8(this, _memory, _modRM);
_instructions16 = new Instructions16(this, _memory, _modRM);
_instructions32 = new Instructions32(this, _memory, _modRM);
_instructions16Or32 = _instructions16;
Expand All @@ -113,16 +114,21 @@ public void ExecuteNextInstruction() {
_loggerService.LoggerPropertyBag.CsIp = new(State.CS, State.IP);

ExecutionFlowRecorder.RegisterExecutedInstruction(State.CS, _internalIp);
#if DEBUG
_lastAddresses.Add($"{State.CS:X4}:{_internalIp:X4}");
#endif
byte opcode = ProcessPrefixes();
if (State.ContinueZeroFlagValue != null && IsStringOpcode(opcode)) {
// continueZeroFlag is either true or false if a rep prefix has been encountered
ProcessRep(opcode);
} else {
try {
ExecOpcode(opcode);
}
catch (CpuException e) {
} catch (CpuException e) {
HandleCpuException(e);
} catch (Exception e) {
_loggerService.Fatal(e, "Cpu Failure {LastAdresses}", _lastAddresses.ToString());
throw;
}
}

Expand Down Expand Up @@ -182,17 +188,17 @@ public void NearRet(int numberOfBytesToPop) {
public uint NextUint32() {
uint res = _memory.UInt32[InternalIpPhysicalAddress];
ExecutionFlowRecorder.RegisterExecutableByte(_memory, State, MachineBreakpoints, State.CS, _internalIp);
ExecutionFlowRecorder.RegisterExecutableByte(_memory, State, MachineBreakpoints, State.CS, (ushort) (_internalIp+1));
ExecutionFlowRecorder.RegisterExecutableByte(_memory, State, MachineBreakpoints, State.CS, (ushort) (_internalIp+2));
ExecutionFlowRecorder.RegisterExecutableByte(_memory, State, MachineBreakpoints, State.CS, (ushort) (_internalIp+3));
ExecutionFlowRecorder.RegisterExecutableByte(_memory, State, MachineBreakpoints, State.CS, (ushort)(_internalIp + 1));
ExecutionFlowRecorder.RegisterExecutableByte(_memory, State, MachineBreakpoints, State.CS, (ushort)(_internalIp + 2));
ExecutionFlowRecorder.RegisterExecutableByte(_memory, State, MachineBreakpoints, State.CS, (ushort)(_internalIp + 3));
_internalIp += 4;
return res;
}

public ushort NextUint16() {
ushort res = _memory.UInt16[InternalIpPhysicalAddress];
ExecutionFlowRecorder.RegisterExecutableByte(_memory, State, MachineBreakpoints, State.CS, _internalIp);
ExecutionFlowRecorder.RegisterExecutableByte(_memory, State, MachineBreakpoints, State.CS, (ushort) (_internalIp+1));
ExecutionFlowRecorder.RegisterExecutableByte(_memory, State, MachineBreakpoints, State.CS, (ushort)(_internalIp + 1));
_internalIp += 2;
return res;
}
Expand All @@ -206,7 +212,7 @@ public byte NextUint8() {

private void HandleCpuException(CpuException cpuException) {
if (_loggerService.IsEnabled(LogEventLevel.Debug)) {
_loggerService.Debug(cpuException,"{ExceptionType} in {MethodName}", nameof(CpuException), nameof(HandleCpuException));
_loggerService.Debug(cpuException, "{ExceptionType} in {MethodName}", nameof(CpuException), nameof(HandleCpuException));
}
if (cpuException.Type is CpuExceptionType.Fault) {
_instructions16Or32 = _instructions16;
Expand Down Expand Up @@ -321,7 +327,7 @@ private void ExecSubOpcode(byte subcode) {
_instructions32.Movsx();
break;
default:
HandleInvalidOpcode(subcode);
HandleInvalidOpcode((ushort)(subcode | 0x0F00));
break;
}
}
Expand Down Expand Up @@ -573,8 +579,8 @@ private void ExecOpcode(byte opcode) {
case 0x61:
_instructions16Or32.Popa();
break;
case 0x62:// BOUND
case 0x63:// ARPL
case 0x62: // BOUND
case 0x63: // ARPL
HandleInvalidOpcode(opcode);
break;
case 0x64:
Expand Down Expand Up @@ -925,41 +931,40 @@ private void ExecOpcode(byte opcode) {
break;
case 0xE0:
case 0xE1: {
// zeroFlag==true => LOOPZ
// zeroFlag==false => LOOPNZ
bool zeroFlag = (opcode & 0x1) == 1;
sbyte address = (sbyte)NextUint8();
bool done = AddressSize switch {
16 => --State.CX == 0,
32 => --State.ECX == 0,
_ => throw new InvalidOperationException($"Invalid address size: {AddressSize}")

};
if (!done && State.ZeroFlag == zeroFlag) {
ushort targetIp = (ushort)(_internalIp + address);
ExecutionFlowRecorder.RegisterJump(State.CS, State.IP, State.CS, targetIp);
_internalIp = targetIp;
}

break;
// zeroFlag==true => LOOPZ
// zeroFlag==false => LOOPNZ
bool zeroFlag = (opcode & 0x1) == 1;
sbyte address = (sbyte)NextUint8();
bool done = AddressSize switch {
16 => --State.CX == 0,
32 => --State.ECX == 0,
_ => throw new InvalidOperationException($"Invalid address size: {AddressSize}")
};
if (!done && State.ZeroFlag == zeroFlag) {
ushort targetIp = (ushort)(_internalIp + address);
ExecutionFlowRecorder.RegisterJump(State.CS, State.IP, State.CS, targetIp);
_internalIp = targetIp;
}
case 0xE2: {
// LOOP
sbyte address = (sbyte)NextUint8();
bool done = AddressSize switch {
16 => --State.CX == 0,
32 => --State.ECX == 0,
_ => throw new InvalidOperationException($"Invalid address size: {AddressSize}")
};

if (!done) {
ushort targetIp = (ushort)(_internalIp + address);
ExecutionFlowRecorder.RegisterJump(State.CS, State.IP, State.CS, targetIp);
_internalIp = targetIp;
}

break;
break;
}
case 0xE2: {
// LOOP
sbyte address = (sbyte)NextUint8();
bool done = AddressSize switch {
16 => --State.CX == 0,
32 => --State.ECX == 0,
_ => throw new InvalidOperationException($"Invalid address size: {AddressSize}")
};

if (!done) {
ushort targetIp = (ushort)(_internalIp + address);
ExecutionFlowRecorder.RegisterJump(State.CS, State.IP, State.CS, targetIp);
_internalIp = targetIp;
}

break;
}
case 0xE3: // JCXZ, JECXZ
Jcc(TestJumpConditionCXZ());
break;
Expand All @@ -976,29 +981,29 @@ private void ExecOpcode(byte opcode) {
_instructions16Or32.OutImm8();
break;
case 0xE8: {
// CALL NEAR
short offset = (short)NextUint16();
ushort nextInstruction = _internalIp;
ushort callAddress = (ushort)(nextInstruction + offset);
NearCall(nextInstruction, callAddress);
break;
}
// CALL NEAR
short offset = (short)NextUint16();
ushort nextInstruction = _internalIp;
ushort callAddress = (ushort)(nextInstruction + offset);
NearCall(nextInstruction, callAddress);
break;
}
case 0xE9: {
short offset = (short)NextUint16();
JumpNear((ushort)(_internalIp + offset));
break;
}
short offset = (short)NextUint16();
JumpNear((ushort)(_internalIp + offset));
break;
}
case 0xEA: {
ushort ip = NextUint16();
ushort cs = NextUint16();
JumpFar(cs, ip);
break;
}
ushort ip = NextUint16();
ushort cs = NextUint16();
JumpFar(cs, ip);
break;
}
case 0xEB: {
sbyte offset = (sbyte)NextUint8();
JumpNear((ushort)(_internalIp + offset));
break;
}
sbyte offset = (sbyte)NextUint8();
JumpNear((ushort)(_internalIp + offset));
break;
}
case 0xEC:
_instructions8.InDx();
break;
Expand Down Expand Up @@ -1330,6 +1335,7 @@ public void JumpNear(ushort ip) {
public void NearCallWithReturnIpNextInstruction(ushort callIP) {
NearCall(_internalIp, callIP);
}

private void NearCall(ushort returnIP, ushort callIP) {
Stack.Push16(returnIP);
HandleCall(CallType.NEAR, State.CS, returnIP, State.CS, callIP);
Expand Down Expand Up @@ -1442,4 +1448,4 @@ public void Accept<T>(T emulatorDebugger) where T : IInternalDebugger {
emulatorDebugger.Visit(this);
State.Accept(emulatorDebugger);
}
}
}
28 changes: 28 additions & 0 deletions src/Spice86.Core/Emulator/CPU/Stack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using Spice86.Core.Emulator.Memory;
using Spice86.Shared.Utils;

using System.Text;

/// <summary>
/// Represents the stack of the CPU.
/// In the x86 architecture, the stack grows downwards, meaning it grows from higher memory addresses to lower memory addresses. <br/>
Expand Down Expand Up @@ -50,6 +52,15 @@ public Stack(IMemory memory, State state) {
/// </summary>
/// <param name="index">The offset from the <see cref="PhysicalAddress"/></param>
/// <returns>The value in memory.</returns>
public byte Peek8(int index) {
return _memory.UInt8[(uint)(PhysicalAddress + index)];
}

/// <summary>
/// Peeks a 16 bit value from the stack
/// </summary>
/// <param name="index">The offset from the <see cref="PhysicalAddress"/></param>
/// <returns>The value in memory.</returns>
public ushort Peek16(int index) {
return _memory.UInt16[(uint)(PhysicalAddress + index)];
}
Expand Down Expand Up @@ -136,4 +147,21 @@ public void SetFlagOnInterruptStack(int flagMask, bool flagValue) {

_memory.UInt16[flagsAddress] = (ushort)value;
}

/// <summary>
/// Returns a string representation of a window around the current stack address.
/// </summary>
/// <param name="range">How many entries to show</param>
/// <returns>A string detailing the addresses and values on the stack around the current stack pointer</returns>
public string PeekWindow(int range = 8) {
var sb = new StringBuilder();
ushort range16 = (ushort)(range << 1);
for (uint i = PhysicalAddress - range16; i < PhysicalAddress + range16; i += 2) {
if (i == PhysicalAddress) {
sb.Append('*');
}
sb.Append("[0x").AppendFormat("{0:X6}", i).Append("] 0x").AppendFormat("{0:X4}", _memory.UInt16[i]).AppendLine();
}
return sb.ToString();
}
}
48 changes: 48 additions & 0 deletions src/Spice86.Core/Emulator/Function/CircularBuffer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
namespace Spice86.Core.Emulator.Function;

using System.Text;

/// <summary>
/// <para>Stores a fixed number of items in a circular buffer.</para>
/// <para>
/// Useful for keeping track of the last X items in a sequence.
/// It "forgets" the oldest item when the buffer is full and just keeps going.
/// </para>
/// </summary>
/// <typeparam name="T">The type of items to store</typeparam>
public class CircularBuffer<T> {
private readonly T[] _buffer;
private int _writeIndex;

/// <summary>
/// Initializes a new instance of the CircularBuffer class.
/// </summary>
/// <param name="capacity">The fixed capacity of the buffer.</param>
public CircularBuffer(int capacity) {
_buffer = new T[capacity];
}

/// <summary>
/// Adds a value to the buffer.
/// </summary>
/// <param name="value"></param>
public void Add(T value) {
_buffer[_writeIndex] = value;
_writeIndex = (_writeIndex + 1) % _buffer.Length;
}

/// <summary>
/// Dumps the buffer to a string.
/// </summary>
/// <returns></returns>
public override string ToString() {
var sb = new StringBuilder();
int bufferLength = _buffer.Length;
for (int i = _writeIndex; i < _writeIndex + bufferLength; i++) {
int bufferIndex = i % bufferLength;
sb.AppendLine(_buffer[bufferIndex]?.ToString());
}

return sb.ToString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public IDictionary<SegmentedAddress, FunctionInformation> ReadFromFileOrCreate(s
return new Dictionary<SegmentedAddress, FunctionInformation>();
}
return File.ReadLines(filePath)
.Select(line => ToFunctionInformation(line))
.Select(ToFunctionInformation)
.OfType<FunctionInformation>()
.Distinct()
.ToDictionary(functionInformation => functionInformation.Address, functionInformation => functionInformation);
Expand All @@ -107,8 +107,8 @@ public IDictionary<SegmentedAddress, FunctionInformation> ReadFromFileOrCreate(s
return NameToFunctionInformation(_loggerService, split[0]);
}

if (_loggerService.IsEnabled(LogEventLevel.Debug)) {
_loggerService.Debug("Cannot parse line {Line} into a function, type is not f", line);
if (_loggerService.IsEnabled(LogEventLevel.Verbose)) {
_loggerService.Verbose("Cannot parse line {Line} into a function, type is not f", line);
}

// Not a function line
Expand Down
14 changes: 13 additions & 1 deletion src/Spice86.Core/Emulator/Function/ExecutionFlowRecorder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using Spice86.Shared.Emulator.Memory;
using Spice86.Shared.Utils;


/// <summary>
/// A class that records machine code execution flow.
/// </summary>
Expand Down Expand Up @@ -46,6 +45,8 @@ public class ExecutionFlowRecorder {
private readonly HashSet<uint> _instructionsEncountered = new(200000);
private readonly HashSet<uint> _executableCodeAreasEncountered = new(200000);

private readonly CircularBuffer<string> _callStack = new(20);

/// <summary>
/// Gets or sets whether we register self modifying machine code.
/// </summary>
Expand Down Expand Up @@ -81,6 +82,9 @@ public ExecutionFlowRecorder() {
/// <param name="toIP">The offset of the address being called.</param>
public void RegisterCall(ushort fromCS, ushort fromIP, ushort toCS, ushort toIP) {
RegisterAddressJump(CallsFromTo, _callsEncountered, fromCS, fromIP, toCS, toIP);
#if DEBUG
_callStack.Add($"{fromCS:X4}:{fromIP:X4} -> {toCS:X4}:{toIP:X4}");
#endif
}

/// <summary>
Expand Down Expand Up @@ -230,4 +234,12 @@ private void RegisterAddressJump(IDictionary<uint, HashSet<SegmentedAddress>> Fr
}
destinationAddresses.Add(new SegmentedAddress(toCS, toIP));
}

/// <summary>
/// Lists the current call stack.
/// </summary>
/// <returns></returns>
public string DumpCallStack() {
return _callStack.ToString();
}
}
Loading

0 comments on commit c88027c

Please sign in to comment.