use anyhow::{Context as _, Result, bail}; use async_trait::async_trait; use collections::HashMap; use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition}; use gpui::AsyncApp; use std::ffi::OsStr; use task::{DebugScenario, ZedDebugConfig}; use crate::*; #[derive(Default)] pub(crate) struct GdbDebugAdapter; impl GdbDebugAdapter { const ADAPTER_NAME: &'static str = "GDB"; } /// Ensures that "-i=dap" is present in the GDB argument list. fn ensure_dap_interface(mut gdb_args: Vec) -> Vec { if !gdb_args.iter().any(|arg| arg.trim() == "-i=dap") { gdb_args.insert(0, "-i=dap".to_string()); } gdb_args } #[async_trait(?Send)] impl DebugAdapter for GdbDebugAdapter { fn name(&self) -> DebugAdapterName { DebugAdapterName(Self::ADAPTER_NAME.into()) } async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result { let mut obj = serde_json::Map::default(); match &zed_scenario.request { dap::DebugRequest::Attach(attach) => { obj.insert("request".into(), "attach".into()); obj.insert("pid".into(), attach.process_id.into()); } dap::DebugRequest::Launch(launch) => { obj.insert("request".into(), "launch".into()); obj.insert("program".into(), launch.program.clone().into()); if !launch.args.is_empty() { obj.insert("args".into(), launch.args.clone().into()); } if !launch.env.is_empty() { obj.insert("env".into(), launch.env_json()); } if let Some(stop_on_entry) = zed_scenario.stop_on_entry { obj.insert( "stopAtBeginningOfMainSubprogram".into(), stop_on_entry.into(), ); } if let Some(cwd) = launch.cwd.as_ref() { obj.insert("cwd".into(), cwd.to_string_lossy().into_owned().into()); } } } Ok(DebugScenario { adapter: zed_scenario.adapter, label: zed_scenario.label, build: None, config: serde_json::Value::Object(obj), tcp_connection: None, }) } fn dap_schema(&self) -> serde_json::Value { json!({ "oneOf": [ { "allOf": [ { "type": "object", "required": ["request"], "properties": { "request": { "type": "string", "enum": ["launch"], "description": "Request to launch a new process" } } }, { "type": "object", "properties": { "program": { "type": "string", "description": "The program to debug. This corresponds to the GDB 'file' command." }, "args": { "type": "array", "items": { "type": "string" }, "description": "Command line arguments passed to the program. These strings are provided as command-line arguments to the inferior.", "default": [] }, "cwd": { "type": "string", "description": "Working directory for the debugged program. GDB will change its working directory to this directory." }, "gdb_path": { "type": "string", "description": "Alternative path to the GDB executable, if the one in standard path is not desirable" }, "gdb_args": { "type": "array", "items": { "type":"string" }, "description": "additional arguments given to GDB at startup, not the program debugged", "default": [] }, "env": { "type": "object", "description": "Environment variables for the debugged program. Each key is the name of an environment variable; each value is the value of that variable." }, "stopAtBeginningOfMainSubprogram": { "type": "boolean", "description": "When true, GDB will set a temporary breakpoint at the program's main procedure, like the 'start' command.", "default": false }, "stopOnEntry": { "type": "boolean", "description": "When true, GDB will set a temporary breakpoint at the program's first instruction, like the 'starti' command.", "default": false } }, "required": ["program"] } ] }, { "allOf": [ { "type": "object", "required": ["request"], "properties": { "request": { "type": "string", "enum": ["attach"], "description": "Request to attach to an existing process" } } }, { "type": "object", "properties": { "pid": { "type": "number", "description": "The process ID to which GDB should attach." }, "program": { "type": "string", "description": "The program to debug (optional). This corresponds to the GDB 'file' command. In many cases, GDB can determine which program is running automatically." }, "target": { "type": "string", "description": "The target to which GDB should connect. This is passed to the 'target remote' command." } }, "required": ["pid"] } ] } ] }) } async fn get_binary( &self, delegate: &Arc, config: &DebugTaskDefinition, user_installed_path: Option, user_args: Option>, user_env: Option>, _: &mut AsyncApp, ) -> Result { // Try to get gdb_path from config let gdb_path_from_config = config .config .get("gdb_path") .and_then(|v| v.as_str()) .map(|s| s.to_string()); let gdb_path = if let Some(path) = gdb_path_from_config { path } else { // Original logic: use user_installed_path or search in system path let user_setting_path = user_installed_path .filter(|p| p.exists()) .and_then(|p| p.to_str().map(|s| s.to_string())); let gdb_path_result = delegate .which(OsStr::new("gdb")) .await .and_then(|p| p.to_str().map(|s| s.to_string())) .context("Could not find gdb in path"); if gdb_path_result.is_err() && user_setting_path.is_none() { bail!("Could not find gdb path or it's not installed"); } user_setting_path.unwrap_or_else(|| gdb_path_result.unwrap()) }; // Arguments: use gdb_args from config if present, else user_args, else default let gdb_args = { let args = config .config .get("gdb_args") .and_then(|v| v.as_array()) .map(|arr| { arr.iter() .filter_map(|v| v.as_str().map(|s| s.to_string())) .collect::>() }) .or(user_args.clone()) .unwrap_or_else(|| vec!["-i=dap".into()]); ensure_dap_interface(args) }; let mut configuration = config.config.clone(); if let Some(configuration) = configuration.as_object_mut() { configuration .entry("cwd") .or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into()); } let mut base_env = delegate.shell_env().await; base_env.extend(user_env.unwrap_or_default()); let config_env: HashMap = config .config .get("env") .and_then(|v| v.as_object()) .map(|obj| { obj.iter() .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string()))) .collect::>() }) .unwrap_or_else(HashMap::default); base_env.extend(config_env); Ok(DebugAdapterBinary { command: Some(gdb_path), arguments: gdb_args, envs: base_env, cwd: Some(delegate.worktree_root_path().to_path_buf()), connection: None, request_args: StartDebuggingRequestArguments { request: self.request_kind(&config.config).await?, configuration, }, }) } }