Skip to main content

debugger/setup/adapters/
cuda_gdb.rs

1//! CUDA-GDB adapter installer
2//!
3//! CUDA-GDB supports two modes:
4//! 1. Native DAP mode (-i=dap): Available in cuda-gdb builds based on GDB 14.1+
5//!    when DAP Python bindings are included (NVIDIA official installs)
6//! 2. cdt-gdb-adapter bridge: For cuda-gdb builds without native DAP (e.g., Arch Linux minimal)
7//!
8//! Architecture (native DAP):
9//!   Client <-> cuda-gdb -i=dap <-> GPU
10//!
11//! Architecture (cdt-gdb-adapter bridge):
12//!   Client <-> cdt-gdb-adapter (DAP) <-> cuda-gdb (MI mode) <-> GPU
13//!
14//! Requirements:
15//! - CUDA Toolkit with cuda-gdb (Linux only)
16//! - For bridge mode: Node.js runtime + cdt-gdb-adapter npm package
17
18use crate::common::{Error, Result};
19use crate::setup::installer::{InstallMethod, InstallOptions, InstallResult, InstallStatus, Installer};
20use crate::setup::registry::{DebuggerInfo, Platform};
21use crate::setup::verifier::{verify_dap_adapter, VerifyResult};
22use async_trait::async_trait;
23use std::path::PathBuf;
24
25use super::gdb_common::{get_gdb_version, is_gdb_version_sufficient};
26
27static INFO: DebuggerInfo = DebuggerInfo {
28    id: "cuda-gdb",
29    name: "CUDA-GDB",
30    languages: &["cuda", "c", "cpp"],
31    platforms: &[Platform::Linux],
32    description: "NVIDIA CUDA debugger for GPU code",
33    primary: true,
34};
35
36pub struct CudaGdbInstaller;
37
38/// Check if cuda-gdb supports native DAP mode by testing "-i=dap"
39async fn has_native_dap_support(cuda_gdb_path: &PathBuf) -> bool {
40    // First check version - needs GDB 14.1+ base
41    if let Some(version) = get_gdb_version(cuda_gdb_path).await {
42        if !is_gdb_version_sufficient(&version) {
43            return false;
44        }
45    } else {
46        return false;
47    }
48
49    // Test if DAP interpreter is available
50    // cuda-gdb without DAP will fail with "Interpreter `dap' unrecognized"
51    let output = tokio::process::Command::new(cuda_gdb_path)
52        .args(["-i=dap", "-batch", "-ex", "quit"])
53        .output()
54        .await;
55
56    match output {
57        Ok(result) => {
58            // Check stderr for "unrecognized" error
59            let stderr = String::from_utf8_lossy(&result.stderr);
60            !stderr.contains("unrecognized") && !stderr.contains("Interpreter")
61        }
62        Err(_) => false,
63    }
64}
65
66#[async_trait]
67impl Installer for CudaGdbInstaller {
68    fn info(&self) -> &DebuggerInfo {
69        &INFO
70    }
71
72    async fn status(&self) -> Result<InstallStatus> {
73        if Platform::current() != Platform::Linux {
74            return Ok(InstallStatus::NotInstalled);
75        }
76
77        // Check for cuda-gdb
78        let Some(cuda_gdb_path) = find_cuda_gdb() else {
79            return Ok(InstallStatus::NotInstalled);
80        };
81
82        let version = get_gdb_version(&cuda_gdb_path).await;
83
84        // Check for native DAP support first (preferred)
85        if has_native_dap_support(&cuda_gdb_path).await {
86            return Ok(InstallStatus::Installed {
87                path: cuda_gdb_path,
88                version,
89            });
90        }
91
92        // Fall back to cdt-gdb-adapter bridge
93        if let Some(cdt_adapter) = find_cdt_gdb_adapter() {
94            return Ok(InstallStatus::Installed {
95                path: cdt_adapter,
96                version,
97            });
98        }
99
100        // cuda-gdb exists but no DAP method available
101        Ok(InstallStatus::Broken {
102            path: cuda_gdb_path,
103            reason: "cuda-gdb found but lacks native DAP support. Install cdt-gdb-adapter: npm install -g cdt-gdb-adapter".to_string(),
104        })
105    }
106
107    async fn best_method(&self) -> Result<InstallMethod> {
108        if Platform::current() != Platform::Linux {
109            return Ok(InstallMethod::NotSupported {
110                reason: "CUDA-GDB GPU debugging is only supported on Linux".to_string(),
111            });
112        }
113
114        // Check for cuda-gdb
115        let Some(cuda_gdb_path) = find_cuda_gdb() else {
116            return Ok(InstallMethod::NotSupported {
117                reason: "CUDA-GDB not found. Install NVIDIA CUDA Toolkit from https://developer.nvidia.com/cuda-downloads".to_string(),
118            });
119        };
120
121        // Check for native DAP support first (preferred)
122        if has_native_dap_support(&cuda_gdb_path).await {
123            return Ok(InstallMethod::AlreadyInstalled { path: cuda_gdb_path });
124        }
125
126        // Fall back to cdt-gdb-adapter bridge
127        if let Some(cdt_adapter) = find_cdt_gdb_adapter() {
128            return Ok(InstallMethod::AlreadyInstalled { path: cdt_adapter });
129        }
130
131        Ok(InstallMethod::NotSupported {
132            reason: "cuda-gdb lacks native DAP support. Install cdt-gdb-adapter: npm install -g cdt-gdb-adapter".to_string(),
133        })
134    }
135
136    async fn install(&self, _opts: InstallOptions) -> Result<InstallResult> {
137        let method = self.best_method().await?;
138
139        match method {
140            InstallMethod::AlreadyInstalled { path } => {
141                let cuda_gdb_path = find_cuda_gdb().ok_or_else(|| {
142                    Error::Internal("CUDA-GDB not found".to_string())
143                })?;
144                let version = get_gdb_version(&cuda_gdb_path).await;
145
146                // Determine if using native DAP or bridge mode
147                if has_native_dap_support(&cuda_gdb_path).await {
148                    // Native DAP mode
149                    Ok(InstallResult {
150                        path: cuda_gdb_path,
151                        version,
152                        args: vec!["-i=dap".to_string()],
153                    })
154                } else {
155                    // cdt-gdb-adapter bridge mode
156                    Ok(InstallResult {
157                        path,
158                        version,
159                        args: vec![format!("--config={{\"gdb\":\"{}\"}}", cuda_gdb_path.display())],
160                    })
161                }
162            }
163            InstallMethod::NotSupported { reason } => {
164                Err(Error::Internal(format!("Cannot install CUDA-GDB: {}", reason)))
165            }
166            _ => Err(Error::Internal("Unexpected installation method".to_string())),
167        }
168    }
169
170    async fn uninstall(&self) -> Result<()> {
171        println!("CUDA-GDB is part of NVIDIA CUDA Toolkit. Uninstall the toolkit to remove it.");
172        Ok(())
173    }
174
175    async fn verify(&self) -> Result<VerifyResult> {
176        let status = self.status().await?;
177
178        match status {
179            InstallStatus::Installed { path, .. } => {
180                let cuda_gdb_path = find_cuda_gdb().ok_or_else(|| {
181                    Error::Internal("CUDA-GDB not found".to_string())
182                })?;
183
184                // Determine verification args based on mode
185                if has_native_dap_support(&cuda_gdb_path).await {
186                    // Native DAP mode
187                    verify_dap_adapter(&path, &["-i=dap".to_string()]).await
188                } else {
189                    // cdt-gdb-adapter bridge mode
190                    verify_dap_adapter(
191                        &path,
192                        &[format!("--config={{\"gdb\":\"{}\"}}", cuda_gdb_path.display())],
193                    ).await
194                }
195            }
196            InstallStatus::Broken { reason, .. } => Ok(VerifyResult {
197                success: false,
198                capabilities: None,
199                error: Some(reason),
200            }),
201            InstallStatus::NotInstalled => Ok(VerifyResult {
202                success: false,
203                capabilities: None,
204                error: Some("Not installed".to_string()),
205            }),
206        }
207    }
208}
209
210/// Locates cuda-gdb binary using NVIDIA Toolkit path conventions
211///
212/// Search order: versioned CUDA installs → /usr/local/cuda → /opt/cuda → CUDA_HOME → PATH
213fn find_cuda_gdb() -> Option<PathBuf> {
214    // Check versioned CUDA installs (e.g., /usr/local/cuda-13.1)
215    // Prefer higher versions which are more likely to have DAP support
216    if let Ok(entries) = std::fs::read_dir("/usr/local") {
217        let mut cuda_paths: Vec<_> = entries
218            .flatten()
219            .filter_map(|e| {
220                let name = e.file_name().to_string_lossy().to_string();
221                if name.starts_with("cuda-") {
222                    let cuda_gdb = e.path().join("bin/cuda-gdb");
223                    if cuda_gdb.exists() {
224                        // Extract version for sorting (e.g., "13.1" from "cuda-13.1")
225                        let version = name.strip_prefix("cuda-").unwrap_or("0.0").to_string();
226                        return Some((version, cuda_gdb));
227                    }
228                }
229                None
230            })
231            .collect();
232
233        // Sort by version descending (higher versions first)
234        cuda_paths.sort_by(|a, b| {
235            let parse_version = |s: &str| -> (u32, u32) {
236                let parts: Vec<&str> = s.split('.').collect();
237                let major = parts.first().and_then(|p| p.parse().ok()).unwrap_or(0);
238                let minor = parts.get(1).and_then(|p| p.parse().ok()).unwrap_or(0);
239                (major, minor)
240            };
241            parse_version(&b.0).cmp(&parse_version(&a.0))
242        });
243
244        if let Some((_, path)) = cuda_paths.first() {
245            return Some(path.clone());
246        }
247    }
248
249    // NVIDIA's standard install location (symlink to versioned install)
250    let default_path = PathBuf::from("/usr/local/cuda/bin/cuda-gdb");
251    if default_path.exists() {
252        return Some(default_path);
253    }
254
255    // Arch Linux installs to /opt/cuda
256    let arch_path = PathBuf::from("/opt/cuda/bin/cuda-gdb");
257    if arch_path.exists() {
258        return Some(arch_path);
259    }
260
261    // CUDA_HOME environment variable
262    if let Ok(cuda_home) = std::env::var("CUDA_HOME") {
263        let cuda_home_path = PathBuf::from(cuda_home).join("bin/cuda-gdb");
264        if cuda_home_path.exists() {
265            return Some(cuda_home_path);
266        }
267    }
268
269    // Fall back to PATH
270    which::which("cuda-gdb").ok()
271}
272
273/// Locates cdt-gdb-adapter (cdtDebugAdapter) binary
274///
275/// Searches npm global bin directories and common locations
276fn find_cdt_gdb_adapter() -> Option<PathBuf> {
277    // Check PATH first
278    if let Ok(path) = which::which("cdtDebugAdapter") {
279        return Some(path);
280    }
281
282    // Check common npm global bin locations
283    if let Ok(home) = std::env::var("HOME") {
284        // nvm installations
285        let nvm_path = PathBuf::from(&home).join(".nvm/versions/node");
286        if nvm_path.exists() {
287            if let Ok(entries) = std::fs::read_dir(&nvm_path) {
288                for entry in entries.flatten() {
289                    let bin_path = entry.path().join("bin/cdtDebugAdapter");
290                    if bin_path.exists() {
291                        return Some(bin_path);
292                    }
293                }
294            }
295        }
296
297        // Standard npm global
298        let npm_global = PathBuf::from(&home).join(".npm-global/bin/cdtDebugAdapter");
299        if npm_global.exists() {
300            return Some(npm_global);
301        }
302
303        // npm prefix bin
304        let npm_prefix = PathBuf::from(&home).join("node_modules/.bin/cdtDebugAdapter");
305        if npm_prefix.exists() {
306            return Some(npm_prefix);
307        }
308    }
309
310    // System-wide npm
311    let system_path = PathBuf::from("/usr/local/bin/cdtDebugAdapter");
312    if system_path.exists() {
313        return Some(system_path);
314    }
315
316    None
317}