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.

Ah ok, it even makes sense that the line numbers are missing because that information isn’t in the WASM (but it would be indeed provided by the DWARF or sourcemap info).

For my C development I cobbled together a debugging setup in VSCode which uses the DWARF-WASM debugging extension and looks and feels just like regular debugging, in the end it’s about glueing together the right extensions and creating a specific launch.json.

I describe that here for CMake + Emscripten, but maybe some of that info can be salvaged to build a similer setup for Zig (apart from CMake Tools, the involved VSCode extensions are not C specific):

1 Like

Cheers, but I really hope I don’t have to switch editors (-:

Turns out, it is easy! You need this untrustworthy looking extension https://chromewebstore.google.com/detail/cc++-devtools-support-dwa/pdcpmagijalfljmkmjngeonclgbbannb – linked from Debugging WebAssembly with modern tools  |  Blog  |  Chrome for Developers The “experimental extension” is built-in nowadays, no need for that. And with that extension, you get full source & line info & even stuff like breakpoints, when you compile in Debug mode. Nice!

Oh yeah, that might work too, IIRC I always had some trouble pointing the Chrome devtools to the sources directory with the Chrome extension. The VSCode extensions doesn’t require that step.