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}