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 <anthony@zed.dev>
This commit is contained in:
Bo Lorentsen
2025-11-06 12:53:59 -05:00
committed by GitHub
co-authored by Anthony
parent a7fac65d62
commit b78f19982f
+79 -16
View File
@@ -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<String>) -> Vec<String> {
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<HashMap<String, String>>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
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::<Vec<_>>()
})
.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<String, String> = 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::<HashMap<String, String>>()
})
.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 {