Emscripten教程之如何調試代碼(六)

翻譯:雲荒杯傾
本文是Emscripten-WebAssembly專欄系列文章之一,更多文章請查看專欄。
也能夠去做者的博客閱讀文章。
歡迎加入Wasm和emscripten技術交流羣,羣聊號碼:939206522。html

調試Emscripten代碼的主要優勢之一是,源代碼既能夠在本地平臺上進行調試,也可使用web瀏覽器日益強大的工具集——包括調試器、分析器和其餘工具。c++

Emscripten提供了許多幫助調試的功能和工具:git

  • 編譯器調試信息flags,容許您在已編譯的代碼中保存調試信息,甚至建立源映射,以便在瀏覽器中調試時能夠單步調試c++源代碼。
  • 調試模式,它產生調試日誌和存儲 編譯時產生的中間文件 進行分析。
  • 編譯器設置,使運行時檢查內存訪問和公共分配錯誤。
  • 還支持手動打印調試emscripten生成代碼,這在某些方面甚至比本地平臺工做效果更好。
  • 自動調試器,它會自動地使用LLVM的中間代碼寫到內存。

本文描述了由Emscripten提供的用於調試的主要工具和設置,以及如何調試一些Emscripten特有的問題。github

調試信息

默認下,若是是優化編譯,Emcc會刪除大部分調試信息。Optimisation級-01和以上刪除LLVM調試信息,也禁用了運行時斷言檢查。優化級別-02以上,代碼被壓縮編譯器改編,變得幾乎不可讀。web

emcc -g標誌可用於在編譯的輸出中保存調試信息。默認狀況下,此選項保護空白、函數名和變量名。segmentfault

你可使用五個級別中的一個來指定標記:-g0、-g一、-g二、-g3和-g4。每一個級別都在最後編譯,以在編譯後的輸出中逐步提供更多的調試信息。g3標誌提供與-g標誌相同級別的調試信息。瀏覽器

g4選項提供了最多的調試信息—--—它生成了源映射(source map),容許您在Firefox、Chrome或Safari瀏覽器的調試器中查看和調試C/C++源代碼。安全

note:
當你既用調試flag又用優化flag時,有些優化可能會被禁掉,好比,若是你使用-O3 -g4 編譯,爲了給你提供足夠多的調試信息,有一些-O3的優化就得禁用掉。

調試模式(EMCC_DEBUG)

EMCC_DEBUG環境變量能夠用來設置啓用/不啓用Emscripten的調試模式:函數

# Linux or Mac OS X
    EMCC_DEBUG=1 ./emcc tests/hello_world.cpp -o hello.html

    # Windows
    set EMCC_DEBUG=1
    emcc tests/hello_world.cpp -o hello.html
    set EMCC_DEBUG=0

使用EMCC_DEBUG = 1設置,emcc會產生調試輸出文件,併爲編譯器的各個編譯階段生成中間文件。
EMCC_DEBUG= 2還爲每趟JavaScript優化器遍歷(pass)生成中間文件。工具

調試日誌和中間文件輸出到TEMP_DIR/emscripten_temp,其中TEMP_DIR默認在/tmp(/tmp的位置在.emscripten配置文件定義)。

能夠對調試日誌進行分析,以對每一個步驟中所作的更改進行分析和檢查。

編譯器設置

Emscripten有許多能夠用於調試的編譯器設置。使用emcc -s選項選擇這些設置,他們將覆蓋任何優化標誌。例如:

./emcc -01 -s ASSERTIONS=1 tests/hello_world

最重要的設置是:

  • ASSERTIONS=1 用於爲內存分配錯誤啓用運行時檢查(例如,寫入比分配更多的內存)。它還定義了Emscripten如何處理程序流中的錯誤。能夠將值設置爲ASSERTIONS=2,以便運行額外的測試。
    不優化編譯時,ASSERTIONS=1是默認開啓的。對於優化編譯的代碼(-01和以上級別)它是關閉的。
  • SAFE_HEAP= 1增長了額外的內存訪問檢查,並將爲諸如非內聯化0(dereferencing 0)和內存對齊等問題提供清晰的錯誤。你也能夠設置SAFE_HEAP_LOG以打印SAFE_HEAP操做。
  • 經過STACK_OVERFLOW_CHECK =1 標記在堆棧的末尾添加一個運行時的令牌值,令牌值會在某些位置被檢查,以驗證用戶代碼是否意外地寫出了堆棧的末尾。雖然溢出Emscripten堆棧不是一個安全問題(JavaScript已經被沙箱化了),但寫出堆棧將會致使全局數據內存損壞和Emscripten堆中動態分配的內存碎片化,這使得應用程序以意想不到的方式失敗。值STACK_OVERFLOW_CHECK = 2啓用了更詳細的堆棧保護檢查,它以犧牲一些性能的代價提供更精確的callstack。若是ASSERTIONS= 1,STACK_OVERFLOW_CHECK默認值爲2,ASSERTIONS爲其餘值時STACK_OVERFLOW_CHECK默認不啓用。

src/settings.js中定義了許多其餘有用的調試設置。有關更多信息,請搜索「check」和「debug」關鍵字的文件。

emcc詳細輸出

用emcc -v選項編譯,將-v傳遞給LLVM,而後在工具鏈上運行Emscripten的內部完整性檢查。

verbose模式還能啓動Emscripten的調試模式(EMCC_DEBUG)以生成編譯器的各個階段的中間文件。

手動打印調試

您還能夠用printf()語句手工編寫源代碼,而後編譯並運行代碼來研究問題。

若是你對問題行有很好的瞭解,你能夠在JavaScript添加print(新的Error().stack)代碼,以獲得堆棧跟蹤。另外還有stackTrace(),它發出堆棧跟蹤,並嘗試使用c++的去除改編的函數名(若是你不想或者不須要讓c++ 函數名去除改編,你能夠調用jsStackTrace())。

調試打印輸出甚至能夠執行任意的JavaScript。例如:

function _addAndPrint($left, $right) {
            $left = $left | 0;
            $right = $right | 0;
            //---
            if ($left < $right) console.log('l<r at ' + stackTrace());
            //---
            _printAnInteger($left + $right | 0);
    }

禁止優化

有時候,編譯的時候,禁用LLVM優化(llvm-opts)或禁用JavaScript優化(js-opts)是頗有用的。

好比說,如下命令即容許調試信息又使用-O2優化(既llvm和js都優化),可是又明顯關閉了js的優化器。

./emcc -O2 --js-opts 0 -g4 tests/hello_world_loop.cpp

這樣就能產生相對於llvm優化的代碼來講更易調試的js代碼:

function _main() {
            var label = 0;
            var $puts=_puts(((8)|0)); //@line 4 "tests/hello_world.c"
            return 1; //@line 5 "tests/hello_world.c"
    }

Emscripten特有問題

內存對齊問題

Emscripten內存表示假定加載和存儲是對齊的。在未對齊的地址上執行正常的加載或存儲可能會失敗。

SAFE_HEAP能夠用來顯示內存對齊問題。

通常來講,最好避免不對齊的讀寫-----他們一般是因爲未定義的行爲致使的。然而,在某些狀況下,它們是不可避免的—----例如,若是要移植的代碼從一些預先存在的數據格式的打包結構(packed structure)中讀取int。

Emscripten支持未對齊的讀寫,但它們要慢得多,並且必須在絕對必要時使用。執行一個不對齊的讀或寫你能夠:

  • 手動讀取單個字節並從新構造所有值
  • 使用emscripten_align*類型,它定義了基本類型的不對齊版本(short,int,float,double)。這些類型的全部操做都是不徹底對齊的(在大多數狀況下使用1個variants,這意味着沒有任何對齊)。

函數指針問題

若是你的函數指針調用獲得一個abort(),那麼問題是在調用時,沒有在預期的函數指針表中找到這個函數指針。

note:
nullFunc是函數指針表中用於填充空索引的函數(b0和b1是優化編譯下它的別名)。指向無效索引的函數指針會調用這個函數,而後調用abort().

有幾個可能的緣由:

  • 您的代碼調用了一個從另外一個類型轉換來的函數指針(這是未定義的行爲,但它確實發生在真實的代碼中)。在優化的Emscripten輸出中,每一個函數指針類型都基於它的原始簽名,存儲在一個單獨的表中,所以您必須調用具備相同簽名的函數指針以得到正確的行爲(更多信息參見代碼可移植性部分中的函數指針問題)。
  • 您的代碼在空指針或者dereferencing 0上調用方法。這種bug能夠由任何類型的編碼錯誤引發,但表現爲函數指針錯誤,由於在運行時的預期表中沒法找到函數。

爲了調試這些問題:

  • 使用-Werror編譯。這就把警告變成了錯誤,這些錯誤可能有用,由於一些未定義行爲的狀況會顯示警告。
  • 使用-s ASSERTIONS=2,獲得一些 關於被調用的函數指針和它的類型 有用的信息。
  • 查看瀏覽器堆棧跟蹤,查看錯誤發生的地方以及應該調用哪一個函數。
  • 使用SAFE_HEAP=1和禁用函數指針別名(aliasing_function_pointer = 0)編譯。這使得錯誤類型的函數指針 不可能在不引發錯誤的狀況下 調用,換句話說就是這樣編譯會使 錯誤類型的函數指針調用 必定會報錯。調用命令:-s SAFE_HEAP=1 -s aliasing_function_pointer =0

aliasing_function_pointer = 0也頗有用,由於它確保調用錯誤表中的指針地址會致使明顯的錯誤。若是沒有這樣的設置,這樣的調用只執行地址上的任何函數,這將很難進行調試。

死循環

無限循環致使您的頁面掛起。在一段時間以後,瀏覽器將通知用戶該頁面被卡住並提供中止或關閉它的選擇。

若是您的代碼中有無限循環,那麼找到問題代碼的一個簡單方法就是使用JavaScript profiler。在Firefox profiler中,若是代碼進入無限循環,您將看到在profiler的末尾有一塊代碼重複執行相同的操做。

AutoDebugger

警告:
這個選項主要爲Emscripten核心開發者提供使用。

Emscripten代碼移植系列文章

Emscripten代碼移植主題系列文章是emscripten中文站點的一部份內容。
本文是第三個主題第二篇文章。
第一個主題介紹代碼可移植性與限制
第二個主題介紹Emscripten的運行時環境
第三個主題第一篇文章介紹鏈接C++和JavaScript
第三個主題第二篇文章介紹embind
第四個主題介紹文件和文件系統
第六個主題介紹Emscripten如何調試代碼

相關文章
相關標籤/搜索