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

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

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

はじめに

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

周囲の WebAssembly メモリからオブジェクトのバイトを見つけることが課題でした。オブジェクトのサイズを把握し、オブジェクトの先頭からバイト数をカウントする必要があります。次のスクリーンショットでは、10 要素の int32 配列の最初のバイトが選択されていますが、配列に属する他のバイトがすぐにはわかりません。オブジェクトに属するすべてのバイトを即座に認識できたら便利だと思いませんか?

1 つのバイトがハイライト表示された元のメモリ インスペクタのスクリーンショット

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

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

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

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

検査対象のオブジェクトの外側のバイトを選択すると、注意が散漫にならないようにハイライトがぼかされます。再びフォーカスを合わせるには、オブジェクトのバイトまたはチップをもう一度クリックします。

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

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

  • Chrome 107 以降を使用している。
  • C/C++ DWARF 拡張機能をインストールします。
  • [DevTools] > [設定] をタップします。 Settings] > [Experiments] > [WebAssemble Debugging: Enable DWARF support] で、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 配列を含む、開いたメモリ インスペクタのスクリーンショット。すべての配列要素がハイライト表示されています。

次に、[スコープ] ペインで 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 * を検査したはずなのに、メモリ インスペクタのチップに *lastNumber: int と表示されています。これはなぜでしょうか。インスペクタでは、C++ スタイルのポインタ逆参照を使用して、表示されるオブジェクトの型が示されます。ポインタをインスペクトすると、ポインタが指している内容が表示されます。

デバッガのステップでハイライトを保持する

Memory Inspector でオブジェクトを表示してデバッガを操作すると、インスペクタは該当すると思われる場合、ハイライトを保持します。当初、この機能はロードマップにはありませんでしたが、デバッグ エクスペリエンスが損なわれることにすぐに気付きました。以下の動画のように、各ステップの後に配列を再検査しなければならないとしたらどうでしょう。

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

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

まとめ

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

次のステップ

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