背景
メモリリークのチェックでValgrindを使用したが動作が重く、解決方法を探していたところgccに組み込まれている機能で動的にメモリリークと不正メモリアクセスをチェックできることが分かったのでまとめる。
LeakSanitizerの概要
- メモリリークの動的チェックツール
- gcc/g++のコンパイルオプションに設定するだけで使用できる
- Valgrindより軽い
- Sanitizerにはリークチェック以外にも色々と機能がある
リークチェックの例
サンプルコード
main.c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char* foo = (char*)malloc(sizeof(char));
return 0; // mallocしたメモリをfreeせずにプログラム終了
}
サンプルコードを下記のコンパイルオプションでビルドする。
$ gcc gcc -fsanitize=leak -g -o main main.c
# -gオプションは出力結果にファイル名とコード行数を表示するために必要
ビルド後出力されたelfファイルを実行すると以下のように出力される。
#1 0x55a32e84b17e in main /home/jkano/main.c:6
の部分で解放されていないメモリが割り当てられた場所を教えてくれる。
$ ./main
=================================================================
==1966==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 1 byte(s) in 1 object(s) allocated from:
#0 0x7fab9071b302 in __interceptor_malloc ../../../../src/libsanitizer/lsan/lsan_interceptors.cpp:75
#1 0x55a32e84b17e in main /home/jkano/main.c:6
#2 0x7fab90508d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
メモリ不正アクセスチェックの例
サンプルコード
main.c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char* foo = (char*)malloc(sizeof(char) * 2);
foo[2] = 0x01; // 確保していない3番目の要素にアクセス
return 0;
}
サンプルコードを下記のコンパイルオプションでビルドする。
gcc -fsanitize=address -g -o main main.c
ビルド後出力されたelfファイルを実行すると以下のように出力される。
#0 0x564ce0e501fe in main /home/jkano/sanitizer/main.c:7
の部分でメモリの不正アクセスが行われた場所を教えてくれる。
=================================================================
==5108==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000012 at pc 0x564ce0e501ff bp 0x7fff4cbbad30 sp 0x7fff4cbbad20
WRITE of size 1 at 0x602000000012 thread T0
#0 0x564ce0e501fe in main /home/jkano/sanitizer/main.c:7
#1 0x7f5e9d6d5d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#2 0x7f5e9d6d5e3f in __libc_start_main_impl ../csu/libc-start.c:392
#3 0x564ce0e500e4 in _start (/home/jkano/sanitizer/main+0x10e4)
0x602000000012 is located 0 bytes to the right of 2-byte region [0x602000000010,0x602000000012)
allocated by thread T0 here:
#0 0x7f5e9d988887 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:145
#1 0x564ce0e501be in main /home/jkano/sanitizer/main.c:6
#2 0x7f5e9d6d5d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/jkano/sanitizer/main.c:7 in main
(以下略)
注意
上記メモリ不正アクセスの例だと、メモリの不正アクセスが行われた時点でプログラムが止まってしまうが、以下を実施することでプログラムを実行し続けることが可能。
- コンパイルオプションの追加:-fsanitize-recover=address
- 例:
$ -fsanitize=address -fsanitize-recover=address -g -o main main.c
- 例:
- 環境変数の設定:ASAN_OPTIONS=halt_on_error=0
- 例:
$ export ASAN_OPTIONS=halt_on_error=0
- 例:
補足
Sanitizerには以下5種類あり、様々な動的コード分析が行える。
- AddressSanitizer メモリ破損チェック
- LeakSanitizer メモリリークチェック
- ThreadSanitizer スレッド間でのデータ競合チェック
- UndefinedBehaviorSanitizer 未定義動作の実行チェック
- MemorySanitizer 未初期化メモリの参照チェック