debugger: Fix gdb adapter and logger (#28280)

There were two bugs that caused the gdb adapter not to work properly,
one on our end and one their end.

The bug on our end was sending `stopOnEntry: null` in our launch request
when stop on entry had no value. I fixed that bug across all dap
adapters

The other bug had to do with python's "great" type system and how we
serialized our unit structs to json; mainly,
`ConfigurationDoneArguments` and `ThreadsArguments`. Gdb seems to follow
a pattern for handling requests where they pass `**args` to a function,
this errors out when the equivalent json is `"arguments": null`.

```py
@capability("supportsConfigurationDoneRequest")
@request("configurationDone", on_dap_thread=True)
def config_done(**args): ### BUG!!
    ...
```

Release Notes:

- N/A
This commit is contained in:
Anthony Eid 2025-04-07 18:02:13 -04:00 committed by GitHub
parent 56ed5dcc89
commit 862d0c07ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 120 additions and 64 deletions

2
Cargo.lock generated
View file

@ -4050,7 +4050,7 @@ dependencies = [
[[package]] [[package]]
name = "dap-types" name = "dap-types"
version = "0.0.1" version = "0.0.1"
source = "git+https://github.com/zed-industries/dap-types?rev=bfd4af0#bfd4af084bbaa5f344e6925370d7642e41d0b5b8" source = "git+https://github.com/zed-industries/dap-types?rev=be69a016ba710191b9fdded28c8b042af4b617f7#be69a016ba710191b9fdded28c8b042af4b617f7"
dependencies = [ dependencies = [
"schemars", "schemars",
"serde", "serde",

View file

@ -425,7 +425,7 @@ core-foundation = "0.10.0"
core-foundation-sys = "0.8.6" core-foundation-sys = "0.8.6"
ctor = "0.4.0" ctor = "0.4.0"
dashmap = "6.0" dashmap = "6.0"
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "bfd4af0" } dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "be69a016ba710191b9fdded28c8b042af4b617f7" }
derive_more = "0.99.17" derive_more = "0.99.17"
dirs = "4.0" dirs = "4.0"
ec4rs = "1.1" ec4rs = "1.1"

View file

@ -3,7 +3,7 @@ use std::ffi::OsStr;
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use async_trait::async_trait; use async_trait::async_trait;
use gpui::AsyncApp; use gpui::AsyncApp;
use task::{DebugAdapterConfig, DebugTaskDefinition}; use task::{DebugAdapterConfig, DebugRequestType, DebugTaskDefinition};
use crate::*; use crate::*;
@ -74,13 +74,37 @@ impl DebugAdapter for GdbDebugAdapter {
} }
fn request_args(&self, config: &DebugTaskDefinition) -> Value { fn request_args(&self, config: &DebugTaskDefinition) -> Value {
let mut args = json!({
"request": match config.request {
DebugRequestType::Launch(_) => "launch",
DebugRequestType::Attach(_) => "attach",
},
});
let map = args.as_object_mut().unwrap();
match &config.request { match &config.request {
dap::DebugRequestType::Attach(attach_config) => { DebugRequestType::Attach(attach) => {
json!({"pid": attach_config.process_id}) map.insert("pid".into(), attach.process_id.into());
} }
dap::DebugRequestType::Launch(launch_config) => {
json!({"program": launch_config.program, "cwd": launch_config.cwd, "stopOnEntry": config.stop_on_entry, "args": launch_config.args.clone()}) DebugRequestType::Launch(launch) => {
map.insert("program".into(), launch.program.clone().into());
if !launch.args.is_empty() {
map.insert("args".into(), launch.args.clone().into());
}
if let Some(stop_on_entry) = config.stop_on_entry {
map.insert(
"stopAtBeginningOfMainSubprogram".into(),
stop_on_entry.into(),
);
}
if let Some(cwd) = launch.cwd.as_ref() {
map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
}
} }
} }
args
} }
} }

View file

@ -83,19 +83,25 @@ impl DebugAdapter for GoDebugAdapter {
} }
fn request_args(&self, config: &DebugTaskDefinition) -> Value { fn request_args(&self, config: &DebugTaskDefinition) -> Value {
match &config.request { let mut args = match &config.request {
dap::DebugRequestType::Attach(attach_config) => { dap::DebugRequestType::Attach(attach_config) => {
json!({ json!({
"processId": attach_config.process_id, "processId": attach_config.process_id,
"stopOnEntry": config.stop_on_entry,
}) })
} }
dap::DebugRequestType::Launch(launch_config) => json!({ dap::DebugRequestType::Launch(launch_config) => json!({
"program": launch_config.program, "program": launch_config.program,
"cwd": launch_config.cwd, "cwd": launch_config.cwd,
"stopOnEntry": config.stop_on_entry,
"args": launch_config.args "args": launch_config.args
}), }),
};
let map = args.as_object_mut().unwrap();
if let Some(stop_on_entry) = config.stop_on_entry {
map.insert("stopOnEntry".into(), stop_on_entry.into());
} }
args
} }
} }

View file

@ -131,19 +131,20 @@ impl DebugAdapter for JsDebugAdapter {
match &config.request { match &config.request {
DebugRequestType::Attach(attach) => { DebugRequestType::Attach(attach) => {
map.insert("processId".into(), attach.process_id.into()); map.insert("processId".into(), attach.process_id.into());
map.insert("stopOnEntry".into(), config.stop_on_entry.into());
} }
DebugRequestType::Launch(launch) => { DebugRequestType::Launch(launch) => {
map.insert("program".into(), launch.program.clone().into()); map.insert("program".into(), launch.program.clone().into());
map.insert("args".into(), launch.args.clone().into());
map.insert( if !launch.args.is_empty() {
"cwd".into(), map.insert("args".into(), launch.args.clone().into());
launch }
.cwd
.as_ref() if let Some(stop_on_entry) = config.stop_on_entry {
.map(|s| s.to_string_lossy().into_owned()) map.insert("stopOnEntry".into(), stop_on_entry.into());
.into(), }
); if let Some(cwd) = launch.cwd.as_ref() {
map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
}
} }
} }
args args

View file

@ -81,16 +81,17 @@ impl DebugAdapter for LldbDebugAdapter {
} }
DebugRequestType::Launch(launch) => { DebugRequestType::Launch(launch) => {
map.insert("program".into(), launch.program.clone().into()); map.insert("program".into(), launch.program.clone().into());
map.insert("stopOnEntry".into(), config.stop_on_entry.into());
map.insert("args".into(), launch.args.clone().into()); if !launch.args.is_empty() {
map.insert( map.insert("args".into(), launch.args.clone().into());
"cwd".into(), }
launch
.cwd if let Some(stop_on_entry) = config.stop_on_entry {
.as_ref() map.insert("stopOnEntry".into(), stop_on_entry.into());
.map(|s| s.to_string_lossy().into_owned()) }
.into(), if let Some(cwd) = launch.cwd.as_ref() {
); map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
}
} }
} }
args args

View file

@ -119,7 +119,7 @@ impl DebugAdapter for PhpDebugAdapter {
"program": launch_config.program, "program": launch_config.program,
"cwd": launch_config.cwd, "cwd": launch_config.cwd,
"args": launch_config.args, "args": launch_config.args,
"stopOnEntry": config.stop_on_entry, "stopOnEntry": config.stop_on_entry.unwrap_or_default(),
}) })
} }
} }

View file

@ -185,7 +185,7 @@ impl DebugPanel {
) -> Vec<Entity<DebugSession>> { ) -> Vec<Entity<DebugSession>> {
self.sessions self.sessions
.iter() .iter()
.filter(|item| item.read(cx).session_id(cx) == Some(*client_id)) .filter(|item| item.read(cx).session_id(cx) == *client_id)
.map(|item| item.clone()) .map(|item| item.clone())
.collect() .collect()
} }
@ -200,7 +200,7 @@ impl DebugPanel {
.find(|item| { .find(|item| {
let item = item.read(cx); let item = item.read(cx);
item.session_id(cx) == Some(client_id) item.session_id(cx) == client_id
}) })
.cloned() .cloned()
} }
@ -227,7 +227,7 @@ impl DebugPanel {
if self if self
.sessions .sessions
.iter() .iter()
.any(|item| item.read(cx).session_id(cx) == Some(*session_id)) .any(|item| item.read(cx).session_id(cx) == *session_id)
{ {
// We already have an item for this session. // We already have an item for this session.
return; return;

View file

@ -75,9 +75,9 @@ impl DebugSession {
}) })
} }
pub(crate) fn session_id(&self, cx: &App) -> Option<SessionId> { pub(crate) fn session_id(&self, cx: &App) -> SessionId {
match &self.mode { match &self.mode {
DebugSessionState::Running(entity) => Some(entity.read(cx).session_id()), DebugSessionState::Running(entity) => entity.read(cx).session_id(),
} }
} }

View file

@ -270,7 +270,7 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
.clone() .clone()
}); });
assert_eq!(client.id(), active_session.read(cx).session_id(cx).unwrap()); assert_eq!(client.id(), active_session.read(cx).session_id(cx));
assert_eq!( assert_eq!(
ThreadId(1), ThreadId(1),
running_state.read(cx).selected_thread_id().unwrap() running_state.read(cx).selected_thread_id().unwrap()
@ -307,7 +307,7 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
.clone() .clone()
}); });
assert_eq!(client.id(), active_session.read(cx).session_id(cx).unwrap()); assert_eq!(client.id(), active_session.read(cx).session_id(cx));
assert_eq!( assert_eq!(
ThreadId(1), ThreadId(1),
running_state.read(cx).selected_thread_id().unwrap() running_state.read(cx).selected_thread_id().unwrap()

View file

@ -1479,7 +1479,7 @@ impl LocalDapCommand for ThreadsCommand {
type DapRequest = dap::requests::Threads; type DapRequest = dap::requests::Threads;
fn to_dap(&self) -> <Self::DapRequest as dap::requests::Request>::Arguments { fn to_dap(&self) -> <Self::DapRequest as dap::requests::Request>::Arguments {
() dap::ThreadsArgument {}
} }
fn response_from_dap( fn response_from_dap(
@ -1568,7 +1568,7 @@ impl LocalDapCommand for Initialize {
} }
#[derive(Clone, Debug, Hash, PartialEq)] #[derive(Clone, Debug, Hash, PartialEq)]
pub(super) struct ConfigurationDone; pub(super) struct ConfigurationDone {}
impl LocalDapCommand for ConfigurationDone { impl LocalDapCommand for ConfigurationDone {
type Response = (); type Response = ();
@ -1581,7 +1581,7 @@ impl LocalDapCommand for ConfigurationDone {
} }
fn to_dap(&self) -> <Self::DapRequest as dap::requests::Request>::Arguments { fn to_dap(&self) -> <Self::DapRequest as dap::requests::Request>::Arguments {
dap::ConfigurationDoneArguments dap::ConfigurationDoneArguments {}
} }
fn response_from_dap( fn response_from_dap(

View file

@ -843,12 +843,17 @@ fn create_new_session(
cx.notify(); cx.notify();
})?; })?;
match session match {
.update(cx, |session, cx| { session
session.initialize_sequence(initialized_rx, cx) .update(cx, |session, cx| session.request_initialize(cx))?
})? .await?;
.await
{ session
.update(cx, |session, cx| {
session.initialize_sequence(initialized_rx, cx)
})?
.await
} {
Ok(_) => {} Ok(_) => {}
Err(error) => { Err(error) => {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {

View file

@ -10,7 +10,7 @@ use super::dap_command::{
VariablesCommand, VariablesCommand,
}; };
use super::dap_store::DapAdapterDelegate; use super::dap_store::DapAdapterDelegate;
use anyhow::{Result, anyhow}; use anyhow::{Context as _, Result, anyhow};
use collections::{HashMap, HashSet, IndexMap, IndexSet}; use collections::{HashMap, HashSet, IndexMap, IndexSet};
use dap::adapters::{DebugAdapter, DebugAdapterBinary}; use dap::adapters::{DebugAdapter, DebugAdapterBinary};
use dap::messages::Response; use dap::messages::Response;
@ -190,7 +190,7 @@ impl LocalMode {
delegate: DapAdapterDelegate, delegate: DapAdapterDelegate,
messages_tx: futures::channel::mpsc::UnboundedSender<Message>, messages_tx: futures::channel::mpsc::UnboundedSender<Message>,
cx: AsyncApp, cx: AsyncApp,
) -> Task<Result<(Self, Capabilities)>> { ) -> Task<Result<Self>> {
Self::new_inner( Self::new_inner(
debug_adapters, debug_adapters,
session_id, session_id,
@ -214,7 +214,7 @@ impl LocalMode {
caps: Capabilities, caps: Capabilities,
fail: bool, fail: bool,
cx: AsyncApp, cx: AsyncApp,
) -> Task<Result<(Self, Capabilities)>> { ) -> Task<Result<Self>> {
use task::DebugRequestDisposition; use task::DebugRequestDisposition;
let request = match config.request.clone() { let request = match config.request.clone() {
@ -349,7 +349,7 @@ impl LocalMode {
messages_tx: futures::channel::mpsc::UnboundedSender<Message>, messages_tx: futures::channel::mpsc::UnboundedSender<Message>,
on_initialized: impl AsyncFnOnce(&mut LocalMode, AsyncApp) + 'static, on_initialized: impl AsyncFnOnce(&mut LocalMode, AsyncApp) + 'static,
cx: AsyncApp, cx: AsyncApp,
) -> Task<Result<(Self, Capabilities)>> { ) -> Task<Result<Self>> {
cx.spawn(async move |cx| { cx.spawn(async move |cx| {
let (adapter, binary) = let (adapter, binary) =
Self::get_adapter_binary(&registry, &config, &delegate, cx).await?; Self::get_adapter_binary(&registry, &config, &delegate, cx).await?;
@ -374,11 +374,11 @@ impl LocalMode {
message_handler, message_handler,
cx.clone(), cx.clone(),
) )
.await? .await
.with_context(|| "Failed to start communication with debug adapter")?
}, },
); );
let adapter_id = adapter.name().to_string().to_owned();
let mut session = Self { let mut session = Self {
client, client,
adapter, adapter,
@ -387,11 +387,8 @@ impl LocalMode {
}; };
on_initialized(&mut session, cx.clone()).await; on_initialized(&mut session, cx.clone()).await;
let capabilities = session
.request(Initialize { adapter_id }, cx.background_executor().clone())
.await?;
Ok((session, capabilities)) Ok(session)
}) })
} }
@ -541,6 +538,12 @@ impl LocalMode {
self.config.label.clone() self.config.label.clone()
} }
fn request_initialization(&self, cx: &App) -> Task<Result<Capabilities>> {
let adapter_id = self.adapter.name().to_string();
self.request(Initialize { adapter_id }, cx.background_executor().clone())
}
fn initialize_sequence( fn initialize_sequence(
&self, &self,
capabilities: &Capabilities, capabilities: &Capabilities,
@ -590,7 +593,7 @@ impl LocalMode {
cx.update(|cx| this.send_all_breakpoints(false, cx))?.await; cx.update(|cx| this.send_all_breakpoints(false, cx))?.await;
if configuration_done_supported { if configuration_done_supported {
this.request(ConfigurationDone, cx.background_executor().clone()) this.request(ConfigurationDone {}, cx.background_executor().clone())
} else { } else {
Task::ready(Ok(())) Task::ready(Ok(()))
} }
@ -849,7 +852,7 @@ impl Session {
let (message_tx, message_rx) = futures::channel::mpsc::unbounded(); let (message_tx, message_rx) = futures::channel::mpsc::unbounded();
cx.spawn(async move |cx| { cx.spawn(async move |cx| {
let (mode, capabilities) = LocalMode::new( let mode = LocalMode::new(
debug_adapters, debug_adapters,
session_id, session_id,
parent_session.clone(), parent_session.clone(),
@ -870,7 +873,6 @@ impl Session {
initialized_tx, initialized_tx,
message_rx, message_rx,
mode, mode,
capabilities,
cx, cx,
) )
}) })
@ -893,7 +895,7 @@ impl Session {
let (message_tx, message_rx) = futures::channel::mpsc::unbounded(); let (message_tx, message_rx) = futures::channel::mpsc::unbounded();
cx.spawn(async move |cx| { cx.spawn(async move |cx| {
let (mode, capabilities) = LocalMode::new_fake( let mode = LocalMode::new_fake(
session_id, session_id,
parent_session.clone(), parent_session.clone(),
breakpoint_store.clone(), breakpoint_store.clone(),
@ -915,7 +917,6 @@ impl Session {
initialized_tx, initialized_tx,
message_rx, message_rx,
mode, mode,
capabilities,
cx, cx,
) )
}) })
@ -1007,6 +1008,25 @@ impl Session {
} }
} }
pub(super) fn request_initialize(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
match &self.mode {
Mode::Local(local_mode) => {
let capabilities = local_mode.clone().request_initialization(cx);
cx.spawn(async move |this, cx| {
let capabilities = capabilities.await?;
this.update(cx, |session, _| {
session.capabilities = capabilities;
})?;
Ok(())
})
}
Mode::Remote(_) => Task::ready(Err(anyhow!(
"Cannot send initialize request from remote session"
))),
}
}
pub(super) fn initialize_sequence( pub(super) fn initialize_sequence(
&mut self, &mut self,
initialize_rx: oneshot::Receiver<()>, initialize_rx: oneshot::Receiver<()>,
@ -1947,7 +1967,6 @@ fn create_local_session(
initialized_tx: oneshot::Sender<()>, initialized_tx: oneshot::Sender<()>,
mut message_rx: futures::channel::mpsc::UnboundedReceiver<Message>, mut message_rx: futures::channel::mpsc::UnboundedReceiver<Message>,
mode: LocalMode, mode: LocalMode,
capabilities: Capabilities,
cx: &mut Context<Session>, cx: &mut Context<Session>,
) -> Session { ) -> Session {
let _background_tasks = vec![cx.spawn(async move |this: WeakEntity<Session>, cx| { let _background_tasks = vec![cx.spawn(async move |this: WeakEntity<Session>, cx| {
@ -2003,7 +2022,7 @@ fn create_local_session(
child_session_ids: HashSet::default(), child_session_ids: HashSet::default(),
parent_id: parent_session.map(|session| session.read(cx).id), parent_id: parent_session.map(|session| session.read(cx).id),
variables: Default::default(), variables: Default::default(),
capabilities, capabilities: Capabilities::default(),
thread_states: ThreadStates::default(), thread_states: ThreadStates::default(),
output_token: OutputToken(0), output_token: OutputToken(0),
ignore_breakpoints: false, ignore_breakpoints: false,