From c88027c8e04e43957705a856f4065ddcffb964f9 Mon Sep 17 00:00:00 2001 From: JvE Date: Fri, 28 Jun 2024 18:43:46 +0200 Subject: [PATCH] Misc small changes accrued over time while debugging Spice86 and reversing Krondor. --- src/Spice86.Core/Emulator/CPU/CPU.cs | 134 +++++++++--------- src/Spice86.Core/Emulator/CPU/Stack.cs | 28 ++++ .../Emulator/Function/CircularBuffer.cs | 48 +++++++ .../Function/Dump/GhidraSymbolsDumper.cs | 6 +- .../Function/ExecutionFlowRecorder.cs | 14 +- .../Emulator/Function/FunctionHandler.cs | 8 +- src/Spice86.Core/Emulator/Gdb/GdbIo.cs | 23 ++- .../Emulator/IOPorts/DefaultIOPortHandler.cs | 4 +- .../Emulator/IOPorts/IOPortDispatcher.cs | 26 ++-- .../InterruptHandlers/Dos/DosInt21Handler.cs | 4 +- .../Dos/Ems/ExpandedMemoryManager.cs | 4 +- .../Emulator/InterruptHandlers/VGA/VgaBios.cs | 4 + .../LoadableFile/Dos/Exe/ExeLoader.cs | 4 +- .../OperatingSystem/DosFileManager.cs | 8 +- .../OperatingSystem/DosMemoryManager.cs | 2 +- .../Structures/DosMemoryControlBlock.cs | 13 +- .../ReverseEngineer/ArgumentFetcher.cs | 103 ++++++++++++++ .../ReverseEngineer/CSharpOverrideHelper.cs | 23 ++- src/Spice86.Logging/LoggerService.cs | 5 +- .../Emulator/Memory/SegmentedAddress.cs | 2 +- 20 files changed, 342 insertions(+), 121 deletions(-) create mode 100644 src/Spice86.Core/Emulator/Function/CircularBuffer.cs create mode 100644 src/Spice86.Core/Emulator/ReverseEngineer/ArgumentFetcher.cs diff --git a/src/Spice86.Core/Emulator/CPU/CPU.cs b/src/Spice86.Core/Emulator/CPU/CPU.cs index 0d07d0822..a775c9853 100644 --- a/src/Spice86.Core/Emulator/CPU/CPU.cs +++ b/src/Spice86.Core/Emulator/CPU/CPU.cs @@ -77,6 +77,7 @@ public class Cpu : IDebuggableComponent { /// CPU uses this internally and adjusts IP after instruction execution is done. /// private ushort _internalIp; + private readonly CircularBuffer _lastAddresses = new(20); private readonly IOPortDispatcher _ioPortDispatcher; @@ -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; @@ -113,6 +114,9 @@ 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 @@ -120,9 +124,11 @@ public void ExecuteNextInstruction() { } else { try { ExecOpcode(opcode); - } - catch (CpuException e) { + } catch (CpuException e) { HandleCpuException(e); + } catch (Exception e) { + _loggerService.Fatal(e, "Cpu Failure {LastAdresses}", _lastAddresses.ToString()); + throw; } } @@ -182,9 +188,9 @@ 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; } @@ -192,7 +198,7 @@ public uint NextUint32() { 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; } @@ -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; @@ -321,7 +327,7 @@ private void ExecSubOpcode(byte subcode) { _instructions32.Movsx(); break; default: - HandleInvalidOpcode(subcode); + HandleInvalidOpcode((ushort)(subcode | 0x0F00)); break; } } @@ -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: @@ -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; @@ -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; @@ -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); @@ -1442,4 +1448,4 @@ public void Accept(T emulatorDebugger) where T : IInternalDebugger { emulatorDebugger.Visit(this); State.Accept(emulatorDebugger); } -} +} \ No newline at end of file diff --git a/src/Spice86.Core/Emulator/CPU/Stack.cs b/src/Spice86.Core/Emulator/CPU/Stack.cs index e9ff0b1d3..6ab062046 100644 --- a/src/Spice86.Core/Emulator/CPU/Stack.cs +++ b/src/Spice86.Core/Emulator/CPU/Stack.cs @@ -3,6 +3,8 @@ using Spice86.Core.Emulator.Memory; using Spice86.Shared.Utils; +using System.Text; + /// /// 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.
@@ -50,6 +52,15 @@ public Stack(IMemory memory, State state) { ///
/// The offset from the /// The value in memory. + public byte Peek8(int index) { + return _memory.UInt8[(uint)(PhysicalAddress + index)]; + } + + /// + /// Peeks a 16 bit value from the stack + /// + /// The offset from the + /// The value in memory. public ushort Peek16(int index) { return _memory.UInt16[(uint)(PhysicalAddress + index)]; } @@ -136,4 +147,21 @@ public void SetFlagOnInterruptStack(int flagMask, bool flagValue) { _memory.UInt16[flagsAddress] = (ushort)value; } + + /// + /// Returns a string representation of a window around the current stack address. + /// + /// How many entries to show + /// A string detailing the addresses and values on the stack around the current stack pointer + 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(); + } } \ No newline at end of file diff --git a/src/Spice86.Core/Emulator/Function/CircularBuffer.cs b/src/Spice86.Core/Emulator/Function/CircularBuffer.cs new file mode 100644 index 000000000..2bb60c69a --- /dev/null +++ b/src/Spice86.Core/Emulator/Function/CircularBuffer.cs @@ -0,0 +1,48 @@ +namespace Spice86.Core.Emulator.Function; + +using System.Text; + +/// +/// Stores a fixed number of items in a circular buffer. +/// +/// 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. +/// +/// +/// The type of items to store +public class CircularBuffer { + private readonly T[] _buffer; + private int _writeIndex; + + /// + /// Initializes a new instance of the CircularBuffer class. + /// + /// The fixed capacity of the buffer. + public CircularBuffer(int capacity) { + _buffer = new T[capacity]; + } + + /// + /// Adds a value to the buffer. + /// + /// + public void Add(T value) { + _buffer[_writeIndex] = value; + _writeIndex = (_writeIndex + 1) % _buffer.Length; + } + + /// + /// Dumps the buffer to a string. + /// + /// + 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(); + } +} \ No newline at end of file diff --git a/src/Spice86.Core/Emulator/Function/Dump/GhidraSymbolsDumper.cs b/src/Spice86.Core/Emulator/Function/Dump/GhidraSymbolsDumper.cs index 07575219b..69c46820b 100644 --- a/src/Spice86.Core/Emulator/Function/Dump/GhidraSymbolsDumper.cs +++ b/src/Spice86.Core/Emulator/Function/Dump/GhidraSymbolsDumper.cs @@ -89,7 +89,7 @@ public IDictionary ReadFromFileOrCreate(s return new Dictionary(); } return File.ReadLines(filePath) - .Select(line => ToFunctionInformation(line)) + .Select(ToFunctionInformation) .OfType() .Distinct() .ToDictionary(functionInformation => functionInformation.Address, functionInformation => functionInformation); @@ -107,8 +107,8 @@ public IDictionary 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 diff --git a/src/Spice86.Core/Emulator/Function/ExecutionFlowRecorder.cs b/src/Spice86.Core/Emulator/Function/ExecutionFlowRecorder.cs index 346d571af..6c1e569ee 100644 --- a/src/Spice86.Core/Emulator/Function/ExecutionFlowRecorder.cs +++ b/src/Spice86.Core/Emulator/Function/ExecutionFlowRecorder.cs @@ -9,7 +9,6 @@ using Spice86.Shared.Emulator.Memory; using Spice86.Shared.Utils; - /// /// A class that records machine code execution flow. /// @@ -46,6 +45,8 @@ public class ExecutionFlowRecorder { private readonly HashSet _instructionsEncountered = new(200000); private readonly HashSet _executableCodeAreasEncountered = new(200000); + private readonly CircularBuffer _callStack = new(20); + /// /// Gets or sets whether we register self modifying machine code. /// @@ -81,6 +82,9 @@ public ExecutionFlowRecorder() { /// The offset of the address being called. 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 } /// @@ -230,4 +234,12 @@ private void RegisterAddressJump(IDictionary> Fr } destinationAddresses.Add(new SegmentedAddress(toCS, toIP)); } + + /// + /// Lists the current call stack. + /// + /// + public string DumpCallStack() { + return _callStack.ToString(); + } } \ No newline at end of file diff --git a/src/Spice86.Core/Emulator/Function/FunctionHandler.cs b/src/Spice86.Core/Emulator/Function/FunctionHandler.cs index 734acedbd..294a04c22 100644 --- a/src/Spice86.Core/Emulator/Function/FunctionHandler.cs +++ b/src/Spice86.Core/Emulator/Function/FunctionHandler.cs @@ -78,8 +78,8 @@ public void Call(CallType callType, ushort entrySegment, ushort entryOffset, ush FunctionCall currentFunctionCall = new(callType, entryAddress, expectedReturnAddress, CurrentStackAddress, recordReturn); _callerStack.Push(currentFunctionCall); - if (_loggerService.IsEnabled(LogEventLevel.Debug)) { - _loggerService.Debug("Calling {CurrentFunction} from {Caller}", currentFunction, caller); + if (_loggerService.IsEnabled(LogEventLevel.Verbose)) { + _loggerService.Verbose("Calling {CurrentFunction} from {Caller}", currentFunction, caller); } currentFunction.Enter(caller); @@ -195,8 +195,8 @@ public bool Ret(CallType returnCallType) { } FunctionInformation? currentFunctionInformation = GetFunctionInformation(currentFunctionCall); bool returnAddressAlignedWithCallStack = AddReturn(returnCallType, currentFunctionCall, currentFunctionInformation); - if (_loggerService.IsEnabled(LogEventLevel.Debug)) { - _loggerService.Debug("Returning from {CurrentFunctionInformation} to {CurrentFunctionCall}", currentFunctionInformation, GetFunctionInformation(CurrentFunctionCall)); + if (_loggerService.IsEnabled(LogEventLevel.Verbose)) { + _loggerService.Verbose("Returning from {CurrentFunctionInformation} to {CurrentFunctionCall}", currentFunctionInformation, GetFunctionInformation(CurrentFunctionCall)); } if (!returnAddressAlignedWithCallStack) { diff --git a/src/Spice86.Core/Emulator/Gdb/GdbIo.cs b/src/Spice86.Core/Emulator/Gdb/GdbIo.cs index fadc04d34..b56f6fcb6 100644 --- a/src/Spice86.Core/Emulator/Gdb/GdbIo.cs +++ b/src/Spice86.Core/Emulator/Gdb/GdbIo.cs @@ -1,14 +1,14 @@ namespace Spice86.Core.Emulator.Gdb; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; +using Serilog.Events; using Spice86.Shared.Interfaces; - using Spice86.Shared.Utils; +using System.Net; +using System.Net.Sockets; +using System.Text; + /// /// Handles the I/O operations for a GDB (GNU Debugger) connection. /// @@ -27,9 +27,8 @@ public sealed class GdbIo : IDisposable { /// The port number to listen on. /// The logger service implementation. public GdbIo(int port, ILoggerService loggerService) { - _loggerService = loggerService; - IPHostEntry host = Dns.GetHostEntry("localhost"); - IPAddress ip = new IPAddress(host.AddressList.First().GetAddressBytes()); + _loggerService = loggerService.WithLogLevel(LogEventLevel.Debug); + IPAddress ip = IPAddress.Any; _tcpListener = new TcpListener(ip, port); } @@ -39,7 +38,7 @@ public GdbIo(int port, ILoggerService loggerService) { public void WaitForConnection() { _tcpListener.Start(); _socket = _tcpListener.AcceptSocket(); - if (_loggerService.IsEnabled(Serilog.Events.LogEventLevel.Information)) { + if (_loggerService.IsEnabled(LogEventLevel.Information)) { int port = ((IPEndPoint)_tcpListener.LocalEndpoint).Port; _loggerService.Information("GDB Server listening on port {Port}", port); _loggerService.Information("Client connected: {@CanonicalHostName}", _socket.RemoteEndPoint); @@ -121,7 +120,7 @@ public string ReadCommand() { chr = _stream.ReadByte(); } string payload = GetPayload(resBuilder); - if (_loggerService.IsEnabled(Serilog.Events.LogEventLevel.Debug)) { + if (_loggerService.IsEnabled(LogEventLevel.Debug)) { _loggerService.Debug("Received command from GDB {GdbPayload}", payload); } return payload; @@ -133,14 +132,14 @@ public string ReadCommand() { /// The response data to send. public void SendResponse(string? data) { if (!IsClientConnected) { - if (_loggerService.IsEnabled(Serilog.Events.LogEventLevel.Debug)) { + if (_loggerService.IsEnabled(LogEventLevel.Debug)) { _loggerService.Debug("Cannot send response, client is not connected anymore"); } // Happens when the emulator thread reaches a breakpoint but the client is gone return; } if (data != null) { - if (_loggerService.IsEnabled(Serilog.Events.LogEventLevel.Verbose)) { + if (_loggerService.IsEnabled(LogEventLevel.Verbose)) { _loggerService.Verbose("Sending response {ResponseData}", data); } _stream?.Write(Encoding.UTF8.GetBytes(data)); diff --git a/src/Spice86.Core/Emulator/IOPorts/DefaultIOPortHandler.cs b/src/Spice86.Core/Emulator/IOPorts/DefaultIOPortHandler.cs index 2dde1349f..5b3168f66 100644 --- a/src/Spice86.Core/Emulator/IOPorts/DefaultIOPortHandler.cs +++ b/src/Spice86.Core/Emulator/IOPorts/DefaultIOPortHandler.cs @@ -96,7 +96,7 @@ public virtual byte ReadByte(int port) { /// The name of the calling method. Automatically populated if not specified. protected void LogUnhandledPortRead(int port, [CallerMemberName] string? methodName = null) { if (_loggerService.IsEnabled(LogEventLevel.Error)) { - _loggerService.Error("Unhandled port read: {PortNumber} in {MethodName}", port, methodName); + _loggerService.Error("Unhandled port read: 0x{PortNumber:X4} in {MethodName}", port, methodName); } } @@ -110,7 +110,7 @@ protected void LogUnhandledPortRead(int port, [CallerMemberName] string? methodN protected void LogUnhandledPortWrite(int port, T value, [CallerMemberName] string? methodName = null) where T : INumber { if (_loggerService.IsEnabled(LogEventLevel.Error)) { - _loggerService.Error("Unhandled port write: {PortNumber}, {Value} in {MethodName}", port, value, + _loggerService.Error("Unhandled port write: 0x{PortNumber:X4}, 0x{Value:X4} in {MethodName}", port, value, methodName); } } diff --git a/src/Spice86.Core/Emulator/IOPorts/IOPortDispatcher.cs b/src/Spice86.Core/Emulator/IOPorts/IOPortDispatcher.cs index 2f14a4348..c4b916af6 100644 --- a/src/Spice86.Core/Emulator/IOPorts/IOPortDispatcher.cs +++ b/src/Spice86.Core/Emulator/IOPorts/IOPortDispatcher.cs @@ -1,4 +1,4 @@ -namespace Spice86.Core.Emulator.IOPorts; +namespace Spice86.Core.Emulator.IOPorts; using Serilog.Events; @@ -37,8 +37,8 @@ public override void InitPortHandlers(IOPortDispatcher ioPortDispatcher) { /// public override byte ReadByte(int port) { if (_ioPortHandlers.TryGetValue(port, out IIOPortHandler? entry)) { - if (_loggerService.IsEnabled(LogEventLevel.Debug)) { - _loggerService.Debug("{MethodName} {PortHandlerTypeName} {PortNumber}", nameof(ReadByte), + if (_loggerService.IsEnabled(LogEventLevel.Verbose)) { + _loggerService.Verbose("{MethodName} {PortHandlerTypeName} {PortNumber}", nameof(ReadByte), entry.GetType(), port); } return entry.ReadByte(port); @@ -50,8 +50,8 @@ public override byte ReadByte(int port) { /// public override ushort ReadWord(int port) { if (_ioPortHandlers.TryGetValue(port, out IIOPortHandler? entry)) { - if (_loggerService.IsEnabled(LogEventLevel.Debug)) { - _loggerService.Debug("{MethodName} {PortHandlerTypeName} {PortNumber}", nameof(ReadWord), + if (_loggerService.IsEnabled(LogEventLevel.Verbose)) { + _loggerService.Verbose("{MethodName} {PortHandlerTypeName} {PortNumber}", nameof(ReadWord), entry.GetType(), port); } return entry.ReadWord(port); @@ -63,8 +63,8 @@ public override ushort ReadWord(int port) { /// public override uint ReadDWord(int port) { if (_ioPortHandlers.TryGetValue(port, out IIOPortHandler? entry)) { - if (_loggerService.IsEnabled(LogEventLevel.Debug)) { - _loggerService.Debug("{MethodName} {PortHandlerTypeName} {PortNumber}", nameof(ReadDWord), + if (_loggerService.IsEnabled(LogEventLevel.Verbose)) { + _loggerService.Verbose("{MethodName} {PortHandlerTypeName} {PortNumber}", nameof(ReadDWord), entry.GetType(), port); } return entry.ReadDWord(port); @@ -76,8 +76,8 @@ public override uint ReadDWord(int port) { /// public override void WriteByte(int port, byte value) { if (_ioPortHandlers.TryGetValue(port, out IIOPortHandler? entry)) { - if (_loggerService.IsEnabled(LogEventLevel.Debug)) { - _loggerService.Debug("{MethodName} {PortHandlerTypeName} {PortNumber} {WrittenValue}", + if (_loggerService.IsEnabled(LogEventLevel.Verbose)) { + _loggerService.Verbose("{MethodName} {PortHandlerTypeName} {PortNumber} {WrittenValue}", nameof(WriteByte), entry.GetType(), port, value); } entry.WriteByte(port, value); @@ -89,8 +89,8 @@ public override void WriteByte(int port, byte value) { /// public override void WriteWord(int port, ushort value) { if (_ioPortHandlers.TryGetValue(port, out IIOPortHandler? entry)) { - if (_loggerService.IsEnabled(LogEventLevel.Debug)) { - _loggerService.Debug("{MethodName} {PortHandlerTypeName} {PortNumber} {WrittenValue}", + if (_loggerService.IsEnabled(LogEventLevel.Verbose)) { + _loggerService.Verbose("{MethodName} {PortHandlerTypeName} {PortNumber} {WrittenValue}", nameof(WriteWord), entry.GetType(), port, value); } entry.WriteWord(port, value); @@ -102,8 +102,8 @@ public override void WriteWord(int port, ushort value) { /// public override void WriteDWord(int port, uint value) { if (_ioPortHandlers.TryGetValue(port, out IIOPortHandler? entry)) { - if (_loggerService.IsEnabled(LogEventLevel.Debug)) { - _loggerService.Debug("{MethodName} {PortHandlerTypeName} {PortNumber} {WrittenValue}", + if (_loggerService.IsEnabled(LogEventLevel.Verbose)) { + _loggerService.Verbose("{MethodName} {PortHandlerTypeName} {PortNumber} {WrittenValue}", nameof(WriteDWord), entry.GetType(), port, value); } entry.WriteDWord(port, value); diff --git a/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs b/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs index 59b1a4618..c088db802 100644 --- a/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs +++ b/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs @@ -73,10 +73,10 @@ private void FillDispatchTable() { AddAction(0x0C, ClearKeyboardBufferAndInvokeKeyboardFunction); AddAction(0x0D, DiskReset); AddAction(0x0E, SelectDefaultDrive); + AddAction(0x19, GetCurrentDefaultDrive); AddAction(0x1A, SetDiskTransferAddress); AddAction(0x1B, GetAllocationInfoForDefaultDrive); AddAction(0x1C, GetAllocationInfoForAnyDrive); - AddAction(0x19, GetCurrentDefaultDrive); AddAction(0x25, SetInterruptVector); AddAction(0x2A, GetDate); AddAction(0x2C, GetTime); @@ -95,9 +95,9 @@ private void FillDispatchTable() { AddAction(0x3F, () => ReadFile(true)); AddAction(0x40, () => WriteFileUsingHandle(true)); AddAction(0x41, () => RemoveFile(true)); + AddAction(0x42, () => MoveFilePointerUsingHandle(true)); AddAction(0x43, () => GetSetFileAttributes(true)); AddAction(0x44, () => IoControl(true)); - AddAction(0x42, () => MoveFilePointerUsingHandle(true)); AddAction(0x45, () => DuplicateFileHandle(true)); AddAction(0x47, () => GetCurrentDirectory(true)); AddAction(0x48, () => AllocateMemoryBlock(true)); diff --git a/src/Spice86.Core/Emulator/InterruptHandlers/Dos/Ems/ExpandedMemoryManager.cs b/src/Spice86.Core/Emulator/InterruptHandlers/Dos/Ems/ExpandedMemoryManager.cs index 4bf9d19a5..912fc3385 100644 --- a/src/Spice86.Core/Emulator/InterruptHandlers/Dos/Ems/ExpandedMemoryManager.cs +++ b/src/Spice86.Core/Emulator/InterruptHandlers/Dos/Ems/ExpandedMemoryManager.cs @@ -585,8 +585,8 @@ public void MapUnmapMultipleHandlePages() { ushort handleId = State.DX; ushort numberOfPages = State.CX; uint mapAddress = MemoryUtils.ToPhysicalAddress(State.DS, State.SI); - if (LoggerService.IsEnabled(LogEventLevel.Information)) { - LoggerService.Information( + if (LoggerService.IsEnabled(LogEventLevel.Verbose)) { + LoggerService.Verbose( "EMS: {@MethodName} Map {@NumberOfPages} pages from handle {@Handle} according to the map at address 0x{@MapAddress:X6}", nameof(MapUnmapMultipleHandlePages), numberOfPages, handleId, mapAddress); } diff --git a/src/Spice86.Core/Emulator/InterruptHandlers/VGA/VgaBios.cs b/src/Spice86.Core/Emulator/InterruptHandlers/VGA/VgaBios.cs index 1032cc08a..716c378b1 100644 --- a/src/Spice86.Core/Emulator/InterruptHandlers/VGA/VgaBios.cs +++ b/src/Spice86.Core/Emulator/InterruptHandlers/VGA/VgaBios.cs @@ -238,6 +238,10 @@ public void SetPaletteRegisters() { break; case 0x12: _vgaFunctions.WriteToDac(State.ES, State.DX, State.BL, State.CX); + if (_logger.IsEnabled(LogEventLevel.Debug)) { + _logger.Debug("{ClassName} INT 10 10 {MethodName} - set block of DAC color registers. {Amount} colors starting at register {StartRegister}, source address: {Segment:X4}:{Offset:X4}", + nameof(VgaBios), nameof(SetPaletteRegisters), State.BL, State.CX, State.ES, State.DX); + } break; case 0x13: if (_logger.IsEnabled(LogEventLevel.Debug)) { diff --git a/src/Spice86.Core/Emulator/LoadableFile/Dos/Exe/ExeLoader.cs b/src/Spice86.Core/Emulator/LoadableFile/Dos/Exe/ExeLoader.cs index eb4cd064a..06bd31b69 100644 --- a/src/Spice86.Core/Emulator/LoadableFile/Dos/Exe/ExeLoader.cs +++ b/src/Spice86.Core/Emulator/LoadableFile/Dos/Exe/ExeLoader.cs @@ -59,8 +59,8 @@ public override byte[] LoadFile(string file, string? arguments) { } throw new UnrecoverableException($"Invalid EXE file {file}"); } - if (_loggerService.IsEnabled(LogEventLevel.Debug)) { - _loggerService.Debug("Read header: {ReadHeader}", exeFile); + if (_loggerService.IsEnabled(LogEventLevel.Verbose)) { + _loggerService.Verbose("Read header: {ReadHeader}", exeFile); } LoadExeFileInMemory(exeFile, _startSegment); diff --git a/src/Spice86.Core/Emulator/OperatingSystem/DosFileManager.cs b/src/Spice86.Core/Emulator/OperatingSystem/DosFileManager.cs index 2c8e02252..be0e80857 100644 --- a/src/Spice86.Core/Emulator/OperatingSystem/DosFileManager.cs +++ b/src/Spice86.Core/Emulator/OperatingSystem/DosFileManager.cs @@ -3,9 +3,6 @@ namespace Spice86.Core.Emulator.OperatingSystem; using Serilog.Events; -using System.Linq; -using System.Diagnostics; - using Spice86.Core.Emulator.Memory; using Spice86.Core.Emulator.OperatingSystem.Devices; using Spice86.Core.Emulator.OperatingSystem.Enums; @@ -14,8 +11,9 @@ namespace Spice86.Core.Emulator.OperatingSystem; using Spice86.Shared.Interfaces; using Spice86.Shared.Utils; -using System.Collections; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; /// /// The class that implements DOS file operations, such as finding files, allocating file handles, and updating the Disk Transfer Area. @@ -399,7 +397,7 @@ public DosFileOperationResult OpenFile(string fileName, byte accessMode) { string? hostFileName = _dosPathResolver.GetFullHostPathFromDosOrDefault(fileName); if (string.IsNullOrWhiteSpace(hostFileName)) { - return FileNotFoundError(fileName); + return FileNotFoundError($"'{fileName}'"); } if (_loggerService.IsEnabled(LogEventLevel.Debug)) { diff --git a/src/Spice86.Core/Emulator/OperatingSystem/DosMemoryManager.cs b/src/Spice86.Core/Emulator/OperatingSystem/DosMemoryManager.cs index 61112828b..a43e6d967 100644 --- a/src/Spice86.Core/Emulator/OperatingSystem/DosMemoryManager.cs +++ b/src/Spice86.Core/Emulator/OperatingSystem/DosMemoryManager.cs @@ -144,7 +144,7 @@ public bool ModifyBlock(ushort blockSegment, ushort requestedSize) { if (block.Size < requestedSize - 1) { if (_loggerService.IsEnabled(Serilog.Events.LogEventLevel.Error)) { - _loggerService.Error("MCB {Block} is too small for requested size {RequestedSize}", block, requestedSize); + _loggerService.Error("MCB {Block} is too small for requested size {RequestedSize}", block.Size, requestedSize); } return false; } diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosMemoryControlBlock.cs b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosMemoryControlBlock.cs index 552ef9de1..684de6c4d 100644 --- a/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosMemoryControlBlock.cs +++ b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosMemoryControlBlock.cs @@ -73,7 +73,6 @@ public DosMemoryControlBlock(IByteReaderWriter byteReaderWriter, uint baseAddres /// public bool IsNonLast => TypeField == McbNonLastEntry; - /// /// Returns if the MCB is valid. /// @@ -116,6 +115,16 @@ public void SetNonLast() { /// public override string ToString() { - return new StringBuilder(System.Text.Json.JsonSerializer.Serialize(this)).Append("typeField: ").Append(TypeField).Append("pspSegment: ").Append(PspSegment).Append("size: ").Append(Size).Append("fileName: ").Append(FileName).ToString(); + return new StringBuilder("IsValid").Append(IsValid) + .Append(" IsFree:").Append(IsFree) + .Append(" IsLast:").Append(IsLast) + .Append(" IsNonLast:").Append(IsNonLast) + .Append(" BaseAddress: ").Append(BaseAddress) + .Append(" UsableSpaceSegment: ").Append(UsableSpaceSegment) + .Append(" TypeField: ").Append(TypeField) + .Append(" PspSegment: ").Append(PspSegment) + .Append(" Size: ").Append(Size) + .Append(" FileName: ").Append(FileName) + .ToString(); } } \ No newline at end of file diff --git a/src/Spice86.Core/Emulator/ReverseEngineer/ArgumentFetcher.cs b/src/Spice86.Core/Emulator/ReverseEngineer/ArgumentFetcher.cs new file mode 100644 index 000000000..cf52c6770 --- /dev/null +++ b/src/Spice86.Core/Emulator/ReverseEngineer/ArgumentFetcher.cs @@ -0,0 +1,103 @@ +namespace Spice86.Core.Emulator.ReverseEngineer; + +using Spice86.Core.Emulator.CPU; +using Spice86.Core.Emulator.Memory; +using Spice86.Shared.Utils; + +/// +/// Helper to get function arguments from the stack. +/// Argument naming is based on cdecl calling convention. +/// +public class ArgumentFetcher { + private readonly Stack _stack; + private readonly IMemory _memory; + private readonly State _state; + + /// + /// Instantiates a new instance. + /// + /// + /// + public ArgumentFetcher(Cpu cpu, IMemory memory) { + _stack = cpu.Stack; + _memory = memory; + _state = cpu.State; + } + + public void Get(out ushort arg1, out uint arg2, out ushort arg3) { + arg1 = _stack.Peek16(4); + arg2 = _stack.Peek32(6); + arg3 = _stack.Peek16(10); + } + + public void Get(out string arg1, out short arg2, out ushort arg3) { + ushort stringPointerOffset = _stack.Peek16(4); + arg2 = (short)_stack.Peek16(6); + arg3 = _stack.Peek16(8); + arg1 = GetStringFromDsPointer(stringPointerOffset); + } + + public void Get(out string arg1, out ushort arg2, out short arg3) { + ushort stringPointerOffset = _stack.Peek16(4); + arg2 = _stack.Peek16(6); + arg3 = (short)_stack.Peek16(8); + arg1 = GetStringFromDsPointer(stringPointerOffset); + } + + public void Get(out ushort arg1, out int arg2, out ushort arg3) { + arg1 = _stack.Peek16(4); + arg2 = (int)_stack.Peek32(6); + arg3 = _stack.Peek16(10); + } + + public void Get(out ushort arg1, out ushort arg2) { + arg1 = _stack.Peek16(4); + arg2 = _stack.Peek16(6); + } + + public void Get(out ushort arg1) { + arg1 = _stack.Peek16(4); + } + + public void Get(out string arg1) { + ushort arg1PointerOffset = _stack.Peek16(4); + arg1 = GetStringFromDsPointer(arg1PointerOffset); + } + + public void Get(out string arg1, out string arg2) { + ushort arg1PointerOffset = _stack.Peek16(4); + ushort arg2PointerOffset = _stack.Peek16(6); + arg1 = GetStringFromDsPointer(arg1PointerOffset); + arg2 = GetStringFromDsPointer(arg2PointerOffset); + } + + public void Get(out ushort arg1, out ushort arg2, out ushort arg3, out ushort arg4) { + arg1 = _stack.Peek16(4); + arg2 = _stack.Peek16(6); + arg3 = _stack.Peek16(8); + arg4 = _stack.Peek16(10); + } + + public void Get(out ushort arg1, out string arg2) { + arg1 = _stack.Peek16(4); + ushort arg2PointerOffset = _stack.Peek16(6); + arg2 = GetStringFromDsPointer(arg2PointerOffset); + } + + public void Get(out ushort arg1, out ushort arg2, out ushort arg3) { + arg1 = _stack.Peek16(4); + arg2 = _stack.Peek16(6); + arg3 = _stack.Peek16(8); + } + + public void Get(out uint arg1, out uint arg2, out ushort arg3) { + arg1 = _stack.Peek32(4); + arg2 = _stack.Peek32(8); + arg3 = _stack.Peek16(12); + } + + private string GetStringFromDsPointer(ushort offset) { + uint address = MemoryUtils.ToPhysicalAddress(_state.DS, offset); + return _memory.GetZeroTerminatedString(address, int.MaxValue); + } +} \ No newline at end of file diff --git a/src/Spice86.Core/Emulator/ReverseEngineer/CSharpOverrideHelper.cs b/src/Spice86.Core/Emulator/ReverseEngineer/CSharpOverrideHelper.cs index fa389bf03..cf3081622 100644 --- a/src/Spice86.Core/Emulator/ReverseEngineer/CSharpOverrideHelper.cs +++ b/src/Spice86.Core/Emulator/ReverseEngineer/CSharpOverrideHelper.cs @@ -265,7 +265,6 @@ public class CSharpOverrideHelper { /// public short Direction16 => State.Direction16; - /// /// Gets the offset value of the Direction Flag for 32 bit CPU instructions. /// @@ -327,7 +326,6 @@ public void DefineFunction(ushort segment, ushort offset, string name) { _functionInformations.Add(address, functionInformation); } - /// /// Registers a function at the specified segmented address.
///
@@ -409,7 +407,6 @@ public Action FarJump(ushort cs, ushort ip) { }; } - /// /// Returns an than makes the CPU perform a instruction when invoked. /// @@ -434,6 +431,7 @@ public Action InterruptRet() { public Action NearJump(ushort ip) { return () => State.IP = ip; } + /// /// Returns an action that performs a near return. /// @@ -538,8 +536,8 @@ private void ExecuteCallEnsuringSameStack(ushort expectedReturnCs, ushort expect ushort actualReturnIp = State.IP; uint actualStackAddress = State.StackPhysicalAddress; // Do not return to the caller until we are sure we are at the right place - while ( actualReturnCs != expectedReturnCs || - actualReturnIp != expectedReturnIp) { + while (actualReturnCs != expectedReturnCs || + actualReturnIp != expectedReturnIp) { SegmentedAddress expectedReturn = new SegmentedAddress(expectedReturnCs, expectedReturnIp); SegmentedAddress actualReturn = new SegmentedAddress(actualReturnCs, actualReturnIp); string message = @@ -615,6 +613,21 @@ public void DoOnTopOfInstruction(ushort segment, ushort offset, Action action) { Machine.MachineBreakpoints.ToggleBreakPoint(breakPoint, true); } + /// + /// Executes the specified action when the byte at the specified segment and offset is written to. + /// + /// The segment of the memory location to watch. + /// The offset of the memory location to watch. + /// The action to execute when the memory location is written to. + public void DoOnMemoryWrite(ushort segment, ushort offset, Action action) { + AddressBreakPoint breakPoint = new( + BreakPointType.WRITE, + MemoryUtils.ToPhysicalAddress(segment, offset), + _ => action.Invoke() + , false); + Machine.MachineBreakpoints.ToggleBreakPoint(breakPoint, true); + } + /// /// Checks if the vtable contains the expected segment and offset values. /// diff --git a/src/Spice86.Logging/LoggerService.cs b/src/Spice86.Logging/LoggerService.cs index ffc658bb3..0ff565477 100644 --- a/src/Spice86.Logging/LoggerService.cs +++ b/src/Spice86.Logging/LoggerService.cs @@ -4,6 +4,7 @@ using Serilog.Core; using Serilog.Events; using Serilog.Exceptions; + using Spice86.Shared.Interfaces; /// @@ -14,7 +15,7 @@ public class LoggerService : ILoggerService { private const string LogFormat = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u4}] [{IP:j}] {Message:lj}{NewLine}{Exception}"; /// - public LoggingLevelSwitch LogLevelSwitch { get; set; } = new(LogEventLevel.Warning); + public LoggingLevelSwitch LogLevelSwitch { get; set; } = new(LogEventLevel.Information); /// public bool AreLogsSilenced { get; set; } @@ -159,4 +160,4 @@ public bool IsEnabled(LogEventLevel level) { _logger ??= _loggerConfiguration.CreateLogger(); return _logger.IsEnabled(level); } -} +} \ No newline at end of file diff --git a/src/Spice86.Shared/Emulator/Memory/SegmentedAddress.cs b/src/Spice86.Shared/Emulator/Memory/SegmentedAddress.cs index edca4c397..440df8d85 100644 --- a/src/Spice86.Shared/Emulator/Memory/SegmentedAddress.cs +++ b/src/Spice86.Shared/Emulator/Memory/SegmentedAddress.cs @@ -117,7 +117,7 @@ public readonly string ToSegmentOffsetRepresentation() { /// /// A string representation of the SegmentedAddress object. public override string ToString() { - return $"{ToSegmentOffsetRepresentation()}/{ConvertUtils.ToHex(ToPhysical())}"; + return $"{Segment:X4}:{Offset:X4}"; } ///