This document outlines the work required to implement debugging support in VS Code for .NET nanoFramework. The current Visual Studio extension uses the old COM-based debug engine (ICorDebug interfaces), which is not compatible with VS Code. VS Code uses the Debug Adapter Protocol (DAP), a JSON-based protocol that defines how development tools communicate with debug adapters.
✅ IMPORTANT: No Wire Protocol Changes Required
This implementation can be done entirely on the host side without any modifications to the Wire Protocol or the nanoFramework interpreter (nf-interpreter). The existing Wire Protocol already provides all necessary debugging commands. The Debug Adapter is purely a translation layer between VS Code's DAP and the existing Wire Protocol.
| Phase | Status | Description |
|---|---|---|
| Phase 1 | ✅ Complete | Foundation & Infrastructure |
| Phase 2 | ✅ Complete | Core Debug Adapter Implementation |
| Phase 3 | ✅ Complete | Wire Protocol Bridge (nf-debugger integrated) |
| Phase 4 | ✅ Complete | Source Mapping & Symbols |
| Phase 5 | ✅ Complete | User Experience |
| Phase 6 | 🔄 Partial | Testing & Quality (documentation complete, tests pending) |
| Phase 7 | ⬜ Not Started | Advanced Features (Future) |
Core debugging functionality is complete! The following major components have been implemented:
-
TypeScript Debug Adapter (
src/debugger/)nanoDebugSession.ts- DAP protocol handlernanoRuntime.ts- Debug runtime coordinationbridge/nanoBridge.ts- JSON-RPC communication with .NET bridge
-
.NET Debug Bridge (
src/debugger/bridge/dotnet/nanoFramework.Tools.DebugBridge/)Program.cs- Main entry point, JSON-RPC command handlingDebugBridgeSession.cs- Core session management, wraps nf-debuggerProtocol/- Command/response types aligned with TypeScript
-
Symbol Resolution (
Symbols/)PdbxModels.cs- XML models for .pdbx files (IL offset mapping)PortablePdbReader.cs- Portable PDB parsing for source locationsSymbolResolver.cs- Source ↔ IL offset resolutionAssemblyManager.cs- Device/local assembly tracking
-
User Experience
- Device auto-detection and selection
- Workspace state persistence for device preference
- Debug output forwarding (Debug.WriteLine support)
The existing Wire Protocol (implemented in nf-interpreter and exposed via nf-debugger library) already supports:
| Debugging Capability | Wire Protocol Command(s) | Status |
|---|---|---|
| Set/Clear Breakpoints | Debugging_Execution_Breakpoints |
✅ Exists |
| Step In/Over/Out | Debugging_Execution_Breakpoints with flags |
✅ Exists |
| Pause/Resume | Debugging_Execution_ChangeConditions |
✅ Exists |
| List Threads | Debugging_Thread_List |
✅ Exists |
| Get Call Stack | Debugging_Thread_Stack |
✅ Exists |
| Inspect Variables | Debugging_Value_GetStack, GetField, GetArray, GetBlock |
✅ Exists |
| Modify Variables | Debugging_Value_SetBlock, SetArray, Assign |
✅ Exists |
| Evaluate Expressions | Debugging_Value_* commands |
✅ Exists |
| Resolve Types/Methods | Debugging_Resolve_Type, _Method, _Field, _Assembly |
✅ Exists |
| Exception Handling | Breakpoint flags: EXCEPTION_THROWN, CAUGHT, UNCAUGHT |
✅ Exists |
| Thread Suspend/Resume | Debugging_Thread_Suspend, _Resume |
✅ Exists |
| Deploy Assemblies | WriteMemory, DeploymentExecute |
✅ Exists |
| Device Info | Monitor_Ping, Monitor_TargetInfo |
✅ Exists |
The Debug Adapter simply translates DAP ↔ Wire Protocol messages. The nf-debugger .NET library already handles all the low-level communication.
The existing Visual Studio extension (nf-Visual-Studio-extension) implements debugging using:
-
CorDebug Engine (
CorDebug.cs,CorDebugProcess.cs,CorDebugThread.cs)- Implements
ICorDebug,ICorDebugProcess,ICorDebugThreadCOM interfaces - Uses Visual Studio's proprietary debug engine infrastructure
- Implements
-
Wire Protocol Communication (via
nf-debuggerlibrary)Engineclass manages communication with the device- Commands like
Debugging_Execution_Breakpoints,Debugging_Thread_Stack, etc. - Serial/USB/Network transport layers
-
Debug Launch Provider (
NanoDebuggerLaunchProvider.cs)- Handles launch configuration and debug session initialization
The native interpreter (nf-interpreter) supports:
- Breakpoint Types: Step In, Step Over, Step Out, Hard, Exception (thrown/caught/uncaught), Thread events
- Thread Management: Create, List, Stack, Kill, Suspend, Resume
- Value Inspection: GetStack, GetField, GetArray, GetBlock, SetBlock, AllocateObject, AllocateString
- Type System: Assemblies, AppDomains, Resolve Type/Field/Method/Assembly
- Execution Control: ChangeConditions, Pause, Resume, Reboot
- Memory Operations: Read, Write, Check, Erase
VS Code requires a Debug Adapter that implements DAP. Key concepts:
- Debug Adapter runs as a separate process or inline
- Communicates via JSON messages over stdin/stdout or TCP
- Standard request/response/event model
- Language-agnostic debugging UI
- Create new TypeScript/Node.js project for the Debug Adapter
- Set up build configuration and dependencies
- Add
@vscode/debugadapterand@vscode/debugprotocolnpm packages - Configure TypeScript compilation settings
Files created:
src/debugger/
├── nanoDebugAdapter.ts ✅ Main debug adapter entry point
├── nanoDebugSession.ts ✅ Debug session management (all DAP handlers)
├── nanoRuntime.ts ✅ Communication with nf-debugger bridge
├── bridge/
│ ├── nanoBridge.ts ✅ .NET bridge communication layer
│ └── dotnet/ ✅ .NET bridge project (DebugBridge)
├── types/
│ └── debugTypes.ts ✅ Type definitions
└── utils/
├── subject.ts ✅ Async notification utility
└── logger.ts ✅ Debug logging utilities
- Research options for using the C# nf-debugger library:
- Option A: Port critical parts to TypeScript/Node.js
- ✅ Option B (CHOSEN): Create a .NET bridge process that the TypeScript adapter communicates with
- Option C: Use Edge.js or similar to call .NET code from Node.js
- Create .NET bridge project structure
- Implement JSON-RPC protocol for TypeScript ↔ .NET communication
- Add
debuggerscontribution point - Define debug configuration schema (
configurationAttributes) - Add
breakpointscontribution for supported languages - Define initial configurations and snippets
- Add activation events for debugging
- Option B: Create a .NET bridge process that the TypeScript adapter communicates with
- Option C: Use Edge.js or similar to call .NET code from Node.js
- Implement Wire Protocol message encoding/decoding in TypeScript
- Implement serial port communication using
serialportnpm package - Implement USB communication (if needed, using
usbnpm package)
- Add
debuggerscontribution point - Define debug configuration schema (
configurationAttributes) - Add
breakpointscontribution for supported languages - Define initial configurations and snippets
- Add activation events for debugging
Example contribution:
{
"contributes": {
"breakpoints": [
{ "language": "csharp" }
],
"debuggers": [
{
"type": "nanoframework",
"label": ".NET nanoFramework Debug",
"program": "./out/debugger/nanoDebugAdapter.js",
"runtime": "node",
"configurationAttributes": {
"launch": {
"required": ["program"],
"properties": {
"program": {
"type": "string",
"description": "Path to the .nfproj or .sln file"
},
"device": {
"type": "string",
"description": "Target device (COM port or IP address)"
},
"stopOnEntry": {
"type": "boolean",
"default": false
}
}
},
"attach": {
"properties": {
"device": {
"type": "string",
"description": "Target device to attach to"
}
}
}
}
}
]
}
}-
initializeRequest- Return debug adapter capabilities -
configurationDoneRequest- Signal configuration complete -
disconnectRequest- Clean up and disconnect
-
launchRequest- Deploy and start debugging -
attachRequest- Attach to running device
-
setBreakPointsRequest- Set source breakpoints -
setFunctionBreakpointsRequest- Set function breakpoints -
setExceptionBreakpointsRequest- Configure exception handling
-
continueRequest- Resume execution -
nextRequest- Step over -
stepInRequest- Step into -
stepOutRequest- Step out -
pauseRequest- Break execution -
terminateRequest- Stop debugging
-
threadsRequest- List active threads -
stackTraceRequest- Get call stack -
scopesRequest- Get variable scopes -
variablesRequest- Get variables in scope
-
evaluateRequest- Evaluate expression -
setVariableRequest- Modify variable value
-
modulesRequest- List loaded assemblies -
exceptionInfoRequest- Get exception details
-
initialized- Adapter ready -
stopped- Execution stopped (breakpoint, step, exception) -
continued- Execution resumed (implicit) -
terminated- Debug session ended -
breakpoint- Breakpoint status changed -
output- Debug console output -
thread- Thread created/exited (TODO: add thread events) -
module- Assembly loaded/unloaded (TODO: add module events)
Note: The .NET bridge now integrates with the actual nf-debugger NuGet package (nanoFramework.Tools.Debugger.Net). All core debug operations are implemented using the real Engine and PortBase classes.
| DAP Request | Wire Protocol Command(s) | Status |
|---|---|---|
launch |
Deploy assemblies, Debugging_Execution_ChangeConditions |
✅ Implemented |
attach |
Monitor_Ping, Debugging_Execution_ChangeConditions |
✅ Implemented |
setBreakpoints |
Debugging_Execution_Breakpoints |
✅ Implemented |
continue |
_engine.ResumeExecution() |
✅ Implemented |
next |
Debugging_Execution_BreakpointDef (STEP_OVER) |
✅ Implemented |
stepIn |
Debugging_Execution_BreakpointDef (STEP_IN) |
✅ Implemented |
stepOut |
Debugging_Execution_BreakpointDef (STEP_OUT) |
✅ Implemented |
pause |
_engine.PauseExecution() |
✅ Implemented |
threads |
_engine.GetThreadList(), _engine.GetThreadStack() |
✅ Implemented |
stackTrace |
_engine.GetThreadStack(), _engine.GetMethodName() |
✅ Implemented |
scopes |
_engine.GetStackFrameInfo() |
✅ Implemented |
variables |
_engine.GetStackFrameValue(), Engine.StackValueKind |
✅ Implemented |
evaluate |
_engine.GetStackFrameValue() |
✅ Implemented |
- Implement
_engine.ResolveType()for variable type names - Implement variable value extraction for all RuntimeValue types:
- RuntimeValue_Primitive (numbers, bool, etc.)
- RuntimeValue_String
- RuntimeValue_Array (element enumeration)
- RuntimeValue_Class (field enumeration)
- RuntimeValue_ValueType
- RuntimeValue_Object
- Cache resolved types for performance (via _variableReferences map)
- Basic breakpoint management in TypeScript
- Breakpoint structure mapped to WireProtocol.Commands.Debugging_Execution_BreakpointDef
- Support for step breakpoints (STEP_IN, STEP_OVER, STEP_OUT, STEP_RETURN flags)
- Map source file locations to IL offsets (needs PE/PDB parsing - Phase 4)
- Handle breakpoint validation (verified/unverified states - Phase 4)
- Support conditional breakpoints (if device supports)
- Support hit count breakpoints
The .NET bridge project (nanoFramework.Tools.DebugBridge) now includes:
- JSON-RPC protocol handling over stdin/stdout
- Command routing infrastructure
- nanoFramework.Tools.Debugger.Net NuGet package integrated
- Initialize command with capability reporting
- Connect/Disconnect using PortBase.CreateInstanceForSerial/Network
- SetBreakpoint/RemoveBreakpoint with BreakpointDef structures
- Continue/Pause using Engine.ResumeExecution/PauseExecution
- Step commands using BreakpointDef with step flags
- GetThreads using Engine.GetThreadList/GetThreadStack
- GetStackTrace using Engine.GetThreadStack and method name resolution
- GetScopes/GetVariables using Engine.GetStackFrameInfo/GetStackFrameValue
- RuntimeValue handling for all value types (primitives, strings, arrays, objects)
- Helper classes: ScopeReference, RuntimeValueReference, ScopeType enum
- Build successful (all compilation errors fixed)
- TypeScript-C# protocol aligned (command, id, args/data)
- .NET bridge compiles successfully
- TypeScript adapter compiles successfully
- Bridge executable responds to JSON-RPC commands
- Gulp task added for building bridge (
gulp build-debug-bridge) - Bridge deployed to
bin/nanoDebugBridge/
Note: This phase provides the critical symbol infrastructure for source-level debugging.
The nanoFramework build process generates .pdbx files (XML-based symbol files) that contain IL mappings between CLR and nanoFramework offsets.
- Research pdbx format from nf-Visual-Studio-extension
- Create
PdbxModels.cs- XML serialization models for .pdbx filesPdbxFile,PdbxAssembly,PdbxClass,PdbxMethod,PdbxFieldPdbxToken- CLR/nanoCLR token mappingPdbxIL- CLR/nanoCLR IL offset mapping
- Implement
PdbxMethod.GetNanoILFromCLRIL()andGetCLRILFromNanoIL()- binary search with interpolation - Create
SymbolResolver.cs- symbol resolution classLoadSymbols()- parse .pdbx filesLoadSymbolsFromDirectory()- batch loadGetBreakpointLocation()- source → IL for breakpointsGetSourceLocation()- IL → source for stack tracesGetMethodInfo()- method metadata lookup
Files created:
src/debugger/bridge/dotnet/nanoFramework.Tools.DebugBridge/Symbols/
├── PdbxModels.cs ✅ Pdbx XML model classes (~340 lines)
└── SymbolResolver.cs ✅ Symbol resolution (~334 lines)
- Add
SymbolResolverfield toDebugBridgeSession - Update
SetBreakpoint()to use symbol resolution for verified breakpoints - Update
GetStackTrace()to return source locations - Add
LoadSymbols()andLoadSymbolsFromDirectory()methods - Add
RebindPendingBreakpoints()to verify pending breakpoints when symbols load - Add
loadSymbolscommand handler toProgram.cs
To get actual source line numbers (not just IL offsets), need to parse portable PDB files.
- Add System.Reflection.Metadata NuGet for PDB parsing
- Create
PortablePdbReader.cs- parses portable PDB filesLoad()- load PDB from file pathLoadFromEmbeddedPdb()- load PDB embedded in PE fileGetSequencePoints()- get sequence points by method tokenFindSequencePoint()- find sequence point for IL offsetFindSequencePointBySourceLocation()- find by source file/line
- Extract sequence points from portable PDBs
- Correlate with .pdbx IL mappings (CLR IL offset → source location)
- Integrate with
SymbolResolverto populate source info in cache
Files created/updated:
src/debugger/bridge/dotnet/nanoFramework.Tools.DebugBridge/Symbols/
├── PortablePdbReader.cs ✅ PDB parsing (~330 lines)
└── SymbolResolver.cs ✅ Updated to use PortablePdbReader
How it works:
- When loading a
.pdbxfile, SymbolResolver also looks for corresponding.pdbfile - The
.pdbxprovides CLR ↔ nanoFramework IL offset mapping - The portable PDB provides CLR IL offset → source file/line mapping
- Combined: nanoFramework IL offset → source location for stack traces
- And: source location → nanoFramework IL offset for breakpoints
- Create
AssemblyManager.cs- tracks device and local assembliesRegisterDeviceAssembly()- register assemblies from deviceAddSearchPath()/ScanLocalAssemblies()- scan for local assembliesGetPdbxPath()- find symbol files for assemblies- CRC32 checksum matching for assembly verification
- Track deployed assemblies with version and checksum
- Match device assemblies to local source by name and checksum
- Detect and report assembly mismatches via
AssemblyMismatchDetectedevent - Integrate with DebugBridgeSession:
- Assembly scanning during
LoadSymbolsFromDirectory() GetAssemblyManager()accessor for assembly state
- Assembly scanning during
Files created:
src/debugger/bridge/dotnet/nanoFramework.Tools.DebugBridge/Symbols/
├── AssemblyManager.cs ✅ Assembly tracking (~340 lines)
Key types:
AssemblyInfo- device assembly (name, version, checksum, device index)LocalAssemblyInfo- local assembly (paths to PE, PDB, PDBX)AssemblyMismatchEventArgs- mismatch notification with reason
- Implement
DebugConfigurationProviderfor dynamic configuration - Provide smart defaults based on workspace
- Auto-detect available devices via SerialPortCtrl
- Basic device selection command added
- Integrate with existing device discovery (
serialportctrl.ts) - Show device picker when multiple devices available
- Remember last used device via workspaceState
Implementation details:
NanoDebugConfigurationProvider.resolveDevice()- intelligent device selection- Stored device preference:
nanoframework.debugDevicein workspace state - Auto-selects if only one device connected
- Shows picker for multiple devices
- Falls back gracefully for launch (vs attach which requires device)
- Forward device output to debug console (infrastructure ready)
- Support expression evaluation in console (evaluate request implemented)
- Handle Debug.WriteLine output (bridge integration complete)
OnEngineMessageevent handler captures device debug output- Forwards to VS Code debug console as "stdout" output events
- Show exception details in UI (exceptionInfoRequest)
- Support "Break on Exception" options (exception filters)
- Display exception call stack (stackTraceRequest)
Status: Documentation complete. Unit and integration tests pending device testing.
- Test Wire Protocol encoding/decoding
- Test DAP message handling
- Test breakpoint management
- Test variable resolution
- Test with real nanoFramework device
- Test with nanoFramework virtual device
- Test all debug scenarios (launch, attach, breakpoints, stepping)
- Update README with debugging instructions
- Added comprehensive Debugging section with features table
- Updated "Known Issues" to reflect debugging support
- Added launch.json configuration documentation
- Added troubleshooting section
- Document launch.json configuration options (in README)
- Add troubleshooting guide (in README and docs/debugging.md)
- Create debugging tutorial (docs/debugging.md)
- Complete debugging guide with all features
- Symbol file explanation
- Architecture overview
- Detailed troubleshooting section
- Investigate Edit and Continue possibilities
- Implement assembly hot-swap if feasible
- Support debugging multiple devices simultaneously
- Implement compound launch configurations
- Support network-connected devices
- Implement secure connection options
- Expose profiling data from device
- Integrate with VS Code performance tools
┌─────────────────────────────────────────────────────────────────┐
│ VS Code │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────────────┐ │
│ │ Debug UI │ │ Breakpoints │ │ Variables/Watch/Stack │ │
│ └──────┬──────┘ └──────┬───────┘ └────────────┬────────────┘ │
│ │ │ │ │
│ └────────────────┴────────────────────────┘ │
│ │ │
│ Debug Adapter Protocol (DAP) │
│ │ (JSON over stdin/stdout) │
└──────────────────────────┼───────────────────────────────────────┘
│
│ ← NEW CODE LIVES HERE
│ (Translation Layer)
▼
┌──────────────────────────────────────────────────────────────────┐
│ nanoFramework Debug Adapter │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ nanoDebugSession.ts │ │
│ │ • Receive DAP requests → Translate → Send WP commands │ │
│ │ • Receive WP responses → Translate → Send DAP events │ │
│ │ • Source/Symbol mapping (PE/PDB files) │ │
│ └───────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────┴───────────────────────────────────┐ │
│ │ nf-debugger Library (EXISTING) │ │
│ │ • Wire Protocol implementation ← NO CHANGES NEEDED │ │
│ │ • Engine class for communication │ │
│ │ • Serial/USB/Network transport │ │
│ └───────────────────────┬───────────────────────────────────┘ │
│ │ │
└──────────────────────────┼───────────────────────────────────────┘
│
┌────────────┴────────────┐
│ Serial/USB/Network │
│ (Existing Transport) │
└────────────┬────────────┘
│
│ ← NO CHANGES BELOW THIS LINE
▼
┌──────────────────────────────────────────────────────────────────┐
│ nanoFramework Device │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ nanoCLR │ │
│ │ • Wire Protocol handler ← NO CHANGES NEEDED │ │
│ │ • Debugger_* commands ← ALREADY IMPLEMENTED │ │
│ │ • Breakpoint engine ← ALREADY IMPLEMENTED │ │
│ │ • Execution control ← ALREADY IMPLEMENTED │ │
│ └───────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
Key Point: All new code is in the Debug Adapter layer. The nf-debugger library and nf-interpreter remain unchanged.
The critical decision is how to use the existing nf-debugger .NET library from the Node.js/TypeScript Debug Adapter:
Options:
| Option | Pros | Cons |
|---|---|---|
| A: .NET Bridge Process | Reuse 100% of existing nf-debugger code, proven implementation, no Wire Protocol reimplementation | Extra process, IPC overhead, .NET runtime dependency |
| B: Port to TypeScript | Native Node.js, single runtime, easier VS Code deployment | Significant effort (~4000+ lines of Wire Protocol code), need to maintain two codebases |
| C: Edge.js/.NET interop | Reuse code, single process | Complex setup, platform-specific issues, maintenance burden |
Recommendation: Option A (.NET Bridge Process) is strongly recommended because:
- ✅ Zero Wire Protocol changes needed
- ✅ Proven, battle-tested communication code
- ✅ Faster time to working debugger
- ✅ Easier to keep in sync with nf-debugger updates
- ✅ The VS extension already uses nf-debugger successfully
The bridge process can communicate with the Debug Adapter via:
- JSON-RPC over stdin/stdout (simple)
- Named pipes (faster)
- Local TCP socket (cross-platform)
- Serial port: Use
serialportnpm package - USB: Use
usbnpm package or delegate to .NET bridge - Network: Standard Node.js
netmodule
- Option to use existing
nanoFramework.Tools.MetadataProcessorfor PE parsing - Or implement lightweight TypeScript parser for essential metadata
{
"dependencies": {
"@vscode/debugadapter": "^1.65.0",
"@vscode/debugprotocol": "^1.65.0",
"serialport": "^12.0.0",
"await-notify": "^1.0.1"
},
"devDependencies": {
"@vscode/debugadapter-testsupport": "^1.65.0"
}
}nanoFramework.Tools.Debugger.NetNuGet package (to be integrated)- .NET 8.0 runtime
- VS Code Debug Adapter Protocol
- VS Code Debugger Extension Guide
- Mock Debug Example
- nf-Visual-Studio-extension
- nf-debugger
- nf-interpreter
| Phase | Estimated Duration | Actual |
|---|---|---|
| Phase 1: Foundation | 2-3 weeks | ✅ Complete |
| Phase 2: Core DAP Implementation | 3-4 weeks | ✅ Complete |
| Phase 3: Wire Protocol Bridge | 2-3 weeks | 🔄 In Progress (need nf-debugger integration) |
| Phase 4: Source Mapping | 2 weeks | ⬜ Not Started |
| Phase 5: User Experience | 2 weeks | ✅ Partially Complete |
| Phase 6: Testing & Documentation | 2 weeks | ⬜ Not Started |
| Total | 13-16 weeks | ~40% Complete |
- Integrate nf-debugger NuGet package into the .NET bridge project
- Implement actual Wire Protocol calls in
DebugBridgeSession.cs - Test with real nanoFramework device
- Implement PE/PDB parsing for source mapping
- Add comprehensive error handling
- Review the existing VS extension source code in
nf-Visual-Studio-extension - Study the
nf-debuggerlibrary'sEngineclass and Wire Protocol commands - Set up the vscode-mock-debug example and understand DAP flow
- Begin with Phase 1.1 - creating the project structure
- Implement a minimal launch/attach flow first, then iterate
Document created: January 2026 Last updated: January 2026