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

Chrome 92 では、線形メモリバッファを検査するツールである Memory Inspector が導入されました。この記事では、Inspector for C/C++ のデバッグの改善と、その過程で直面した技術的な課題について説明します。

C/C++ デバッグと Memory Inspector を初めて使用する場合は、以下の関連するブログ投稿をご覧ください。

はじめに

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

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

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

Memory Inspector でオブジェクトがハイライト表示される

Chrome 107 以降では、Memory Inspector で C/C++ メモリ オブジェクトのすべてのバイトがハイライト表示されます。これにより、周囲のメモリと区別できます。

配列が鮮やかに強調表示された、更新されたメモリ インスペクタのスクリーンショット

Memory Inspector の実際の使い方については、次の動画をご覧ください。Memory Inspector で配列 x を表示すると、ハイライト表示されたメモリが Memory Viewer に表示され、そのすぐ上に新しいチップが表示されます。このチップは、ハイライト表示された思い出の名前と種類を知らせます。チップをクリックすると、オブジェクトのメモリに移動できます。チップにカーソルを合わせると、十字アイコンが表示されます。クリックするとハイライトを削除できます。

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

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

お試しになりたい場合は、そこで次の作業が必要になります。

  • Chrome 107 以降を使用している。
  • C/C++ DWARF 拡張機能をインストールします。
  • DWARF デバッグは、[DevTools] > [設定] の順にタップします。 Settings] > [Experiments] > [WebAssemble Debugging: Enable DWARF support] で有効にします。
  • こちらのデモページを開きます。
  • ページに表示される指示に沿って操作します。

デバッグ例

このセクションでは、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 配列が表示されたメモリ インスペクタのスクリーンショット。すべての配列要素がハイライト表示されています。

次に、[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 でオブジェクトを表示してデバッガでステップすると、Inspector でまだ適用可能であると判断された場合はハイライトが維持されます。当初、この機能はロードマップに含まれていませんでしたが、デバッグ エクスペリエンスが損なわれることにすぐに気づきました。以下の動画のように、ステップごとに配列を再検査しなければならない状況を想像してみてください。

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

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

おわりに

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

次のステップ

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