Skip to content

Commit

Permalink
feat(debugger): Memory View breakpoints
Browse files Browse the repository at this point in the history
Signed-off-by: Maximilien Noal <noal.maximilien@gmail.com>
  • Loading branch information
maximilien-noal committed Oct 30, 2024
1 parent 69176d0 commit d00623f
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 15 deletions.
2 changes: 1 addition & 1 deletion src/Spice86/ViewModels/DebugWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public DebugWindowViewModel(IInstructionExecutor cpu, State cpuState, Stack stac
VideoCardViewModel = new(vgaRenderer, videoState);
CpuViewModel = new(cpuState, stack, memory, pauseHandler, uiDispatcher);
MidiViewModel = new(externalMidiDevice);
MemoryViewModels.Add(new(memory, pauseHandler, messenger, uiDispatcher, textClipboard, storageProvider, structureViewModelFactory));
MemoryViewModels.Add(new(memory, BreakpointsViewModel, pauseHandler, messenger, uiDispatcher, textClipboard, storageProvider, structureViewModelFactory));
CfgCpuViewModel = new(executionContextManager, pauseHandler, new PerformanceMeasurer());
}

Expand Down
69 changes: 58 additions & 11 deletions src/Spice86/ViewModels/MemoryViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using CommunityToolkit.Mvvm.Messaging;
using Spice86.Core.Emulator.Memory;
using Spice86.Core.Emulator.VM;
using Spice86.Core.Emulator.VM.Breakpoint;
using Spice86.Infrastructure;
using Spice86.MemoryWrappers;
using Spice86.Messages;
Expand All @@ -26,6 +27,7 @@ public partial class MemoryViewModel : ViewModelWithErrorDialog {
private readonly IMessenger _messenger;
private readonly IPauseHandler _pauseHandler;
private readonly IUIDispatcher _uiDispatcher;
private readonly BreakpointsViewModel _breakpointsViewModel;

public enum MemorySearchDataType {
Binary,
Expand Down Expand Up @@ -98,8 +100,52 @@ public uint? EndAddress {
TryUpdateHeaderAndMemoryDocument();
}
}

[RelayCommand(CanExecute = nameof(IsPaused))]

[ObservableProperty]
private bool _creatingMemoryBreakpoint;

private bool IsSelectionRangeValid() => SelectionRange is not null && StartAddress is not null;

[RelayCommand(CanExecute = nameof(IsSelectionRangeValid))]
private void BeginCreateMemoryBreakpoint() {
if(StartAddress is not null && SelectionRange is not null) {
CreatingMemoryBreakpoint = true;
ulong address = StartAddress.Value + SelectionRange.Value.Start.ByteIndex;
BreakpointAddress = address.ToString(CultureInfo.InvariantCulture);
}
}

[ObservableProperty]
private string? _breakpointAddress;

[ObservableProperty]
private BreakPointType _selectedBreakpointType = BreakPointType.ACCESS;

public BreakPointType[] BreakpointTypes => [BreakPointType.ACCESS, BreakPointType.WRITE, BreakPointType.READ];

private bool IsBreakpointAddressValid() => string.IsNullOrWhiteSpace(BreakpointAddress) == false && TryParseMemoryAddress(_breakpointAddress, out _);

Check notice

Code scanning / CodeQL

Unnecessarily complex Boolean expression Note

The expression 'A == false' can be simplified to '!A'.

private void OnBreakPointReached(BreakPoint breakPoint) {
string message = $"{breakPoint.BreakPointType} breakpoint was reached.";
_pauseHandler.RequestPause(message);
_messenger.Send(new StatusMessage(DateTime.Now, this, message));
}

[RelayCommand(CanExecute = nameof(IsBreakpointAddressValid))]
private void ConfirmCreateMemoryBreakpoint() {
CreatingMemoryBreakpoint = false;
if(!string.IsNullOrWhiteSpace(BreakpointAddress) &&
TryParseMemoryAddress(BreakpointAddress, out ulong? breakpointAddressValue)) {
AddressBreakPoint addressBreakPoint = new(SelectedBreakpointType,
(long)breakpointAddressValue, OnBreakPointReached, false);
_breakpointsViewModel.AddAddressBreakpoint(addressBreakPoint);
}
}

[RelayCommand]
private void CancelCreateMemoryBreakpoint() => CreatingMemoryBreakpoint = false;

[RelayCommand(CanExecute = nameof(IsSelectionRangeValid))]
public async Task CopySelection() {
if (SelectionRange is not null && StartAddress is not null) {
byte[] memoryBytes = _memory.ReadRam(
Expand Down Expand Up @@ -222,11 +268,12 @@ private void GoToFoundOccurence() {

private readonly IHostStorageProvider _storageProvider;

public MemoryViewModel(IMemory memory, IPauseHandler pauseHandler, IMessenger messenger, IUIDispatcher uiDispatcher,
public MemoryViewModel(IMemory memory, BreakpointsViewModel breakpointsViewModel, IPauseHandler pauseHandler, IMessenger messenger, IUIDispatcher uiDispatcher,
ITextClipboard textClipboard, IHostStorageProvider storageProvider, IStructureViewModelFactory structureViewModelFactory,
bool canCloseTab = false, uint startAddress = 0, uint endAddress = A20Gate.EndOfHighMemoryArea) : base(textClipboard) {
_pauseHandler = pauseHandler;
_uiDispatcher = uiDispatcher;
_breakpointsViewModel = breakpointsViewModel;
_memory = memory;
_pauseHandler.Pausing += OnPause;
IsPaused = pauseHandler.IsPaused;
Expand Down Expand Up @@ -286,7 +333,7 @@ private void NewMemoryView() {
}

private void CreateNewMemoryView(uint? startAddress = null) {
MemoryViewModel memoryViewModel = new(_memory, _pauseHandler, _messenger, _uiDispatcher, _textClipboard,
MemoryViewModel memoryViewModel = new(_memory, _breakpointsViewModel, _pauseHandler, _messenger, _uiDispatcher, _textClipboard,
_storageProvider,
_structureViewModelFactory, canCloseTab: true);
if (startAddress is not null) {
Expand Down Expand Up @@ -327,12 +374,12 @@ private async Task DumpMemory() {
[RelayCommand(CanExecute = nameof(IsPaused))]
private void EditMemory() {
IsEditingMemory = true;
if (MemoryEditAddress is not null && TryParseMemoryAddress(MemoryEditAddress, out uint? memoryEditAddressValue)) {
MemoryEditValue = Convert.ToHexString(_memory.ReadRam((uint)(MemoryEditValue?.Length ?? sizeof(ushort)), memoryEditAddressValue.Value));
if (MemoryEditAddress is not null && TryParseMemoryAddress(MemoryEditAddress, out ulong? memoryEditAddressValue)) {
MemoryEditValue = Convert.ToHexString(_memory.ReadRam((uint)(MemoryEditValue?.Length ?? sizeof(ushort)), (uint)memoryEditAddressValue.Value));

Check warning

Code scanning / CodeQL

Dereferenced variable may be null Warning

Variable
memoryEditAddressValue
may be null at this access because it has a nullable type.
}
}

private bool TryParseMemoryAddress(string? memoryAddress, [NotNullWhen(true)] out uint? address) {
private bool TryParseMemoryAddress(string? memoryAddress, [NotNullWhen(true)] out ulong? address) {
if (string.IsNullOrWhiteSpace(memoryAddress)) {
address = null;
return false;
Expand All @@ -348,13 +395,13 @@ private bool TryParseMemoryAddress(string? memoryAddress, [NotNullWhen(true)] ou

return true;
}
} else if (uint.TryParse(memoryAddress, CultureInfo.InvariantCulture, out uint value)) {
} else if (ulong.TryParse(memoryAddress, CultureInfo.InvariantCulture, out ulong value)) {
address = value;

return true;
}
} catch (Exception e) {
Dispatcher.UIThread.Post(() => ShowError(e));
_uiDispatcher.Post(() => ShowError(e));
}
address = null;

Expand All @@ -366,12 +413,12 @@ private bool TryParseMemoryAddress(string? memoryAddress, [NotNullWhen(true)] ou

[RelayCommand]
private void ApplyMemoryEdit() {
if (!TryParseMemoryAddress(MemoryEditAddress, out uint? address) ||
if (!TryParseMemoryAddress(MemoryEditAddress, out ulong? address) ||
MemoryEditValue is null ||
!long.TryParse(MemoryEditValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out long value)) {
return;
}
DataMemoryDocument?.WriteBytes(address.Value, BitConverter.GetBytes(value));
DataMemoryDocument?.WriteBytes(address!.Value, BitConverter.GetBytes(value));
IsEditingMemory = false;
}
}
61 changes: 58 additions & 3 deletions src/Spice86/Views/MemoryView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
<ScrollViewer>
<Grid RowDefinitions="Auto,*">
<StackPanel Orientation="Horizontal" Grid.Row="0">
<Button Command="{Binding EditMemoryCommand}" Content="Edit..." />
<Button Command="{Binding EditMemoryCommand}" Content="Edit..." />
<Button Command="{Binding BeginCreateMemoryBreakpointCommand}" Content="Breakpoint..." />
<Button Command="{Binding StartMemorySearchCommand}" Content="Search..." />
<Button Command="{Binding DumpMemoryCommand}" Content="Dump..." />
<Button
Expand Down Expand Up @@ -197,8 +198,62 @@
</Grid>
</dialogHost:DialogHost.DialogContent>
</dialogHost:DialogHost>

<userControls:ErrorModalDialogUserControl Grid.Row="0" Grid.RowSpan="2" />
<Rectangle Fill="{Binding $parent[Window].Background}" Opacity="0.5" Grid.Row="0" Grid.RowSpan="2"
IsVisible="{Binding CreatingMemoryBreakpoint}"/>
<dialogHost:DialogHost
Grid.Row="0" Grid.RowSpan="2"
Background="{Binding $parent[Window].Background}"
CloseOnClickAway="False"
IsOpen="{Binding CreatingMemoryBreakpoint}">
<dialogHost:DialogHost.DialogContent>
<Grid RowDefinitions="Auto,*,Auto">
<Label
Grid.Row="0"
HorizontalAlignment="Center"
Content="Creating memory breakpoint"
FontWeight="Bold" />
<Grid
Grid.Row="1"
ColumnDefinitions="Auto,*"
RowDefinitions="Auto,Auto">
<Label
Grid.Row="0"
Grid.Column="0"
Content="Memory Address to break on:" />
<TextBox
Grid.Row="0"
Grid.Column="1"
FontFamily="RobotoMonoFont"
Text="{Binding BreakpointAddress}"
Watermark="linear or segmented address" />
<Label
Grid.Row="1"
Grid.Column="0"
Content="Access type::" />
<ComboBox
Grid.Row="1"
Grid.Column="1"
SelectedItem="{Binding SelectedBreakpointType}"
ItemsSource="{Binding BreakpointTypes, Mode=OneTime}" />
</Grid>
<StackPanel
Grid.Row="2"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
Command="{Binding ConfirmCreateMemoryBreakpointCommand}"
Content="OK"
IsDefault="True" />
<Button
Command="{Binding CancelCreateMemoryBreakpointCommand}"
Content="Cancel"
IsCancel="True" />
</StackPanel>
</Grid>
</dialogHost:DialogHost.DialogContent>
</dialogHost:DialogHost>

<userControls:ErrorModalDialogUserControl Grid.Row="0" Grid.RowSpan="2" />
</Grid>
</ScrollViewer>
</UserControl>

0 comments on commit d00623f

Please sign in to comment.