Skip to content

Commit 043c97d

Browse files
authored
Debugger improvements (#26)
* Refactor `DebuggerViewModel` and related classes: Simplify breakpoint management, update `IEmulatorMemory` visibility, and streamline debugging configuration. * Add Debugger UI improvements: Enable breakpoint toggling, restructure menu, and enhance debugging logic. * Refactor pause/resume logic: Introduce `ResumeFromDebugMessage` and `PauseForDebugMessage`, adjust `DebuggerViewModel` and `MainWindowViewModel` to improve debugging flow consistency. * Refactor breakpoint handling and emulator reset logic: Improve breakpoint event subscription, update dialog handling for debugger views, and streamline emulator initialization and debugging configuration. * Formatting * Update C# naming abbreviations in `.DotSettings` * Better breakpoint handling * Update readme * Update readme * Fix link * Rename * Typo * Update readme * Improve breakpoint handling and create a base breakpoint class for future memory breakpoints * Enhance breakpoint manager: refactor breakpoint handling logic, introduce `Condition` property, and update number format settings. * Refactor breakpoint handling, implement memory breakpoints * Refactor breakpoint handling * Refactor debugger breakpoint handling
1 parent f935df5 commit 043c97d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+758
-319
lines changed

README.md

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,38 @@
44
My own ZX Spectrum emulator written in C# and Avalonia UI. It emulates classic ZX Spectrum 16K, 48K and
55
128K computers.
66

7-
This is work in progress, I've been improving it quite a lot recently. However it is stable and usable.
8-
It can run most of the games and demos without any problems.
7+
It is quite accurate and stable, it can run most of the games and demos without any issues, load protected
8+
tapes.
99

10-
It's a cross-platform emulator that runs on Windows, Linux and macOS. Developed on macOS, so mostly tested
11-
on this platform. Needs some more testing on Linux.
10+
It's a cross-platform emulator that runs on Windows, Linux and macOS. Developed on macOS, so generally tested
11+
on this platform. Needs some more testing on Linux, seems to be running fine on Windows.
1212

13-
This is my hobby project which I always wanted to do. It has been a lot of fun, and quite challenging, too.
14-
ZX Spectrum was my first computer and I love it. I am planning to keep it alive since I created it for my own
15-
use and I am using it to play games and demos.
13+
This is my hobby project which I always wanted to do. It has been a lot of fun, and quite a challenge.
14+
There are other emulators out there, but my focus was on making it better structured, easier to understand.
15+
It is written all by hand, no AI generated code.
16+
17+
ZX Spectrum was my first computer and I still love it. I am planning to keep this project alive since
18+
I have created it for my personal use to play games and demos. It is a lot of fun.
1619

1720
It uses several of my own libraries that I created for this project:
18-
- [Z80 CPU emulator](https://github.com/oldbit-com/Z80/tree/spectron)
19-
- [File format handling](https://github.com/oldbit-com/Spectron.Files)
20-
- [Audio player](https://github.com/oldbit-com/Beep)
21-
- [Gamepad support](https://github.com/oldbit-com/Joypad)
21+
22+
| Library | Description |
23+
|--------------------------------------------------------|----------------------------------------------|
24+
| [Z80](https://github.com/oldbit-com/Z80/tree/spectron) | Generic Z80 CPU emulator |
25+
| [Files](https://github.com/oldbit-com/Spectron.Files) | Handles TZX, Z80, SNA and other file formats |
26+
| [Beep](https://github.com/oldbit-com/Beep) | Basic audio player, cross-platform, native |
27+
| [Joypad](https://github.com/oldbit-com/Joypad) | Gamepad handler, cross-platform, native |
28+
29+
Solution consists of several projects:
30+
31+
| Project | Description |
32+
|------------------------|--------------------------------------------------------|
33+
| Spectron | Avalonia based UI |
34+
| Spectron.Debugger | Fully featured Code debugger, includes UI and controls |
35+
| Spectron.Disassembly | Simple Z80 disassembler, used by the debugger |
36+
| **Spectron.Emulation** | This is the core of the emulator, e.g. the main thing |
37+
| Spectron.Recorder | Audio and Video recording helper |
38+
2239

2340
![Main Window](docs/default.png?raw=true "Main Window")
2441

@@ -153,9 +170,9 @@ done in the background by converting static frames to a video stream with audio,
153170

154171
## Debugger
155172
Debugger is available in the emulator. It is a simple debugger that allows you to inspect the CPU registers,
156-
memory and disassembly. You can step through the code, set breakpoints. This is still work in progress.
173+
memory and disassembly. You can step through the code, set breakpoints.
157174

158-
### Commands
175+
### Shortcuts
159176
- **Step Over** - `F10` \
160177
For `CALL`, `JR cc`, `JP cc`, `DJNZ`, `LDIR` or `LDDR` instructions, debugger will try to step over the subroutine.
161178
- **Step Into** - `F11` \
@@ -164,6 +181,19 @@ memory and disassembly. You can step through the code, set breakpoints. This is
164181
Debugger will step out of the subroutine using the current return address on the stack.
165182
So this will only work if the value on the stack contains return address.
166183

184+
### Breakpoints
185+
186+
Two types of breakpoints are supported: on register value change or on memory write
187+
188+
#### Register breakpoints
189+
Typical scenario is use a condition for PC register, for example `PC==32768`. This would break execution
190+
at the specified address. However any register can be used, for example `HL==16384`, `A==0x79`etc.
191+
192+
#### Memory breakpoints
193+
Memory breakpoints can have two forms: trigger when specific value is written to e memory cell or more generic
194+
when memory cell is written. For example `16384==32` will break when value `32` has been written to `16384` memory address.
195+
If condition is just `16384`, execution will break when any value has been written to that address.
196+
167197
### Immediate window instructions, case insensitive:
168198
- `HELP` - print help information
169199
- `PRINT [expression]` or `? [expression]` - prints a value of the expression
@@ -205,5 +235,4 @@ memory and disassembly. You can step through the code, set breakpoints. This is
205235
- [Material Icons](https://github.com/SKProCH/Material.Icons)
206236
- [ZX Spectrum Font](https://github.com/comptic/zx-spectrum-font)
207237
- [Hack Font](https://sourcefoundry.org/hack/)
208-
- [VT220 Font](https://github.com/svofski/glasstty/blob/master/Glass_TTY_VT220.ttf)
209-
238+
- [VT220 Font](https://github.com/svofski/glasstty/blob/master/Glass_TTY_VT220.ttf)

Spectron.sln.DotSettings

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IFF/@EntryIndexedValue">IFF</s:String>
1010
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IM/@EntryIndexedValue">IM</s:String>
1111
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IX/@EntryIndexedValue">IX</s:String>
12+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IXH/@EntryIndexedValue">IXH</s:String>
13+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IXL/@EntryIndexedValue">IXL</s:String>
1214
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IY/@EntryIndexedValue">IY</s:String>
15+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IYH/@EntryIndexedValue">IYH</s:String>
16+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IYL/@EntryIndexedValue">IYL</s:String>
1317
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MAPRAM/@EntryIndexedValue">MAPRAM</s:String>
1418
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PC/@EntryIndexedValue">PC</s:String>
1519
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SP/@EntryIndexedValue">SP</s:String>
Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,8 @@
11
namespace OldBit.Spectron.Debugger.Breakpoints;
22

3-
public class Breakpoint(Register register, int value)
3+
public abstract class Breakpoint
44
{
5-
public Guid Id { get; } = Guid.NewGuid();
6-
75
public bool IsEnabled { get; set; } = true;
86

9-
public int Value { get; set; } = value;
10-
11-
public Register Register { get; set; } = register;
12-
13-
public int? ValueAtLastHit { get; set; } = value;
14-
15-
public bool ShouldRemoveOnHit { get; set; } = false;
16-
17-
public override string ToString() => $"{Register} == ${Value:X4}";
7+
public string Condition { get; init; } = string.Empty;
188
}
Lines changed: 89 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,114 @@
1-
using OldBit.Z80Cpu;
2-
using OldBit.Z80Cpu.Events;
1+
using OldBit.Spectron.Emulation;
32

43
namespace OldBit.Spectron.Debugger.Breakpoints;
54

5+
public class BreakpointHitEventArgs(Word previousAddress) : EventArgs
6+
{
7+
public Word PreviousAddress { get; } = previousAddress;
8+
}
9+
610
public class BreakpointHandler : IDisposable
711
{
8-
private readonly BreakpointManager _breakpointManager;
9-
private readonly Z80 _cpu;
12+
private Emulator _emulator;
13+
private bool _isEnabled;
14+
private Word _previousAddress;
15+
private Word _currentAddress;
16+
17+
public BreakpointManager BreakpointManager { get; }
18+
19+
public Word PreviousAddress { get; private set; }
20+
21+
public bool IsBreakpointHit { get; private set; }
22+
23+
public bool IsEnabled
24+
{
25+
get => _isEnabled;
26+
set
27+
{
28+
_isEnabled = value;
29+
HandleIsEnabled();
30+
}
31+
}
1032

11-
public event EventHandler<EventArgs>? BreakpointHit;
33+
public event EventHandler<BreakpointHitEventArgs>? BreakpointHit;
1234

13-
public BreakpointHandler(BreakpointManager breakpointManager, Z80 cpu)
35+
public BreakpointHandler(Emulator emulator)
1436
{
15-
_breakpointManager = breakpointManager;
16-
_cpu = cpu;
37+
_emulator = emulator;
1738

18-
_cpu.BeforeInstruction += BeforeInstruction;
39+
BreakpointManager = new BreakpointManager(emulator.Cpu);
40+
41+
SubscribeToEvents();
42+
}
43+
44+
public void Update(Emulator emulator)
45+
{
46+
UnsubscribeFromEvents();
47+
SubscribeToEvents();
48+
49+
BreakpointManager.Update(emulator.Cpu);
50+
51+
_emulator = emulator;
1952
}
2053

21-
private void BeforeInstruction(BeforeInstructionEventArgs e)
54+
private void BeforeInstruction(Word pc)
2255
{
23-
if (!_breakpointManager.CheckHit())
56+
_previousAddress = _currentAddress;
57+
_currentAddress = pc;
58+
59+
IsBreakpointHit = BreakpointManager.IsRegisterBreakpointHit();
60+
61+
if (!IsBreakpointHit)
2462
{
63+
PreviousAddress = pc;
2564
return;
2665
}
2766

28-
e.IsBreakpoint = true;
67+
_emulator.Break();
68+
69+
BreakpointHit?.Invoke(this, new BreakpointHitEventArgs(_previousAddress));
70+
}
2971

30-
BreakpointHit?.Invoke(this, EventArgs.Empty);
72+
private void MemoryOnMemoryUpdated(Word address)
73+
{
74+
IsBreakpointHit = BreakpointManager.IsMemoryBreakpointHit(address, _emulator.Memory);
75+
76+
if (!IsBreakpointHit)
77+
{
78+
return;
79+
}
80+
81+
_emulator.Break();
82+
83+
BreakpointHit?.Invoke(this, new BreakpointHitEventArgs(_currentAddress));
84+
}
85+
86+
private void HandleIsEnabled()
87+
{
88+
UnsubscribeFromEvents();
89+
90+
if (IsEnabled)
91+
{
92+
SubscribeToEvents();
93+
}
94+
}
95+
96+
private void SubscribeToEvents()
97+
{
98+
_emulator.Cpu.BeforeInstruction += BeforeInstruction;
99+
_emulator.Memory.MemoryUpdated += MemoryOnMemoryUpdated;
100+
}
101+
102+
private void UnsubscribeFromEvents()
103+
{
104+
_emulator.Cpu.BeforeInstruction -= BeforeInstruction;
105+
_emulator.Memory.MemoryUpdated -= MemoryOnMemoryUpdated;
31106
}
32107

33108
public void Dispose()
34109
{
35110
GC.SuppressFinalize(this);
36111

37-
_cpu.BeforeInstruction -= BeforeInstruction;
112+
UnsubscribeFromEvents();
38113
}
39114
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
namespace OldBit.Spectron.Debugger.Breakpoints;
2+
3+
public sealed class BreakpointList
4+
{
5+
private readonly List<Breakpoint> _breakpoints = [];
6+
7+
public IReadOnlyList<Breakpoint> Breakpoints => _breakpoints;
8+
public IReadOnlyList<RegisterBreakpoint> Register { get; private set; } = [];
9+
public IReadOnlyList<MemoryBreakpoint> Memory { get; private set; } = [];
10+
11+
public void Add(Breakpoint breakpoint)
12+
{
13+
_breakpoints.Add(breakpoint);
14+
15+
SynchronizeLists();
16+
}
17+
18+
public void AddIfNotExists(Breakpoint breakpoint)
19+
{
20+
if (!_breakpoints.Contains(breakpoint))
21+
{
22+
Add(breakpoint);
23+
}
24+
}
25+
26+
public void Remove(Breakpoint breakpoint)
27+
{
28+
_breakpoints.Remove(breakpoint);
29+
30+
SynchronizeLists();
31+
}
32+
33+
public void Replace(Breakpoint original, Breakpoint replacement)
34+
{
35+
var existingIndex = _breakpoints.IndexOf(original);
36+
37+
if (existingIndex < 0)
38+
{
39+
return;
40+
}
41+
42+
_breakpoints[existingIndex] = replacement;
43+
44+
SynchronizeLists();
45+
}
46+
47+
private void SynchronizeLists()
48+
{
49+
Register = _breakpoints.Where(x => x is RegisterBreakpoint).Cast<RegisterBreakpoint>().ToList();
50+
Memory = _breakpoints.Where(x => x is MemoryBreakpoint).Cast<MemoryBreakpoint>().ToList();
51+
}
52+
}

0 commit comments

Comments
 (0)