Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ target


# Test binaries
tests/e2e/test_*
tests/e2e/test_*
tests/fixtures/cuda_test
50 changes: 50 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,56 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.1] - 2026-01-25

### Added

- **Delve (Go) Support**: Full Go debugging via Delve DAP
- `setup go` / `setup delve` - Install and configure Delve adapter
- TCP transport mode for Delve's DAP server
- Go project detection via `go.mod` / `go.sum`
- `mode: "exec"` for pre-compiled binaries
- Delve-specific `stopAtEntry` handling

- **GDB Support**: Native DAP support for GDB 14.1+
- `setup gdb` - Install and configure GDB adapter
- Uses `-i=dap` interpreter mode for direct DAP communication
- Version detection and validation (requires GDB ≥14.1)

- **CUDA-GDB Support**: NVIDIA GPU debugging via cuda-gdb
- `setup cuda-gdb` - Install and configure CUDA-GDB adapter
- **Dual-mode architecture**: Automatically detects best mode
- Native DAP (`-i=dap`) for NVIDIA official installs with DAP support
- cdt-gdb-adapter bridge for minimal builds (e.g., Arch Linux)
- CUDA project detection via `*.cu` files
- Linux-only (NVIDIA driver limitation)

- **Initial Breakpoints**: Set breakpoints before program starts
- `--break` / `-b` flag for `start` command
- Set multiple breakpoints: `debugger start ./prog --break main --break file.c:42`
- Essential for adapters that don't support `stopOnEntry` (e.g., cdt-gdb-adapter)
- Breakpoints set during DAP configuration phase (before `configurationDone`)

- **Adapter-specific Stop-on-Entry**: Proper handling for different adapters
- GDB/CUDA-GDB: `stopAtBeginningOfMainSubprogram`
- Delve: `stopAtEntry`
- Others: `stopOnEntry`

### Fixed

- **cuda-gdb Version Parsing**: Handle cuda-gdb's "exec:" wrapper line in version output
- Parser now searches for "GNU gdb X.Y" pattern across all lines
- Correctly extracts base GDB version (14.2) instead of cuda-gdb version (13.1)

- **Address Parsing**: Enhanced address extraction in DAP client and verifier

### Documentation

- Added `docs/plan/cuda-gdb.md` with architecture details and tested features
- Added `docs/plan/go-delve-support.md` with Go debugging guide
- Updated `src/setup/adapters/CLAUDE.md` with adapter-specific behaviors
- Added `src/setup/adapters/README.md` with usage examples

## [0.1.0] - 2026-01-18

### Added
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "debugger-cli"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
description = "LLM-friendly debugger CLI using the Debug Adapter Protocol"
license = "GPL-3.0-only"
Expand Down
155 changes: 155 additions & 0 deletions docs/plan/cuda-gdb.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# GDB and CUDA-GDB Support Implementation Plan

## Overview

This plan adds DAP support for GDB and CUDA-GDB debuggers to debugger-cli.

### Key Discoveries (2026-01-24)

1. **CUDA-GDB Native DAP Support Varies by Distribution**:
- NVIDIA official installs (Ubuntu via `cuda-gdb-13-1` package): Native DAP works via `-i=dap`
- Arch Linux `cuda` package: Minimal build without DAP Python bindings ("Interpreter `dap' unrecognized")

2. **Dual-Mode Architecture**: The adapter now supports two modes:
- **Native DAP** (preferred): `cuda-gdb -i=dap` when available
- **cdt-gdb-adapter bridge** (fallback): Node.js-based MI-to-DAP bridge for minimal builds

3. **Stop-on-Entry Behavior**:
- Native DAP (GDB 14.1+): Uses `stopAtBeginningOfMainSubprogram` (not `stopOnEntry`)
- cdt-gdb-adapter: Does NOT support stop-on-entry; use a breakpoint on `main` instead

### Architecture

```
Native DAP Mode (preferred):
Client <-> cuda-gdb -i=dap <-> GPU

Bridge Mode (fallback for minimal builds):
Client <-> cdt-gdb-adapter (Node.js) <-> cuda-gdb (MI mode) <-> GPU
```

### Requirements

| Mode | Requirements |
|------|--------------|
| Native DAP | cuda-gdb based on GDB 14.1+ with DAP Python bindings |
| Bridge (fallback) | cuda-gdb (any version) + Node.js + cdt-gdb-adapter (`npm install -g cdt-gdb-adapter`) |

### Mode Detection

The adapter automatically detects the best mode:
1. Check if cuda-gdb has GDB 14.1+ base version
2. Test `-i=dap -batch -ex quit` for "Interpreter `dap' unrecognized" error
3. If native DAP works → use it; otherwise → use cdt-gdb-adapter bridge

## Adapter-Specific Behaviors

### Stop-on-Entry

| Adapter | Stop-on-Entry Support | Parameter |
|---------|----------------------|-----------|
| lldb-dap | ✅ | `stopOnEntry: true` |
| GDB native DAP | ✅ | `stopAtBeginningOfMainSubprogram: true` |
| Delve (Go) | ✅ | `stopAtEntry: true` |
| debugpy | ✅ | `stopOnEntry: true` |
| cdt-gdb-adapter | ❌ | Use `--break main` instead |

### Using --break for Initial Breakpoints

For adapters that don't support stop-on-entry (like cdt-gdb-adapter), or when you want to stop at a specific location:

```bash
# Stop at main function
debugger start ./program --break main

# Stop at specific line
debugger start ./program --break src/main.cu:42

# Multiple breakpoints
debugger start ./program --break main --break vectorAdd
```

Initial breakpoints are set during the DAP configuration phase (between `initialized` event and `configurationDone`), ensuring they're active before program execution begins.

## Version Parsing

cuda-gdb outputs a wrapper message on the first line which broke simple version parsing:

```
exec: /opt/cuda/bin/cuda-gdb
NVIDIA (R) CUDA Debugger
13.1 release
...
GNU gdb (GDB) 14.2
```

The parser now searches for "GNU gdb X.Y" pattern to extract the base GDB version (14.2), ignoring the cuda-gdb version (13.1).

## Tested Features

Tested on Lambda Labs VM (A10 GPU, cuda-gdb 13.1 with native DAP):

| Feature | Status |
|---------|--------|
| Stop at entry point | ✅ via `stopAtBeginningOfMainSubprogram` |
| Backtrace | ✅ Shows source location |
| Local variables | ✅ |
| Function breakpoint on kernel | ✅ |
| CUDA thread visibility | ✅ Shows GPU threads (e.g., `cuda00001400006`) |
| Continue to completion | ✅ |

Tested on Arch Linux (cuda-gdb 13.1 minimal, via cdt-gdb-adapter bridge):

| Feature | Status |
|---------|--------|
| DAP initialize | ✅ |
| Function breakpoint | ✅ verified=True |
| Hit breakpoint | ✅ reason="function breakpoint" |
| Stack trace | ✅ |
| Scopes (Locals/Registers) | ✅ |
| Continue to completion | ✅ |
| Stop-on-entry | ❌ (use `--break main` instead) |

## Code Changes Summary

### Files Modified

| File | Change |
|------|--------|
| `src/setup/adapters/gdb_common.rs` | Fixed version parser to handle cuda-gdb's "exec:" wrapper |
| `src/setup/adapters/cuda_gdb.rs` | Added native DAP detection + cdt-gdb-adapter fallback |
| `src/dap/types.rs` | Added `stopAtBeginningOfMainSubprogram` field |
| `src/daemon/session.rs` | Set GDB-specific stop flag for gdb/cuda-gdb adapters |

### Key Functions

**`has_native_dap_support(cuda_gdb_path)`** in `cuda_gdb.rs`:
```rust
// 1. Check version >= 14.1
// 2. Run: cuda-gdb -i=dap -batch -ex quit
// 3. Check stderr for "Interpreter `dap' unrecognized"
// 4. Return true if no error (native DAP available)
```

**`parse_gdb_version(output)`** in `gdb_common.rs`:
```rust
// Skip "exec:" wrapper lines
// Search for "GNU gdb X.Y" pattern
// Return X.Y as version string
```

## Decision Log

| Decision | Reasoning |
|----------|-----------|
| Native DAP preferred over bridge | Zero dependencies, direct control, better performance |
| Fallback to cdt-gdb-adapter | Arch Linux and similar minimal builds lack DAP Python bindings |
| Auto-detect mode at setup time | User doesn't need to know which mode is available |
| Use `stopAtBeginningOfMainSubprogram` for GDB | GDB's DAP implementation uses this parameter, not `stopOnEntry` |
| Version check extracts GDB base version | cuda-gdb version (13.1) differs from GDB base (14.2) |

## Known Limitations

1. **cdt-gdb-adapter stop-on-entry**: Not supported. Use `--break main` as workaround.
2. **GPU compute capability**: CUDA 13.1 requires sm_75+ (Turing or newer). Older GPUs cannot run CUDA code.
3. **Kernel debugging context**: Breakpoints in kernels may show CPU-side context during `cudaDeviceSynchronize`.
12 changes: 10 additions & 2 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,32 @@ pub async fn dispatch(command: Commands) -> Result<()> {
args,
adapter,
stop_on_entry,
initial_breakpoints,
} => {
spawn::ensure_daemon_running().await?;
let mut client = DaemonClient::connect().await?;

let program = program.canonicalize().unwrap_or(program);

let result = client
let has_initial_breakpoints = !initial_breakpoints.is_empty();

let _result = client
.send_command(Command::Start {
program: program.clone(),
args,
adapter,
stop_on_entry,
initial_breakpoints: initial_breakpoints.clone(),
})
.await?;

println!("Started debugging: {}", program.display());

if stop_on_entry {
if has_initial_breakpoints {
println!("Set {} initial breakpoint(s)", initial_breakpoints.len());
}

if stop_on_entry || has_initial_breakpoints {
println!("Stopped at entry point. Use 'debugger continue' to run.");
} else {
println!("Program is running. Use 'debugger await' to wait for a stop.");
Expand Down
5 changes: 5 additions & 0 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ pub enum Commands {
/// Stop at program entry point
#[arg(long)]
stop_on_entry: bool,

/// Set initial breakpoint(s) before program starts (file:line or function name)
/// Can be specified multiple times: --break main --break src/file.c:42
#[arg(long = "break", short = 'b')]
initial_breakpoints: Vec<String>,
},

/// Attach to a running process
Expand Down
5 changes: 3 additions & 2 deletions src/daemon/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,14 @@ async fn handle_command_inner(
args,
adapter,
stop_on_entry,
initial_breakpoints,
} => {
if session.is_some() {
return Err(Error::SessionAlreadyActive);
}

let new_session =
DebugSession::launch(config, &program, args, adapter, stop_on_entry).await?;
DebugSession::launch(config, &program, args, adapter, stop_on_entry, initial_breakpoints).await?;
*session = Some(new_session);

Ok(json!({
Expand Down Expand Up @@ -242,7 +243,7 @@ async fn handle_command_inner(
}

// === State Inspection ===
Command::StackTrace { thread_id, limit } => {
Command::StackTrace { thread_id: _, limit } => {
let sess = session.as_mut().ok_or(Error::SessionNotActive)?;
let frames = sess.stack_trace(limit).await?;

Expand Down
Loading
Loading