delve dap: supporting "console" property and debugging with root privileges #1626
Replies: 5 comments 24 replies
-
LLDB's solution seems like the cleanest one to me, having a generic DAP proxy inside delve is pretty depressing (I've been saying this for years, the RunInTerminal thing was designed backwards it shouldn't be the debugger's job to decide whether it actually wants to be in a terminal after it has already been started). For unix-like systems the |
Beta Was this translation helpful? Give feedback.
-
Looks like they are calling AttachConsole and then presumably something else calls GetStdHandle. It looks reasonable, the |
Beta Was this translation helpful? Give feedback.
-
Yes, that would be an appropriate use of a hidden cli command. |
Beta Was this translation helpful? Give feedback.
-
The original proposal is great, and exactly how users and debug adapter client implementers expect things to work.
It's pretty disappointing if that original, standard proposal, is scrapped in favour of "Vscode-specific approach", also known as "requiring every debug adapter client author to write adapter-specific-code (or worse, as suggested above and entire actual adapter!) in order for it to be usable". I thought this kind of decision making was limited to LSP servers and has mostly been avoided thus far in DAP-land (as you have nicely summarised above). Has a final decision been made on this? If so, is there any scope for reviewing this decision? It wouldn't be so bad actually if the default behaviour of delve was to send the stdout of the program over the DAP channel. It currently doesn't do this so there's no way to even see the stdout of the debugged program currently with most otherwise-protocol-conforming clients. I just added support for delve dap to vimspector and the first user who tried it said "can't see the stdout", which I can understand being a blocker. Having to re-engineer the complex DAP startup process, or "implementing a proxy DA on top of the github.com/go-delve/delve/blob/master/service/dap package and use it instead of dlv dap" for this one adapter seems overkill, whether it's "not too hard" or otherwise :/ To provide some background, my goal is to have a DAP client that is completely server agnostic, and you can plug any DAP-compliant server in by just launching the adapter and communicating with it over stdio or a socket*. All facilities of such debugger UI are provided by DAP requests that are documented in the protocol. I'm not the only DAP client author with little free time and no desire to write complex server-specific untestable code. So far, this has been successful (after a fashion) but there is an endless battle to ensure that adapters don't assume they are being run in VScode or rely on vscode specifics. Alas, I don't expect to win this battle, but I'll keep fighting so long as I have the energy! * FWIW we tried to do the same for LSP, but that's a forlorn hope. |
Beta Was this translation helpful? Give feedback.
-
This is an implementation proposal for feature requests:
#124 debug: support "console" property in launch.json
#558 debug: abiltiy to debug with root privileges
and many others
Implementation proposal
The console attribute provides users an option to specify how to launch and interact with the debugged program (debugee).
Support for integrated and external terminals are available via DAP’s RunInTerminal request and can address many issues that stemmed from inability to access terminal from debugged programs. See @polinasok’s summary for the known issues.
Overview
Currently,
vscode-go
extension integratesdlv dap
by implementing an inlined debug adapter that spawnsdlv dap
, connects to thedlv dap
server through a TCP port, convertsdlv dap
's stdout/stderr intoOutputEvent
, and proxies all the DAP communication between VS Code anddlv dap
.By default
dlv
runs the program to debug as a foreground process, so the debugged program can access to the terminaltty
ifdlv dap
runs from a regular terminal. However, vscode-go's thin adapter runs without tty sodlv
and the debugged program do not havetty
in the current setup.We propose to extend the inlined debug adapter to support the
console
property by delegating the job of startingdlv
to VS Code. VS Code can access the integrated terminals and the external terminals. In order to debug a program with root privileges, the debugger (dlv
) has to run with root privileges. Access to the terminal allows us to runsudo dlv
.When a debug session starts,
vscode-go
thin adapter).note: at this point, the debug adapter already knows the value of
console
property for the session.initialize
request with the editor's capabilities (e.g.supportsRunInTerminal
).runInTerminal
command (dlv dap --client-addr=single_use_server_addr
). IfasRoot
property is specified, the adapter will request to runsudo dlv dap
command.dlv dap
from the specified terminal.dlv dap
command connects to the single use TCP server and the connection betweendlv dap
and the thin adapter is estabilished.dlv dap
.DAP RunInTerminal or VS Code's terminal APIs
There are two different approaches for letting VS Code run
dlv dap
from the integrated terminalsRunInTerminal
.VS Code's terminal API provides great flexibility compared to the VS Code's RunInTerminal feature which is accessible only through DAP. However, we choose to utilize VS Code's
RunInTerminal
implementation because:RunInTerminal
supports external terminal mode, too. We don't have to worry about platform-specific implementation to support external terminal mode.RunInTerminal
implementation is maintained by VS Code team, and proven to work for many other debug extensions already.Violation of DAP spec
Strictly speaking, sending a
RunInTerminal
request beforeinitialize
response is incorrect. According to DAP spec:One option to correctly using
RunInTerminal
will look like:initialize
response specifying minimal capabilities, stash the originalinitialize
request, and complete the initialize message handshake.runInTerminal
.dlv dap
starts and connection is established.initialize
request todlv dap
.initialize
response fromdlv dap
, compute the difference in capabilities and send ancapabilities
event to VS Code.We see VS Code accept
RunInTerminal
arriving beforeinitialize
response empirically. We understand relying on this unintended behavior is not ideal, but this simplifies the implementation significantly.Another option we are considering is to propose to expose the
RunInTerminal
implementation as part of VS Code's debug API. That will even allow us to set up the connection withdlv dap
before VS Code sends theinitialize
request. See microsoft/vscode#136523Implementation
go-delve/delve#2568: add
--client-addr
flag to run dap with a predefined clientvscode-go console_mode
--client-addr
when console=integrated or externaldlv-dap
after receivinginitialize
request (previously, we started before receivinginitialize
request)runInTerminal
requestvscode-go supports
asRoot
propertycosmetic changes
env
computation to avoid flooding the terminal with many env variablesTODO
dlv dap
handleenv
property so environment variables are just passed throughlaunch
configuration instead ofrunInTerminal
.dlv dap
sendlog
messages asOutputEvent
to avoid polluting the terminal.Caveats
debugserver
for launching and tracing the debug target. When users issue Ctrl+C from the terminal, the debugserver receives it and exits without properly detaching or terminating the debug target. This is a known issue and we don't know how to handle this properly yet.Prior Art and Alternatives considered
According to DAP Spec, RunInTerminal “is typically used to launch the debuggee in a terminal provided by the client.”
How to launch the debugee is an implementation detail.
Many debug adapters maintain a clear separation between the adapter and the debugger. Even when they share a single frontend, often they have a special “launcher” command or script that starts the target program with a debugger attacher, or starts the target program with the runtime debugging functionality enabled.
A typical launch request handling, that does not involve an integrated/external terminal, starts the target program with the launcher. Then the adapter communicates with the debugger or the runtime. When an integrated/external terminal is needed, those debug adapters just need to ask the editor to run the same launch command.
Here are some examples:
(DISCLAIMER: I gathered the following info by skimming through their source, so it’s possible that some details here may not be quite right. Let me know if need correction)
Debugpy
debugpy is a debugger that implements DAP. Their implementation is the primary inspiration of this proposal, so it shares much commonality. When debugpy server receives a launch request, it launches the debugee using the launcher programwhich connects back to the debug adapter at start (similar to the rendezvous port in this proposal) and adds DAP handler. And the debugpy server works as a proxy and delegates request handling to the handler started by the launcher.
When an integrated/external terminal is needed, the debug adapter sends the launch command to the editor instead of running it directly. A typical launch command sent with RunInTerminal looks like this:
To enable debugging with root privileges, the launcher will start with
sudo
.Reference: Debug Adapter code
The old python debug adapter ptvsd is also implemented in the same way. See the implementation https://github.com/microsoft/ptvsd/tree/master/src/ptvsd.
MIEngine (c++ debug adapter, open-source)
MIEngine is an open-sourced version of C++ debug adapter VS Code is using. The debug adapter and the debugger are separate, and the debug adapter launches the debugger and debugee (platform-dependent). Unlike python debug adapters that communicate mostly over a TCP socket with the launcher, it sets up pipes (or named pipes for Windows) for communication with the debugger when launching the debugger using RunInTerminal.
For debugging with root privileges, it launches the debugger with the sudo command.
Reference: RunInTerminalTransport.cs code
LLDB Debug Adapter (codelldb)
This is a debug adapter for LLDB, so the roles of debug adapter and the debugger are cleanly separated. It’s possible to run a LLDB command using RunInTerminal, but they took a different path. The debug adapter (codelldb) requests the editor to run the codelldb “in a terminal agent mode”. The terminal agent mode codelldb provides access to the tty. The adapter waits on a TCP socket waiting for the terminal agent mode codelldb dials in, provides the tty info, and connects the tty with LLDB/debugee’s stdio streams. This allows the debug adapter to manage the debugee.
Running LLDB as root is not directly supported. Users either have to disable the security feature, or use the remote debugging (start lldb-server in platform mode and connect to it).
Alternatives considered
Launching only the debugee in the terminal and attaching
We considered launching the target program and attach to the program using the PID information RunInTerminal response may include. microsoft/vscode#61640 (comment)
(+): Only the exact target program command appears in the terminal, so it may look cleaner.
(-): I don’t know an easy way to start a process in suspended mode. It may be possible to write a separate special launcher to achieve the job (maybe reuse some of Delve’s pkg/proc or service/debugger code), that needs work too.
(-): The pid field of RunInTerminal response may not be available. See microsoft/vscode#61640 (comment)
(-): Delve dap still needs changes to handle a new mode in its teardown logic - it looks like attach mode internally, but it is a launch and we need cleanup. This may not be too difficult though.
(-): Support for debugging with root privileges will be tricky. To attach to a program that runs as a root, either we need to run delve as a root or ask users to remove all security features.
Launching a tty agent like
codelldb
We considered writing our own terminal agent and connecting the Terminal’s tty with debugged program’s stdio streams.
I am not sure how much work is necessary to implement cross-platform solutions.
(+): Looks cleaner from the terminal. Log or error messages from Delve will not pollute the terminal.
(+): Favored by a delve dev and has a higher chance of getting the
RunInTerminal
implementation added from thedlv dap
.(-): Delve's
tty
flag will take less used code path, and we need a terminal agent that works in all target platforms.(-): Support for debugging with root privileges can't be implemented with this (codelldb doesn’t support directly).
Using VS Code terminal API
In VS Code, we can create our own terminal and start
dlv dap
from the terminal by sending the command using VS Code Terminal API before starting a debug session using DebugAdapterDescriptorFactory.createDebugAdapterDescriptor API.(+): Change in Delve is unnecessary.
(-): This is an editor specific solution, and other DAP clients have to implement their own hacks.
(-): We cannot take advantage of VS Code team’s engineering effort.
(-): With the Terminal API, we can send the command text and there is no feedback. We need a separate mechanism to detect when
dlv dap
server is actually up and running. Options:Prior proposals
Proposal 1 (2021/07): Support
RunInTerminal
request andconsole
mode fromdlv dap
Initially we proposed to implement the
RunInTerminal
support and the proxying logic insidedlv dap
. This would allowdlv dap
to work as a fully functioning debug adapter, and other editors can benefit from it.However, this adds complexity to Delve side. Given that
RunInTerminal
is not part of traditionaldebugger
's functionality, and supporting features beyond what's necessary for debugging (i.e. `RunInTerminal) is still questionable, this proposal is held off.The
dlv dap
command tightly integrates the debug-adapter-as-server functionality and the traditional debugger functionality. For the sake of simplicity, this document will use "Delve (server)” to refer to the debug-adapter-as-server functionality, and “Delve (debugger)” to refer to the debugger functionality.When receiving a launch request with the console property, Delve (server) will ask the editor to start the Delve (debugger) using the RunInTerminal request. Then the Editor will start the Delve (debugger) in the specified console. To know exactly when the Delve (debugger) is ready, Delve (adapter) will open a TCP port for rendezvous. Once the Delve (debugger) is ready, it will dial into the rendezvous port. Once they are connected, Delve (server) will forward the cached initialize request and launch request and work as a simple proxy between the Editor and the Delve (debugger).
env
attribute. In this proposal, Delve (debugger) runs in a separate terminal, we need to tell Delve (debugger) to use the same environment variables. RunInTerminal accepts ‘env’ parameter, but prepends ‘env’ in the command line. Sending the full list of environment variables as RunInTerminal ‘env’ parameter floods the terminal. Either we need to shrink the environment variable list or make Delve (debugger) recognize theenv
attribute.Beta Was this translation helpful? Give feedback.
All reactions