使用新型工具對 WebAssembly 進行偵錯

Ingvar Stepanyan
Ingvar Stepanyan

目前的道路

一年前,Chrome 宣布初步支援 這項工具針對 Chrome 開發人員工具中的 原生 WebAssembly 偵錯

我們展示了基本的步伐支援,並探討一些商機 使用 DWARF 資訊而非 來源對應日後開放使用:

  • 解析變數名稱
  • 美化排版類型
  • 評估原始語言中的運算式
  • ...還有更多!

今天我們很高興向大家展示大家承諾使用的功能 Emscripten 和 Chrome 開發人員工具團隊的成長 尤其是 C 和 C++ 應用程式

正式開始前,別忘了這目前是 Beta 版 只有透過最新版所有工具 您必須自行承擔風險。如果遇到任何問題,請回報給 https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350.

讓我們先從上次相同的簡單的 C 範例開始:

#include <stdlib.h>

void assert_less(int x, int y) {
  if (x >= y) {
    abort();
  }
}

int main() {
  assert_less(10, 20);
  assert_less(30, 20);
}

如要編譯,我們會使用最新的 Emscripten,並傳遞 -g 旗標,就像在原始文章中一樣,納入偵錯資訊:

emcc -g temp.c -o temp.html

現在,我們就能從 localhost HTTP 伺服器提供產生的網頁 (適用於 例如 serve) 請使用最新的 Chrome Canary 開啟應用程式。

這次,我們需要與 Chrome 整合的輔助擴充功能。 並協助解讀所有偵錯資訊 WebAssembly 檔案中編碼請前往以下網頁進行安裝: 連結:goo.gle/wasm-debugging-extension

您也應該在 DevTools 的「Experiments」中啟用 WebAssembly 偵錯功能。開啟 Chrome 開發人員工具,按一下位於 依序點選「開發人員工具」窗格右上角的「實驗」面板 然後勾選「WebAssembly Debugging:啟用 DWARF 支援」

開發人員工具設定的「實驗」窗格

關閉「設定」後,開發人員工具會建議重新載入,以便套用設定,因此我們就這麼做吧。一次性 設定。

接下來,我們可以返回「來源」面板,啟用「遇到例外狀況時暫停」(⏸ 圖示),然後勾選「遇到已偵測到的例外狀況時暫停」,並重新載入網頁。您應該會看到開發人員工具在例外狀況時暫停:

「Sources」面板的螢幕截圖,顯示如何啟用「Pause on caught exceptions」

根據預設,它會停止在由 Emscripten 產生的黏合程式碼上,但會停留在 右圖顯示了「Call Stack」檢視畫面,代表 並可以前往先前叫用的 C 行 abort:

開發人員工具在 `assert_less` 函式中暫停,並在「範圍」檢視畫面中顯示 `x` 和 `y` 的值

現在,您查看「範圍」檢視畫面會顯示原始名稱 以及 C/C++ 程式碼中變數的值,您不再需要 瞭解 $localN 這類的破壞性名稱是什麼意思,以及它們與 所編寫的原始碼

這不僅適用於整數等原始值,也適用於複合值 例如結構、類別、陣列等!

豐富類型支援

一起來看看較複雜的範例。這次我們將使用以下 C++ 程式碼繪製 Mandelbrot 分形

#include <SDL2/SDL.h>
#include <complex>

int main() {
  // Init SDL.
  int width = 600, height = 600;
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* window;
  SDL_Renderer* renderer;
  SDL_CreateWindowAndRenderer(width, height, SDL_WINDOW_OPENGL, &window,
                              &renderer);

  // Generate a palette with random colors.
  enum { MAX_ITER_COUNT = 256 };
  SDL_Color palette[MAX_ITER_COUNT];
  srand(time(0));
  for (int i = 0; i < MAX_ITER_COUNT; ++i) {
    palette[i] = {
        .r = (uint8_t)rand(),
        .g = (uint8_t)rand(),
        .b = (uint8_t)rand(),
        .a = 255,
    };
  }

  // Calculate and draw the Mandelbrot set.
  std::complex<double> center(0.5, 0.5);
  double scale = 4.0;
  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      std::complex<double> point((double)x / width, (double)y / height);
      std::complex<double> c = (point - center) * scale;
      std::complex<double> z(0, 0);
      int i = 0;
      for (; i < MAX_ITER_COUNT - 1; i++) {
        z = z * z + c;
        if (abs(z) > 2.0)
          break;
      }
      SDL_Color color = palette[i];
      SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
      SDL_RenderDrawPoint(renderer, x, y);
    }
  }

  // Render everything we've drawn to the canvas.
  SDL_RenderPresent(renderer);

  // SDL_Quit();
}

如您所見 這個例子裡有 50 行程式碼 外部 API,像是 SDL 程式庫 圖形和複數 C++ 標準程式庫。

我將使用與上述相同的 -g 標記來編譯,以便納入偵錯資訊,同時也會要求 Emscripten 提供 SDL2 程式庫,並允許任意大小的記憶體:

emcc -g mandelbrot.cc -o mandelbrot.html \
     -s USE_SDL=2 \
     -s ALLOW_MEMORY_GROWTH=1

透過瀏覽器造訪產生的網頁時,可以看到 一些隨機色彩的碎形形狀:

示範網頁

再次開啟開發人員工具時,可以看到原始 C++ 檔案。不過,這次程式碼中沒有錯誤 (呼!),因此我們改為在程式碼開頭設定一些中斷點。

當我們重新載入網頁時,偵錯工具會直接在我們的 C++ 來源:

開發人員工具已在「SDL_Init」呼叫時暫停

右側可以看到所有變數,但只有 widthheight 目前已經初始化,所以並沒有太多

現在讓我們在 Mandelbrot 主要迴圈中設定另一個中斷點,然後繼續執行

已在巢狀迴圈中暫停開發人員工具

目前,palette 已填入一些隨機的顏色。 我們可以同時展開陣列本身和 SDL_Color 結構並檢查其元件,以驗證是否 沒有問題 (例如,「Alpha 版」管道一律會 直到完全不透明度為止)。同樣地,我們也可以展開並查看 儲存在 center 變數中複數的虛構部分。

如果您想存取的深層巢狀屬性 前往「Scope」檢視畫面,您可以使用「Console」(控制台)。 以及評估與否!不過請注意,較複雜的 C++ 運算式不會 。

控制檯面板顯示「palette[10].r`」的結果

我們可以暫停執行幾次,看看內部 x 的變化情形,方法是再次查看「範圍」檢視畫面、將變數名稱加入監控清單、在控制台中評估,或是將滑鼠游標懸停在原始程式碼中的變數上:

來源中變數「x」的工具提示,顯示其值「3」

從這裡,我們可以逐步執行或略過 C++ 陳述式,並觀察其他變數的變化情形:

顯示「color」、「point」和其他變數值的工具提示和範圍檢視畫面

好的,當有偵錯資訊可用時,上述功能就很實用,但 對於不是以偵錯功能建構的程式碼 有哪些選項?

原始 WebAssembly 偵錯

舉例來說,我們要求 Emscripten 為 與其自己從原始碼進行編譯,至少 因為偵錯工具目前無法尋找相關聯的來源。 讓我們再次一步步進入 SDL_RenderDrawColor

開發人員工具顯示 `mandelbrot.wasm` 的解組畫面

我們將回到原始的 WebAssembly 偵錯體驗。

雖然這看起來有點嚇人,而且大多數網頁開發人員都不需要處理,但有時您可能需要對沒有偵錯資訊的建構程式庫進行偵錯,可能是因為這是您無法控制的 3rd 方程式庫,或是您遇到只會在實際環境中發生的錯誤。

為協助處理這些情況,我們也對基本偵錯體驗進行了一些改善。

首先,如果您之前使用過原始 WebAssembly 偵錯功能 請注意,整個反組譯碼現在會顯示在單一檔案中 更猜測 Sources 項目 wasm-53834e3e/ wasm-53834e3e-7 可能對應的函式。

新建名稱產生配置

我們也改善了反組譯檢視畫面中的名稱。先前看到的畫面 只是數字索引,而在函式中,則完全沒有名稱。

現在我們產生名稱的方法與其他拆解工具類似 WebAssembly 名稱部分的提示, 匯入/匯出路徑,最後,如果其他連線失敗 這些值取決於項目的類型和索引,例如 $func123。你可以 以剛才的螢幕擷取畫面為例 以及更易於理解的堆疊追蹤

當沒有可用的類型資訊時,您可能很難檢查基本類型以外的任何值。舉例來說,指標會顯示為一般整數,而您無法得知記憶體中儲存了什麼。

記憶體檢查

先前您只能展開 WebAssembly 記憶體物件 (在「Scope」檢視畫面中以 env.memory 表示) 包括個別位元組這在一些簡單的情況下運作良好,但不太適合擴充,而且不允許以位元組值以外的格式重新解讀資料。我們也新增了線性記憶體檢查器這項功能,可協助您解決這個問題。

如果您在 env.memory 上按一下滑鼠右鍵,現在應該會看到名為「檢查記憶體」的新選項:

範圍窗格中 `env.memory` 的內容選單,顯示「檢查記憶體」項目

點擊完畢後,畫面會顯示記憶體檢查器, 您可以在十六進位和 ASCII 檢視中檢查 WebAssembly 記憶體, 然後前往特定地址 不同格式:

開發人員工具中的「記憶體檢查工具」窗格,顯示記憶體的十六進位和 ASCII 檢視畫面

進階情況和注意事項

剖析 WebAssembly 程式碼

開啟開發人員工具時,WebAssembly 程式碼會「降級」至未最佳化的版本,以便進行偵錯。這個版本的速度較慢,因此您無法在 DevTools 開啟時使用 console.timeperformance.now 和其他方法來評估程式碼的速度,因為您取得的數字根本無法代表實際效能。

相反地,您應使用開發人員工具的「效能」面板,該面板會以全速執行程式碼,並提供各函式所花費時間的詳細分析:

顯示各種 Wasm 函式的剖析面板

或者,您也可以在 開發人員工具關閉後執行應用程式 檢查完成後再開啟即可檢查控制台

我們日後將會改善剖析情境,但目前主要是 注意所有事項想進一步瞭解 WebAssembly 分層情境,請參閱 WebAssembly 編譯管道的說明文件。

在不同機器 (包括 Docker/主機) 上建構及偵錯

在 Docker、虛擬機器或遠端建構伺服器上進行建構時 您可能會遇到來源檔案路徑 您用於建構作業的檔案系統路徑 Chrome 開發人員工具在這種情況下,檔案會顯示在「Sources」面板中,但無法載入。

為修正這個問題,我們在 C/C++ 擴充功能選項。您可以使用它重新對應任意路徑,並協助開發人員工具找出來源。

舉例來說,如果主體機器上的專案位於路徑底下 C:\src\my_project,但是在下列位置的 Docker 容器中建構: 該路徑以 /mnt/c/src/my_project 表示, 即可在偵錯期間將這些路徑指定為前置字串:

C/C++ 偵錯擴充功能的選項頁面

系統會選擇第一個相符的前置字元。如果您熟悉其他 C++ 此選項與 set substitute-path 指令類似 或 LLDB 中的 target.source-map 設定

對最佳化的版本進行偵錯

與其他語言一樣,最佳化功能停用時,偵錯功能的運作效果最佳。最佳化作業可能會將函式內嵌至另一個函式、重新排序 或者移除程式碼的某些部分, 可能混淆偵錯工具,因此您就是使用者。

如果您不介意較受限的偵錯體驗,但仍想對經過最佳化的版本進行偵錯,那麼除了函式內嵌功能外,大部分的最佳化功能都會正常運作。我們會設法解決 日後,請改用 -fno-inline 您在編譯任何 -O 層級最佳化時,請停用該功能,例如:

emcc -g temp.c -o temp.html \
     -O3 -fno-inline

分離偵錯資訊

偵錯資訊會保留許多程式碼詳細資料,包括定義的類型、變數、函式、範圍和位置,以及任何對偵錯工具有用的資訊。因此,它通常會比程式碼本身還大。

為加快 WebAssembly 模組的載入和編譯作業,您可能會想要將這些偵錯資訊拆分為個別的 WebAssembly 檔案。如要在 Emscripten 中執行這項操作,請傳遞 -gseparate-dwarf=… 標記,並附上所需的檔案名稱:

emcc -g temp.c -o temp.html \
     -gseparate-dwarf=temp.debug.wasm

在這種情況下,主應用程式只會儲存檔案名稱 temp.debug.wasm,當您開啟 DevTools 時,輔助擴充功能就能找到並載入該檔案。

搭配上述最佳化功能使用時,這項功能甚至可用於發布幾乎已最佳化的應用程式正式版本,並在日後使用本機端檔案進行偵錯。在這種情況下,我們還需要覆寫已儲存的網址,以便擴充功能找到側邊檔案,例如:

emcc -g temp.c -o temp.html \
     -O3 -fno-inline \
     -gseparate-dwarf=temp.debug.wasm \
     -s SEPARATE_DWARF_URL=file://[local path to temp.debug.wasm]

待續...

太好了,那是很多新功能的誕生!

有了這些全新整合功能,Chrome 開發人員工具就成為可行且強大的偵錯工具,不僅適用於 JavaScript,也適用於 C 和 C++ 應用程式,讓您更輕鬆地使用各種技術建構的應用程式,並將其帶入共用的跨平台網頁。

不過,我們的旅程尚未結束。可望 從這裡開始:

  • 清除偵錯過程中的粗糙邊緣。
  • 新增對自訂類型格式設定器的支援。
  • 我們正在努力改善 剖析
  • 新增程式碼涵蓋率支援功能,讓項目更容易找到 未使用的程式碼
  • 改善對主控台評估作業中運算式的支援功能。
  • 新增支援更多語言。
  • …還有更多!

同時,請在自己的程式碼上試用目前的 Beta 版,並將發現的問題回報至 https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350

下載預覽頻道

建議您使用 Chrome CanaryDevBeta 版做為預設的開發瀏覽器。透過這些預覽版本,您可以存取開發人員工具中的最新功能、測試最先進的網路平台 API,以及找出網站的問題,以免使用者發現問題。

與 Chrome 開發人員工具團隊聯絡

請使用下列選項,討論文章中的新功能和變更,或任何與開發人員工具相關的內容。