Skip to content

Commit 5138a8a

Browse files
authored
Feature/vscode debugger (#166)
* Add VSCode debugger extension and a .NET debug adapter library. * Supports source debuging for cc65 toolchain. * Also supports disassembly debugging of binaries. * Integrated .NET debug adapter library in Avalonia UI Desktop (via TCP) app and stand-alone simple console host (via STDIO). * Fix functional 6502 test to also work on Mac and Linux.
1 parent c355f8e commit 5138a8a

101 files changed

Lines changed: 15307 additions & 191 deletions

File tree

Some content is hidden

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

.vscode/launch.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,48 @@
6464
"console": "integratedTerminal",
6565
"stopAtEntry": false
6666
},
67+
{
68+
"name": "Avalonia desktop app with TCP debug server",
69+
"type": "coreclr",
70+
"request": "launch",
71+
"preLaunchTask": "build avalonia desktop app",
72+
"program": "${workspaceFolder}/src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Desktop/bin/Debug/net10.0/Highbyte.DotNet6502.App.Avalonia.Desktop.dll",
73+
"args": ["--enableExternalDebug", "--debug-port", "6502", "--console-log", "-l", "Debug"],
74+
"cwd": "${workspaceFolder}/src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Desktop/bin/Debug/net10.0",
75+
"console": "integratedTerminal",
76+
"stopAtEntry": false
77+
},
78+
{
79+
"name": "Avalonia desktop app with auto starting C64, loading specified program, and enable TCP debug server",
80+
"type": "coreclr",
81+
"request": "launch",
82+
"preLaunchTask": "build avalonia desktop app",
83+
"program": "${workspaceFolder}/src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Desktop/bin/Debug/net10.0/Highbyte.DotNet6502.App.Avalonia.Desktop.dll",
84+
"args": ["--system", "C64", "--start", "--waitForSystemReady", "--loadPrg", "~/source/repos/dotnet-6502/samples/Assembler/C64/Raster/Build/smooth_scroller_and_raster.prg", "--runLoadedProgram", "--enableExternalDebug", "--debug-port", "6502", "--console-log", "-l", "Debug"],
85+
"cwd": "${workspaceFolder}/src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Desktop/bin/Debug/net10.0",
86+
"console": "integratedTerminal",
87+
"stopAtEntry": false
88+
},
89+
6790
{
6891
"name": "C#: Highbyte.DotNet6502.App.Avalonia.Browser",
6992
"type": "dotnet",
7093
"request": "launch",
7194
"projectPath": "${workspaceFolder}/src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Browser/Highbyte.DotNet6502.App.Avalonia.Browser.csproj",
7295
"launchConfigurationId": "TargetFramework=;Avalonia.Browser"
7396
},
97+
{
98+
"name": "Attach to Avalonia desktop app",
99+
"type": "coreclr",
100+
"request": "attach",
101+
"processName": "Highbyte.DotNet6502.App.Avalonia.Desktop"
102+
},
103+
{
104+
"name": "Attach to DotNet6502 VSCode Debug Adapter ConsoleApp",
105+
"type": "coreclr",
106+
"request": "attach",
107+
"processName": "Highbyte.DotNet6502.DebugAdapter.ConsoleApp"
108+
},
74109
{
75110
"name": ".NET Core Attach",
76111
"type": "coreclr",

.vscode/tasks.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,22 @@
9797
"isDefault": true
9898
}
9999
},
100+
{
101+
"label": "build simple debug adapter console host",
102+
"command": "dotnet",
103+
"type": "process",
104+
"args": [
105+
"build",
106+
"${workspaceFolder}/src/apps/Highbyte.DotNet6502.DebugAdapter/Highbyte.DotNet6502.DebugAdapter.ConsoleApp.csproj",
107+
"/property:GenerateFullPaths=true",
108+
"/consoleloggerparameters:NoSummary"
109+
],
110+
"problemMatcher": "$msCompile",
111+
"group": {
112+
"kind": "build",
113+
"isDefault": true
114+
}
115+
},
100116
{
101117
"label": "test",
102118
"command": "dotnet",

Directory.Packages.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@
7575
<PackageVersion Include="SkiaSharp.Views.Blazor" Version="3.119.1" />
7676
<!-- SourceLink -->
7777
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
78+
<!-- StreamJsonRpc for Debug Adapter Protocol -->
79+
<PackageVersion Include="StreamJsonRpc" Version="2.19.27" />
7880
<!-- System packages -->
7981
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
8082
<PackageVersion Include="System.Net.Http.Json" Version="10.0.0" />

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@
4545

4646
See [Desktop Apps](doc/DESKTOP_APPS.md) for download links for pre-built executables and instructions for Windows, Linux, and macOS.
4747

48+
## VS Code debugger extension
49+
50+
| [VSCode source debugging](tools/vscode-extension/README.md)| [VSCode disassembly debugging](tools/vscode-extension/README.md) |
51+
| ----------------------------------------------- | ------------------------------------------------- |
52+
| <img src="doc/Screenshots/VSCode_source_debug.png" title="VSCode source debug"/> | <img src="doc/Screenshots/VSCode_disassembly_debug.png" title="VSCode disassembly debug"/> |
53+
4854
## Other features
4955

5056
| [Run 6502 machine code in your own .NET apps](doc/CPU_LIBRARY.md) | [Machine code monitor](doc/MONITOR.md) | [C64 Basic AI code completion](doc/SYSTEMS_C64_AI_CODE_COMPLETION.md) |

benchmarks/Highbyte.DotNet6502.Benchmarks/Commodore64/C64ExecuteFrameBenchmark.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System.Runtime.InteropServices;
22
using BenchmarkDotNet.Attributes;
3-
using BenchmarkDotNet.Engines;
43
using BenchmarkDotNet.Jobs;
54
using Highbyte.DotNet6502.Systems;
65
using Highbyte.DotNet6502.Systems.Commodore64;
@@ -17,8 +16,6 @@ public class C64ExecuteFrameBenchmark
1716
{
1817
private C64 _c64WithInstrumentation = default!;
1918
private C64 _c64WithoutInstrumentation = default!;
20-
private SystemRunner _systemRunnerWithInstrumentation = default!;
21-
private SystemRunner _systemRunnerWithoutInstrumentation = default!;
2219
private ushort _startAddress;
2320

2421
[Params(1)]
@@ -50,8 +47,6 @@ public void Setup()
5047
LoadProgram(_c64WithInstrumentation.Mem, _startAddress);
5148
LoadProgram(_c64WithoutInstrumentation.Mem, _startAddress);
5249

53-
_systemRunnerWithInstrumentation = new SystemRunner(_c64WithInstrumentation);
54-
_systemRunnerWithoutInstrumentation = new SystemRunner(_c64WithoutInstrumentation);
5550
}
5651

5752
private void LoadProgram(Memory mem, ushort startAddress)
@@ -96,7 +91,7 @@ public void ExecFrameWithoutInstrumentation()
9691
_c64WithoutInstrumentation.CPU.PC = _startAddress;
9792
for (var i = 0; i < NumberOfFramesToExecute; i++)
9893
{
99-
var execEvaluatorTriggerResult = _c64WithoutInstrumentation.ExecuteOneFrame(_systemRunnerWithoutInstrumentation);
94+
var execEvaluatorTriggerResult = _c64WithoutInstrumentation.ExecuteOneFrame();
10095
}
10196
}
10297

@@ -106,7 +101,7 @@ public void ExecFrameWithInstrumentation()
106101
_c64WithInstrumentation.CPU.PC = _startAddress;
107102
for (var i = 0; i < NumberOfFramesToExecute; i++)
108103
{
109-
var execEvaluatorTriggerResult = _c64WithInstrumentation.ExecuteOneFrame(_systemRunnerWithInstrumentation);
104+
var execEvaluatorTriggerResult = _c64WithInstrumentation.ExecuteOneFrame();
110105
}
111106
}
112107
}

benchmarks/Highbyte.DotNet6502.Benchmarks/Commodore64/C64ExecuteInstructionBenchmark.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System.Runtime.InteropServices;
22
using BenchmarkDotNet.Attributes;
3-
using BenchmarkDotNet.Engines;
43
using BenchmarkDotNet.Jobs;
54
using Highbyte.DotNet6502.Systems;
65
using Highbyte.DotNet6502.Systems.Commodore64;
@@ -17,8 +16,6 @@ public class C64ExecuteInstructionBenchmark
1716
{
1817
private C64 _c64WithInstrumentation = default!;
1918
private C64 _c64WithoutInstrumentation = default!;
20-
private SystemRunner _systemRunnerWithInstrumentation = default!;
21-
private SystemRunner _systemRunnerWithoutInstrumentation = default!;
2219
private ushort _startAddress;
2320

2421
//[Params(1)]
@@ -50,8 +47,6 @@ public void Setup()
5047
LoadProgram(_c64WithInstrumentation.Mem, _startAddress);
5148
LoadProgram(_c64WithoutInstrumentation.Mem, _startAddress);
5249

53-
_systemRunnerWithInstrumentation = new SystemRunner(_c64WithInstrumentation);
54-
_systemRunnerWithoutInstrumentation = new SystemRunner(_c64WithoutInstrumentation);
5550
}
5651

5752
private void LoadProgram(Memory mem, ushort startAddress)
@@ -96,7 +91,7 @@ public void ExecInsWithoutInstrumentation()
9691
_c64WithoutInstrumentation.CPU.PC = _startAddress;
9792
for (var i = 0; i < NumberOfInstructionsToExecute; i++)
9893
{
99-
_c64WithoutInstrumentation.ExecuteOneInstruction(_systemRunnerWithoutInstrumentation, out InstructionExecResult instructionExecResult);
94+
_c64WithoutInstrumentation.ExecuteOneInstruction(out InstructionExecResult instructionExecResult);
10095
}
10196
}
10297

@@ -106,7 +101,7 @@ public void ExecInsWithInstrumentation()
106101
_c64WithInstrumentation.CPU.PC = _startAddress;
107102
for (var i = 0; i < NumberOfInstructionsToExecute; i++)
108103
{
109-
_c64WithInstrumentation.ExecuteOneInstruction(_systemRunnerWithInstrumentation, out InstructionExecResult instructionExecResult);
104+
_c64WithInstrumentation.ExecuteOneInstruction(out InstructionExecResult instructionExecResult);
110105
}
111106
}
112107
}

doc/APPS_CONSOLE_MONITOR.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Cross-platform desktop console application with a only UI being a machine code m
55

66
<img align="top" src="Screenshots/ConsoleMonitor.png" width="25%" height="25%" title="Machine code monitor native console host window" />
77

8+
> Note: There is also the [VS Code debugger extension](../tools/vscode-extension/README.md) that can be used for debugging both assembly source and raw disassembly. It's a lost more powerful than the built-in machine code monitor described here.
9+
810
Technologies
911
- UI: standard .NET console.
1012
- Input: standard .NET console.

doc/DEBUG_ADAPTER_TCP.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# TCP Debug Adapter Integration
2+
3+
## Overview
4+
5+
The debug adapter now supports TCP transport in addition to STDIN/STDOUT, enabling debugging of desktop GUI applications like the Avalonia Desktop app.
6+
7+
## Architecture
8+
9+
### Library Structure
10+
11+
- **Highbyte.DotNet6502.DebugAdapter** (library)
12+
- `IDebugAdapterTransport` - Interface for transport implementations
13+
- `StdioTransport` - STDIN/STDOUT implementation for console apps
14+
- `TcpTransport` - TCP socket implementation for network connections
15+
- `TcpDebugAdapterServer` - TCP listener that accepts debug connections
16+
- `DapProtocol` - Debug Adapter Protocol message handling
17+
- `DebugAdapterLogic` - Core DAP request/response logic
18+
- `Ca65DbgParser` - Debug symbol parser
19+
20+
### Console Application
21+
22+
- **Highbyte.DotNet6502.DebugAdapter.ConsoleApp**
23+
- Uses `StdioTransport` for VSCode extension integration
24+
- Launched by VSCode extension as a child process
25+
- Maintains backward compatibility with existing VSCode extension
26+
27+
### Desktop Application Integration
28+
29+
- **Highbyte.DotNet6502.App.Avalonia.Desktop**
30+
- Integrated `TcpDebugAdapterServer` for TCP-based debugging
31+
- Accepts `--debug-port <port>` command-line argument
32+
- Accepts `--debug-wait` flag to wait for debugger connection before starting
33+
- Runs debug adapter server on background thread
34+
35+
## Usage
36+
37+
### Avalonia Desktop App with TCP Debug Adapter
38+
39+
Start the Avalonia Desktop app with debug adapter enabled:
40+
41+
```bash
42+
./Highbyte.DotNet6502.App.Avalonia.Desktop --debug-port 6502
43+
```
44+
45+
To wait for the debugger to connect before starting:
46+
47+
```bash
48+
./Highbyte.DotNet6502.App.Avalonia.Desktop --debug-port 6502 --debug-wait
49+
```
50+
51+
Combined with console logging:
52+
53+
```bash
54+
./Highbyte.DotNet6502.App.Avalonia.Desktop --debug-port 6502 --console-log -l Debug
55+
```
56+
57+
### VSCode Configuration
58+
59+
To debug the Avalonia Desktop app from VSCode, you'll need to add a launch configuration that connects to the TCP port:
60+
61+
```json
62+
{
63+
"type": "dotnet6502-debug",
64+
"request": "attach",
65+
"name": "Attach to Avalonia Desktop",
66+
"debugServer": 6502,
67+
"program": "${workspaceFolder}/samples/Assembler/GenericComputer/snake6502/build/snake6502.prg",
68+
"stopOnEntry": true,
69+
"trace": true
70+
}
71+
```
72+
73+
**Note:** The VSCode extension may need updates to support the `debugServer` configuration property for TCP connections.
74+
75+
## Implementation Details
76+
77+
### TCP Transport
78+
79+
The `TcpTransport` class implements `IDebugAdapterTransport` using a `TcpClient` and `NetworkStream`:
80+
81+
- Reads DAP messages with Content-Length headers
82+
- Writes DAP messages with proper framing
83+
- Fires `Disconnected` event when connection is closed
84+
85+
### TCP Debug Adapter Server
86+
87+
The `TcpDebugAdapterServer` class:
88+
89+
- Listens on `IPAddress.Loopback` (localhost only)
90+
- Accepts a single client connection at a time
91+
- Fires `ClientConnected` event with a `TcpTransport` instance
92+
- Supports port 0 for random port assignment
93+
94+
### Avalonia Desktop Integration
95+
96+
The Avalonia Desktop app:
97+
98+
1. Parses `--debug-port` and `--debug-wait` command-line arguments
99+
2. Creates a `TcpDebugAdapterServer` when debug port is specified
100+
3. Handles `ClientConnected` event by:
101+
- Creating `DapProtocol` and `DebugAdapterLogic` instances
102+
- Starting a message loop on a background thread
103+
- Logging to a separate file in the temp directory
104+
4. Optionally waits for debugger connection (30 second timeout)
105+
5. Continues with normal application startup
106+
107+
### Debug Logging
108+
109+
Debug adapter activity is logged to:
110+
111+
```
112+
$TMPDIR/dotnet6502-debugadapter-avalonia-{timestamp}.log
113+
```
114+
115+
This log file contains:
116+
- Connection events
117+
- DAP messages sent/received
118+
- Errors and exceptions
119+
120+
## Future Enhancements
121+
122+
1. **VSCode Extension Updates**
123+
- Add support for `debugServer` configuration property
124+
- Allow attaching to running desktop applications
125+
- Provide UI for discovering running instances
126+
127+
2. **Multiple Client Support**
128+
- Allow multiple simultaneous debug connections
129+
- Share breakpoints and state across clients
130+
131+
3. **Auto-Discovery**
132+
- Broadcast availability on local network
133+
- Allow VSCode to discover running instances
134+
135+
4. **Security**
136+
- Add authentication token support
137+
- Support remote debugging with encryption
138+
139+
## Testing
140+
141+
### Manual Testing
142+
143+
1. Start the Avalonia Desktop app with debug port:
144+
```bash
145+
./Highbyte.DotNet6502.App.Avalonia.Desktop --debug-port 6502 --debug-wait --console-log
146+
```
147+
148+
2. Connect with a TCP client (e.g., `nc`):
149+
```bash
150+
nc localhost 6502
151+
```
152+
153+
3. Send a DAP initialize request:
154+
```
155+
Content-Length: 122
156+
157+
{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"dotnet6502","pathFormat":"path"}}
158+
```
159+
160+
4. Verify response is received
161+
162+
### Automated Testing
163+
164+
Future work: Add integration tests that:
165+
- Start the desktop app with debug port
166+
- Connect via TCP
167+
- Send DAP requests
168+
- Verify responses
169+
- Test breakpoint functionality

0 commit comments

Comments
 (0)