MemorySanitizer:
fast detector of uninitialized memory use in C++
2015/7/8
光成滋生
C/C++の未初期化メモリ実行時チェックツール
http://research.google.com/pubs/pub43308.html
の紹介
動作速度のコストは2.5x, メモリ使用量は2x程度
Clangに組み込まれていて-fsanitize=memoryで利用可能
未初期化メモリ(UUM : Use of Uninitialized Memory)の
トラッキングも可能
実際にGoogleのたくさんの開発で利用されている
2/11
MemorySanitizer(以下MSan)
valgrind(Memcheck)
UUM、ヒープオーバーフロー、free後の利用の検出
1byteに2bitの情報
addressable+init, not addr, addr+uninit, addr+partially init
false positiveが0に近い
20x遅い(マルチスレッドでもっと遅い)
Dr. Memory
Memcheckの2xほど
マルチスレッドでちょっとfalse positive
Intel Inspector XE
遅い
3/11
Related work
各1bitに1bit割り当てる(0:init/defined, 1:not)
Shadow = App & (~0x4000|032)
Originはトラッキングのときに使う
どちらもMAP_NORESERVEをつける(Swap対象としない)
4/11
Shadow memory
Application
Origin
Shadow
Protected
0x0000|032
0x2000|032
0x4000|032
0x6000|032
0x7fff|132
UM(未初期化メモリ)は伝達する
C++11では未初期化objectのlvalueからrvalueへの変
換は未定義だった
C++14でindeterminate valueに緩和される
struct A { char x; int y; };を8byteのmemcpyに最適化さ
れてもエラーを出さないように
UMにしたがって条件分岐、システムコールの呼び出
し、ポインタdereferenceしたときにエラーを出力
5/11
Shadow propagation
false positiveを出さない方針
その条件の元でできるだけfalse negativeを減らす
記号 : A = B op C(オリジナル), A', B', C':shadow
6/11
Shadow propagation rule
命令 ルール
A = load P check P', load (P & shadowMask)
*P = A check P', *(P & shadowMask) = A'
A = const A' = 0
A = undef A' = 0xff
A = B & C A' = (B'&C')|(B&C')|(B'&C)
A = B | C A' = (B'&C')|(~B&C')|(B'&~C)
A = B ^ C A' = B' | C'
A = B << C A' = (sign extend(C'!=0))|(B'<<C)
A = B * (C * (1 << D))
A' = B' << Dで代用(一般はA' = B'| C')
bitwiseにやってるとfalse positiveになる例
clangはこれを *(unsigned char*)s > 7と最適化する
X : unsigned, X':対応するshadowのとき
Xのとり得る範囲は[VMin(X, X'), VMax(X, X')]
VMin(X, X') = X & (~X')
VMax(X, X') = X | X'
正確だがベンチマークで50%の速度低下
7/11
複雑なパターン(1/2)
struct S { int a:3; int b:5; };
bool f(S* s) { return s->b; }
等号比較
A = B == Cを
D = B ^ C, A = D == 0に変換
D' = B'|C', A'=(!(D & ~D')) && (D' != 0)
条件演算子
A = B ? C : D
BがundefでもCとDが同じでdefならAはdefになる
A' = B' ? ((C ^ D) | C' | D') : (B ? C' : D')
8/11
複雑なパターン(2/2)
vector instruction
同じ数だけ並べる
thread safety
global lockは遅いので別のやりかたで
CAS, RMWは以前の値は全てinitializedと仮定
function calls
va_listの扱いが大変
run-time library
300ほどの関数をintercept
9/11
その他
valgrindの10倍ぐらい速い
10/11
performance
未初期化参照が分かってもそれがどこにあった
か分からないと辛い
源流を保持する
11/11
origin tracking
#include <stdio.h>
int arr[2];
void shift() { arr[1] = arr[0]; }
void push(int *p) {
shift();
arr[0] = *p;
}
int pop() {
int x = arr[1];
shift();
return x;
}
void func1() {
int local_var;
push(&local_var);
}
int main() {
func1();
shift();
return pop();
}
clang-3.5 -fsanitize-memory-track-origins=2 -g -fsanitize=memory t.c
&& ./a.out
==22068== WARNING: MemorySanitizer: use-of-uninitialized-value
#0 0x7f87ee79de13 (/home/shigeo/a.out+0x8ee13)
#1 0x7f87ed600ec4 (/lib/x86_64-linux-gnu/libc.so.6+0x21ec4)
#2 0x7f87ee79d7bc (/home/shigeo/a.out+0x8e7bc)
...
Uninitialized value was created by an allocation of 'local_var' in
the stack frame of function 'func1'
#0 0x7f87ee79dce0 (/home/shigeo/a.out+0x8ece0)
SUMMARY: MemorySanitizer: use-of-uninitialized-value ??:0 ??
ちなみにgcc t.c -Wall –Wextraでは警告は何もでない。
% valgrind --track-origins=yes ./a.out
==22149== Syscall param exit_group(status) contains uninitialised
byte(s)
==22149== at 0x4EF8309: _Exit (_exit.c:32)
==22149== by 0x4E7321A: __run_exit_handlers (exit.c:97)
==22149== by 0x4E732A4: exit (exit.c:104)
==22149== by 0x4E58ECB: (below main) (libc-start.c:321)
==22149== Uninitialised value was created by a stack allocation
==22149== at 0x400560: func1 (t.c:13)

Memory sanitizer