delve dap: supporting "console" property and debugging with root privileges #1626
Replies: 5 comments 24 replies
-
Beta Was this translation helpful? Give feedback.
-
|
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.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
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-goextension integratesdlv dapby implementing an inlined debug adapter that spawnsdlv dap, connects to thedlv dapserver through a TCP port, convertsdlv dap's stdout/stderr intoOutputEvent, and proxies all the DAP communication between VS Code anddlv dap.By default
dlvruns the program to debug as a foreground process, so the debugged program can access to the terminalttyifdlv dapruns from a regular terminal. However, vscode-go's thin adapter runs without tty sodlvand the debugged program do not havettyin the current setup.We propose to extend the inlined debug adapter to support the
consoleproperty by delegating the job of startingdlvto 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-gothin adapter).note: at this point, the debug adapter already knows the value of
consoleproperty for the session.initializerequest with the editor's capabilities (e.g.supportsRunInTerminal).runInTerminalcommand (dlv dap --client-addr=single_use_server_addr). IfasRootproperty is specified, the adapter will request to runsudo dlv dapcommand.dlv dapfrom the specified terminal.dlv dapcommand connects to the single use TCP server and the connection betweendlv dapand 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 dapfrom 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
RunInTerminalimplementation because:RunInTerminalsupports external terminal mode, too. We don't have to worry about platform-specific implementation to support external terminal mode.RunInTerminalimplementation is maintained by VS Code team, and proven to work for many other debug extensions already.Violation of DAP spec
Strictly speaking, sending a
RunInTerminalrequest beforeinitializeresponse is incorrect. According to DAP spec:One option to correctly using
RunInTerminalwill look like:initializeresponse specifying minimal capabilities, stash the originalinitializerequest, and complete the initialize message handshake.runInTerminal.dlv dapstarts and connection is established.initializerequest todlv dap.initializeresponse fromdlv dap, compute the difference in capabilities and send ancapabilitiesevent to VS Code.We see VS Code accept
RunInTerminalarriving beforeinitializeresponse 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
RunInTerminalimplementation as part of VS Code's debug API. That will even allow us to set up the connection withdlv dapbefore VS Code sends theinitializerequest. See microsoft/vscode#136523Implementation
go-delve/delve#2568: add
--client-addrflag to run dap with a predefined clientvscode-go console_mode
--client-addrwhen console=integrated or externaldlv-dapafter receivinginitializerequest (previously, we started before receivinginitializerequest)runInTerminalrequestvscode-go supports
asRootpropertycosmetic changes
envcomputation to avoid flooding the terminal with many env variablesTODO
dlv daphandleenvproperty so environment variables are just passed throughlaunchconfiguration instead ofrunInTerminal.dlv dapsendlogmessages asOutputEventto avoid polluting the terminal.Caveats
debugserverfor 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
codelldbWe 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
RunInTerminalimplementation added from thedlv dap.(-): Delve's
ttyflag 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 dapfrom 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 dapserver is actually up and running. Options:Prior proposals
Proposal 1 (2021/07): Support
RunInTerminalrequest andconsolemode fromdlv dapInitially we proposed to implement the
RunInTerminalsupport and the proxying logic insidedlv dap. This would allowdlv dapto work as a fully functioning debug adapter, and other editors can benefit from it.However, this adds complexity to Delve side. Given that
RunInTerminalis 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 dapcommand 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).
envattribute. 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 theenvattribute.Beta Was this translation helpful? Give feedback.
All reactions