foundry-rs / foundry-rs/foundry
feat(debugger): Debug Adapter Protocol
- Dominant language
- Rust
- Stars
- 10.6k
- Forks
- 2.6k
- Avg merge
- 18h 20m
- Merged PRs (30d)
- 510
Description
### Component
Forge
### Describe the feature you would like
Support [Microsoft's Debug Adapter Protocol (DAP)](https://microsoft.github.io/debug-adapter-protocol//), so that folks can use the debuggers built into VSCode / Neovim / Emacs / Helix / etc.
They handle all of the same UI stuff (e.g. [Disassembly View](https://devblogs.microsoft.com/cppblog/visual-studio-code-c-july-2021-update-disassembly-view-macro-expansion-and-windows-arm64-debugging/#disassembly-view), [memory hex view](https://code.visualstudio.com/updates/v1_64#_viewing-and-editing-binary-data)) and more, without having to leave your IDE (can [launch tests from the gutter](https://github.com/PraneshASP/vscode-foundry-test-runner), set breakpoints interactively in the code editor, etc). Actually every feature of the current debugger can be expressed in DAP, e.g. [`type SteppingGranularity = 'statement' | 'line' | 'instruction';`](https://microsoft.github.io/debug-adapter-protocol/specification#Types_SteppingGranularity).
Here's another one: https://github.com/robertaachenw/solidity-debugger/blob/main/VSCodeExt/src/mockDebug.ts
### Additional context
I mentioned this in a comment somewhere, but it could be good to track it as its own issue.
I could take a look when the debugger is in a more stable state. It probably involves exposing library bindings so that a headless executable could be built (but who knows, maybe communicating with a subprocess via stdin could be viable). Ideally it could serve over TCP (`vscode.DebugAdapterServer`) or stdio (`vscode.DebugAdapterExecutable`).
Here's an explanation of how it works: https://microsoft.github.io/debug-adapter-protocol//overview.html
A tutorial implementation: https://github.com/microsoft/vscode-mock-debug
A real implementation: https://github.com/microsoft/vscode-debugadapter-node/blob/main/adapter/src/runDebugAdapter.ts
Truffle's [VSCode extension seems to use DAP](https://github.com/trufflesuite/vscode-ext/blob/5fd3e5454776215a9296bdc3818e9f30116667d9/src/debugAdapter/configuration/debugAdapterDescriptorFactory.ts#L4-L11) with [these capabilities](https://github.com/trufflesuite/vscode-ext/blob/5fd3e5454776215a9296bdc3818e9f30116667d9/src/debugAdapter/debugSession.ts#L80-L89) (I'll try it in Neovim to make sure).
Not many DAP are implemented in Rust. But:
* [`debug_types`](https://github.com/hirosystems/debug_types) could probably be used to check the DAP implementation.
* [`dap` crate](https://lib.rs/crates/dap) goes a bit further and actually implements some of the boilerplate like parsing/sending Content-Length, serializing from/to BufReader/BufWriter, etc.
* [Here](https://github.com/pileghoff/retread/blob/main/Cargo.toml)'s a project using the above crate. It's a pretty simple setup, they just use a 3-state machine: https://github.com/pileghoff/retread/blob/main/src/app_state.rs . Each command handler is only a few lines.
### Further investigation
Based on the latest state ([`iFrostizz:franfran/debugger-args`](https://github.com/iFrostizz/foundry/tree/franfran/debugger-args)), it seems that:
* The debugger does not actually run the debuggee, but rather replays a trace. This is not an issue, as only 2/41 methods make use of dynamic execution: SetExecution and SetVariable. The plus side is that this makes `StepBack` supported.
* It looks like the implementation is actually not modular; there is no client-server or frontend/backend distinction, querying is coupled with rendering (also, it is hardcoded TUI, there is no option for stdio). This might make it a bit harder to support alternative interfaces to the debugger.
* Actually, coupled implementation seems like a lot more work IMO. You have to do all the UI stuff for like browsing the source code yourself, instead of just having headless state transition functions (functional core), which can be driven by a UI (imperative shell).
* [Source mappings](https://docs.soliditylang.org/en/v0.4.24/miscellaneous.html#source-mappings) are not passed to the [`DebuggerArgs`](https://github.com/iFrostizz/foundry/blob/b513de33a604ea0b6a46102d8fa1a4cf8b83aa67/crates/ui/src/debugger.rs#L9-L18). This means breakpoints in source code (DAP `SetBreakpoints`; instruction-level breakpoint is `SetInstructionBreakpoints`) won't work, nor inlay hints / hover of variable values (DAP `Variables`), nor scopes (DAP `Scopes`), nor source-based navigation (DAP `StepIn`, `StepOut`, `StepOver`).
EDIT: Solidity's support is less than I thought: https://github.com/ethereum/solidity/issues/13720 https://github.com/ethereum/solidity/issues/9461 . I had a quick look at at the AST JSON for some contracts and it seemed borderline usable but IDK. I'm going to try [Solidity Debugger Pro](https://github.com/robertaachenw/solidity-debugger) to see if it's actually workable.
Contributor guide
Assessment
This issue has not been assessed yet.