Memory Inspector を C/C++ デバッグ用に拡張する

Chrome 92 では、リニア メモリバッファを検査するためのツールである Memory Inspector を導入しました。この記事では、C/C++ デバッグ用インスペクタの改善点と、その過程で発生した技術的な課題について説明します。

C/C++ デバッグと Memory Inspector を初めて使用する場合は、関連するブログ投稿を以下に示します。

はじめに

Memory Inspector は、リニア メモリバッファのより強力なデバッグ オプションを提供します。C/C++ の場合、WebAssembly メモリ内の C/C++ メモリ オブジェクトを検査できます。

周囲の WebAssembly メモリ内でオブジェクトのバイトを認識することが課題でした。オブジェクトの開始時点からオブジェクトのサイズとバイト数を把握しておく必要があります。以下のスクリーンショットでは、10 個の要素からなる int32 配列の最初のバイトが選択されていますが、他のどのバイトが配列に属しているかはすぐにはわかりません。オブジェクトに属するすべてのバイトを即座に認識できれば便利です。

1 バイトが強調表示された元の Memory Inspector のスクリーンショット

Memory Inspector でのオブジェクトのハイライト表示

Chrome 107 以降では、メモリ インスペクタで C/C++ メモリ オブジェクトのすべてのバイトがハイライト表示されます。これにより、周囲の記憶と区別しやすくなります。

配列が鮮やかにハイライト表示された、更新された Memory Inspector のスクリーンショット

Memory Inspector の動作を確認するには、以下の動画をご覧ください。Memory Inspector の配列 x を表示すると、メモリビューアにハイライト表示されたメモリが、そのすぐ上に新しいチップとともに表示されます。このチップには、ハイライト表示された思い出の名前と種類が表示されます。チップをクリックすると、オブジェクトのメモリに移動します。チップにカーソルを合わせると、十字アイコンが表示されます。このアイコンをクリックすると、ハイライト表示を解除できます。

検査するオブジェクトの外のバイトを選択すると、気が散らないようハイライト表示のフォーカスがずれます。もう一度フォーカスを合わせるには、オブジェクトのバイトまたはチップをもう一度クリックします。

オブジェクトのハイライト表示のサポートは配列に限定されません。また、構造体、オブジェクト、ポインタを検査することもできます。これらの変更により、C/C++ アプリのメモリの探索がこれまで以上に簡単になりました。

試してみる場合は、そこで次の作業が必要になります。

  • Chrome 107 以降を使用している。
  • C/C++ DWARF 拡張機能をインストールします。
  • DevTools で DWARF デバッグを有効にします >設定] をタップします。 [設定] >テスト >WebAssemble デバッグ: DWARF サポートを有効にする
  • こちらのデモページを開きます。
  • ページの手順に沿って設定します。

デバッグの例

このセクションでは、C/C++ のデバッグに Memory Inspector を使用する方法を説明するため、単純なバグを見てみましょう。以下のコードサンプルでは、プログラマーが整数配列を作成し、ポインタ演算を使用して最後の要素を選択することにしました。残念ながら、プログラマーはポインタの計算を間違えてしまい、最後の要素を出力する代わりに、ナンセンスな値を出力しています。

#include <iostream>

int main()
{
    int numbers[] = {1, 2, 3, 4};
    int *ptr = numbers;
    int arraySize = sizeof(numbers)/sizeof(int);
    int* lastNumber = ptr + arraySize;  // Can you notice the bug here?
    std::cout <<../ *lastNumber <<../ '\n';
    return 0;
}

プログラマーは、問題をデバッグするために Memory Inspector を使用します。こちらのデモを参考にしてください。まず、[Memory Inspector] で配列を検査し、numbers 配列に想定どおりに整数 1234 のみが含まれていることを確認します。

int32 配列が検査された状態で開いた Memory Inspector のスクリーンショットすべての配列要素がハイライト表示されています。

次に、[Scope] ペインに lastNumber 変数が表示され、ポインタが配列の外側の整数を指していることがわかります。この知識を利用して、プログラマーは行 8 のポインタ オフセットを誤ってカウントしていることに気付きます。ptr + arraySize - 1 になっているはずです。

開いたメモリ インスペクタのスクリーンショット。「lastNumber」という名前のポインタが指すメモリがハイライト表示されています。ハイライト表示されたメモリは、以前にハイライト表示された配列の最後のバイトの直後にあります。

これは単純な例ですが、オブジェクトのハイライト表示によってメモリ オブジェクトのサイズと位置が効果的に伝わることがわかります。これにより、C/C++ アプリのメモリ内で何が起きているかをより深く理解できます。

DevTools でハイライト表示される箇所が特定される仕組み

このセクションでは、C/C++ デバッグを可能にするツールのエコシステムについて説明します。具体的には、DevTools、V8、C/C++ DWARF 拡張機能、Emscripten を使用して、Chrome での C/C++ デバッグを可能にする方法を学びます。

DevTools で C/C++ デバッグの機能を最大限に活用するには、次の 2 つが必要です。

  • Chrome にインストールされている C/C++ DWARF 拡張機能
  • こちらのブログ投稿に記載されているように、最新の Emscripten コンパイラで WebAssembly にコンパイルされた C/C++ ソースファイル

しかし、なぜこうなってしまったのでしょう。Chrome の JavaScript エンジンと WebAssembly エンジンである V8 では、C または C++ の実行方法がわかりません。C/C++ から WebAssembly へのコンパイラである Emscripten を使用すると、C または C++ でビルドされたアプリを WebAssembly としてコンパイルして、ブラウザで実行できます。

コンパイル中に、emscripten は DWARF デバッグデータをバイナリに埋め込みます。大まかに言うと、このデータは拡張機能が、C/C++ 変数に対応する WebAssembly 変数を特定するために役立ちます。これにより、V8 が実際に WebAssembly を実行している場合でも、DevTools で C++ 変数を表示できます。ご興味がありましたら、DWARF デバッグデータの例については、こちらのブログ投稿をご覧ください。

それでは、lastNumber を公開するとどうなるでしょうか。メモリアイコンをクリックすると、DevTools によって検査対象の変数がすぐにチェックされます。次に、lastNumber のデータ型とロケーションについて拡張機能をクエリします。拡張機能がその情報を返すとすぐに、Memory Inspector は関連するメモリスライスを表示し、そのタイプを把握して、オブジェクトのサイズも表示します。

前の例の lastNumber を見ると、lastNumber: int * が検査されていることがわかりますが、Memory Inspector のチップには *lastNumber: int と表示されています。インスペクタでは、C++ スタイルのポインタ逆参照を使用して、表示されるオブジェクトの型が示されます。ポインタを検査すると、インスペクタにそのポインタが指しているものが表示されます。

デバッガ ステップのハイライト表示

Memory Inspector でオブジェクトを表示してデバッガを操作すると、インスペクタは該当すると思われる場合、ハイライトを保持します。当初、この機能はロードマップに含まれていませんでしたが、デバッグ エクスペリエンスが低下することがすぐに判明しました。下の動画のように、各ステップの後に配列を再検査する必要があると想像してみてください。

デバッガが新しいブレークポイントにヒットすると、Memory Inspector は再度 V8 と、前のハイライトに関連する変数の拡張機能をクエリします。次に、オブジェクトの位置と種類を比較します。一致した場合、ハイライトは維持されます。上の動画では、配列 x に対する for ループの書き込みが行われています。これらのオペレーションでは配列の型や位置は変更されないため、配列はハイライト表示されたままになります。

これがポインタにどのように影響するか不思議に思われるかもしれません。ハイライト表示されたポインタがあり、それを別のオブジェクトに再割り当てすると、ハイライト表示されたオブジェクトの古い位置と新しい位置が異なり、ハイライトが消えます。新しくポイントされたオブジェクトは WebAssembly メモリ内の任意の場所に存在し、以前のメモリ位置とはあまり関係がない可能性が高いため、新しいメモリ位置にジャンプするよりもハイライトを削除する方がより明確になります。[スコープ] ペインでポインタのメモリアイコンをクリックすると、ポインタを再度ハイライト表示できます。

まとめ

この記事では、C/C++ デバッグ用の Memory Inspector の改善点について説明しました。この新機能により、C/C++ アプリのメモリのデバッグが簡単になることを願っています。さらに改善するためのご提案がありましたら、バグを報告してお知らせください。

次のステップ

詳しくは、次をご覧ください。