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