Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Internal debugger memory and execution breakpoints #874

Open
wants to merge 34 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4964844
feature: DISASM view address breakpoint
maximilien-noal Oct 1, 2024
ad73fcf
feature: Create/Delete Address Breakpoint
maximilien-noal Oct 1, 2024
19336b4
feat: DISASM view segmented addressing mode
maximilien-noal Oct 7, 2024
6f50bed
docs: Use cref
maximilien-noal Oct 12, 2024
260db55
feat: stack view
maximilien-noal Oct 12, 2024
0a8d5d4
refactor: Invoke ICommand instead of async void
maximilien-noal Oct 27, 2024
69176d0
feat(debugger): basic Breakpoints View
maximilien-noal Oct 27, 2024
4b23df7
feat(debugger): Memory View breakpoints
maximilien-noal Oct 30, 2024
3397aec
chore: No CPU Stack View. Will redo it later.
maximilien-noal Nov 3, 2024
94a3f4f
refactor: Status message view
maximilien-noal Nov 3, 2024
d01024c
refactor: Send UI Messages on UI thread
maximilien-noal Nov 3, 2024
7501822
feat: DISASM view breakpoint form
maximilien-noal Nov 3, 2024
d619fcc
fix: Breakpoints weren't activated
maximilien-noal Nov 3, 2024
c45fced
feat: Show some registers addresses as ASCII
maximilien-noal Nov 3, 2024
57e9bd5
feat: DISASM view Step Over button
maximilien-noal Nov 3, 2024
87a1f20
chore: IsAddressBreakpointAt remove (BreakPointHolder)
maximilien-noal Nov 5, 2024
631f36b
refactor: No padding between DISASM colums
maximilien-noal Nov 5, 2024
64432f7
refactor: Smaller DISASM view font
maximilien-noal Nov 5, 2024
45b8dc3
refactor: Always possible to create/remove breakpoint
maximilien-noal Nov 5, 2024
5dc54be
feat: Move CS:IP here command (UI)
maximilien-noal Nov 5, 2024
8ca3d15
feat: Go to CS:IP on pause if nothing is shown
maximilien-noal Nov 5, 2024
f245b92
refactor: DISASM has less colums, same data
maximilien-noal Nov 7, 2024
47c8cbf
chore: Remove+Sort usings
maximilien-noal Nov 7, 2024
07c9819
refactor: No out-of-sync HasBreakpoint status (UI)
maximilien-noal Nov 7, 2024
fe8a8f5
refactor: 'Refresh' button naming (debugger XAML)
maximilien-noal Nov 7, 2024
91593a9
refactor: Debugger Vertical Tabs Header Layout
maximilien-noal Nov 7, 2024
b2960e3
refactor: 'Breakpoint here' commands CanExecute
maximilien-noal Nov 7, 2024
9e57156
feat: Slightly better StatusBar-like control
maximilien-noal Nov 9, 2024
62f448a
chore: Merge branch 'master' into branch
maximilien-noal Nov 11, 2024
1e19339
refactor: Memory view buttons layout
maximilien-noal Nov 11, 2024
6095a47
feat: Go To Function (Internal Debugger)
maximilien-noal Nov 11, 2024
59de795
refactor: DISASM view buttons layout
maximilien-noal Nov 11, 2024
d6979de
refactor: Select matching function by address
maximilien-noal Nov 11, 2024
269f3d3
feat: Inline function name in MASM column
maximilien-noal Nov 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public InMemoryAddressSwitcher(IIndexable memory) {
public SegmentedAddress? DefaultAddress { get; set; }

/// <summary>
/// Sets the segmented address. PhysicalLocation needs to be initialized before calling this method.
/// Sets the segmented address. <see cref="PhysicalLocation"/> needs to be initialized before calling this method.
/// </summary>
/// <param name="segment">Segment</param>
/// <param name="offset">Offset</param>
Expand Down
1 change: 1 addition & 0 deletions src/Spice86/Controls/StatusBar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ internal sealed class StatusBar : StackPanel {

static StatusBar() {
OrientationProperty.OverrideDefaultValue(typeof(StatusBar), Orientation.Horizontal);
HorizontalAlignmentProperty.OverrideDefaultValue<StatusBar>(HorizontalAlignment.Stretch);
}
}
2 changes: 2 additions & 0 deletions src/Spice86/Controls/StatusBarItem.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
namespace Spice86.Controls;

using Avalonia.Controls;
using Avalonia.Layout;

/// <summary>
/// Control that implements an item inside a StatusBar.
/// </summary>
internal sealed class StatusBarItem : ContentControl {
static StatusBarItem() {
HorizontalContentAlignmentProperty.OverrideDefaultValue<StatusBarItem>(HorizontalAlignment.Stretch);
IsTabStopProperty.OverrideDefaultValue(typeof(StatusBarItem), false);
}
}
3 changes: 3 additions & 0 deletions src/Spice86/Converters/InstructionToStringConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public class InstructionToStringConverter : IValueConverter {
}
_outputString.Clear();
var output = new StringOutput();
if (!string.IsNullOrWhiteSpace(cpuInstructionInfo.FunctionName)) {
_outputString.AppendLine($"{cpuInstructionInfo.FunctionName} entry point");
}
// Don't use instr.ToString(), it allocates more, uses masm syntax and default options
_formatter.Format(instr, output);
_outputString.AppendLine(output.ToStringAndReset());
Expand Down
3 changes: 3 additions & 0 deletions src/Spice86/Messages/StatusMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Spice86.Messages;

public record StatusMessage(DateTime Time, object Origin, string Message);
7 changes: 6 additions & 1 deletion src/Spice86/Models/Debugging/CpuInstructionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ namespace Spice86.Models.Debugging;

using Iced.Intel;

using Spice86.Shared.Emulator.Memory;

public partial class CpuInstructionInfo : ObservableObject {
[ObservableProperty] private string? _stringRepresentation;
[ObservableProperty] private bool _hasBreakpoint;
[ObservableProperty] private string? _functionName;
[ObservableProperty] private bool _isCsIp;
[ObservableProperty] private uint _address;
[ObservableProperty] private string? _segmentedAddress;
[ObservableProperty] private string? _addressInformation;
[ObservableProperty] private SegmentedAddress _segmentedAddress;
[ObservableProperty] private ushort _IP16;
[ObservableProperty] private uint _IP32;
[ObservableProperty] private int _length;
Expand Down
3 changes: 0 additions & 3 deletions src/Spice86/Models/Debugging/ExceptionInfo.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
namespace Spice86.Models.Debugging;

using System.Reflection;

public record ExceptionInfo(string? TargetSite, string Message, string? StackTrace);
14 changes: 14 additions & 0 deletions src/Spice86/Models/Debugging/FunctionInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Spice86.Models.Debugging;

using CommunityToolkit.Mvvm.ComponentModel;

using Spice86.Shared.Emulator.Memory;

public partial class FunctionInfo : ObservableObject {
[ObservableProperty] private string? _name;
[ObservableProperty] private uint _address;

public override string ToString() {
return $"{Address}: {Name}";
}
}
2 changes: 0 additions & 2 deletions src/Spice86/Models/Debugging/SoundChannelInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ namespace Spice86.Models.Debugging;

using CommunityToolkit.Mvvm.ComponentModel;

using System.ComponentModel;

public partial class SoundChannelInfo : ObservableObject {
[ObservableProperty] private int _volume;
[ObservableProperty] private float _stereoSeparation;
Expand Down
2 changes: 0 additions & 2 deletions src/Spice86/Models/Debugging/VideoCardInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ namespace Spice86.Models.Debugging;

using CommunityToolkit.Mvvm.ComponentModel;

using Spice86.Core.Emulator.Devices.Video.Registers;
using Spice86.Core.Emulator.Devices.Video.Registers.CrtController;
using Spice86.Core.Emulator.Devices.Video.Registers.General;
using Spice86.Core.Emulator.Devices.Video.Registers.Graphics;
using Spice86.ViewModels;

public partial class VideoCardInfo : ObservableObject {
[ObservableProperty]
Expand Down
7 changes: 5 additions & 2 deletions src/Spice86/Spice86DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,9 @@ public Spice86DependencyInjection(ILoggerService loggerService, Configuration co
dmaController, soundBlaster.Opl3Fm, softwareMixer, mouse, mouseDriver,
vgaFunctionality, pauseHandler);

IDictionary<SegmentedAddress, FunctionInformation> functionsInformation = reader.ReadGhidraSymbolsFromFileOrCreate();
InitializeFunctionHandlers(configuration, machine, loggerService,
reader.ReadGhidraSymbolsFromFileOrCreate(), functionHandler, functionHandlerInExternalInterrupt);
functionsInformation, functionHandler, functionHandlerInExternalInterrupt);

ProgramExecutor programExecutor = new(configuration, emulatorBreakpointsManager,
emulatorStateSerializer, memory, cpu, cfgCpu, state,
Expand Down Expand Up @@ -241,9 +242,11 @@ public Spice86DependencyInjection(ILoggerService loggerService, Configuration co
DebugWindowViewModel? debugWindowViewModel = null;
if (textClipboard != null && hostStorageProvider != null && uiThreadDispatcher != null) {
IMessenger messenger = WeakReferenceMessenger.Default;
debugWindowViewModel = new DebugWindowViewModel(cpu, state, memory,
debugWindowViewModel = new DebugWindowViewModel(cpu, state, stack, memory,
midiDevice, videoState.DacRegisters.ArgbPalette, softwareMixer, vgaRenderer, videoState,
cfgCpu.ExecutionContextManager, messenger, uiThreadDispatcher, textClipboard, hostStorageProvider,
emulatorBreakpointsManager,
functionsInformation,
new StructureViewModelFactory(configuration, loggerService, pauseHandler),
pauseHandler);
}
Expand Down
51 changes: 51 additions & 0 deletions src/Spice86/ViewModels/BreakpointViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
namespace Spice86.ViewModels;

using CommunityToolkit.Mvvm.ComponentModel;

using Spice86.Core.Emulator.VM;
using Spice86.Core.Emulator.VM.Breakpoint;
using Spice86.Models.Debugging;

public partial class BreakpointViewModel : ViewModelBase {
private readonly BreakPoint _breakPoint;
private readonly EmulatorBreakpointsManager _emulatorBreakpointsManager;

public BreakpointViewModel(EmulatorBreakpointsManager emulatorBreakpointsManager, AddressBreakPoint breakPoint) {
_breakPoint = breakPoint;
_emulatorBreakpointsManager = emulatorBreakpointsManager;
IsEnabled = true;
Address = breakPoint.Address;
}

public BreakPointType Type => _breakPoint.BreakPointType;

//Can't get out of sync since GDB can't be used at the same tiem as the internal debugger
[ObservableProperty]
private bool _isEnabled;

public bool IsRemovedOnTrigger => _breakPoint.IsRemovedOnTrigger;

public long Address { get; }

public void Toggle() {
if (IsEnabled) {
Disable();
} else {
Enable();
}
}

public void Enable() {
_emulatorBreakpointsManager.ToggleBreakPoint(_breakPoint, on: true);
IsEnabled = true;
}

public void Disable() {
_emulatorBreakpointsManager.ToggleBreakPoint(_breakPoint, on: false);
IsEnabled = false;
}

internal bool IsFor(CpuInstructionInfo instructionInfo) {
return _breakPoint is AddressBreakPoint addressBreakPoint && addressBreakPoint.Address == instructionInfo.Address;
}
}
70 changes: 70 additions & 0 deletions src/Spice86/ViewModels/BreakpointsViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
namespace Spice86.ViewModels;

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

using Spice86.Core.Emulator.VM;
using Spice86.Core.Emulator.VM.Breakpoint;
using Spice86.Models.Debugging;

using System.Collections.ObjectModel;

public partial class BreakpointsViewModel : ViewModelBase {
private readonly EmulatorBreakpointsManager _emulatorBreakpointsManager;

public BreakpointsViewModel(EmulatorBreakpointsManager emulatorBreakpointsManager) {
_emulatorBreakpointsManager = emulatorBreakpointsManager;
}

[ObservableProperty]
private ObservableCollection<BreakpointViewModel> _breakpoints = new();

[RelayCommand(CanExecute = nameof(ToggleSelectedBreakpointCanExecute))]
private void ToggleSelectedBreakpoint() {
if (SelectedBreakpoint is not null) {
SelectedBreakpoint.Toggle();
}
}

private bool ToggleSelectedBreakpointCanExecute() => SelectedBreakpoint is not null;

[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(RemoveBreakpointCommand))]
[NotifyCanExecuteChangedFor(nameof(ToggleSelectedBreakpointCommand))]
private BreakpointViewModel? _selectedBreakpoint;

internal void AddAddressBreakpoint(AddressBreakPoint addressBreakPoint) {
var breakpointViewModel = new BreakpointViewModel( _emulatorBreakpointsManager, addressBreakPoint);
Breakpoints.Add(breakpointViewModel);
SelectedBreakpoint = breakpointViewModel;
SelectedBreakpoint.Enable();
}

private bool RemoveBreakpointCanExecute() => SelectedBreakpoint is not null;


[RelayCommand(CanExecute = nameof(RemoveBreakpointCanExecute))]
private void RemoveBreakpoint() {
if (SelectedBreakpoint is not null) {
DeleteBreakpoint(SelectedBreakpoint);
}
}

internal void RemoveUserExecutionBreakpoint(CpuInstructionInfo instructionInfo) {
DeleteBreakpoint(Breakpoints.FirstOrDefault(x => x.IsFor(instructionInfo) && x is
{ IsRemovedOnTrigger: false, Type: BreakPointType.EXECUTION }));
}

internal bool HasUserExecutionBreakpoint(CpuInstructionInfo instructionInfo) {
return Breakpoints.Any(x => x.IsFor(instructionInfo) && x is
{ IsRemovedOnTrigger: false, Type: BreakPointType.EXECUTION });
}

private void DeleteBreakpoint(BreakpointViewModel? breakpoint) {
if (breakpoint is null) {
return;
}
breakpoint.Disable();
Breakpoints.Remove(breakpoint);
}
}
14 changes: 5 additions & 9 deletions src/Spice86/ViewModels/CfgCpuViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using AvaloniaGraphControl;

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

using Spice86.Core.Emulator.CPU.CfgCpu;
using Spice86.Core.Emulator.CPU.CfgCpu.ControlFlowGraph;
Expand Down Expand Up @@ -32,18 +33,13 @@ public CfgCpuViewModel(ExecutionContextManager executionContextManager, IPauseHa
IPerformanceMeasurer performanceMeasurer) {
_executionContextManager = executionContextManager;
_performanceMeasurer = performanceMeasurer;
pauseHandler.Pausing += OnPausing;
pauseHandler.Pausing += () => UpdateGraphCommand.Execute(null);
}

private void OnPausing() {
[RelayCommand]
private async Task UpdateGraph() {
Graph = null;
// This call of an async method from a sync method uses the async method and discards the Task.
// If this method fails, it will not crash the process.
// Instead, it will fire the TaskScheduler.UnobservedTaskException event.
// See https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md
// The alternative, making this event handler 'async void' would crash the process in case of an exception,
// unless a try/catch is put over the *entire* code of the sync method.
_ = UpdateCurrentGraphAsync();
await UpdateCurrentGraphAsync();
}

partial void OnMaxNodesToDisplayChanging(int value) => Graph = null;
Expand Down
20 changes: 17 additions & 3 deletions src/Spice86/ViewModels/CpuViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace Spice86.ViewModels;
using CommunityToolkit.Mvvm.ComponentModel;

using Spice86.Core.Emulator.CPU;
using Spice86.Core.Emulator.Memory;
using Spice86.Core.Emulator.VM;
using Spice86.Infrastructure;
using Spice86.Models.Debugging;
Expand All @@ -14,15 +15,17 @@ namespace Spice86.ViewModels;

public partial class CpuViewModel : ViewModelBase {
private readonly State _cpuState;
private readonly IMemory _memory;

[ObservableProperty]
private StateInfo _state = new();

[ObservableProperty]
private CpuFlagsInfo _flags = new();

public CpuViewModel(State state, IPauseHandler pauseHandler, IUIDispatcher uiDispatcher) {
public CpuViewModel(State state, Stack stack, IMemory memory, IPauseHandler pauseHandler, IUIDispatcher uiDispatcher) {
_cpuState = state;
_memory = memory;
pauseHandler.Pausing += () => uiDispatcher.Post(() => _isPaused = true);
_isPaused = pauseHandler.IsPaused;
pauseHandler.Resumed += () => uiDispatcher.Post(() => _isPaused = false);
Expand All @@ -46,8 +49,6 @@ private void VisitCpuState(State state) {
Flags.PropertyChanged -= OnStatePropertyChanged;
}

return;

void OnStatePropertyChanged(object? sender, PropertyChangedEventArgs e) {
if (sender is null || e.PropertyName == null || !_isPaused) {
return;
Expand All @@ -59,6 +60,16 @@ void OnStatePropertyChanged(object? sender, PropertyChangedEventArgs e) {
}
}
}

[ObservableProperty]
private string? _esiString;

[ObservableProperty]
private string? _ediString;

[ObservableProperty]
private string? _espString;

private void UpdateCpuState(State state) {
State.AH = state.AH;
State.AL = state.AL;
Expand Down Expand Up @@ -91,6 +102,9 @@ private void UpdateCpuState(State state) {
State.GS = state.GS;
State.SS = state.SS;
State.IP = state.IP;
EspString = _memory.GetZeroTerminatedString(State.ESP, 32);
EsiString = _memory.GetZeroTerminatedString(State.ESI, 32);
EdiString = _memory.GetZeroTerminatedString(State.EDI, 32);
State.Cycles = state.Cycles;
State.IpPhysicalAddress = state.IpPhysicalAddress;
State.StackPhysicalAddress = state.StackPhysicalAddress;
Expand Down
Loading
Loading