patractlabs / wasm

The Patract Labs WASM Kit
https://patractlabs.github.io/wasm/
MIT License
1 stars 0 forks source link

Trap Handling #1

Closed clearloop closed 3 years ago

clearloop commented 3 years ago

Traps

Find a solution to handle traps in wasm execution

Keywords

  • frame_info
  • look_up_frame_info
  • look_up_trap_info

Issues

  • WebAssembly/WASI/issues/159
  • WebAssembly/debugging/issues/5

Under some conditions, certain instructions may produce a trap, which immediately aborts execution. Traps cannot be handled by WebAssembly code, but are reported to the outside environment, where they typically can be caught.

#Traps concept in WebAssembly Overview

Administrative Instructions in WebAssembly Execution

In order to express the reduction of traps, calls, and control instructions, the syntax of instructions is extended to include the following administrative instructions:

instr  ::=  ...
        |   trap
        |   invoke funcaddr
        |   init_elem tabbleaddr u32 funcidx*
        |   init_data memaddr u32 byte*
        |   labeš¯‘› { instr* } instr* end
        |   frameš¯‘› { frame } instr* end

The trap instruction represents the occurrence of a trap. Traps are bubbled up through nested instruction sequences, ultimately reducing the entire program to a single š¯—¨š¯—‹š¯–ŗš¯—‰ instruction, signaling abrupt termination.

#Administrative Instructions

Runtime Result

A result is the outcome of a computation. It is either a sequence of values or a trap.

result  ::=  val*
             trap

#Trap as results in runtime

clearloop commented 3 years ago

Here is an example of handling or not

Rust Code

#[no_mangle]
pub fn _start() {
  panic!("Hello, world!");
}

Results

wasmtime

Error: failed to run main module `target/wasm32-unknown-unknown/debug/bt.wasm`

Caused by:
    0: failed to invoke command default
    1: wasm trap: unreachable
       wasm backtrace:
         0: 0x1cda - <unknown>!__rust_start_panic
         1: 0x1cce - <unknown>!rust_panic
         2: 0x1c9e - <unknown>!std::panicking::rust_panic_with_hook::hc5713da015ebaa19
         3:  0x26e - <unknown>!std::panicking::begin_panic::{{closure}}::h8e62ab0ea555186f
         4:  0xfba - <unknown>!std::sys_common::backtrace::__rust_end_short_backtrace::h34a944558df1326a
         5:  0x15c - <unknown>!std::panicking::begin_panic::h03c636dac2b8fb70
         6: 0x1a65 - <unknown>!_start

wasmer

error: failed to run `target/wasm32-unknown-unknown/debug/bt.wasm`
ā”‚   1: RuntimeError: unreachable
           at __rust_start_panic (bt.wasm[67]:0x1cda)
           at rust_panic (bt.wasm[66]:0x1cce)
           at std::panicking::rust_panic_with_hook::hc5713da015ebaa19 (bt.wasm[65]:0x1c9e)
           at std::panicking::begin_panic::{{closure}}::h8e62ab0ea555186f (bt.wasm[2]:0x26e)
           at std::sys_common::backtrace::__rust_end_short_backtrace::h34a944558df1326a (bt.wasm[36]:0xfba)
           at std::panicking::begin_panic::h03c636dac2b8fb70 (bt.wasm[0]:0x15c)
           at _start (bt.wasm[55]:0x1a65)
ā•°ā”€> 2: unreachable

wavm

Runtime exception: wavm.reachedUnreachable
Call stack:
  host!/Users/mercury/.wasmer/bin/wavm!unreachableTrap(WAVM::Runtime::ContextRuntimeData*)+41
  wasm!target/wasm32-unknown-unknown/debug/bt.wasm!__rust_start_panic+0
  wasm!target/wasm32-unknown-unknown/debug/bt.wasm!rust_panic+14
  wasm!target/wasm32-unknown-unknown/debug/bt.wasm!_ZN3std9panicking20rust_panic_with_hook17hc5713da015ebaa19E+109
  wasm!target/wasm32-unknown-unknown/debug/bt.wasm!_ZN3std9panicking11begin_panic28_$u7b$$u7b$closure$u7d$$u7d$17h8e62ab0ea555186fE+65
  wasm!target/wasm32-unknown-unknown/debug/bt.wasm!_ZN3std10sys_common9backtrace26__rust_end_short_backtrace17h34a944558df1326aE+41
  wasm!target/wasm32-unknown-unknown/debug/bt.wasm!_ZN3std9panicking11begin_panic17h03c636dac2b8fb70E+34
  wasm!target/wasm32-unknown-unknown/debug/bt.wasm!_start+13
  thnk!C to WASM thunk!()->()+0
  host!/Users/mercury/.wasmer/bin/wavm!WAVM::Platform::catchSignals(void (*)(void*), bool (*)(void*, WAVM::Platform::Signal, WAVM::Platform::CallStack&&), void*)+151
  host!/Users/mercury/.wasmer/bin/wavm!WAVM::Runtime::unwindSignalsAsExceptions(std::__1::function<void ()> const&)+87
  host!/Users/mercury/.wasmer/bin/wavm!WAVM::Runtime::invokeFunction(WAVM::Runtime::Context*, WAVM::Runtime::Function const*, WAVM::IR::FunctionType, WAVM::IR::UntaggedValue const*, WAVM::IR::UntaggedValue*)+317
  host!/Users/mercury/.wasmer/bin/wavm!State::execute(WAVM::IR::Module const&, WAVM::Runtime::Instance*)+1393
  host!/Users/mercury/.wasmer/bin/wavm!State::run(char**)+8474
  host!/Users/mercury/.wasmer/bin/wavm!std::__1::__function::__func<State::runAndCatchRuntimeExceptions(char**)::'lambda'(), std::__1::allocator<State::runAndCatchRuntimeExceptions(char**)::'lambda'()>, void ()>::operator()()+21
  <2 redundant frames omitted>
  host!/Users/mercury/.wasmer/bin/wavm!WAVM::Runtime::catchRuntimeExceptions(std::__1::function<void ()> const&, std::__1::function<void (WAVM::Runtime::Exception*)> const&)+18
  host!/Users/mercury/.wasmer/bin/wavm!State::runAndCatchRuntimeExceptions(char**)+104
  host!/Users/mercury/.wasmer/bin/wavm!main+1290

zsh: abort      wavm run --abi=emscripten --function=_start

wasm3

Error: [Fatal] repl_call: [trap] unreachable executed
Error: [trap] unreachable executed ()

wasm3 log config

wasmi

thread 'main' panicked at ': Trap(Trap { kind: Unreachable })', src/bin/instantiate.rs:87:14
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
clearloop commented 3 years ago

The panic trace is stored in the name section, which is listed in the appendix of WebAssembly Specification.

parity-wasm

parity-wasm provides a parse_names function for parsing name_section in struct Module

Corresponding custom section with proper header will convert to name sections If some of them will fail to be decoded, Err variant is returned with the list of (index, Error) tuples of failed sections.

https://github.com/paritytech/parity-wasm/blob/5b56959a1ef3b7841352fd59a5cfb1c474dc1ff0/src/elements/module.rs#L405-L434

This function can generate the target name section but need to be filtered.

wasmer

wasmer builds RuntimeErrorInner with TrapCode and program counter,

struct RuntimeErrorInner {
    /// The source error (this can be a custom user `Error` or a [`TrapCode`])
    source: RuntimeErrorSource,
    /// The reconstructed Wasm trace (from the native trace and the `GlobalFrameInfo`).
    wasm_trace: Vec<FrameInfo>,
    /// The native backtrace
    native_trace: Backtrace,
}

impl RuntimeErrorInner {
    fn from_trap(trap: Tap) -> Self {
        // ...
    }
}

enum Trap {
    // ...
    /// A trap raised from machine code generated from Wasm
    Wasm {
        /// The program counter in generated code where this trap happened.
        pc: usize,
        /// Native stack backtrace at the time the trap occurred
        backtrace: Backtrace,
        /// Optional trapcode associated to the signal that caused the trap
        signal_trap: Option<TrapCode>,
    }, 
}

wasmtime

For wasmtime, there is a FrameInfo generated along with FunctionInfo from program counter

/// Information about trap.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct TrapInformation {
    /// The offset of the trapping instruction in native code.
    /// It is relative to the beginning of the function.
    pub code_offset: binemit::CodeOffset,
    /// Code of the trap.
    pub trap_code: ir::TrapCode,
}

struct TrapInner {
    reason: TrapReason,
    wasm_trace: Vec<FrameInfo>,
    native_trace: Backtrace,
}

/// Creates a new `Trap`.
///
/// * `store` - this is optionally provided, if available. If `None` we'll
///   look up the last store, if available, used to call wasm code on the
///   stack.
///
/// * `trap_pc` - this is the precise program counter, if available, that
///   wasm trapped at. This is used when learning about the wasm stack trace
///   to ensure we assign the correct source to every frame.
///
/// * `reason` - this is the wasmtime-internal reason for why this trap is
///   being created.
///
/// * `native_trace` - this is a captured backtrace from when the trap
///   occurred, and this will iterate over the frames to find frames that
///   lie in wasm jit code.
fn new_with_trace(
    store: Option<&Store>,
    trap_pc: Option<usize>,
    reason: TrapReason,
    native_trace: Backtrace,
) -> Self {}
clearloop commented 3 years ago

So what is the program counter? How to get it?

/// The inner `u32` points to what?
struct Func(u32);

Function Name

let fn_name = module.function_names.get(&fn_index);
clearloop commented 3 years ago

Prints functions in Call Stack (wasmi)

[
    FuncRef(
        Internal { signature=Signature { params: [], return_type: None } },
    ),
    FuncRef(
        Internal { signature=Signature { params: [I32, I32, I32], return_type: None } },
    ),
    FuncRef(
        Internal { signature=Signature { params: [I32], return_type: None } },
    ),
    FuncRef(
        Internal { signature=Signature { params: [I32], return_type: None } },
    ),
    FuncRef(
        Internal { signature=Signature { params: [I32, I32, I32, I32], return_type: None } },
    ),
    FuncRef(
        Internal { signature=Signature { params: [I32, I32], return_type: None } },
    ),
]

Comparing to the ideal outputs

Error: failed to run main module `panic.wasm`

Caused by:
    0: failed to invoke command default
    1: wasm trap: unreachable
       wasm backtrace:
         0: 0x3a93 - <unknown>!__rust_start_panic
         1: 0x3a87 - <unknown>!rust_panic
         2: 0x3a57 - <unknown>!std::panicking::rust_panic_with_hook::h7ba07724d623fbd6
         3: 0x17eb - <unknown>!std::panicking::begin_panic::{{closure}}::haa116d4044ff002f
         4: 0x1cf6 - <unknown>!std::sys_common::backtrace::__rust_end_short_backtrace::h4273a622fa86868a
         5: 0x16d9 - <unknown>!std::panicking::begin_panic::hc8f5829adc10d800
         6: 0x1678 - <unknown>!_start

It's fine.

[
    "rust_panic",
    "_ZN3std9panicking20rust_panic_with_hook17hc5713da015ebaa19E",
    "_ZN3std9panicking11begin_panic28_$u7b$$u7b$closure$u7d$$u7d$17he51f9abd5f6c28d2E",
    "_ZN3std10sys_common9backtrace26__rust_end_short_backtrace17hc867af7cfda96dafE",
    "_ZN3std9panicking11begin_panic17h0a859a469eb06beaE",
    "_ZN5panic23panic_in_wasm_backtrace17ha67ca05cd299040fE",
    "_start",
]

Try to decode with https://github.com/alexcrichton/rustc-demangle

clearloop commented 3 years ago
[
    rust_panic,
    std::panicking::rust_panic_with_hook,
    std::panicking::begin_panic::{{closure}},
    std::sys_common::backtrace::__rust_end_short_backtrace,
    std::panicking::begin_panic,
    panic::panic_in_wasm_backtrace,
    _start,
]
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Trap(Trap { kind: Unreachable })', src/bin/instantiate.rs:84:6
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Done, patractlabs/wasmi@80bb84b.