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

Chrome 92 では、リニアメモリ バッファを検査するためのツールである メモリ インスペクタを導入しました。この記事では、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 で配列 x を表示すると、Memory Viewer にハイライト表示されたメモリと、そのすぐ上に新しいチップが表示されます。このチップには、ハイライト表示されたメモリの名前とタイプが表示されます。チップをクリックすると、オブジェクトのメモリに移動します。チップにカーソルを合わせると、× アイコンが表示されます。このアイコンをクリックすると、ハイライトが解除されます。

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

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

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

  • Chrome 107 以降を使用している。
  • C/C++ DWARF 拡張機能をインストールします。
  • DevTools > 設定] をタップします。 [設定] > [試験運用版] > [WebAssemble デバッグ: DWARF サポートを有効にする] で 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 を使用して問題をデバッグします。こちらのデモで確認できます。まず、メモリ インスペクタで配列を調べ、numbers 配列に整数 1234 のみが含まれていることを確認します。

検査対象の int32 配列を含む、開いた Memory Inspector のスクリーンショット。配列要素はすべてハイライト表示されます。

次に、[スコープ] ペインで 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 を公開すると、実際にはどのような処理が行われるのでしょうか?メモリ アイコンをクリックすると、検査する変数がチェックされます。次に、lastNumber のデータ型と場所について拡張機能にクエリを実行します。拡張機能がその情報で応答するとすぐに、Memory Inspector は関連するメモリスライスを表示し、そのタイプを把握してオブジェクトのサイズも表示できます。

前の例の lastNumber を見ると、lastNumber: int * を検査したはずなのに、メモリ インスペクタのチップに *lastNumber: int と表示されています。これはなぜでしょうか。インスペクタは、C++ スタイルのポインタ デリファレンスを使用して、表示されるオブジェクトのタイプを示します。ポインタをインスペクトすると、ポインタが指している内容が表示されます。

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

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

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

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

まとめ

この記事では、C/C++ デバッグ用の Memory Inspector の改善点について説明しました。これらの新機能が、C/C++ アプリのメモリのデバッグ作業を簡素化することを願っています。改善のアイデアがございましたら、バグを報告してご連絡ください。

次のステップ

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