Skip to content

Debugging

Debugging with GDB

To debug a WebAssembly module using GDB, ensure that your module is compiled with debugging information (e.g., using the -g flag during compilation). Additionally, the runtime itself must be built in debug mode, with make lind-debug, to enable effective debugging of both the runtime and the module. This allows GDB to access symbol information from both your program and the runtime.

Note: Current limitations in GDB support for WebAssembly include lack of instruction-level inspection. Commands like layout split and si (step instruction) may break the terminal. It’s recommended to use layout src for source-level debugging.

Note: GDB debugs the Rust runtime, not the C code running inside the WebAssembly module.


Running GDB with the runtime

Build the debug runtime, then start GDB against it. Which invocation you need depends on how the program was compiled.

For a dynamic build — what lind_compile produces by default:

make lind-debug
sudo gdb --args ./build/lind-boot --preload env=/lib/libc.cwasm --preload env=/lib/libm.cwasm malloc-test.cwasm

For a static build (lind_compile -s), the preloads are not needed:

make lind-debug
sudo gdb --args ./build/lind-boot malloc-test.cwasm

Explanation of arguments:

  • gdb --args: Passes arguments to the program through GDB.
  • sudo: The runtime chroots into lindfs, which requires root. scripts/bin/lind_run normally does this for you.
  • ./build/lind-boot: The runtime binary. make lind-debug installs the debug build here, at the same path as the release build.
  • --preload env=/lib/libc.cwasm --preload env=/lib/libm.cwasm: The shared lind-glibc and libm a dynamic build resolves its imports against. lind_run passes these for you; running lind-boot directly under GDB means passing them yourself.
  • malloc-test.cwasm: Your program, as a path inside lindfs (see Getting Started).

Run ./build/lind-boot --help for the full set of runtime options.


Example Debugging Session

  1. Start GDB
    Launch GDB with the runtime and your WebAssembly module (dynamic build shown, see above for the static variant):

    sudo gdb --args ./build/lind-boot --preload env=/lib/libc.cwasm --preload env=/lib/libm.cwasm malloc-test.cwasm
    

  2. Set Breakpoints
    In the GDB prompt, set breakpoints as needed:

    (gdb) break main
    

  3. Run the Program
    Start execution:

    (gdb) run
    

  4. Inspect and Debug
    Use GDB commands to step through and inspect your code:

    (gdb) next
    (gdb) print p
    (gdb) continue
    


Additional Resources


Other Debugging Techniques

Disabling Signals for Debugging

The signal-disable feature added in this PR allows lind-wasm to run binaries without inserting Wasmtime epoch signals, which is useful for debugging purposes. When this feature is enabled, the signal handler is not set, and any unexpected signals (e.g., timeouts or faults) will cause the program to crash directly in RawPOSIX, making issues easier to trace.

⚠️ Warning: This feature is intended for debugging only and should not be used in production environments.

To use this feature, compile lind-wasm with the signal-disable feature enabled. Here’s how to do it:

Building with the Feature:

From the root of the repository, navigate to src/wasmtime and build with the signal-disable feature:

cd src/wasmtime

# Build lind-wasm with the signal-disable feature
cargo build --features signal-disable

Debugging at WASM/WAT Level

Two host-defined functions, lind_debug_num() and lind_debug_str(), are imported into the compiled WASM binary to support debugging at the WASM/WAT level. These functions facilitate debugging at the WASM/WAT level, allowing for the inspection of stack values and memory contents in environments where traditional debuggers (like GDB) cannot easily attach or provide visibility.

Building with the Feature:

Build the project from the root of the repository with lind-debug:

make lind-debug

Usage:

  1. Decompile the WASM binary

Convert existing .wasm file to .wat format:

bash wasm2wat <filename.wasm> --enable-all -o <filename.wat>

  1. Add Debug Calls

Open the .wat file and locate the area to inspect. Since these functions return their input back to the stack, you must either use the returned value or drop it to maintain stack integrity.

Example: Debugging an Integer

;; Push a value or local onto the stack
local.get 0
;; Call the debugger (prints value to host stderr)
call $__lind_debug_num
;; Drop the returned value to keep the stack clean
drop

Example: Debugging a String

;; Push the memory offset (pointer) where the string starts
i32.const 1024
;; Call the debugger (prints value to host stderr)
call $__lind_debug_str
;; Drop the returned value to keep the stack clean
drop

⚠️ Warning: Use the offset of the pre-defined string in the binary. Defining a new string at an uncalculated offset might result in segmentation fault.

  1. Recompile to WASM

After inserting debug calls, convert the file back to a binary:

wat2wasm <filename.wat> --enable-threads -o <filename.wasm>