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

Ingvar Stepanyan
Ingvar Stepanyan

目前的道路

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

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

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

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

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

讓我們先從上次相同的簡單的 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 標記 (與原始貼文相同) 以加入偵錯 每個 ACL 都由一或多個項目組成 而這些項目包含兩項資訊

emcc -g temp.c -o temp.html

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

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

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

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

關閉「設定」後,開發人員工具會建議自行重新載入 開始套用設定吧一次性 設定。

現在我們可以返回來源面板,啟用暫停 例外狀況 (⏸ 圖示),然後勾選在偵測到的例外狀況時暫停和 請重新載入網頁。發生例外狀況時,開發人員工具應會暫停:

「來源」面板的螢幕截圖,說明如何啟用「在偵測到例外狀況時暫停」

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

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

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

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

支援豐富類型

一起來看看較複雜的範例。這個 我們將使用 Mandelbrot 碎石 以下 C++ 程式碼:

#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 如何 再次進入「Scope」檢視畫面,並且新增 在控制台中評估變數名稱 將滑鼠遊標懸停在原始碼中的變數上:

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

從這裡開始或逐步執行 C++ 陳述式,並觀察 其他變數也會發生變化:

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

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

原始 WebAssembly 偵錯

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

開發人員工具顯示 `mandelbrot.wasm` 的反組譯碼檢視畫面

我們將返回原始 WebAssembly 偵錯服務。

這看起來有點恐怖,大多數網頁程式開發人員也不會 但有時您可能會想對 無論是否使用偵錯資訊 您無法控制的第三方程式庫 才會發生其中一種只發生在實際工作環境中的錯誤

有鑑於此,我們對基本功能 以及偵錯體驗

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

新建名稱產生配置

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

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

沒有可用的類型資訊時,可能難以檢查 例如基元以外的任何值 視為一般整數,無法得知它們背後的內容 記憶體用量

記憶體檢查

先前您只能展開 WebAssembly 記憶體物件 (在「Scope」檢視畫面中以 env.memory 表示) 包括個別位元組這在一些簡單的情況下適用 更方便地擴充資料,同時禁止重新解讀資料 格式。我們新增了一項功能 這也適用於線性記憶體檢查器

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

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

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

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

進階情境和注意事項

剖析 WebAssembly 程式碼

開啟開發人員工具後,WebAssembly 程式碼會變得「分層」為 以便啟用偵錯功能這個版本的速度明顯較慢 這表示您無法依賴 console.timeperformance.now 以及其他評估程式碼速度的方法 因為您得到的數據並不代表實際成效 。

請改用開發人員工具效能面板 就能完整執行程式碼 不同函式所花費時間的詳細分析:

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

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

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

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

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

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

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

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

第一個相符的前置字元「wins」。如果您熟悉其他 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,輔助擴充功能將可找出並 會在您開啟開發人員工具時載入

結合上述的最佳化設定後, 甚至可用於推送幾乎最佳化的 然後在本機端檔案對它們進行偵錯。在本例中 我們還需要覆寫儲存的網址,以利擴充功能 找出側邊檔案,例如:

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++ 應用程式 , 並將其分享到共用的跨平台網路。

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

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

在此同時,請協助我們測試您專屬的程式碼,並回報發現的問題 問題 https://bugs.chromium.org/p/chromium/issues/entry?template=DevTools+issue.

下載預覽頻道

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

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

請使用下列選項,討論貼文中的新功能和異動,或與開發人員工具相關的其他事項。

  • 歡迎透過 crbug.com 提出建議或意見。
  • 使用「更多選項」更多 > 回報開發人員工具問題說明 >在開發人員工具中回報開發人員工具問題
  • 前往 @ChromeDevTools 張貼 Tweet。
  • 歡迎在「開發人員工具」推出「最新消息」YouTube 影片或「開發人員工具秘訣」YouTube 影片留言。