WASM stack traces/debugging support in browsers

Hey,

currently I have a project where I run some freestanding WASM blob in the browser. When it panics, firefox gives me nothing, but chromium gives me a stack trace of WASM addresses. I can use these with wasm-tools addr2line game.wasm 0xf00 to get the corresponding source location. This it quite annoying, but maybe nothing Zig can do about: apparently browsers only (want to) support mapping files, but LLVM only outputs DWARF information.

Did anyone find a nice solution to this? I didn’t find a DWARF → mapping tool, and also wasn’t able to create a custom panic function that gathers its stack trace, as freestandig WASM cannot trace its own stack (or something like that).

Hmm, this must be a Zig thing. When I build a C project via Emscripten Firefox actually gives me a slightly nicer callstack than Chrome. E.g FF:

Uncaught RuntimeError: unreachable executed
    tick http://127.0.0.1:8080/clear-sapp.js:1377
clear-sapp.wasm:5283:1
    init http://127.0.0.1:8080/clear-sapp.wasm:5283
    _sapp_call_init http://127.0.0.1:8080/clear-sapp.wasm:46871
    _sapp_frame http://127.0.0.1:8080/clear-sapp.wasm:43520
    _sapp_emsc_frame_animation_loop http://127.0.0.1:8080/clear-sapp.wasm:13243
    tick http://127.0.0.1:8080/clear-sapp.js:1377

Chrome:

Uncaught RuntimeError: unreachable
    at clear-sapp.wasm.init (clear-sapp.wasm:0x14a3)
    at clear-sapp.wasm._sapp_call_init (clear-sapp.wasm:0xb717)
    at clear-sapp.wasm._sapp_frame (clear-sapp.wasm:0xaa00)
    at clear-sapp.wasm._sapp_emsc_frame_animation_loop (clear-sapp.wasm:0x33bb)
    at tick (clear-sapp.js:1377:34)

I think the presence of DWARF or source-map information is irrelevant for the stack trace, because the function names are encoded right in the WASM (but this human-readable name seems to be optional). E.g. this is a wasm-dis part when compiled via Emscripten with -g:

 (func $init
  (unreachable)
 )
 (func $frame
  (local $0 i32)
  (local $1 f32)

… while in release mode functions have a number as name:

 (func $13
  (unreachable)
 )
 (func $14 (result i32)
  (global.get $global$0)
 )
 (func $15 (param $0 i32) (result i32)
  (global.set $global$0

Back to Zig: are you linking with Emscripten? Because when I simply add the “-g” flag to the emcc option I get a nice readable callstack in the browser:

clear.html:43 panic: Bla blub
printErr @ clear.html:43
clear.html:43 Cannot print stack trace: debug info unavailable for target
printErr @ clear.html:43
clear.html:47 onerror: Uncaught RuntimeError: unreachable
clear.wasm:0x13f5c Uncaught RuntimeError: unreachable
    at clear.wasm.process.abort (clear.wasm:0x13f5c)
    at clear.wasm.debug.defaultPanic (clear.wasm:0x1336b)
    at clear.wasm.clear.init (clear.wasm:0x72b53)
    at clear.wasm._sapp_call_init (clear.wasm:0x7c49)
    at clear.wasm._sapp_frame (clear.wasm:0x71dd)
    at clear.wasm._sapp_emsc_frame_animation_loop (clear.wasm:0x4de5)
    at tick (clear.js:5088:30)

(the function clear.wasm.clear.init is Zig code, everything starting with _sapp_ is C code)

I guess when those human readable function names are missing when using Zig as linker that this must be a missing feature in the Zig linker.

apparently browsers only (want to) support mapping files

WASM debugging usually happens via DWARF these days, not via source maps :slight_smile: But as I said above, that should be irrelevant for the missing names in the WASM stack trace.

1 Like

Ah, sorry, I wasn’t precise enough. I also get the function names, but miss out the line numbers, e.g.

Uncaught (in promise) RuntimeError: unreachable
    at orion_wasm.wasm.main.panic__struct_1740.panic (orion_wasm.wasm-004eb112:0x866)
    at orion_wasm.wasm.debug.FullPanic((function 'panic')).reachedUnreachable (orion_wasm.wasm-004eb112:0x2f47)
    at orion_wasm.wasm.debug.assert (orion_wasm.wasm-004eb112:0x2a6a)
    at orion_wasm.wasm.main.render (orion_wasm.wasm-004eb112:0x3c453)
    at orion_wasm.wasm.main.advance (orion_wasm.wasm-004eb112:0x387ff)
    at draw (client.html:273:25)

That’s what I meant with addr2line, with addr2line orion_wasm.wasm 0x3ea73 I can get the location of the function call, e.g.

client$ wasm-tools addr2line game.wasm 0x3c453
0x3c453: main.render […]/src/main.zig:2272:19

where indeed an assert is called. So you are right – the stack trace itself is not the problem, the automatic resolving is. And this information is AFAIU in DWARF/source mappings.

Also I would prefer to skip emscripten and use wasm-freestanding as a target. Mostly because on ReleaseSmall it’s still 25% smaller in my case. But if something work with wasm-emscripten, ofc I could just build two versions.