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}