From b78f19982f00373d2794db1b7330ea6c221a84cd Mon Sep 17 00:00:00 2001 From: Bo Lorentsen Date: Thu, 6 Nov 2025 18:53:59 +0100 Subject: [PATCH] Allow for using config specific `gdb` binaries in gdb adapter (#37193) I really need this for embedded development in IDF and Zephyr, where the chip vendors sometimes provide there own specialized version of the tools, and I need to direct zed to use these. The current GDB adapter only supports the gdb it find in the normal search path, and it also seems like we where not able to transfer gdb specific startup arguments (only `-i=dap` is set) . In order to fix this I (semi wipe using GPT-4.1) expanded the GDB adapter with 2 new config options : * **gdb_path** holds a full path to the gdb executable, for now only full path or relative to cwd * **gdb_args** an array holding additional arguments given to gdb on startup It seemed to me, like the `env` config did not transferred to gdb, so this is added to. I have tested this locally, and it seems to work not only compile :-) Release Notes: debugger: Adds gdb_path and gdb_args to gdb debug adapter options debugger: Fix bug where gdb debug sessions wouldn't inherit the shell environment from Zed --------- Co-authored-by: Anthony --- crates/dap_adapters/src/gdb.rs | 95 ++++++++++++++++++++++++++++------ 1 file changed, 79 insertions(+), 16 deletions(-) diff --git a/crates/dap_adapters/src/gdb.rs b/crates/dap_adapters/src/gdb.rs index 12489247c5..46488cd550 100644 --- a/crates/dap_adapters/src/gdb.rs +++ b/crates/dap_adapters/src/gdb.rs @@ -1,10 +1,9 @@ -use std::ffi::OsStr; - 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::*; @@ -16,6 +15,14 @@ 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 { @@ -99,6 +106,18 @@ impl DebugAdapter for GdbDebugAdapter { "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." @@ -164,21 +183,49 @@ impl DebugAdapter for GdbDebugAdapter { user_env: Option>, _: &mut AsyncApp, ) -> Result { - let user_setting_path = user_installed_path - .filter(|p| p.exists()) - .and_then(|p| p.to_str().map(|s| s.to_string())); + // 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 = delegate - .which(OsStr::new("gdb")) - .await - .and_then(|p| p.to_str().map(|s| s.to_string())) - .context("Could not find gdb in path"); + 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())); - if gdb_path.is_err() && user_setting_path.is_none() { - bail!("Could not find gdb path or it's not installed"); - } + 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"); - let gdb_path = user_setting_path.unwrap_or(gdb_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() { @@ -187,10 +234,26 @@ impl DebugAdapter for GdbDebugAdapter { .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: user_args.unwrap_or_else(|| vec!["-i=dap".into()]), - envs: user_env.unwrap_or_default(), + arguments: gdb_args, + envs: base_env, cwd: Some(delegate.worktree_root_path().to_path_buf()), connection: None, request_args: StartDebuggingRequestArguments {