helix-editor / helix

A post-modern modal text editor.
https://helix-editor.com
Mozilla Public License 2.0
33.4k stars 2.47k forks source link

Debugging support with DAP connection #505

Open dsseng opened 3 years ago

dsseng commented 3 years ago

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:

  1. 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) {
    }
}

  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~~
  2. Don't care about that, tcp_process transport will find free port and start debug adapter itself.
  3. Run DAP example cargo run --example dap-dlv or dap-lldb
  4. 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:

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).

kirawi commented 3 years ago

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.

dsseng commented 3 years ago

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.

archseer commented 3 years ago

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

dsseng commented 3 years ago

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?

dsseng commented 3 years ago

I started some development for DAP locally. Currently trying to make a transport layer.

archseer commented 3 years ago

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.

dsseng commented 3 years ago

It's not json-rpc. Currently copied lsp transport, will later isolate the common code

dsseng commented 3 years ago

Currently I already have an implementation (example for library, not in editor) that does some simple tasks:

archseer commented 3 years ago

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/

dsseng commented 3 years ago

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:!

dsseng commented 3 years ago

@archseer It's up https://github.com/helix-editor/helix/pull/574

archseer commented 3 years ago

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.