走近 WebAssembly 之調試大法
作者:周志鵬博客
前言
WebAssembly是什麼?
下面是來自官方的定義:
WebAssembly or wasm is a new portable, size- and load-time-efficient format suitable for compilation to the web.
關鍵詞:」format」,WebAssembly 是一種編碼格式,適合編譯到web上運行。
事實上,WebAssembly可以看做是對JavaScript的加強,彌補JavaScript在執行效率上的缺陷。
它是一個新的語言,它定義了一種AST,並可以用位元組碼的格式表示。
它是對瀏覽器的加強,瀏覽器能夠直接理解WebAssembly並將其轉化為機器碼。
它是一種目標語言,任何其他語言都可以編譯成WebAssembly在瀏覽器上運行。
想像一下,在計算機視覺,遊戲動畫,視頻編解碼,數據加密等需要需要高計算量的領域,如果想在瀏覽器上實現,並跨瀏覽器支持,唯一能做的就是用JavaScript來運行,這是一件吃力不討好的事情。而WebAssembly可以將現有的用C,C++編寫的庫直接編譯成WebAssembly運行到瀏覽器上, 並且可以作為庫被JavaScript引用。那就意味著我們可以將很多後端的工作轉移到前端,減輕伺服器的壓力。這是WebAssembly最為吸引人的特性。並且WebAssembly是運行於沙箱中,保證了其安全性。
更多關於WebAssembly基礎入門, 可以看下這篇文章:https://segmentfault.com/a/1190000012110615, 寫得很詳細。
(後文將主要使用名稱表示 WebAssembly)
怎麼調試?
稍微了解的人應該知道,在chrome或者firefox的開發者面板中可以很方便對js代碼加斷點、查看變數、單步執行等等,非常方便!
既然主要是運行在web瀏覽器上的(當然也可以在非web環境中運行,參見官方文檔描述:http://webassembly.org/docs/non-web/),主要的調試方式無非是使用開發者控制台了!
但問題在於上文中說了是一種二進位格式,雖然有可讀的文本格式,但是調試起來依然比較費勁,最主要的問題是:你是不是更想能夠調試被編譯之前的源代碼?
調試探索
搜了很多資料,走了不少彎路,總算是摸索出一條可行的調試之路!(當然如果你有更好的調試方法,請告訴我哦!)
環境&工具準備
wasm編譯環境 docker版 , 鏡像 zhouzhipeng/wasm-build
Firefox最新開發者版, 下載地址
文本編輯器
說明:如果你想定製自己的wasm編譯環境docker鏡像,強烈建議在ubuntu中參考官方文檔步驟搭建: http://webassembly.org/getting-started/developers-guide/
實驗代碼準備
github地址:https://github.com/zhouzhipeng/wasm-debug-test
編寫一個簡單的c程序,求兩個數的平方和
debug.c
intsumOfSquare(inta,intb){
intt1=a*a;
intt2=b*b;
returnt1+t2;
}
編譯debug.c —> debug.wasm
使用上節中的docker鏡像: zhouzhipeng/wasm-build
#1.先運行wasm編譯docker容器 (鏡像託管在docker官方hub,可能會比較慢,請耐心等待)
?wasm-debug-testgit:(master)?dockerrun-it--namewasm-test-v$(pwd):/data/zhouzhipeng/wasm-buildbash
#2.編譯debug.c為debug.wasm 文件
root@f4d3ee71bec8:/data# cd /data/
root@f4d3ee71bec8:/data# emcc debug.c -O1 -s WASM=1 -s SIDE_MODULE=1 -o debug.wasm
說明:關於命令細節,可以參考:http://kripken.github.io/emscripten-site/docs/compiling/WebAssembly.html
編寫測試頁面
說下大致邏輯:頁面載入時會載入文件並初始化,給頁面上的按鈕綁定click事件,點擊時調用上面中的函數。
debug.html
// 下面這些配置是作為wasm初始化用的,去掉某一個會報錯。
constimportObj={
env:{
memory:newWebAssembly.Memory({initial:256,maximum:256}),
memoryBase:,
tableBase:,
table:newWebAssembly.Table({initial:10,element:"anyfunc"}),
abort:function(){}
}
};
// 直接使用 WebAssembly.instantiateStream的方式會報錯,說是 debug.wasm 資源不是 application/wasm 格式s.
fetch("./debug.wasm").then(response=>
response.arrayBuffer()
).then(bytes=>WebAssembly.instantiate(bytes,importObj)).then(results=>{
instance=results.instance;
varsumOfSquare=instance.exports._sumOfSquare;//注意這裡導出的方法名前有下劃線!!
varbutton=document.getElementById("run");
button.addEventListener("click",function(){
varinput1=3;
varinput2=4;
alert("sumOfSquare("+input1+","+input2+")="+sumOfSquare(input1,input2));
},false);
});
運行查看效果
為了簡單起見,直接用在當前目錄臨時啟動一個http服務:
?wasm-debug-testgit:(master)?python-mSimpleHTTPServer8081
Serving HTTPon0.0.0.0port8081...
打開Firefox開發者版瀏覽器訪問:http://localhost:8081/debug.html
點擊click按鈕:
很好,一切運行正常。接下來,嘗試使用斷點調試,並查看局部變數等。
基本調試
進入debugger面板,找到如下文件(wasm的可視化文本格式,是不是跟彙編指令很像?!所以名字帶有assembly,哈哈)
並在對應代碼行處打個斷點:
好的,我們繼續,再次點一下「click」 按鈕,斷點會進來:
注意上圖紅框中的局部變數var0,var1 就是我們的input1和input2,
可以接著用單步執行(注意不是旁邊的step over 按鈕,是箭頭所示的step in !! 可能是bug):
對函數棧稍有了解的應該知道:上述指令如 get_local , i32.mul 等等會進行系列入棧、出棧操作,所以你看不到我們當時定義的臨時變數 t1,t2, 它操作的直接是棧頂的元素.
firefox看不到stack棧中的元素,下文進階調試中會用chrome瀏覽器演示下,感興趣的客官請繼續往下看!!
進階調試
尼瑪,說好的調試c/c++源代碼呢!!!!
需要調試c源碼,前面的emcc編譯命令需要加點參數,關聯一下 source map:
root@f4d3ee71bec8:/data# emcc debug.c -O1 -s WASM=1 -s SIDE_MODULE=1 -o debug.wasm -g4 --source-map-base http://localhost:8081/
root@f4d3ee71bec8:/data# ls
處理debug.wasm (二進位) 無法查看,其他的都可以看下:
root@f4d3ee71bec8:/data# cat debug.wast
(module
(type$FUNCSIG$vi(func(parami32)))
(import"env""table"(table2anyfunc))
(import"env""memoryBase"(global$memoryBasei32))
(import"env""tableBase"(global$tableBasei32))
(import"env""abort"(func$abort(parami32)))
(global$STACKTOP(muti32)(i32.const))
(global$STACK_MAX(muti32)(i32.const))
(global$fp$_sumOfSquare i32(i32.const1))
(elem(get_global$tableBase)$b0$_sumOfSquare)
(export"__post_instantiate"(func$__post_instantiate))
(export"_sumOfSquare"(func$_sumOfSquare))
(export"runPostSets"(func$runPostSets))
(export"fp$_sumOfSquare"(global$fp$_sumOfSquare))
(func$_sumOfSquare(;1;)(param$i32)(param$1i32)(resulti32)
;;@debug.c:2:
(set_local$
(i32.mul
(get_local$)
(get_local$)
)
)
....後面內容省略
{"version":3,"sources":["debug.c"],"names":[],"mappings":"mNACA,OACA,OACA"}
是不是有種恍然大明白的感覺! 跟調試混淆的js 的方式很像。
刷新瀏覽器,看一下:
多了一個debug.c ! 是的,說明我們的sourcemap 生效了, 順手在第二行打個斷點。
點擊click按鈕,瞅一瞅:
有點尷尬,我明明打的第二行,斷點卻進入了第三行。。。(開發版。)
更加美中不足的是,如上圖右上角紅框,變數居然也無法查看!! 有點憂傷,不過好在右下角的局部變數還能看個大概。
所以我的建議是:在debug.c 中打個斷點,然後進入debug.html:xxxx 中單步調試, 如下,此時是可以雙擊進入的,兩邊斷點狀態是同步的:
填坑
在【基本調試】章節留了一坑,說可以用chrome 看下運行時的stack 棧狀態,以下放一張截圖證明我沒有說謊 :
看到紅色箭頭處,有沒有想起來 get_local 0 ,其實就是把我們的input1 (3) 壓入棧了。可以單步一步步執行看下出棧/入棧效果.
另:chromd雖然也能調試wast,但是它把內容拆分成了好多小部分,不太方便調試。但是優勢在於比firefox開發版多了個stack 查看功能。 很實用!!
總結
運行wasm編譯環境的鏡像: zhouzhipeng/wasm-build
編譯命令:
本文演示源碼地址:https://github.com/zhouzhipeng/wasm-debug-test
官方工具推薦:
https://wasdk.github.io/WasmFiddle/
https://webassembly.studio/
參考文獻
http://webassemblycode.com/using-browsers-debug-webassembly/
https://segmentfault.com/a/1190000012110615
http://kripken.github.io/emscripten-site/docs/compiling/WebAssembly.html
覺得本文對你有幫助?請分享給更多人
關注「前端大全」,提升前端技能
※主流瀏覽器圖片反防盜鏈方法總結
※一篇文章教會你 Event loop——瀏覽器和 Node
TAG:前端大全 |