bytecodealliance / componentize-py

Apache License 2.0
157 stars 19 forks source link

Advisory: How may I debug a Python code of WASM component? #127

Open yophilav opened 5 days ago

yophilav commented 5 days ago

Hi componentize-py folks!

Thank you for a great presentation at WASMCON a while ago! I just now catching up with your nifty tool. What is your debugging guideline for a Python-turn-WASM component? I'm investigating a feasibility if somehow I could magically debug the python code of a WASM component.

I ran a quick experiement where I built a lib.wasm from Python code lib.py which export add() function that has an embedded breakpoint using componentize-py. The lib.wasm component is then loaded into a Rust host app using wasmtime. The goal here is to see if I could set a breakpoint for the python application code.

Dependency

- wasmtime 25.0.2
- componentize-py 0.14.0
- Python 3.8.10

main.rs

use wasmtime::*;

fn main() ->  wasmtime::Result<()> {
    let mut config = Config::new();
    config.debug_info(true);

    let engine = Engine::new(&config)?;

    let bytes = std::fs::read("<path/to/lib.wasm>")?;
    let component = wasmtime::component::Component::new(&engine, bytes)?;

    let linker = wasmtime::component::Linker::new(&engine);
    let mut store = Store::new(&engine, ());

    let instance = linker.instantiate(&mut store, &component)?;

    let add = instance.get_typed_func::<(i32, i32), (i32,)>(&mut store, "add")?;
    let result = add.call(&mut store, (20,22))?;
    println!("Result from add module: {:?}", result.0);

    Ok(())
}

lib.wit

package example:bearmath;
world bearmath {
  export add: func(l: s32, r: s32) -> s32;
}

lib.py

import bearmath
import pdb
class Bearmath(bearmath.Bearmath):
    def add(self, l, r) -> int:
        x = l + r

        # Attempt to programatically set a breakpoint / trace here.
        pdb.set_trace()

        return x

After all the files have been created, I then run componentize-py -d lib.wit -w bearmath componentize --stub-wasi lib -o lib.wasm to create lib.wasm. Then build and run the Rust program with cargo run. Unfortunately, it didn't work and crash with wasm trap: wasm 'unreachable' instruction executed. Even though, I can see with wasm-tools print --skeleton ./lib.wasm that there is add() export and the WASM file has debug symbol enabled.

So... I'm writing to you on what's your thought on the debugging story for a WASM component. I'm new to Python and this development space. Maybe the scenario is silly and a developer should debug their code in Python before turning into a WASM component. Anyhow, I really appreciate any input you may have on the topic! :smiley:

Thank you so much, Bear

dicej commented 4 days ago

Hi @yophilav. Thanks for opening this issue.

I'll admit I hadn't tried using pdb before (with or without Wasm); I'm also new to Python. However, I just tried it and it seems to work okay for me, mostly. Here's what I did (from inside a clone of this repo):

 $ mkdir test
 $ cd test
 $ cat >app.py <<EOF
from command import exports
import pdb

class Run(exports.Run):
    def run(self) -> None:
        print("Hello, world!")
        pdb.set_trace()
        print("and hello again")
EOF
 $ python3 -m venv .venv
 $ source .venv/bin/activate
(.venv)  $ pip install componentize-py
(.venv)  $ cargo install wasmtime-cli
(.venv)  $ curl -OL https://github.com/WebAssembly/wasi-cli/archive/refs/tags/v0.2.2.tar.gz
(.venv)  $ tar xf v0.2.2.tar.gz
(.venv)  $ componentize-py -d wasi-cli-0.2.2/wit -w wasi:cli/command@0.2.2 componentize app -o cli.wasm
(.venv)  $ wasmtime run cli.wasm
Hello, world!
> /0/app.py(8)run()
(Pdb) p Run
<class 'app.Run'>
(Pdb) p pdb
<module 'pdb' from '/python/pdb.py'>
(Pdb) exit
Traceback (most recent call last):
  File "/0/app.py", line 8, in run
  File "/python/bdb.py", line 90, in trace_dispatch
  File "/python/bdb.py", line 115, in dispatch_line
bdb.BdbQuit
thread '<unnamed>' panicked at runtime/src/lib.rs:499:25:
Python function threw an unexpected exception
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Error: failed to run main module `cli.wasm`

Caused by:
    0: failed to invoke `run` function
    1: error while executing at wasm backtrace:
           0: 0xb72d22 - libcomponentize_py_runtime.so!__rust_start_panic
           1: 0xb6c1a7 - libcomponentize_py_runtime.so!rust_panic
           2: 0xb6c17a - libcomponentize_py_runtime.so!std::panicking::rust_panic_with_hook::hdd590f21ce188187
           3: 0xb5e38a - libcomponentize_py_runtime.so!std::panicking::begin_panic_handler::_$u7b$$u7b$closure$u7d$$u7d$::hd832f8b925ef66b0
           4: 0xb5e2b2 - libcomponentize_py_runtime.so!std::sys::backtrace::__rust_end_short_backtrace::h7a76da6cd7a8845a
           5: 0xb6ba4c - libcomponentize_py_runtime.so!rust_begin_unwind
           6: 0xb9f8a5 - libcomponentize_py_runtime.so!core::panicking::panic_fmt::h84bfbbb850b16446
           7: 0xaed6c8 - libcomponentize_py_runtime.so!componentize-py#Dispatch
           8: 0x223b376 - libcomponentize_py_bindings.so!wasi:cli/run@0.2.2#run-export
    2: wasm trap: wasm `unreachable` instruction executed

As you can see, pdb worked until I exited, at which point it hit an unreachable. As indicated by the error message ("Python function threw an unexpected exception"), it looks like pdb threw an exception from the run function I exported in my app, which the componentize-py runtime didn't know how to convert to a result at the component boundary, so it did the only thing it could: it trapped. We can fix that by wrapping the body of run in try/except:

(.venv)  $ cat >app.py <<EOF
> from command import exports
import pdb

class Run(exports.Run):
    def run(self) -> None:
        try:
            print("Hello, world!")
            pdb.set_trace()
            print("and hello again")
        except:
            return
EOF
(.venv)  $ componentize-py -d wasi-cli-0.2.2/wit -w wasi:cli/command@0.2.2 componentize app -o cli.wasm
Component built successfully
(.venv)  $ wasmtime run cli.wasm
Hello, world!
> /0/app.py(9)run()
(Pdb) exit
(.venv)  $

Hope that helps!

dicej commented 4 days ago

BTW, your main.rs doesn't use wasmtime-wasi, so things like stdio won't work, meaning pdb won't have any way to provide an interactive debug session. So you'll want to use wasmtime-wasi there and make sure you're not using the --stub-wasi option to componentize-py when building your component.

yophilav commented 4 days ago

Thank you, that's very cool! Let me try to repoduce your result and see if I can get a debug session going through a host app.