最新のツールを使用した WebAssembly のデバッグ

Ingvar Stepanyan
Ingvar Stepanyan

これまでの道のり

1 年前、Chrome は Chrome DevTools でのネイティブ WebAssembly デバッグの初期サポートを発表しました。

基本的なステップ実行のサポートを紹介し、機会について説明しました。 代わりに DWARF 情報を使用する ソースマップは将来的に利用可能になります。

  • 変数名の解決
  • プリティ プリントのタイプ
  • ソース言語での式の評価
  • ほか多数

本日は、期待していた機能が実現した様子をご紹介します Emscripten と Chrome DevTools のチームは 特に C および C++ アプリの場合は特にそうです。

なお、これはまだベータ版であることにご留意ください 更新するには、すべてのツールの最新バージョンを使用する必要があります。 ご自身の責任においてご利用ください。問題が発生した場合は、 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 DevTools と統合され、WebAssembly ファイルにエンコードされたすべてのデバッグ情報を解釈するヘルパー拡張機能も必要になります。インストールするには、goo.gle/wasm-debugging-extension のリンクにアクセスしてください。

また、DevTools で WebAssembly デバッグを有効にします。 テスト。Chrome DevTools を開いて、歯車アイコン()アイコンをクリックします。 [DevTools] ペインの右上で、[Experiments] パネルに移動します。 [WebAssembly Debugging: Enable DWARF support] チェックボックスをオンにします。

DevTools 設定の [テスト] ペイン

[Settings] を閉じると、DevTools によって再読み込みが提案されます。 設定を適用するだけです1 回限りは以上です できます。

これで、[ソース] パネルに戻り、[例外で停止](⏸ アイコン)を有効にして、[キャッチされた例外で停止] をオンにして、ページを再読み込みできます。DevTools が例外で一時停止しているはずです。

[一時停止した例外で一時停止] を有効にする方法を示す [Sources] パネルのスクリーンショット

デフォルトでは、Emscripten で生成されたグルーコードで停止しますが、 [Call Stack] ビューが表示されます。これは、 呼び出された元の C 行に移動できます。 abort:

DevTools が「assert_less」関数で一時停止し、スコープビューに「x」と「y」の値が表示されている

これで、[スコープ] ビューで、C/C++ コード内の変数の元の名前と値を確認できるようになりました。$localN などのマングリングされた名前の意味や、作成したソースコードとの関係を把握する必要がなくなりました。

これは、整数などのプリミティブ値だけでなく、構造体、クラス、配列などの複合型にも適用されます。

リッチタイプのサポート

より複雑な例で、これらのことを確認しましょう。この マンデルブロ フラクタルを 次の 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();
}

ご覧のとおり、このアプリケーションはまだかなり小さく、1 つの ファイルには 50 行のコードが含まれていますが、今回は の SDL ライブラリなどの 画像や、画像から得られる複素数を C++ 標準ライブラリ。

上記と同じ -g フラグを使用してコンパイルし、デバッグ情報を含めます。また、Emscripten に SDL2 ライブラリを提供し、任意サイズのメモリを許可するよう指示します。

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

生成されたページをブラウザでアクセスすると、 ランダムな色のフラクタル図形:

デモページ

DevTools を開くと、元の C++ ファイルが再び表示されます。この ただし、コードにエラーはないため、次のように設定します。 ブレークポイントを追加します。

ページを再度読み込むと、デバッガは C++ ソース内で一時停止します。

DevTools が「SDL_Init」呼び出しで一時停止する

右側にはすでにすべての変数が表示されていますが、width のみです。 height は現在初期化されているため、何も実行することは 検査します。

メインのマンデルブロ ループ内に別のブレークポイントを設定して、 少し前にスキップします。

ネストされたループ内で DevTools が一時停止する

この時点で、palette はランダムな色で塗りつぶされています。 配列自体も個々の配列自体も展開できます SDL_Color 構造を検証し、そのコンポーネントを検査して、 すべて正常です(たとえば、「アルファ」チャンネルは常に にします)。同様に、center 変数に格納されている複素数の実数部と虚数部を開いて確認できます。

他の方法では見つけにくい、深くネストされたプロパティにアクセスする場合は、 [スコープ] ビューに移動するには、コンソール 評価も行われています。ただし、より複雑な C++ 式はまだサポートされていません。

「palette[10].r」の結果を示すコンソール パネル

実行を数回再開して、内部 x の状態を確認してみましょう。 再度 [スコープ] ビューを表示して、 変数名をウォッチリストに追加するか、コンソールで評価するか、 ソースコード内の変数にカーソルを合わせます。

ソース内の変数「x」の上に表示されたツールチップ。値は「3」です。

ここから、C++ ステートメントをステップインまたはステップ オーバーして、 他の変数も変化しています。

`color`、`point`、その他の変数の値が表示されているツールチップとスコープビュー

デバッグ情報が利用可能な場合は、すべて問題なく動作しますが、デバッグ オプションを使用してビルドされていないコードをデバッグする場合はどうすればよいでしょうか。

未加工の WebAssembly デバッグ

たとえば、ソースから自分でコンパイルするのではなく、Emscripten にビルド済みの SDL ライブラリを提供するように依頼したため、少なくとも現時点では、デバッガが関連するソースを見つける方法はありません。もう一度 SDL_RenderDrawColor に移動しましょう。

「mandelbrot.wasm」の分解ビューを示す DevTools

WebAssembly デバッグの生の体験に戻りました。

少し怖いように見えますが、ほとんどのウェブ デベロッパーが対処する必要はありません。ただし、デバッグ情報なしでビルドされたライブラリをデバッグする必要がある場合があります。これは、制御できないサードパーティ ライブラリであるか、本番環境でのみ発生するバグに遭遇しているためです。

そのような場合に備えて、基本的な デバッグ経験も豊富です。

まず、以前に元の WebAssembly デバッグを行ったことがある場合は、逆アセンブル全体が 1 つのファイルに表示されるようになりました。ソース エントリ wasm-53834e3e/ wasm-53834e3e-7 がどの関数に対応しているかを推測する必要がなくなりました。

新しい名前生成スキーム

また、逆アセンブル ビューの名前も改善されました。以前は、数値インデックスのみが表示されていました。関数の場合は、名前が表示されませんでした。

現在、他の逆アセンブル ツールと同様に、WebAssembly の名前セクションのヒント、インポート / エクスポート パスを使用して名前を生成しています。それでも名前が生成されない場合は、$func123 などのアイテムのタイプとインデックスに基づいて名前を生成します。Google Chat では 上のスクリーンショットでは、これはすでにわずかに スタックトレースと逆アセンブルが わかりやすくなっています

利用可能な型情報がない場合、検査が難しい可能性がある プリミティブ以外のすべての値(たとえば、ポインタは 通常の整数として格納され、その背後に何が保存されているかは できます。

メモリ検査

以前は、[Scope] ビューで env.memory で表される WebAssembly メモリ オブジェクトを展開して検索するだけでした。 表示されます。いくつかの簡単なシナリオでうまくいったが、 拡張が特に便利でデータの再解釈ができず データをバイト値以外の形式で指定します。これにも役立つ新機能として、リニアメモリ インスペクタが追加されました。

env.memory を右クリックすると、[メモリを検査] という新しいオプションが表示されます。

[Inspect Memory] が表示されている [Scope] ペインの [env.memory] のコンテキスト メニューアイテム

クリックすると Memory Inspector が表示されます。 16 進数または ASCII ビューで WebAssembly メモリを検査できます。 特定の住所への移動、 保存できます。

DevTools の Memory Inspector ペインに、メモリの 16 進数ビューと ASCII ビューが表示されている

高度なシナリオと注意点

WebAssembly コードのプロファイリング

DevTools を開くと、デバッグを有効にするために、WebAssembly コードが最適化されていないバージョンに「階層化」されます。このバージョンは動作が遅く、 つまり、console.timeperformance.now は などの方法でコードの速度を測定する方法を 表示される数字は実際のパフォーマンスを表すとは限らないため、 ありません。

代わりに、DevTools の [Performance] パネルを使用する必要があります。 これによりコードが最大速度で実行され さまざまな部門に費やされた時間の詳細な内訳:

さまざまな Wasm 関数を示すプロファイリング パネル

または、DevTools を閉じてアプリケーションを実行することもできます。 完了したら、それらを開いてコンソールを調べます。

プロファイリング シナリオは今後改善される予定ですが、現時点では注意が必要です。WebAssembly の階層化シナリオの詳細については、WebAssembly コンパイル パイプラインのドキュメントをご覧ください。

異なるマシン(Docker / ホストを含む)でのビルドとデバッグ

Docker、仮想マシン、リモートビルド サーバーでビルドする場合、 多くの場合、ソースファイルへのパスがファイル パスに 自分のファイル システムのパスと一致しない場合に、 確認できます。この場合、ファイルは [ソース] パネルに表示されますが、読み込まれません。

この問題を修正するため、 C/C++ 拡張オプションを使用します。これを使用して任意のパスのリマッピングを行い、DevTools がソースを見つけられるようにすることができます。

たとえば、ホストマシン上のプロジェクトがパス C:\src\my_project にあるが、そのパスが /mnt/c/src/my_project として表される Docker コンテナ内でビルドされている場合、これらのパスを接頭辞として指定することで、デバッグ中に再マッピングできます。

C/C++ デバッグ拡張機能のオプション ページ

最初に一致したプレフィックスが「勝ち」ます。他の C++ 言語に習熟している場合、 デバッガ。このオプションは set substitute-path コマンドに似ています。 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 を開いたときにヘルパー拡張機能がそのファイルを検出して読み込むことができます。

この機能は、上記のような最適化と組み合わせることで、ほぼ最適化されたアプリケーションの本番環境ビルドを配布し、後でローカルサイドファイルでデバッグすることもできます。この場合、拡張機能がサイドファイルを検出できるように、保存されている URL をオーバーライドする必要があります。次に例を示します。

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 DevTools は JavaScript だけでなく、C と C++ のアプリでも実用的で強力なデバッガになります。これにより、さまざまなテクノロジーで構築されたアプリを、共有のクロス プラットフォーム ウェブにこれまで以上に簡単に移行できるようになります。

しかし、私たちの歩みはまだ終わっていません。今後取り組む予定の機能の一部をご紹介します。

  • デバッグ エクスペリエンスの粗さを解消。
  • カスタム型フォーマッタのサポートを追加しました。
  • 現在、 プロファイリング(WebAssembly アプリの場合)
  • 見つけやすくするためにコード カバレッジのサポートを追加しました。 ありません。
  • コンソール評価での式のサポートを改善しました。
  • 対応言語の追加。
  • …など

ご自身のコードで現在のベータ版をお試しいただき、問題が見つかった場合は https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350 までご報告ください。

プレビュー チャネルをダウンロードする

デフォルトの開発用ブラウザとして Chrome の CanaryDevBeta を使用することを検討してください。これらのプレビュー チャンネルでは、最新の DevTools 機能にアクセスしたり、最先端のウェブ プラットフォーム API をテストしたり、ユーザーよりも早くサイトの問題を見つけたりすることができます。

Chrome DevTools チームへの問い合わせ

以下のオプションを使用して、投稿の新機能や変更点、または DevTools に関連するその他のことについて話し合います。