|
| 1 | +# Contributing |
| 2 | + |
| 3 | +Thanks for your interest in improving the Soroban Debugger! This document covers |
| 4 | +how to get a development environment running and the conventions we follow. |
| 5 | + |
| 6 | +## Development setup |
| 7 | + |
| 8 | +The quickest path is the included **devcontainer**, which installs the full |
| 9 | +toolchain — Rust with the wasm targets, the Stellar CLI, and `komet-node` (via |
| 10 | +`kup install komet-node`, which also pulls the K toolchain and prebuilt |
| 11 | +semantics). Open the repo in VSCode and choose *Reopen in Container*, or use the |
| 12 | +GitHub Codespaces button. See [`.devcontainer/Dockerfile`](.devcontainer/Dockerfile). |
| 13 | + |
| 14 | +To set things up by hand instead, you need: |
| 15 | + |
| 16 | +- **Node.js** ≥ 22 |
| 17 | +- For the live pipeline only: a Rust toolchain with the `wasm32v1-none` (or |
| 18 | + `wasm32-unknown-unknown`) target, the [Stellar CLI](https://developers.stellar.org/docs/tools/cli), |
| 19 | + and [`komet-node`](https://github.com/runtimeverification/komet-node). |
| 20 | + |
| 21 | +Then: |
| 22 | + |
| 23 | +```bash |
| 24 | +npm install |
| 25 | +``` |
| 26 | + |
| 27 | +## Everyday commands |
| 28 | + |
| 29 | +```bash |
| 30 | +npm run build # bundle to dist/extension.js (esbuild) |
| 31 | +npm run watch # rebuild on change |
| 32 | +npm run check-types # tsc --noEmit |
| 33 | +npm run lint # eslint |
| 34 | +npm test # tsc -p tsconfig.test.json, then mocha (~2 min) |
| 35 | +``` |
| 36 | + |
| 37 | +Press **F5** (*Run Extension*) to open an Extension Development Host with the |
| 38 | +extension loaded and the [`examples/`](examples/) workspace open. Pick a |
| 39 | +configuration from the Run and Debug view — the **Replay … with symbols** |
| 40 | +configs need no toolchain at all. |
| 41 | + |
| 42 | +## Testing conventions |
| 43 | + |
| 44 | +- **Write tests first.** New behavior should arrive with a failing test that |
| 45 | + describes it, then the implementation that makes it pass. The suite is the |
| 46 | + contract; keep it green (`npm test`) before opening a PR. |
| 47 | +- Replay logic is deliberately free of the `vscode` API so it can be unit-tested |
| 48 | + in plain Node. Keep `vscode`-only code in `extension.ts`. |
| 49 | +- Tests run automatically in [CI](.github/workflows/ci.yml) on every push and |
| 50 | + pull request (Node 22): type-check, lint, build, and test. |
| 51 | + |
| 52 | +### Regenerating fixtures |
| 53 | + |
| 54 | +The DWARF/trace fixtures under `test/fixtures/` are real build + trace outputs |
| 55 | +and must stay matched (the wasm's DWARF and the trace's positions are checked |
| 56 | +against each other). Regenerate them **as a pair**: |
| 57 | + |
| 58 | +```bash |
| 59 | +scripts/make-fixtures.sh # rebuild the debug wasms + capture matching traces |
| 60 | +node scripts/verify-addresses.mjs # re-derive the address-space ground truth vs a live komet-node |
| 61 | +``` |
| 62 | + |
| 63 | +These need the full toolchain (Rust + Stellar CLI + komet-node). |
| 64 | + |
| 65 | +## How it works |
| 66 | + |
| 67 | +The debug adapter is a **trace-replay cursor machine**. [komet-node](https://github.com/runtimeverification/komet-node) |
| 68 | +executes a whole transaction and returns the *entire* execution trace — one |
| 69 | +record per WebAssembly instruction — and the adapter loads that into an |
| 70 | +in-memory model and services every DAP stepping request by moving a cursor. |
| 71 | +Because the whole recording is in memory, stepping *backward* is just as cheap |
| 72 | +as stepping forward. The adapter runs in-process in the extension host |
| 73 | +(`DebugAdapterInlineImplementation`). |
| 74 | + |
| 75 | +- **The build injects debug info without touching your `Cargo.toml`.** It sets |
| 76 | + `CARGO_PROFILE_RELEASE_DEBUG=true` / `CARGO_PROFILE_RELEASE_STRIP=none` for |
| 77 | + `stellar contract build`, so the wasm carries DWARF. The **pristine linker |
| 78 | + output** (`target/…/release/deps/*.wasm`) is what gets uploaded, because the |
| 79 | + Stellar CLI's metadata-injection step rewrites the wasm and strips the DWARF |
| 80 | + line programs. |
| 81 | +- **DWARF → Rust.** An in-repo DWARF v4/v5 line-table parser (`src/dwarf/`) maps |
| 82 | + wasm code offsets to Rust `file:line`. Breakpoints set in Rust source verify |
| 83 | + against the executed trace (sliding forward to the nearest executed line). |
| 84 | +- **No-DWARF fallback.** A prebuilt wasm without debug info — or a `rawTrace` |
| 85 | + replay without `wasmPath` — degrades gracefully to disassembly-only debugging: |
| 86 | + frames carry an instruction pointer but no source. |
| 87 | +- **Positions are validated.** komet-node's `pos` is relative to the section |
| 88 | + being executed (e.g. the code section for function code, the globals section |
| 89 | + for global initializers), so every record is cross-checked against the static |
| 90 | + disassembly and only trusted when the mnemonics agree. komet-node's tracer |
| 91 | + stops at instructions it cannot decode (printing them as `unknown`, e.g. |
| 92 | + `if`), so a trace can be a prefix of the full execution. |
| 93 | + |
| 94 | +### Architecture |
| 95 | + |
| 96 | +``` |
| 97 | +extension.ts VSCode glue: config provider + inline adapter factory |
| 98 | +debugAdapter/ |
| 99 | + SorobanDebugSession DAP handlers (cursor moves + StoppedEvents, disassembly) |
| 100 | + TraceModel records, cursor, call-depth, line + instruction stepping |
| 101 | + artifacts.ts wasm bytes -> { mapper, disassembly, validated positions } |
| 102 | + backends/ |
| 103 | + RawTraceBackend replay a JSONL trace file (+ optional wasmPath for symbols) |
| 104 | + LiveBackend turnkey build + spawn + deploy + trace |
| 105 | +komet/ |
| 106 | + trace.ts JSONL -> TraceRecord[] (K-style mnemonics, section-relative pos) |
| 107 | + mnemonics.ts K-style instr arrays -> wasm mnemonics ('i64.const 255') |
| 108 | + KometClient.ts JSON-RPC client (getHealth/sendTransaction/traceTransaction/...) |
| 109 | +soroban/scval.ts launch args -> ScVals (@stellar/stellar-sdk) |
| 110 | +wasm/ |
| 111 | + sections.ts wasm section walker (offsets, custom-section lookup) |
| 112 | + Disassembly.ts static disassembly (wasmparser), code-offset addressed |
| 113 | +dwarf/ DWARF v4/v5 .debug_line/.debug_info parser -> LineTable |
| 114 | +sourcemap/ |
| 115 | + SourceMapper the mapping seam the adapter talks to |
| 116 | + DwarfSourceMapper trace index / code offset -> Rust file:line (+ breakpoints) |
| 117 | + NullSourceMapper no-DWARF fallback (disassembly-only) |
| 118 | +``` |
| 119 | + |
| 120 | +All replay logic is free of the `vscode` API, so it can be unit-tested in plain |
| 121 | +Node; the `vscode`-only glue lives in `extension.ts`. For a deep dive on the |
| 122 | +stepping model, see [`docs/stepping.md`](docs/stepping.md). |
| 123 | + |
| 124 | +## Pull requests |
| 125 | + |
| 126 | +- Branch off `main` and keep PRs focused on a single change. |
| 127 | +- Make sure `npm run check-types`, `npm run lint`, and `npm test` all pass. |
| 128 | +- Write clear commit messages that explain the *why*, not just the *what*. |
| 129 | +- Update the [CHANGELOG](CHANGELOG.md) under *Unreleased* for user-facing changes. |
| 130 | + |
| 131 | +## Reporting bugs |
| 132 | + |
| 133 | +Because the debugger replays a captured trace, a JSONL trace file is often the |
| 134 | +most useful thing to attach to a bug report — it reproduces a session with no |
| 135 | +toolchain or node required (`rawTrace` in a launch config). See the issue |
| 136 | +templates when you open an issue. |
0 commit comments