Previous: , Up: Compiling to WASM   [Contents][Index]


2.3 Low-level development tools

The (hoot repl) module provides a set of REPL commands to assist with inspecting and debugging WASM modules. As a matter of course, Hoot’s Scheme compiler should not cause low-level WASM runtime errors, but when it does, or when working with the WASM toolchain directly, these REPL tools may provide some assistance.

To install the REPL commands, simply import the module:

scheme@(guile-user)> ,use (hoot repl)

To see a list of all the WASM commands, run:

scheme@(guile-user)> ,help wasm

To demonstrate the debugging features, let’s create a trivial module with a buggy function:

scheme@(guile-user)> (define src
                       '(module
                         (func (export "main") (param $x i32) (result i32)
                               (i32.add (local.get $x)
                                        (unreachable)))))

When called, this function will hit the unreachable instruction and throw a runtime error. Let’s compile the WAT source, load it into the VM, and get a reference to the main function:

scheme@(guile-user)> ,use (wasm resolve) (wasm vm) (wasm wat)
scheme@(guile-user)> (define wasm (validate-wasm (resolve-wasm (wat->wasm src))))
scheme@(guile-user)> (define instance (instantiate-wasm wasm))
scheme@(guile-user)> (define main (wasm-instance-export-ref instance "main"))

To trap the WASM runtime error and open a WASM debugging REPL, the wasm-catch REPL command can be prefixed before an expression:

scheme@(guile-user)> ,wasm-catch (main 7)
ice-9/boot-9.scm:1674:22: In procedure raise-exception:
ERROR:
  1. &wasm-runtime-error:
      instruction: (unreachable)
      position: (func 0 1)
      instance: #<wasm-instance 140506559041920>
      stack: #<<wasm-stack> items: (7)>
      blocks: ((wasm-block))
      locals: #(7)
  2. &message: "WASM runtime error: unreachable"
  3. &irritants: ()

Entering WASM debug prompt. Type `,help wasm' for info or `,q' to continue.
scheme@(guile-user) [1]>

Once in a WASM debug context, many of the other REPL commands become usable. To highlight the instruction where execution has paused, use wasm-pos:

scheme@(guile-user) [1]> ,wasm-pos
(func 0 (param $x i32) (result i32)
  (local.get 0)
  <<< (unreachable) >>>
  (i32.add))

To print the contents of the values stack, use wasm-stack:

scheme@(guile-user) [1]> ,wasm-stack
Value stack:
  0:	7

To print the contents of the function locals, use wasm-locals:

scheme@(guile-user) [1]> ,wasm-locals
Locals:
  0:	7

To evaluate arbitary WASM instructions in the current context, either in an attempt to repair interpreter state or just for fun, use wasm-eval:

scheme@(guile-user) [1]> ,wasm-eval '(local.get 0)
scheme@(guile-user) [1]> ,wasm-stack
Value stack:
  0:	7
  1:	7

There are now two i32 values on the stack. If we were to proceed with execution, the next instruction, i32.add, should add them together and return a result of 14. To resume execution, use wasm-continue:

scheme@(guile-user) [1]> ,wasm-continue
$5 = 14

Evaluating arbitrary WASM commands in a debugging context is very helpful when trying to understand the nature of a bug, but bear in mind that cursed things may happen during the process as there is no validation applied. This goes especially for when you try to resume execution.

See Interpreter for detailed information on running WASM within Guile and Toolchain reference in general for working with WASM directly.

REPL Command: wasm-trace exp

Evaluate exp with verbose WASM tracing enabled. This will print out every instruction along with the state of the value stack and function locals at the time of evaluation.

REPL Command: wasm-freq exp

Evaluate exp and print out a table showing how many times each kind of WASM instruction was executed as well as a total instruction count.

REPL Command: wasm-catch exp

Catch and debug WASM runtime errors that are raised by evaluating exp.

The following commands are usable only in the context of a WASM debug REPL:

REPL Command: wasm-stack

Print the state of the WASM stack.

REPL Command: wasm-locals

Print the state of the WASM function locals.

REPL Command: wasm-pos

Print the current function disassembly and highlight the instruction where WASM execution has paused.

REPL Command: wasm-eval instr

Evaluate the WASM instruction instr in the current debug context. Use this when attempting to fix the state of the WASM stack or locals before attempting to resume with ,wasm-continue.

The following commands behave differently depending on if they are run within a WASM debug REPL or not.

REPL Command: wasm-dump [wasm]

Display information about wasm, or the current WASM instance when debugging.

REPL Command: wasm-continue

When in a debugger, exit and resume WASM execution. In the event that this is run after trapping a runtime error, your warranty is void and all bets are off! While it may be dangerous, this does allow one to manually fix the WASM interpreter state manually with ,wasm-eval and attempt to proceed, which can come in handy sometimes.

When not in a debugger, set the WASM execution mode to continue without interruption. In other words, deactive the instruction stepper if it is active.

REPL Command: wasm-step

When in a debugger, resume WASM execution but pause before the next instruction is evaluated.

When not in a debugger, set WASM execution to pause before each instruction is evaluated.


Previous: High-level development tools, Up: Compiling to WASM   [Contents][Index]