dfinity / motoko

Simple high-level language for writing Internet Computer canisters
Apache License 2.0
517 stars 97 forks source link

Step-through debugging #1244

Open ggreif opened 4 years ago

ggreif commented 4 years ago

This is in-progress. Can read, but there are still infelicities. A savvy nix user (from within DFINITY) will be able to debug Motoko using the information bits herein. (On Linux the experience will be more smooth, because wasmtime is more reliable there than on macOS.)

Getting wasmtime on track with DWARF-5

See https://github.com/bytecodealliance/wasmtime/issues/932.

We (language team or execution team) could contribute patches.

Location expressions (PR merged, part of v0.20.0)

There is an entire sublanguage in DWARF dealing with encoding the location of information such that a debugger can find it. wasmtime translates these expressions from the DWARF5 side to the target architecture's conventions, but currently lacks support for many operations (DW_OP_*), most notably for branching. https://github.com/bytecodealliance/wasmtime/pull/2143 proposes to fix fixes this.

Debugging adapter for Motoko

This turned out to be easier than expected. The VS-Code debugger works nicely with wasmtime debug info JIT under LLDB. One has to set wasmtime as the program and ["-g", "<wasm-file>"] as the arguments. Then the breakpoints can be set and various features are present when running the program.

A small example to be tested in VSCode's debug environment (DAP) Author as `$ cat fak.c`: ``` c++ #include unsigned fak(unsigned n, unsigned acc) { if (!n) return acc; return fak(n - 1, acc * n); } int main(void) { printf("Hello fak(10) = %u\n", fak(10, 1)); return 0; } ``` Compile as: `clang -g fak.c -o fak` Then debug as: ``` shell $ lldb fak (lldb) b main Breakpoint 1: where = fak`main + 4 at fak.c:9:3, address = 0x0000000100000eb4 (lldb) run ```

Stand-alone linking

This is about implementing enough of the system interface to be able to ingest (predefined) Candid IDL messages into a stand-alone Wasm binary.

moc -wasi-system-api is a first step, and will allow to test a lot of the architecture, but we want a bit more in the middle term.

What we need is a shim library that implements the IC system interface (either on top of WASI or node) and thus enable running canisters outside of their natural habitat under a DWARF-assisted debugger (lldb or maybe Chrome) with a certain level of fidelity.

Download of message histories

Conversely we'll need a mode of the IC that allows to pull out messages of actual transactions for debugging purposes. This is a very sensitive issue, and needs careful consideration, but is essential for a realistic (and useful) debugging experience.

When multi-party message histories are not needed, a watered-down alternative could be to just record the client (JS-side) message dialog with the IC. This should be easy, considering that all messages already pass through the Candid library.

Compiling/linking in the message history

moc -local-system-api would create a standalone Wasm binary with the message history (and strace-like external trace) compiled in. A user would just run this as lldb -- wasmtime -g <app> to debug.

Add DWARF-5 features to Motoko

First step is #1163. PRs split out from this to facilitate review:

With https://github.com/bytecodealliance/wasmtime/pull/1410 we can load (carefully crafted) DWARF-5 into wasmtime. Other issues (e.g. in gimli) are linked from there.

Packaging the beast

Finally I have something working, both on Mac and Linux we can have an IDE inside of nix-shell that

For completeness, here is my current configuration:

`settings.json`
{
    "lldb.library": "/nix/store/xrb5fcc9mc70prs73wp9znkj01pzsf6p-lldb-10.0.1/lib/liblldb.dylib",
    "debug.allowBreakpointsEverywhere": true,
    "lldb.launch.sourceLanguages": [
        "cpp",
        "rust",
        "motoko"
    ],
    "lldb.launch.sourceMap": {
        "." : "${workspaceFolder}"
    },
    "lldb.launch.initCommands": [
        "set set plugin.jit-loader.gdb.enable on",
        "set show plugin.jit-loader.gdb.enable"
    ],
    "update.mode": "none",
    "update.showReleaseNotes": false,
    "extensions.autoCheckUpdates": false,
    "extensions.autoUpdate": false,
    "workbench.settings.enableNaturalLanguageSearch": false,
    "workbench.enableExperiments": false
}
`.vscode/launch.json`
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug",
            "program": "/nix/store/hgkfqvqr6r9cl2j5pxsfizv426kdsvlp-wasmtime-0.20.0/bin/wasmtime",
            "args": ["-g", "sumsX.mo.wasm"],
            "cwd": "${workspaceFolder}"
        }
    ]
}
`.vscode/tasks.json`
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Build Motoko",
            "type": "shell",
            "command": "/Users/ggreif/motoko/src/moc",
            "args": ["-g", "-o", "sumsX.mo.wasm", "sumsX.mo", "-wasi-system-api"],
            "options": {"env": {"MOC_RTS": "/Users/ggreif/motoko/rts/mo-rts.wasm"}},
            "group": "build"
        }
    ]
}

Packaging up more components

The way forward could be integrating these components into a polished Motoko IDE:


Local replica debugging

Another interesting alternative is to attach to the running local replica (dfx-like environment), now that it uses wasmtime. Several prerequisites are required to make this work:

I assume that the replica utilises wasmtime as a library (as opposed to as a process), so lldb must be pointed to the running replica. Also, the replica must still provide the DWARF JIT functionality.

Emerging alternatives

There is a lot going on in the WebAssembly debugging space that is promising to offer a less convoluted way of debugging canisters. Below listed are two projects that I am aware of.

wasminspect

There is a new debugging variant on the horizon, in the form of https://github.com/kateinoigakukun/wasminspect. This is a bytecode interpreter-based debugger with a similar command structure to lldb, thus potentially usable in an IDE plugin environment. However initial evaluation has shown that the project is still very immature and needs a lot of love to even enter in consideration. Issues and PRs are filed, the maintainer appears to be semi-responsive.

wasmrun

Ömer's Wasm project is also progressing.

Speculative outlook

With a canister-forking technology it could become feasible to add live debugging to the IC. A forked canister (system under test) needs to be wrapped into a debugging context, and making the CLI debug commands accessible via Candid. When debugging is concluded the wrapped fork could be deleted like any other canister.

This debug method could be essential as a last-resort solution for problems unique to live IC canisters, where local debugging fails to reproduce. (It could be run in a local testate too.)

kritzcreek commented 4 years ago

Gabor and I just managed to step through the Rust equivalent of the fak.c program inside VSCode, through lldb, via wasmtime. If we can manage to emit debug information like Rust this should give us a promising way forward.

osa1 commented 4 years ago

Regarding "Stand-alone linking", what's the use case exactly?

Implementing a "mock" system API in C or Rust and generating stand-alone wasm binaries would be very helpful for debugging the compiler and RTS issues, but I don't see how that would be useful for users. Could you elaborate more on this? Do we want to allow users to debug their canisters without interacting with the IC or drun?

ggreif commented 4 years ago

@osa When I say user I mean developers in charge of maintaining canisters. I don't think end-users of the IC (as in "I just watched a few CanCan videos, here are the links, awww") should get debug privileges (like downloading message histories and syscall traces). The use-case I envision is:

  1. some end-user complains about some strange behaviour on social media (twitter, GitHub)
  2. developer tries find bug by code-reading and fails
  3. developer downloads canister history (for some time period)
  4. builds a local Wasm executable with history linked in
  5. run executable under lldb, debug, debug...
  6. bug found, eureka!
  7. bug fixed
  8. canister upgrade pushed into IC
  9. happy end-users 😄

It basically boils down to rapid turnaround cycles. That said, I expect that developers can use plain -wasi-system-api to initially debug their algorithms. So this feature will come to play only when advanced debugging is needed. It is also predicated on the execution team's support for downloads.