Address Sanitizer を使用したメモリ破損のデバッグ

このドキュメントでは、AGDE を使用する際に特別なデバッグツールを有効にする方法について説明します。これらのツールは、診断が困難なメモリの破損や上書きのエラーに対して活用できます。

HWAddress Sanitizer と Address Sanitizer

HWAddress Sanitizer(HWASan)と Address Sanitizer(ASan)は、次のようなメモリの破損と上書きのエラーをデバッグする際に活用できるデバッグツールです。

  • スタック バッファのオーバーフローとアンダーフロー
  • ヒープバッファのオーバーフローとアンダーフロー
  • スコープ外でのスタックの使用
  • 二重解放とワイルド解放のエラー
  • 返却後のスタック使用(HWASan のみ)

問題をデバッグする場合や自動テストの一環として使用する場合にのみ、HWASan や ASan を有効にすることをおすすめします。これらのツールは高性能ですが、使用するとデメリットもあります。

ランタイムの動作

HWASan や ASan を有効にすると、いずれもアプリのメモリ破損を自動的にチェックします。

メモリエラーが検出されると、アプリは SIGBART(シグナルの中断)エラーでクラッシュし、logcat に詳細なメッセージが出力されます。メッセージのコピーも /data/tombstones の下のファイルに書き込まれます。

次のようなエラー メッセージが表示されます。

ERROR: HWAddressSanitizer: tag-mismatch on address 0x0042a0826510 at pc 0x007b24d90a0c
WRITE of size 1 at 0x0042a0826510 tags: 32/3d (ptr/mem) in thread T0
    #0 0x7b24d90a08  (/data/app/com.example.hellohwasan-eRpO2UhYylZaW0P_E0z7vA==/lib/arm64/libnative-lib.so+0x2a08)
    #1 0x7b8f1e4ccc  (/apex/com.android.art/lib64/libart.so+0x198ccc)
    #2 0x7b8f1db364  (/apex/com.android.art/lib64/libart.so+0x18f364)
    #3 0x7b8f2ad8d4  (/apex/com.android.art/lib64/libart.so+0x2618d4)

0x0042a0826510 is located 0 bytes to the right of 16-byte region [0x0042a0826500,0x0042a0826510)
allocated here:
    #0 0x7b92a322bc  (/apex/com.android.runtime/lib64/bionic/libclang_rt.hwasan-aarch64-android.so+0x212bc)
    #1 0x7b24d909e0  (/data/app/com.example.hellohwasan-eRpO2UhYylZaW0P_E0z7vA==/lib/arm64/libnative-lib.so+0x29e0)
    #2 0x7b8f1e4ccc  (/apex/com.android.art/lib64/libart.so+0x198ccc)

前提条件

HWASan の要件

HWASan を使用するには:

  • AGDE 24.1.99 以降を使用する必要があります。
  • アプリは NDK 26 以降を使用してビルドする必要があります。
  • アプリは、ターゲット SDK 34 以降でビルドされている必要があります。
  • ターゲットは、Android 14(API レベル 34)以降を搭載した arm64-v8a デバイスである必要があります。

プロジェクトで共有 C++ 標準ライブラリを使用する

既知の問題により、ASan は、libc++_static を使用した場合に発生する C++ の例外の処理には対応していません。libc++_shared を使用している場合、この問題は発生しません。

HWASan には演算子 newdelete が独自に実装されていますが、標準ライブラリがプロジェクトに静的にリンクされている場合は使用できません。

この設定を変更するには、このドキュメントの「C++ 標準ライブラリをリンクする」セクションをご覧ください。

フレーム ポインタ生成を有効にする

HWASan と ASan は、フレーム ポインタに基づく高速アンワインダーを使用して、メモリの割り当てイベントと割り当て解除イベントのスタック トレース情報を生成します。これらの機能を使用するには、C++ コンパイラ設定でフレーム ポインタ生成を有効にする必要があります。すなわち、フレーム ポインタの省略の最適化を無効にする必要があります。

この設定を変更するには、このドキュメントの「フレーム ポインタ生成を有効にする」セクションをご覧ください。

HWASan または ASan を使用するように Visual Studio プロジェクトを設定する

HWASan または ASan を有効にする

HWASan または ASan を有効にするには、プロジェクトの [プロパティ] ページ[構成のプロパティ] > [全般] に移動します。

現在のプロジェクトに関する Visual Studio Solution Explorer の [プロパティ] メニュー。

図 1: Visual Studio Solution Explorer ウィンドウでの対象プロジェクトの [プロパティ] オプション

[全般] プロパティが表示され、[Address Sanitizer] 設定がハイライト表示されている、対象プロジェクトの [プロパティ] ページ ダイアログ。

図 2: [全般] プロジェクト プロパティの [Address Sanitizer(ASan)] 設定

プロジェクトで HWASan を有効にするには、[Address Sanitizer(ASan)] 設定を [ハードウェア ASan を有効にする(fsanitize=hwaddress)] に変更します。

プロジェクトで ASan を有効にするには、[Address Sanitizer(ASan)] 設定を [ASan を有効にする(fsanitize=address)] に変更します。

フレーム ポインタ生成を有効にする

フレーム ポインタの生成は、[フレーム ポインタを省略] の C/C++ コンパイラ設定で管理します。内容は、プロジェクトの [プロパティ] ページ[構成のプロパティ] > [C/C++] > [最適化])で確認できます。

[C/C++ 最適化] プロパティが表示され、[フレーム ポインタを省略] 設定がハイライトされている対象プロジェクトの [プロパティ] ページ ダイアログ。

図 3: [フレーム ポインタを省略] 設定を確認できる場所

HWASan または ASan を使用する場合は、[フレーム ポインタを省略] 設定を [いいえ(-fno-omit-frame-pointer)] に設定します。

共有ライブラリ モードで C++ 標準ライブラリをリンクする

C++ 標準ライブラリのリンカーモード設定は、プロジェクトの [プロパティ] ページ([プロジェクトのデフォルト] セクションの [構成のプロパティ] > [全般])で確認できます。

[全般] カテゴリが選択され、[STL の使用] 設定がハイライト表示されている対象プロジェクトの [プロパティ] ページ ダイアログ。

図 4: C++ 標準ライブラリのリンカーモード設定を確認できる場所

HWASan または ASan を使用する場合は、[STL の使用] を [C++ 標準ライブラリ(.so)を使用] に設定します。この設定にすると、C++ 標準ライブラリが共有ライブラリとしてプロジェクトにリンクされます。HWASan と ASan が正しく機能するために必要な設定です。

Address Sanitizer で使用するビルド構成を作成する

HWASan または ASan を一時的に使用するだけの場合は、そのために新たなビルド構成を作成するのがためらわれる場合があります。たとえば、プロジェクトが小規模の場合、機能を調査している場合、またはテスト中に見つかった問題に対応しようとしている場合などが考えられます。

ただし、HWASan または ASan が有効で、定期的に使用する場合は、Teapot サンプルに示すように、HWASan または ASan 用に新しいビルド構成を作成することを検討してください。たとえば、単体テストの一環として、または夜間にゲームのスモークテストを実施する際に、Address Sanitizer を定期的に実行する場合などには新しいビルド構成を作成します。

多数のサードパーティ ライブラリを使用する大規模なプロジェクトがあり、通常、それらのライブラリを C++ 標準ライブラリに静的にリンクする場合は、個別のビルド構成を作成することが非常に有効な場合があります。専用のビルド構成を使用すると、常に正確なプロジェクト設定を維持できます。

ビルド構成を作成するには、プロジェクトの [プロパティ] ページで [構成マネージャー…] ボタンをクリックし、[アクティブなソリューションの構成] プルダウンを使用します。次に、 を選択し、適切な名前(例: HWASan 有効化用)で新しいビルド構成を作成します。

カスタム メモリ アロケータで HWASan を使用する

HWASan は、malloc(または new)を介して割り振られたメモリを自動的にインターセプトし、ポインタにタグを挿入してタグの不一致を確認します。

ただし、カスタム メモリ アロケータを使用している場合、HWASAN はカスタム メモリ割り振りメソッドを自動的にインターセプトできません。したがって、カスタム メモリ アロケータで HWASan を使用する場合は、メモリ アロケータをインストルメント化して、HWASAN を明示的に呼び出します。これは、数行のコードのみで行うことができます。

前提条件

呼び出す必要がある HWASan メソッドは、次のヘッダーで定義されています。

#include "sanitizer/hwasan_interface.h"

メモリ割り当て方法を計測する

  1. 16 バイトのブロック粒度とアライメントでオブジェクトを割り当てます。たとえば、24 バイトの固定サイズのオブジェクトを提供するプール アロケータがある場合は、割り当てを 32 バイトに切り上げて、16 バイトにアライメントします。

  2. 8 ビットタグを生成します。値 0 ~ 16 は内部使用専用のため、タグで使用することはできません。

  3. HWASan を有効にして、そのタグでメモリ領域のトラッキングを開始します。

    __hwasan_tag_memory((void*) address, tag, size);
    
  4. ポインタの上位 8 ビットにタグを挿入します。

    address = __hwasan_tag_pointer((void*) address, tag);
    

メモリ解放メソッドを計測する

  1. メモリ領域のタグをリセットして、既存のタグ付きポインタを介したアクセスが失敗するようにします。

    __hwasan_tag_memory(__hwasan_tag_pointer(ptr, 0), 0, size);
    

事前割り当てされたオブジェクト プールの操作

メモリ アロケータがプールでオブジェクトを事前割り当てし、オブジェクトを実際に解放するのではなくプールに戻す場合、割り当て解除メソッドはメモリとポインタのタグを新しい値で直接上書きできます。

```
__hwasan_tag_memory(__hwasan_tag_pointer(ptr, 0), tag, size);
ptr = __hwasan_tag_pointer((void*)ptr, tag);
```

この手法を使用する場合、割り当てメソッドでポインタやメモリブロックにタグを付ける必要はありません。プールでオブジェクトを事前割り当てするときに、ポインタとメモリブロックにタグを付けます。このスタイルを使用する例については、PoolAllocator のサンプルをご覧ください。