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}