Debugging Ashes Programs
This document covers how to compile Ashes programs with debug information, set up the VS Code debug extension, and use GDB or LLDB for source-level debugging.
Supported platforms:
- Linux — GDB (default) or LLDB
- Windows — GDB via MSYS2
The Ashes DAP server (
ashes-dap) is the single debug adapter for both debuggers. Each debugger runs as a subprocess driven over its own Debug Adapter Protocol interpreter:gdb --interpreter=dap(built into GDB 14+) orlldb-dap(the DAP binary that ships with LLDB).
Related documents:
- CLI Specification — Debug Mode for the
--debug/-gflag reference.- Compiler Architecture for the compilation pipeline.
Table of Contents
- Quick Start
- Prerequisites
- Compiling with Debug Info
- VS Code Extension Setup
- Launch Configuration Reference
- Choosing a Debugger
- Debugging Workflow
- Debugging with GDB / LLDB Directly
- Troubleshooting
Quick Start
# 1. Compile with debug info
ashes compile --debug examples/hello.ash -o hello
# 2. Open VS Code, set breakpoints, then start the Ashes debug configuration
# from Run and DebugPrerequisites
| Requirement | Minimum Version | Notes |
|---|---|---|
| Ashes compiler | Latest | ashes compile --debug support |
| GDB or LLDB | GDB 14.0+ / LLDB 18.0+ | Native debugger backend, driven over DAP. GDB 14+ has a built-in DAP interpreter (requires a Python-enabled GDB build); LLDB 18+ ships lldb-dap. |
| VS Code | 1.80+ | IDE with debugging UI |
| Ashes VS Code extension | 0.0.1+ | Language support, diagnostics, and debugging |
Installing GDB
The Ashes DAP server communicates with GDB through GDB's built-in Debug Adapter Protocol interpreter (gdb --interpreter=dap), available since GDB 14 on Python-enabled builds (the default on every major distro and MSYS2).
Linux (Debian/Ubuntu):
sudo apt install gdbLinux (Fedora/RHEL):
sudo dnf install gdbWindows (via MSYS2):
pacman -S mingw-w64-x86_64-gdbInstalling LLDB
The Ashes DAP server communicates with LLDB through lldb-dap, the Debug Adapter Protocol binary included in the standard LLDB distribution — no extra frontend is needed. (Plain lldb has no machine protocol: the old lldb-mi frontend left the LLVM tree and is not packaged by modern distros, so ashes-dap speaks DAP to lldb-dap instead.)
Linux (Debian/Ubuntu):
sudo apt install lldbLinux (Arch/CachyOS):
sudo pacman -S lldbTip: Set the
ashes.debuggerVS Code setting to"lldb"(see Choosing a Debugger below).
Installing the VS Code Extension
The Ashes VS Code extension bundles everything you need: language support (syntax highlighting, diagnostics, formatting) and debug support (DAP adapter, breakpoints, stepping).
From marketplace (future):
code --install-extension mattiashognas.ashes-vscodeLocal development build:
bash scripts/install-vscode-extension-local.shThis script publishes the LSP server, DAP server, and compiler locally, builds the extension, packages a VSIX, and installs it into VS Code. On Windows, run it from WSL. If you are targeting the Windows VS Code build, pass --target-rid win-x64.
Alternatively, build the extension without bundled servers (they will be downloaded from GitHub Releases on first activation):
cd vscode-extension
npm install
npm run compile # Build the extensionThen install via Ctrl+Shift+P → Extensions: Install from VSIX… or use the local install script:
bash scripts/install-vscode-extension-local.shThe extension automatically uses the bundled DAP server — no manual PATH configuration is needed.
Compiling with Debug Info
Use the --debug or -g flag with ashes compile or ashes run:
# Compile with debug info (defaults to -O0)
ashes compile --debug main.ash -o main
# Short form
ashes compile -g main.ash
# Compile with debug info and explicit optimization (honored, up to -O3)
ashes compile --debug -O2 main.ash
# Run with debug info
ashes run --debug main.ashWhat --debug Does
| Effect | Description |
|---|---|
| DWARF metadata | Embeds .debug_info, .debug_line, .debug_abbrev, etc. into the binary. |
| Optimization | Without explicit -O, defaults to -O0. An explicit -O1/-O2/-O3 is honored (no cap). |
| Source mapping | Each IR instruction carries the source file, line, and column it originated from. |
| Compilation output | An extra Debug: yes line appears in the success summary. |
Optimization Interaction
| Flags | Effective Optimization |
|---|---|
--debug | -O0 |
--debug -O0 | -O0 |
--debug -O1 | -O1 |
--debug -O2 | -O2 |
--debug -O3 | -O3 |
--debug defaults to -O0 for the most faithful single-stepping (no variables eliminated, no code reordered or inlined). An explicit optimization level is honored so a profiled debug build matches the optimized binary's inlining — at -O0/-O1 a sampled profile exaggerates call overhead because no inliner runs. The emitted DWARF stays valid at every level: the backend re-verifies the module after optimization whenever debug info is requested, so an inliner-mangled location fails the build rather than shipping as broken debug data. At -O2/-O3 line/variable mapping is necessarily coarser (inlined frames, folded variables), so prefer -O0 when stepping and an explicit -O2 only when profiling.
VS Code Extension Setup
Step 1: Install the Ashes Extension
Install the Ashes VS Code extension — it includes both language support and debugging support. See Prerequisites for installation instructions.
Step 2: Create a Launch Configuration
Create .vscode/launch.json in your project root:
{
"version": "0.2.0",
"configurations": [
{
"name": "Ashes: Launch",
"type": "ashes",
"request": "launch",
"program": "${workspaceFolder}/out/${workspaceFolderBasename}",
"cwd": "${workspaceFolder}",
"stopOnEntry": false
}
]
}Tip: VS Code can auto-generate this. Click Run > Add Configuration… and select Ashes: Launch from the snippet list.
Step 3: Create a Build Task (Optional)
Create .vscode/tasks.json to compile before debugging:
{
"version": "2.0.0",
"tasks": [
{
"label": "ashes: compile (debug)",
"type": "shell",
"command": "${command:ashes.getCompilerPath}",
"args": [
"compile",
"--debug",
"${file}",
"-o",
"${workspaceFolder}/out/${workspaceFolderBasename}"
],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
}
]
}The ${command:ashes.getCompilerPath} variable asks the extension to resolve the compiler path using the same logic as the Ashes commands and debugger: ashes.compilerPath override first, then bundled/downloaded toolchain assets. That avoids depending on a global ashes installation or a private cache path.
Then add "preLaunchTask": "ashes: compile (debug)" to your launch configuration to auto-compile before each debug session.
Step 4: Set Breakpoints and Debug
- Open a
.ashfile in VS Code. - Click in the gutter (left of line numbers) to set breakpoints.
- Press F5 to start debugging.
- Use the Debug toolbar to step through code.
Launch Configuration Reference
All properties for type: "ashes" launch configurations:
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
program | string | Yes | — | Path to the compiled Ashes binary. Must be compiled with --debug. Supports VS Code variables like ${workspaceFolder}. |
args | string[] | No | [] | Command-line arguments passed to the program at runtime. |
cwd | string | No | ${workspaceFolder} | Working directory for the debugged program. |
stopOnEntry | boolean | No | false | When true, the debugger pauses at the program entry point before any user code runs. |
debuggerType | string | No | (from setting) | Native debugger backend: "gdb" or "lldb". When omitted, the value of the ashes.debugger extension setting is used. |
debuggerPath | string | No | (auto) | Path to the debugger binary. Defaults to gdb when debuggerType is "gdb" and to lldb-dap when it is "lldb". For "lldb", point this at an lldb-dap binary; for "gdb", at a GDB 14+ binary. Set this if the binary is not on your PATH. |
Example Configurations
Minimal — single file:
{
"name": "Debug hello.ash",
"type": "ashes",
"request": "launch",
"program": "${workspaceFolder}/hello"
}With arguments:
{
"name": "Debug with args",
"type": "ashes",
"request": "launch",
"program": "${workspaceFolder}/out/myapp",
"args": ["--input", "data.txt"],
"cwd": "${workspaceFolder}"
}Custom GDB path:
{
"name": "Debug (custom GDB)",
"type": "ashes",
"request": "launch",
"program": "${workspaceFolder}/out/myapp",
"debuggerPath": "/usr/local/bin/gdb"
}Using LLDB (Linux):
{
"name": "Debug (LLDB)",
"type": "ashes",
"request": "launch",
"program": "${workspaceFolder}/out/myapp",
"debuggerType": "lldb"
}Stop on entry (useful for inspecting initial state):
{
"name": "Debug (stop on entry)",
"type": "ashes",
"request": "launch",
"program": "${workspaceFolder}/out/myapp",
"stopOnEntry": true
}Project with pre-launch build:
{
"name": "Debug project",
"type": "ashes",
"request": "launch",
"program": "${workspaceFolder}/out/myproject",
"preLaunchTask": "ashes: compile (debug)"
}Choosing a Debugger
The Ashes DAP server supports two native debugger backends:
| Backend | Binary | Best For | Notes |
|---|---|---|---|
| GDB | gdb | Linux, Windows (MSYS2) | Default. Driven over GDB's built-in DAP interpreter (GDB 14+). |
| LLDB | lldb-dap | Linux | Ships with LLDB 18+. Driven over the Debug Adapter Protocol. |
Extension Setting
Set the default debugger for all launch configurations:
- Open Settings (Ctrl+,).
- Search for
ashes.debugger. - Choose
gdborlldb.
Or in settings.json:
{
"ashes.debugger": "lldb"
}Per-Configuration Override
Add "debuggerType" to a specific launch configuration to override the extension setting:
{
"name": "Debug with LLDB",
"type": "ashes",
"request": "launch",
"program": "${workspaceFolder}/out/myapp",
"debuggerType": "lldb"
}Debugging Workflow
Setting Breakpoints
- Line breakpoints: Click in the gutter or press F9 on a line.
- Breakpoints map to the
.ashsource file and line number via DWARF.debug_lineinformation.
Stepping
| Action | Keyboard | Description |
|---|---|---|
| Continue | F5 | Resume execution until next breakpoint or exit. |
| Step Over | F10 | Execute the current line and stop at the next line. |
| Step Into | F11 | Step into a function call. |
| Step Out | Shift+F11 | Execute until the current function returns. |
| Stop | Shift+F5 | Terminate the debugging session. |
Inspecting Variables
When paused at a breakpoint, the Variables pane in VS Code shows local bindings and their values. Ashes types are displayed as:
| Ashes Type | Display Example |
|---|---|
Int | 42 |
Float | 3.14 |
Bool | true |
String | "hello" |
Note: Complex types (lists, ADTs, closures) display as raw memory values in the initial implementation. Pretty-printing is planned for a future phase.
Debugging with GDB / LLDB Directly
You can also debug Ashes binaries directly without VS Code:
GDB
# Compile with debug info
ashes compile --debug main.ash -o main
# Start GDB
gdb ./main
# In GDB:
(gdb) break main.ash:5 # Set breakpoint at line 5
(gdb) run # Start execution
(gdb) next # Step over
(gdb) step # Step into
(gdb) print x # Print variable
(gdb) backtrace # Show call stack
(gdb) continue # Continue execution
(gdb) quit # Exit GDBLLDB
# Compile with debug info
ashes compile --debug main.ash -o main
# Start LLDB
lldb ./main
# In LLDB:
(lldb) breakpoint set --file main.ash --line 5
(lldb) run
(lldb) next
(lldb) step
(lldb) frame variable # Show locals
(lldb) bt # Show call stack
(lldb) continue
(lldb) quitTroubleshooting
"Cannot start debugging: no program specified"
Ensure your launch.json has a program property pointing to a compiled binary (not a .ash source file). The binary must be compiled with --debug.
"Failed to start gdb" / "Failed to start lldb-dap"
- Verify the debugger is installed:
gdb --version(14.0 or newer) orlldb-dap --version. - GDB's DAP interpreter requires a Python-enabled GDB build (the default everywhere);
gdb --interpreter=dapfailing with an interpreter error means the build lacks Python support. lldb-dapis part of the LLDB package on every major distro; installing LLDB provides it.- If the binary is not on
PATH, setdebuggerPathin your launch configuration (for"lldb", it must point at anlldb-dapbinary).
Breakpoints not hitting
- Ensure the binary was compiled with
--debug(or-g). - Ensure optimization is at
-O0or-O1(higher levels may optimize away breakpoint locations). - Verify the source file path in the breakpoint matches the path used during compilation.
"ashes-dap not found" or "DAP server not found"
The DAP server is downloaded automatically by the extension on first activation. If you see this error, verify your internet connection and check the VS Code notifications for download errors.
For local development, use the install script which bundles the DAP server:
bash scripts/install-vscode-extension-local.shIf you installed from the marketplace, try reinstalling the extension.
Variables show as <optimized out>
This happens when optimization eliminates variables. Recompile with -O0:
ashes compile --debug -O0 main.ash -o mainDebug info not present in binary
Verify with readelf:
readelf -S ./main | grep debugYou should see sections like .debug_info, .debug_line, .debug_abbrev, etc. If not, the binary was not compiled with --debug.
Architecture
- VS Code sends DAP requests (setBreakpoints, continue, stackTrace, etc.) over stdin/stdout to the DAP server.
- ashes-dap drives the selected debugger as a subprocess, speaking the Debug Adapter Protocol over the child's stdio:
gdb --interpreter=dapor LLDB's bundledlldb-dap. Both run through one shared backend that adds Ashes-aware value formatting and smooths over dialect differences (GDB reports arguments in a separate scope, evaluate result shapes differ). - GDB or LLDB uses
ptraceto control the debuggee and reads DWARF debug info from the binary to map machine code back to source locations. - Responses flow back through the same chain.
