Skip to content

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+) or lldb-dap (the DAP binary that ships with LLDB).

Related documents:


Table of Contents

  1. Quick Start
  2. Prerequisites
  3. Compiling with Debug Info
  4. VS Code Extension Setup
  5. Launch Configuration Reference
  6. Choosing a Debugger
  7. Debugging Workflow
  8. Debugging with GDB / LLDB Directly
  9. Troubleshooting

Quick Start

bash
# 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 Debug

Prerequisites

RequirementMinimum VersionNotes
Ashes compilerLatestashes compile --debug support
GDB or LLDBGDB 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 Code1.80+IDE with debugging UI
Ashes VS Code extension0.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):

bash
sudo apt install gdb

Linux (Fedora/RHEL):

bash
sudo dnf install gdb

Windows (via MSYS2):

bash
pacman -S mingw-w64-x86_64-gdb

Installing 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):

bash
sudo apt install lldb

Linux (Arch/CachyOS):

bash
sudo pacman -S lldb

Tip: Set the ashes.debugger VS 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):

bash
code --install-extension mattiashognas.ashes-vscode

Local development build:

bash
bash scripts/install-vscode-extension-local.sh

This 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):

bash
cd vscode-extension
npm install
npm run compile           # Build the extension

Then install via Ctrl+Shift+PExtensions: Install from VSIX… or use the local install script:

bash
bash scripts/install-vscode-extension-local.sh

The 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:

bash
# 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.ash

What --debug Does

EffectDescription
DWARF metadataEmbeds .debug_info, .debug_line, .debug_abbrev, etc. into the binary.
OptimizationWithout explicit -O, defaults to -O0. An explicit -O1/-O2/-O3 is honored (no cap).
Source mappingEach IR instruction carries the source file, line, and column it originated from.
Compilation outputAn extra Debug: yes line appears in the success summary.

Optimization Interaction

FlagsEffective 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:

json
{
  "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:

json
{
  "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

  1. Open a .ash file in VS Code.
  2. Click in the gutter (left of line numbers) to set breakpoints.
  3. Press F5 to start debugging.
  4. Use the Debug toolbar to step through code.

Launch Configuration Reference

All properties for type: "ashes" launch configurations:

PropertyTypeRequiredDefaultDescription
programstringYesPath to the compiled Ashes binary. Must be compiled with --debug. Supports VS Code variables like ${workspaceFolder}.
argsstring[]No[]Command-line arguments passed to the program at runtime.
cwdstringNo${workspaceFolder}Working directory for the debugged program.
stopOnEntrybooleanNofalseWhen true, the debugger pauses at the program entry point before any user code runs.
debuggerTypestringNo(from setting)Native debugger backend: "gdb" or "lldb". When omitted, the value of the ashes.debugger extension setting is used.
debuggerPathstringNo(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:

json
{
  "name": "Debug hello.ash",
  "type": "ashes",
  "request": "launch",
  "program": "${workspaceFolder}/hello"
}

With arguments:

json
{
  "name": "Debug with args",
  "type": "ashes",
  "request": "launch",
  "program": "${workspaceFolder}/out/myapp",
  "args": ["--input", "data.txt"],
  "cwd": "${workspaceFolder}"
}

Custom GDB path:

json
{
  "name": "Debug (custom GDB)",
  "type": "ashes",
  "request": "launch",
  "program": "${workspaceFolder}/out/myapp",
  "debuggerPath": "/usr/local/bin/gdb"
}

Using LLDB (Linux):

json
{
  "name": "Debug (LLDB)",
  "type": "ashes",
  "request": "launch",
  "program": "${workspaceFolder}/out/myapp",
  "debuggerType": "lldb"
}

Stop on entry (useful for inspecting initial state):

json
{
  "name": "Debug (stop on entry)",
  "type": "ashes",
  "request": "launch",
  "program": "${workspaceFolder}/out/myapp",
  "stopOnEntry": true
}

Project with pre-launch build:

json
{
  "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:

BackendBinaryBest ForNotes
GDBgdbLinux, Windows (MSYS2)Default. Driven over GDB's built-in DAP interpreter (GDB 14+).
LLDBlldb-dapLinuxShips with LLDB 18+. Driven over the Debug Adapter Protocol.

Extension Setting

Set the default debugger for all launch configurations:

  1. Open Settings (Ctrl+,).
  2. Search for ashes.debugger.
  3. Choose gdb or lldb.

Or in settings.json:

json
{
  "ashes.debugger": "lldb"
}

Per-Configuration Override

Add "debuggerType" to a specific launch configuration to override the extension setting:

json
{
  "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 .ash source file and line number via DWARF .debug_line information.

Stepping

ActionKeyboardDescription
ContinueF5Resume execution until next breakpoint or exit.
Step OverF10Execute the current line and stop at the next line.
Step IntoF11Step into a function call.
Step OutShift+F11Execute until the current function returns.
StopShift+F5Terminate 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 TypeDisplay Example
Int42
Float3.14
Booltrue
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

bash
# 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 GDB

LLDB

bash
# 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) quit

Troubleshooting

"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) or lldb-dap --version.
  • GDB's DAP interpreter requires a Python-enabled GDB build (the default everywhere); gdb --interpreter=dap failing with an interpreter error means the build lacks Python support.
  • lldb-dap is part of the LLDB package on every major distro; installing LLDB provides it.
  • If the binary is not on PATH, set debuggerPath in your launch configuration (for "lldb", it must point at an lldb-dap binary).

Breakpoints not hitting

  • Ensure the binary was compiled with --debug (or -g).
  • Ensure optimization is at -O0 or -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
bash scripts/install-vscode-extension-local.sh

If you installed from the marketplace, try reinstalling the extension.

Variables show as <optimized out>

This happens when optimization eliminates variables. Recompile with -O0:

bash
ashes compile --debug -O0 main.ash -o main

Debug info not present in binary

Verify with readelf:

bash
readelf -S ./main | grep debug

You should see sections like .debug_info, .debug_line, .debug_abbrev, etc. If not, the binary was not compiled with --debug.


Architecture

  1. VS Code sends DAP requests (setBreakpoints, continue, stackTrace, etc.) over stdin/stdout to the DAP server.
  2. ashes-dap drives the selected debugger as a subprocess, speaking the Debug Adapter Protocol over the child's stdio: gdb --interpreter=dap or LLDB's bundled lldb-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).
  3. GDB or LLDB uses ptrace to control the debuggee and reads DWARF debug info from the binary to map machine code back to source locations.
  4. Responses flow back through the same chain.