-
Notifications
You must be signed in to change notification settings - Fork 812
Closed
Labels
DebugIssues related to the debugging functionality of the extension.Issues related to the debugging functionality of the extension.FrozenDueToAge
Milestone
Description
Delve supports debug adapter protocol natively (dlv dap
).
https://github.com/go-delve/delve/tree/master/service/dap
Implement the debug feature using it and deprecate the dlv wrapper (src/debug/goDebug.ts
).
Metadata
Metadata
Assignees
Labels
DebugIssues related to the debugging functionality of the extension.Issues related to the debugging functionality of the extension.FrozenDueToAge
Type
Projects
Milestone
Relationships
Development
Select code repository
Activity
[-]Debug: use dlv's DAP implementation[/-][+]debug: use Delve's DAP implementation[/+]polinasok commentedon May 27, 2020
DAP-in-delve work is tracked under go-delve/delve#1515
It relies on a new library repo for DAP support in Go: https://github.com/google/go-dap
You can reference delve's dap/server.go::handleRequest() for an account of which DAP requests are already supported in delve and which ones are still TODO.
lggomez commentedon May 31, 2020
I would like to add that some of the current issues due to incompatibility between the current DAP and delve that should be solved (at least partially) are the following: #118 #119 #129 #130
Tacking these would help a lot to the goal of achieving a seamless debug experience
polinasok commentedon Jun 10, 2020
Overview
This overview is being updated as new options and details surface in the comments below.
Before
debuggers
under type "go".DebugAdapterExecutable
.In response to `launch` or `attach` request, the DA launches `dlv --headless` command (that builds, executes or attaches to the debugee) or connects to an already running delve server.
dlv debug <program> --headless=true --listen=<host>:<port> --api-version=<apiVersion>
dlv test <program> --headless=true --listen=<host>:<port> --api-version=<apiVersion>
dlv exec <program> --headless=true --listen=<host>:<port> --api-version=<apiVersion>
Request type of ‘launch’ with mode ‘remote’ is deprecated, please use request type ‘attach’ with mode ‘remote’ instead
<host>:<port>
(or default)dlv attach <processId> --headless=true --listen=127.0.0.1:42309 --api-version=<apiVersion> --wd=<cwd>
<host>:<port>
After (Option 1)
dlv dap
command at the beginning of each debug session of the new debug type, usingDebugAdapterServer
. (To be revisited if dlv-dap starts supporting more than one debug session, so we can reuse a single server instance.) This can be done similarly to how thedlv
command is spawned in the existing adaptor. The server will run at port specified by debugServer, defined dynamically within the extension as part ofDebugConfigurationProvider
implementation. The user-specified option inlaunch.json
can still be used for debugging.In response to `launch` or `attach` request, delve-adapter will build, test, execute or attach to the debugee.
go debug
, launch processgo test
, launch processAfter (Option 2)
DebugAdapterExecutable
.initialize
request? (delve supports it anyway, so it can be used by itself)dlv dap
be launched/connected to oninitialize
orlaunch/attach
?Open Question: will this option make inline mode possible?
Open Question: will this option make RunInTerminal support easier?
eliben commentedon Jun 10, 2020
I have an experimental branch where a new debug adapter is added alongside the existing one, and registered under a different type -
godlvdap
. A PR showing the diff is here.As @polinasok's message above details, it registers a new debugger with a new entry in
packages.json
for thedebuggers
contribution point. For the purposes of this demo, the new adapter is just a copy of the existing adapter with some extra logging thrown in so we could distinguish between them. There are a couple more minor changes required, like registering a configuration provider for the new debugger ingoMain.ts
.The prototype works. When debugging Go code, we can select between the two different debug adaptors by adjusting the
"type"
field in the project'slaunch.json
.eliben commentedon Jun 10, 2020
[...]
Another alternative is to retain a thin debug adapter even when Delve's DAP functionality is complete. This debug adapter will take care of launching Delve and then will just pass-through all DAP commands back and forth.
The obvious cost of this alternative is having to maintain an additional debug adapter. That said, this adapter is expected to be very minimal.
The potential benefits of this approach:
LaunchRequestArguments
interface contains a lot of information that's outside the spec of the DAP protocol. It's specific to how vscode invokes debug adapters. If we talk to Delve directly, we'll have to encode all this logic in Delve itself, making it vscode-specific. If folks want to use Delve with other IDEs (which we already know is the case!) they will have to implement their own logic inside Delve or masquerade as vscode.dlv
is specified directly inpackage.json
. There's a difference between an adapter whose code ships with the extension, so the extension can safely assume it will findgoDebug.ts
, and betweendlv
which doesn't ship with the extension. It's likely thatdlv
won't be installed on new users' machines, and if it is, it's likely that the version will be wrong. We need a graceful way to handle this and it's not 100% clear how to do this without an adapter.hyangah commentedon Jun 10, 2020
The debug node client supports multiple ways of running a debug adapter.
https://github.com/microsoft/vscode-mock-debug/blob/master/src/extension.ts#L57
Can we utilize that and by default run the thin wrapper in inlined mode?
eliben commentedon Jun 10, 2020
Yes, though it's still not 100% clear to me what the tradeoff is. It seems like this allows us to debug the adapter and the extension in the same process, which is nice. This seems like a fairly new option in vscode, and I imagine the current Go debug adapter could also use this since it's written in TS/JS.
[adding more details]
Re-reading the documentation carefully again, when we use the
DebugAdapterExecutable
option (which is what happens when the path is specified inpackage.json
), vscode expects to talk to the adapter via stdin/stdout, and not sockets. To talk to an adapter via sockets, we have to setdebugServer
. Therefore, if we want vscode to talk directly to Delve without an adapter in between, we'll have to either ask users to havedebugServer
in eachlaunch.json
or we'll have to adapt Delve to talk DAP over stdin/stdout as well.quoctruong commentedon Jun 10, 2020
@eliben When you register the debug configuration provider, there is a method called resolveDebugConfiguration that you can use to intercept the configuration before sending it to the adapter. This is where you can set the
debugServer
on behalf of the user.@polinasok I also agree with @eliben's suggestion that we should have a thin layer of DAP in VSCode unless we want to move some of the existing logic that we have like path inference into Delve.
polinasok commentedon Jun 10, 2020
@quoctruong I was just going to ask you to chime in on the option of the thin adaptor, which we discussed as potentially necessary because of your path inference work. Could you please add more details/links as to what logic will end up in the thin adaptor for this?
For inspiration, python has a DAP adaptor to launch PTVSD, handling the rest of communication directly over DAP. The comment says
polinasok commentedon Jun 11, 2020
I updated the overview comment to include the 2nd option with the thin adapter, so we have both at a glance.
polinasok commentedon Jun 11, 2020
@eliben
Agreed that we want to avoid pushing anything vscode-specific into Delve.
I believe so far we have been successful in avoid this, but please do let me know if you think otherwise.
The current debug adaptor also gets all these extra arguments and just ignores them. That configuration passes through all the layers, so I think the idea is to just capture all attributes in one place and to let each layer handle those that of interest to it.
Since the arguments are a free-form map and not a struct, we do not have any of those vscode-specific fields coded anywhere. Are you concerned that they are polluting the request that delve receives even if it never reads those fields? Or do you think we should avoid having delve handle launch requests all-together and go back to specifying the program on the delve command line?
Do you know of a specific example where the current approach might cause issues? I have not looked into other IDEs very closely yet.
My general understanding is that launch.json-like configuration is of part of the contract, even if DAP spec does not specify it in detail. Some fields are editor-specific. Others are adapter-specific. So the adapter (and in our case delve) has to make it clear, which argument fields it relies on and vscode or any other IDE have to supply them in launch/attach requests. And how they source them from the user or internally (via launch.json or something else), will be up to each client.
Your concern about the dlv installation is very valid. I believe by default you get a "no debug adaptor" message, which is not very useful because the user would not know that delve is the adaptor and delve is missing and needs to be installed. However, if debugServer option is used, it takes precedence over the executable in package.json, which is the very last fallback. So whatever extension code launches delve as an adapter and sets debugServer, can in theory do the same kind of error-checking and messaging that the current adapter does.
I tried to highlight in my overview comment that debugServer will be set in the extension code (goDebugConfiguration.ts). Looks like it was not clear that that we will not need any input from the user via launch.json (unless they want to debug an external adapter). Reworded a bit. Please let me know if it is still unclear.
31 remaining items
hyangah commentedon Nov 13, 2020
Updates:
RunInTerminal
prototype in the legacy debug adapter (https://go-review.googlesource.com/c/vscode-go/+/268461). We now have better understanding of the nature of such reverse requests.dlv dap
early to serveinitialize
(in the context of answering some open questions of Option 2.Based on our experiments, I want to propose some adjustments in the plan:
We want to eliminate the node.js-based thin adapter, and the extension will start a local
dlv dap
server for a debug session.The
dlv dap
can work like Option 1 for simple launch requests, but can switch to Option 2 mode depending on the launch/attach request types.Why?
dlv dap
allows a cleaner initialization handshake process, and works efficiently for the most common scenario (Case 1 below).dlv dap
, the feature will be accessible by other editors easily, and helpdlv dap
be more feature complete.dlv dap
side. And this feature can be beneficial to other editors.resolveDebugConfiguration
as @quoctruong commented. The current DAP predates most of the VSCode Debug APIs, that's why some of VS Code specific features leaked to the adapter. We already moved some to the extension side utilizing theresolveDebugConfiguration
. If we ever need vscode-specific modification after the launch configuration is sent, that's a failure of the protocol spec. :-)Disadvantages:
dlv dap
has to support different modes of operations, and becomes more complex - but I hope switching to go makes it more accessible to a wider group of go developers.dlv dap
need to be able to communicate over stdin/stdout in addition to the network socket for better user experience.How?
Initialization
dlv dap
usingDebugAdapterExecutable
mode adaptor factory. VSCode and DelveDAP will communicate overstdin/stdout
.initialize
request, and VSCode sends thelaunch
request.Case 1: Launch (local,
debug/test/exec
mode) without need forRunInTerminal
noDebug
is false, DelveDAP will build (debug/test
) and start Debugee for normal debug setup. IfnoDebug
is true, DelveDAP will build (debug/test
) and start the program (no ptracing). Debugee uses DelveDAP'sstdin/stdout/stderr
, so its output will appear in the standardDEBUG CONSOLE
along with other Delve DAP's logging/error messages.Case 2: Launch (local,
debug/test
mode) with need forRunInTerminal
RunInTerminal
request to the editor with the command to start DelveDAP' (e.g.-listen=localhost:<port>
etc). VSCode will open an integrated or external terminal, start DelveDAP' from the terminal, and send a response.RunInTerminal
).DEBUG CONSOLE
.Case 3: Attach, Local: DelveDAP will
attach
to the existing process, so Debugee will have its own stdin/stdout/stderr.Case 4: Attach, Remote: DelveDAP connects to the remote
dlv dap
process (DelveDAP'').dlv exec/debug/test
command. On the other hand,dlv dap
does not launch the debugee process until it receives theLaunch
request.Note: We also found
--accept-multiclient
use case. Many users favor the full-featured dlv CLI to the limited DEBUG CONSOLE that depends on DAP's evaluate requests. For those users, we can make DelveDAP launchDelveDAP'
in multiclient mode, and launchdlv connect
usingRunInTerminal
. One caveat is thatdlv connect
needs Delve's JSON-RPC interface but thedelve dap
server does not support it. So, we need a way to make a delve server speak in both interfaces.In this new setup, VSCode will warn users properly if the dlv binary is not found. And, the extension will try to ensure the dlv binary exists and supply the right executable path through VSCode's DebugAdapterDescriptorFactory.
/cc @polinasok @eliben @quoctruong @suzmue @aarzilli @derekparker
polinasok commentedon Jan 19, 2021
@hyangah could you please comment as to why you think we need to have a DA connection over stdin/out? Since delve is designed to talk over a socket, that would require some major changes to delve and/or implementation of an extra layer.
Currently vscode offers us the following debug adapter options:
A. single-use: server is terminated and restarted with each debug session
B. multi-use: client connection is terminated and restarted with each debug session
If we do not know of anything that must be in the thin (TS or Go) adapter, then I propose that we, at least initially, consider option 2 above and run dlv-dap as a server, communicating with the editor over a socket and handling the full sequence of the debug session requests without any intermediaries. We can use our version of
DebugAdapterDescriptorFactory
configuring the adapter asDebugAdapterServer
that would create the server (unless we were already given host/port by the remote attach mode) and connect to it increateDebugAdapterDescriptor
from the extension. As the need for another layer evolves (e.g. status quo dlv-dap is released and we work onRunInTerminal
), we can always insert it as needed, can't we?The current dlv-dap server terminates itself with every disconnect request, so we would need to redo this for every debug session. But as we upgrade dlv-dap with more features, we can let the server survive beyond a single client connection and accept more connections. This ties nicely into supporting
--accept-multiclient
that we need to support--continue
for remote mode and to support simultaneous connections from multiple clients, e.g. to take advantage of alternative UIs (like @hyangah suggestion to usedlv connect
if we upgrade the server to speak both APIs).In the single-use server case, the server will terminate itself on disconnect. We should use random ports to avoid any conflicts (see discussion in microsoft/debug-adapter-protocol#126). In the multi-use server case, we should be able to terminate in
dispose
as shown in the mock example. And we could use the same mechanism to clean up any rogue single-use servers that did not properly clean themselves up or check that the previous server is gone when we launch a new one.I have not prototyped this yet, so this is all theoretical. Plus my TypeScript familiarity is very superficial, so take my next suggestions with a grain of salt. I will revise them as I learn more. But for now I was thinking we could use the same spawn mechanism to launch the dlv-dap server process as we currently do in
debugAdatper
to start headless dlv, which allows us to register callbacks on close, error, etc. There is also thenet
module that gives us an asynchronous network wrapper. Seems like we need a way to detect and clean-up unresponsive servers. Are there additional lifetime management concerns here we need to think through?Also, here is a pointer to a Java server set-up although in this case the server is both LSP and DAP server and starts up as soon as the project is open:
https://github.com/microsoft/vscode-java-debug/blob/9be9832e4775201f5c01618ebde4df692e000138/src/javaDebugAdapterDescriptorFactory.ts#L10-L34
polinasok commentedon Jan 26, 2021
I have confirmed with @weinand that to implement a single-use local version of:
we can:
DebugAdapterDescriptorFactory
to start and connect to dlv-dap server (example) whenever a debug session is triggeredvscode.DebugAdapterServer
(api)If we make dlv-dap multi-use (able to accept multiple sequential client connections, each launching/attach to new process), we can change the factory to create it only for the first debug session, then just keep reconnecting to it for every new debug session trigger, and then clean it up in
dispose()
, which is called when the extension is deactivated (example). See go-delve/delve#2329 for more details.In the remote case, the user would launch dlv-dap on the command line on whatever machine of the process, then specify the following:
host=... port=... request='launch' mode='debug'/'test'/'exec' program=...
host=... port=... request='attach' mode='local' processId=...
We can add these configurations to goDebugConfigurations.
The host and the port can be obtained from
vscode.DebugConfiguration
before the debug adapter is triggered, but I need to figure out how to access it from the factory that will need to connect to this server. Request and mode will drive launch/attach requests as usual. We will need to be clear if the program path is a local or remote one and how it relates to any of the mappings specified by the user's launch config.I also have an idea to add support for just "Connect to server", by adding an option to dlv-dap to specify processId or program on the command-line and then making the launch/attach request a no-op or a sanity check. See go-delve/delve#2328 for more details.
gopherbot commentedon Feb 2, 2021
Change https://golang.org/cl/288954 mentions this issue:
src/goDebugFactory.ts: run dlv-dap as a server and connect as server
src/goDebugFactory.ts: run dlv-dap as a server and connect as server
hyangah commentedon May 5, 2021
dlv-dap is available from the stable extension for try.
https://github.com/golang/vscode-go/blob/master/docs/dlv-dap.md
Many details had changed this year. Since we passed the initial brainstorming and design stage, and most discussion happens outside this specific issue, I will close this.
There are still a few missing features and caveats, and we still have many tasks planned for polishing.
We will keep tracking of the remaining tasks for v0 in the project dashboard https://github.com/golang/vscode-go/projects/3