1use std::ffi::OsStr;
  2
  3use anyhow::{Context as _, Result, bail};
  4use async_trait::async_trait;
  5use collections::HashMap;
  6use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
  7use gpui::AsyncApp;
  8use task::{DebugScenario, ZedDebugConfig};
  9
 10use crate::*;
 11
 12#[derive(Default)]
 13pub(crate) struct GdbDebugAdapter;
 14
 15impl GdbDebugAdapter {
 16    const ADAPTER_NAME: &'static str = "GDB";
 17}
 18
 19#[async_trait(?Send)]
 20impl DebugAdapter for GdbDebugAdapter {
 21    fn name(&self) -> DebugAdapterName {
 22        DebugAdapterName(Self::ADAPTER_NAME.into())
 23    }
 24
 25    async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
 26        let mut obj = serde_json::Map::default();
 27
 28        match &zed_scenario.request {
 29            dap::DebugRequest::Attach(attach) => {
 30                obj.insert("request".into(), "attach".into());
 31                obj.insert("pid".into(), attach.process_id.into());
 32            }
 33
 34            dap::DebugRequest::Launch(launch) => {
 35                obj.insert("request".into(), "launch".into());
 36                obj.insert("program".into(), launch.program.clone().into());
 37
 38                if !launch.args.is_empty() {
 39                    obj.insert("args".into(), launch.args.clone().into());
 40                }
 41
 42                if !launch.env.is_empty() {
 43                    obj.insert("env".into(), launch.env_json());
 44                }
 45
 46                if let Some(stop_on_entry) = zed_scenario.stop_on_entry {
 47                    obj.insert(
 48                        "stopAtBeginningOfMainSubprogram".into(),
 49                        stop_on_entry.into(),
 50                    );
 51                }
 52                if let Some(cwd) = launch.cwd.as_ref() {
 53                    obj.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
 54                }
 55            }
 56        }
 57
 58        Ok(DebugScenario {
 59            adapter: zed_scenario.adapter,
 60            label: zed_scenario.label,
 61            build: None,
 62            config: serde_json::Value::Object(obj),
 63            tcp_connection: None,
 64        })
 65    }
 66
 67    fn dap_schema(&self) -> serde_json::Value {
 68        json!({
 69            "oneOf": [
 70                {
 71                    "allOf": [
 72                        {
 73                            "type": "object",
 74                            "required": ["request"],
 75                            "properties": {
 76                                "request": {
 77                                    "type": "string",
 78                                    "enum": ["launch"],
 79                                    "description": "Request to launch a new process"
 80                                }
 81                            }
 82                        },
 83                        {
 84                            "type": "object",
 85                            "properties": {
 86                                "program": {
 87                                    "type": "string",
 88                                    "description": "The program to debug. This corresponds to the GDB 'file' command."
 89                                },
 90                                "args": {
 91                                    "type": "array",
 92                                    "items": {
 93                                        "type": "string"
 94                                    },
 95                                    "description": "Command line arguments passed to the program. These strings are provided as command-line arguments to the inferior.",
 96                                    "default": []
 97                                },
 98                                "cwd": {
 99                                    "type": "string",
100                                    "description": "Working directory for the debugged program. GDB will change its working directory to this directory."
101                                },
102                                "env": {
103                                    "type": "object",
104                                    "description": "Environment variables for the debugged program. Each key is the name of an environment variable; each value is the value of that variable."
105                                },
106                                "stopAtBeginningOfMainSubprogram": {
107                                    "type": "boolean",
108                                    "description": "When true, GDB will set a temporary breakpoint at the program's main procedure, like the 'start' command.",
109                                    "default": false
110                                },
111                                "stopOnEntry": {
112                                    "type": "boolean",
113                                    "description": "When true, GDB will set a temporary breakpoint at the program's first instruction, like the 'starti' command.",
114                                    "default": false
115                                }
116                            },
117                            "required": ["program"]
118                        }
119                    ]
120                },
121                {
122                    "allOf": [
123                        {
124                            "type": "object",
125                            "required": ["request"],
126                            "properties": {
127                                "request": {
128                                    "type": "string",
129                                    "enum": ["attach"],
130                                    "description": "Request to attach to an existing process"
131                                }
132                            }
133                        },
134                        {
135                            "type": "object",
136                            "properties": {
137                                "pid": {
138                                    "type": "number",
139                                    "description": "The process ID to which GDB should attach."
140                                },
141                                "program": {
142                                    "type": "string",
143                                    "description": "The program to debug (optional). This corresponds to the GDB 'file' command. In many cases, GDB can determine which program is running automatically."
144                                },
145                                "target": {
146                                    "type": "string",
147                                    "description": "The target to which GDB should connect. This is passed to the 'target remote' command."
148                                }
149                            },
150                            "required": ["pid"]
151                        }
152                    ]
153                }
154            ]
155        })
156    }
157
158    async fn get_binary(
159        &self,
160        delegate: &Arc<dyn DapDelegate>,
161        config: &DebugTaskDefinition,
162        user_installed_path: Option<std::path::PathBuf>,
163        user_args: Option<Vec<String>>,
164        user_env: Option<HashMap<String, String>>,
165        _: &mut AsyncApp,
166    ) -> Result<DebugAdapterBinary> {
167        let user_setting_path = user_installed_path
168            .filter(|p| p.exists())
169            .and_then(|p| p.to_str().map(|s| s.to_string()));
170
171        let gdb_path = delegate
172            .which(OsStr::new("gdb"))
173            .await
174            .and_then(|p| p.to_str().map(|s| s.to_string()))
175            .context("Could not find gdb in path");
176
177        if gdb_path.is_err() && user_setting_path.is_none() {
178            bail!("Could not find gdb path or it's not installed");
179        }
180
181        let gdb_path = user_setting_path.unwrap_or(gdb_path?);
182
183        let mut configuration = config.config.clone();
184        if let Some(configuration) = configuration.as_object_mut() {
185            configuration
186                .entry("cwd")
187                .or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into());
188        }
189
190        Ok(DebugAdapterBinary {
191            command: Some(gdb_path),
192            arguments: user_args.unwrap_or_else(|| vec!["-i=dap".into()]),
193            envs: user_env.unwrap_or_default(),
194            cwd: Some(delegate.worktree_root_path().to_path_buf()),
195            connection: None,
196            request_args: StartDebuggingRequestArguments {
197                request: self.request_kind(&config.config).await?,
198                configuration,
199            },
200        })
201    }
202}