gdb.rs

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