獲取 C++ WebAssembly 的 stacktrace

語言: CN / TW / HK

搜尋 C++ Stacktrace

一開始對 WebAssembly 一無所知。認為 C++ 和其他語言一樣應該能提供一個語言自帶的 stacktrace 吧。但是各種 libunwind 的庫在 emscripten 上怎麼使用,沒一篇文章說明。

The Pain of Debugging WebAssembly 根據這篇文章 At present, WASM does not support stack unwinding. 也就是說 native 平臺上那套主動發起 unwind 去拿 stacktrace 的做法在 wasm 上是行不通的。

-fwasm-exceptions

搜尋 emscripten 的文件,發現了 "-fwasm-exceptions"。但是並不明白這個開關實際做了什麼,就用上了。

實際上這個開關乾的事情是把 throw std::runtime_error 這樣的 throw 語法,編譯成了 throw opcode,是一個 WebAssembly VM 的擴充套件指令。然後 chrome 的 v8 就會根據這個 opcode,丟擲一個 WebAssembly.Exception。

但是問題是 WebAssembly.Exception.prototype.stack - WebAssembly | MDN 說明 Exceptions from WebAssembly code do not include a stack trace by default. If WebAssembly code needs to provide a stack trace, it must call a JavaScript function to create the exception

注意這裡的 must call。然而 llvm 直接就把 throw 編譯成 throw opcode 了,怎麼編譯成 call javascript function 來拋這個異常呢?沒找到辦法。結果就是 throw 出來的異常可以在 chrome 的 console 裡打出異常棧來,但是沒法用 error.stack 從 js 側獲取到,也就沒法上報給 sentry 了。

-fexceptions

實際 emscripten 的 -fexceptions 是最簡單的。它直接把 cxxabi 提供的 __cxa_throw 給偽造了 http:// github.com/emscripten-c ore/emscripten/blob/main/src/library_exceptions.js 。在 C++ 裡的 throw,實際上調到了 js 的函式裡。然後我們就可以很容易通過 --js-library 的方式來提供自己的 __cxa_throw 來從 js 丟擲 WebAssembly.RuntimeException,從而獲得異常棧了。

這麼做的代價就是沒法在 C++ 裡 catch 了。emscripten 預設的實現是把 ptr 這個整數直接 throw 了,這樣丟擲的不是 Error 物件,也就是無法獲得異常棧的。所以這個預設實現把我給矇蔽了,以為 v8 沒有獲得異常棧的能力。實際上仔細實驗一下就可以發現,如果在 C++ WASM 裡呼叫了一個 js 方法,然後 js 方法裡拋了異常,這個時候是可以看到異常棧的。也就是我們有的時候可以看到異常棧,有的時候看不到原因。