This guide provides detailed information about debugging .NET nanoFramework applications using the VS Code extension.
- Overview
- Getting Started
- Debug Configurations
- Breakpoints
- Stepping Through Code
- Inspecting Variables
- Debug Console
- Exception Handling
- Symbol Files
- Troubleshooting
- Architecture
The VS Code extension provides full source-level debugging support for .NET nanoFramework applications. This includes:
- Setting and hitting breakpoints
- Stepping through code (step into, step over, step out)
- Inspecting local variables and object properties
- Evaluating expressions
- Viewing call stacks with source locations
- Breaking on exceptions
- Debug console output (Debug.WriteLine)
- .NET 10.0 Runtime - Required for the debug bridge
- nanoFramework device - Connected via USB/Serial with nanoFramework firmware
- Built project - Your project must be built with debug symbols
- Open your nanoFramework project in VS Code
- Build the project using
nanoFramework: Build Project - Press
F5or click "Run and Debug" in the Activity Bar - Select your device when prompted (if multiple are connected)
- The debugger will deploy your code and stop at the entry point
VS Code uses launch.json to configure debug sessions. Create this file in .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "nanoFramework: Launch",
"type": "nanoframework",
"request": "launch",
"program": "${workspaceFolder}/bin/Debug/${workspaceFolderBasename}.pe",
"stopOnEntry": true,
"deployAssemblies": true
}
]
}| Option | Type | Default | Description |
|---|---|---|---|
name |
string | required | Display name for the configuration |
type |
string | required | Must be "nanoframework" |
request |
string | required | "launch" or "attach" |
program |
string | required | Path to .pe file or assembly directory |
device |
string | "" |
Device connection (COM port or IP). Empty for auto-detect |
verbosity |
string | "none" |
Logging verbosity level. See Verbosity Levels |
verbose |
boolean | false |
(Deprecated) Enable verbose debug output. Use verbosity instead |
The verbosity option controls how much debug output is shown in the Debug Console:
| Level | Description |
|---|---|
"none" |
No debug bridge logging output. Only application output (Debug.WriteLine) is shown |
"information" |
Shows important events: connection status, errors, deployment progress, and key debugging milestones |
"debug" |
Full diagnostic output including internal operations, symbol resolution details, breakpoint status, and wire protocol details. Useful for troubleshooting debugger issues |
Example with verbosity:
{
"name": "Debug (Verbose)",
"type": "nanoframework",
"request": "launch",
"program": "${workspaceFolder}/bin/Debug",
"verbosity": "debug"
}| Option | Type | Default | Description |
|---|---|---|---|
stopOnEntry |
boolean | true |
Pause execution at program entry |
deployAssemblies |
boolean | true |
Deploy assemblies before starting |
When using "request": "attach", the debugger connects to an already-running application without deploying new code.
Launch with specific device:
{
"name": "Debug on COM3",
"type": "nanoframework",
"request": "launch",
"program": "${workspaceFolder}/bin/Debug/MyApp.pe",
"device": "COM3",
"stopOnEntry": false
}Attach to running device:
{
"name": "Attach to Device",
"type": "nanoframework",
"request": "attach",
"device": "COM3",
"program": "${workspaceFolder}/bin/Debug"
}- Click in the editor gutter (left margin) next to a line number
- Press
F9to toggle a breakpoint on the current line - Right-click in the gutter for breakpoint options
| Type | Description |
|---|---|
| Line Breakpoint | Standard breakpoint that pauses at a specific line |
| Conditional | Breaks only when a condition is true (limited support) |
| Logpoint | Logs a message without pausing (use Debug.WriteLine instead) |
- Red circle: Verified breakpoint (will be hit)
- Gray circle: Unverified breakpoint (symbols not loaded or line not executable)
- Red circle with dot: Breakpoint is hit (execution paused here)
- Set breakpoints before starting the debug session for best results
- Breakpoints on empty lines or comments won't be hit
- If a breakpoint shows as unverified, rebuild your project
Once paused at a breakpoint, use these commands:
| Action | Shortcut | Description |
|---|---|---|
| Continue | F5 |
Resume execution until next breakpoint |
| Step Over | F10 |
Execute current line, don't enter functions |
| Step Into | F11 |
Execute current line, enter function calls |
| Step Out | Shift+F11 |
Run until current function returns |
| Restart | Ctrl+Shift+F5 |
Restart the debug session |
| Stop | Shift+F5 |
End the debug session |
The Variables panel in the Debug sidebar shows:
- Locals: Variables in the current scope
- Arguments: Parameters passed to the current function
Click the arrow next to complex objects to expand and view their properties:
▼ myObject
Name: "Test"
Value: 42
▼ Items
[0]: "First"
[1]: "Second"
Add expressions to the Watch panel to monitor their values:
- Click the + in the Watch panel
- Type an expression (e.g.,
myVariable,array.Length,obj.Property) - The value updates each time execution pauses
Hover over a variable in the editor to see its current value in a tooltip.
The Debug Console serves two purposes:
All Debug.WriteLine() calls from your nanoFramework application appear here:
Debug.WriteLine("Temperature: " + temperature);
Debug.WriteLine($"Status: {status}");Type expressions in the Debug Console input to evaluate them:
> myVariable
42
> array.Length
5
> DateTime.UtcNow
{01/15/2026 10:30:45}
The debugger can pause when exceptions occur. Configure this in the Breakpoints panel:
- All Exceptions: Break on any thrown exception
- Uncaught Exceptions: Break only on unhandled exceptions
When an exception occurs, the debugger shows:
- Exception type and message
- Stack trace with source locations
- Exception properties (InnerException, etc.)
Symbol files enable source-level debugging by mapping IL offsets to source locations.
| File | Purpose |
|---|---|
*.pdbx |
nanoFramework IL offset mapping (CLR ↔ nanoCLR) |
*.pdb |
Portable PDB with source line information |
*.pe |
nanoFramework portable executable |
Symbols are automatically loaded from:
- The directory specified in
program - The workspace folder's bin/Debug directory
If breakpoints aren't working:
- Check that
.pdbxand.pdbfiles exist alongside your.pefile - Ensure the files match (same build)
- Rebuild the project to regenerate symbol files
Symptom: "Device not found" error when starting debug session
Solutions:
- Verify device is connected and powered on
- Check Device Manager (Windows) or
ls /dev/tty*(Linux/Mac) for COM port - Ensure nanoFramework firmware is running (not bootloader mode)
- Try the
nanoFramework: Select Debug Devicecommand - Disconnect and reconnect the USB cable
Symptom: Breakpoints show as unverified or code runs past them
Solutions:
- Rebuild the project (
nanoFramework: Build Project) - Ensure
deployAssemblies: truein launch.json - Check that source files match the deployed code
- Verify
.pdbxand.pdbfiles exist in output directory
Symptom: Error message when pressing F5
Solutions:
- Check Debug Console for specific error messages
- Verify .NET 10.0 is installed:
dotnet --list-runtimes - Ensure no other application is using the COM port
- Try restarting VS Code
Symptom: Variables panel shows errors instead of values
Solutions:
- Ensure you're paused at a breakpoint (not running)
- Some optimized variables may not be readable
- Try adding the variable to a Watch expression
Symptom: Step Over enters functions, or stepping skips lines
Causes:
- Code optimization may combine or eliminate lines
- Async/await code has different stepping behavior
- Inlined methods are stepped through
The debugging system consists of three main components:
┌─────────────────────────────────────────────────────────────┐
│ VS Code │
│ ┌─────────────┐ ┌────────────┐ ┌───────────────────────┐ │
│ │ Debug UI │ │ Breakpoints│ │ Variables/Watch/Stack │ │
│ └──────┬──────┘ └──────┬─────┘ └───────────┬───────────┘ │
│ └────────────────┴────────────────────┘ │
│ │ │
│ Debug Adapter Protocol (DAP) │
└──────────────────────────┼───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ nanoFramework Debug Adapter │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ TypeScript Layer (nanoDebugSession, nanoRuntime) │ │
│ └──────────────────────┬───────────────────────────────┘ │
│ │ JSON-RPC │
│ ┌──────────────────────┴───────────────────────────────┐ │
│ │ .NET Bridge (DebugBridgeSession, SymbolResolver) │ │
│ └──────────────────────┬───────────────────────────────┘ │
│ │ Wire Protocol │
└─────────────────────────┼────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ nanoFramework Device │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ nanoCLR Runtime + Your Application │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
- TypeScript Debug Adapter: Handles DAP communication with VS Code
- .NET Debug Bridge: Uses nf-debugger library to communicate with device
- Symbol Resolver: Maps source locations to IL offsets using .pdbx and .pdb files
- Wire Protocol: Low-level communication with the nanoFramework device
For more technical details, see the work-debug-todo.md implementation plan.