helix
helix copied to clipboard
Debugging support with DAP connection
Describe your feature request
Add support for setting breakpoints and running debug session, backed by DAP (debug adapter protocol). Maybe this needs to be done after WASM plugins.
DAP spec is here. The only relevant thing found on crates.io is DAP types crate. DAP should be rather simple to parse with Serde.
Neovim has nvim-dap plugin implementing this feature, VS Code has DAP built-in.
Current state: an initial implementation of DAP with some types, editor connection. To test DAP client:
- Create a debuggee program:
package main
import "fmt"
func main() {
a := "Hello"
b := "World"
fmt.Println(a, b)
for {
}
}
#include <stdio.h>
void main() {
char *a = "Hello";
char *b = "World";
printf("%s %s\n", a, b);
while (1) {
}
}
-
go build -gcflags '-N -l' main.go
to build binary with readable variables or use:dbg source main.go
for Go. 2.1.gcc main.c -o main -O0 -g
for C program ~~3. Start up Delve (chosen as a monolithic DAP+debugger in one binary) on port 7777:dlv dap -l 127.0.0.1:7777
3.1. Or lldb:lldb-vscode -p 7777
~~ - Don't care about that,
tcp_process
transport will find free port and start debug adapter itself. - Run DAP example
cargo run --example dap-dlv
ordap-lldb
- Press enter when you want to continue communicating to debugger.
Editor integration works with Go, C/C++ and Rust programs at the moment. ~~Just enter directory containing main.go
/main
(build output)/target/debug/rustdebug
for Rust and work with it.~~ Now you need to specify target manually, see languages.toml
In editor I currently use example attached as a zip.
godebug.zip
TODO:
- [x] DAP runs over TCP/IP. ~~Not stdio~~. So,
Transport
must be able to use TCP sockets and the lifecycle of debug adapters - [x] More types, requests, events implemented. I'll do that in some time.
- [x]
attach
- [x] parse
stopped
event - [x] Pass unused events to application or stream all the events in some way via channel
- [x]
pause
,step
~~,restart
,eval
~~ - [ ]
restart
- [ ]
supportsRestartRequest
- [ ]
- [x]
eval
- [x] conditional breakpoints
- [x] logpoints
- [x] conditional logpoints
- [x] Some internal state tracking
- [x]
breakpoint
event - [ ]
progress*
events - [ ]
output
event's data can be recorded as well - [x] Better state handling (replace
is_running
) - [ ] BreakpointLocations (not supported by lldb and Delve)
- [ ] Goto (no supported debugger)
- [ ] Completions Request for eval autocompletion (no supported debugger)
- [ ] supportsCompletionsRequest
- [x] setExceptionBreakpoints
- [ ] SetVariable Request
- [ ] Source Request
- [x] Check support for features in debugger
- [x] Breakpoints
- [ ]
supportsTerminateRequest
- [x]
- [x] Editor UI connection
- [x] Breakpoints setting
- [x] Show unverified breakpoints and breakpoints moved by debugger
- [x] Debugger state management
- [x] Variable introspection
- [x] Scrollable window for variables
- [x] Highlight stack pointer
- [x] Scroll to pos
- [ ] REPL for
eval
(after commpletions for eval are done) - [x] Attach support
- [x] Pretty-print errors instead of crashing when something goes wrong with debugger
- [x] multi-thread support for stack pointers
- [x] Preview of stack location
- [x] Go to previous stack frame
- [ ] Column-precision for breakpoints
- [x] Set breakpoints by mouse
- [x] Right click opens the prompt for editing condition
- [x] ~~Middle click~~ Right click + Alt opens the prompt for editing condition
- [x] those (:top:) ideas, but with keybindings
- [x] Breakpoints setting
- [x] Fix
Tried sending event into a closed channel
for events - [x] Split out interface for adapter-specific
launch
andattach
args - [x] Configuration for languages to specify how to start and configure debuggers for them ~~, maybe some debug adapters' quirks~~.
- [x] Names for launch configs
- [x] Templates
- [x] Pickers
- [ ] Show defaults and allow editing args
- [x] Non-string arguments
- [ ] Multiple debuggers for language (needs a use case, gdb will be the first one)
- [ ] Consider adding workspace configs for debug targets or parsing existing ones.
- [ ] Test debuggers, add them to editor.
- [x] Delve's
dlv dap
- [x] lldb with lldb-vscode
- [x] Node.js
- [ ] gdb integration, which means rr could be supported, test it as well
- [x] Initial connection to gdbserver with lldb, :warning: Is not compatible enough, OpenOCD and rr did not work
- [ ] https://github.com/microsoft/debugpy/
- [ ] Chromium
- [ ] Firefox
- [x] Delve's
- [ ] Docs for everything
Local ``languages.toml` for Node:
[[language]]
name = "javascript"
[language.debugger]
command = "node"
args = [ "/usr/share/code-insiders/resources/app/extensions/ms-vscode.node-debug2/out/src/nodeDebug.js" ]
Adjust path to extension in VSCode (or location where you unpacked the extension manually).
I feel like this is something that is common enough to be a built-in feature. Though we could also go ahead and package it with Helix as a default plugin.
Built-in seems the best. Plugins that depend on other plugins looks like not-so-cool idea.
Edit: and, of course, some APIs might be available for plugins. Maybe even some generics for custom, not DAP-compliant debuggers registered by plugins.
Yes, given that LSP is built in, DAP will be built in too. Note that both GDB and LLDB don't support DAP and implement a custom protocol, though LLDB seems to now offer a wrapper: https://github.com/llvm/llvm-project/tree/main/lldb/tools/lldb-vscode
DAP is more common, so built in. Others might be added through wrappers and then using plugin API. Maybe GDB might be shipped as an official plugin?
I started some development for DAP locally. Currently trying to make a transport layer.
We should be able to reuse most of the LSP low-level code, it's the same protocol. Just the high level requests are different.
It's not json-rpc. Currently copied lsp transport, will later isolate the common code
Currently I already have an implementation (example for library, not in editor) that does some simple tasks:
- connect to DAP via netcat (will implement proper TCP later, for now just command-line communication)
- initialize debugger and get capabilities (using Delve now since it's self-contained adapter+debugger)
- load binary (launch request)
- set a breakpoint in code and confirm it's been set
-
configurationDone
- let debugger start target - wait until target stops on breakpoint
- continue
- stop debug adapter
Interesting, so DAP doesn't run over stdin/stdout?
I think the way forward will be to define a transport and all the request/response protocol types in helix-dap
, then construct a session type that will be similar to doc.language_server
. I figured attach
would be an easy way to get going, along with being able to set breakpoints. We will need to come up with a proposal on how to integrate with the editor, and I'd leave any larger UI changes until later (we'll probably need a custom vars component).
I took a look at emacs dap-mode today: https://emacs-lsp.github.io/dap-mode/page/gallery/
E.g. delve doesn't. It's a TCP/IP socket:
dlv dap -h
[EXPERIMENTAL] Starts a TCP server communicating via Debug Adaptor Protocol (DAP).
The server is headless and requires a DAP client like vscode to connect and request a binary
to be launched or process to be attached to. The following modes are supported:
- launch + exec (executes precompiled binary, like 'dlv exec')
- launch + debug (builds and launches, like 'dlv debug')
- launch + test (builds and tests, like 'dlv test')
- attach + local (attaches to a running process, like 'dlv attach')
The server does not yet support asynchronous request-response communication, so features
like pausing or setting breakpoints while the program is running are not yet available.
The server does not accept multiple client connections (--accept-multiclient),
a feature that is often relied on to enable --continue with remote debugging.
Usage:
dlv dap [flags]
Global Flags:
--accept-multiclient Allows a headless server to accept multiple client connections.
--allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
--api-version int Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
--backend string Backend selection (see 'dlv help backend'). (default "default")
--build-flags string Build flags, to be passed to the compiler. For example: --build-flags="-tags=integration -mod=vendor -cover -v"
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
--disable-aslr Disables address space randomization
--headless Run debug server only, in headless mode.
--init string Init file, executed by the terminal client.
-l, --listen string Debugging server listen address. (default "127.0.0.1:0")
--log Enable debugging server logging.
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
-r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect')
--wd string Working directory for running the program.
I'm currently tidying up the code and will push it to GitHub for us to comfortably look at it and collaborate. Stack traces work atm :rocket:!
@archseer It's up https://github.com/helix-editor/helix/pull/574
Looks like it can be both ways. Either it's an executable we're responsible with starting and then communicating via stdio, or it's a server (that can also be externally running, we're not responsible for) and we attach to via TCP.