More Related Content More from MITSUNARI Shigeo More from MITSUNARI Shigeo (20) ゆるバグ2. • Windows Xpのバグ
• gdbのバグ
• objdumpのバグ
• Windowsのstackの仕様
• Linux on Travis-CIでだけエラー
• inline化される?
• Visual Studioのバグ
• PHPのバグ
遭遇したもの
2 / 36
3. • 発端
• 当時(2001) Windows 2000でとあるコーデックを開発中
• ソースコードのインデントがおかしかったのでツールで整形
• 一部手動でスペースをタブに変換するなど
• コーデック自体の挙動が変わるはずはない
• しかし
• 実行するとWindows 2000が突如再起動
• ???
• Windows 98みたいにすぐ落ちることはあまりないんだけど
• もう一度試してもやっぱり再起動
• 整形までだとちゃんと動く
• ???
整形したらえらいことになった
3 / 36
5. • 文字を出力するだけ
• 後にループしなくてもOKと判明
• 発売直前の(開発用)Windows Xpでも発生
• こんなのでOSが落ちるの?
コード最小化
#include <stdio.h>
int main(void)
{
for (;;) {
printf("hung up¥t¥t¥b¥b¥b¥b¥b¥b");
}
return 0;
}
5 / 36
6. • ML(メーリングリスト) ; 今のTwitterみたいなもの
• fj.os.ms-windows.programming
• https://groups.google.com/forum/#!msg/fj.os.ms-
windows.programming/0c2WdfjwK4Q/fC7sHDh2jkgJ
• omp.os.ms-windows.programmer.win32
• https://groups.google.com/forum/#!msg/comp.os.ms-
windows.programmer.win32/uZd_19YEdRM/JXBR0FTsV2sJ
• 反応
• this is not just a joke
• NT4でも落ちた
• printf("¥t¥b¥b");だけでも落ちた
• Perlでも落ちた / ○○でも落ちた / 言語に依らない
• 実は結構なセキュリティホールだったかも
fjなどのニュースグループに投稿
6 / 36
7. • よくある日常
• あるプログラムが落ちたのでデバッグしようとgdb上で起動
• まずはrで実行して落ちたところでバックトレース(bt)
• あれ、Command not foundって何?
• よくみるとコマンドプロンプトに戻ってる
いつもと違うgdb
% gdb ./a.out
GNU gdb (GDB) 7.7
Copyright (C) 2014 Free Software Foundation, Inc.
...
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./a.out...Segmentation fault (core dumped)
% r
r: Command not found.
% bt
bt: Command not found.
%
7 / 36
8. • 再掲
• dmesgを見てみる
よく見ると落ちているのはgdb
Reading symbols from
./a.out...Segmentation fault (core dumped)
% dmesg | tail
[8895844.655909] gdb[18402]: segfault at 7ffc284a1ff8
ip 0000000000741fc7 sp 00007ffc284a1fc0 error 6 in
gdb[400000+5a0000]
8 / 36
10. • 中身に意味はないがgdb 7.7が落ちるコード
削られたコード
#include <utility>
#include <stdio.h>
struct BaseHolder { };
template<class Func>
struct Holder : public BaseHolder {
Func func;
explicit Holder(Func&& func):func(std::forward<Func>(func)) {}
};
struct Runner {
BaseHolder *holder_;
template<class Func>
explicit Runner(Func && func):holder_(new Holder<Func>(func)) {}
~Runner() { delete holder_; }
};
template<class T>void f(T &) {
auto g =[&](){};
Runner{g};
}
int main() {
int a = 0;
f(a);
}
10 / 36
13. • 原因判明
• 同じバイト列なのに逆アセンブル結果が違う
• vcvtpd2dqxとvcvtpd2dq / vcvtpd2dqyとvcvtpd2dq
• 途中に(Z)のバイト列が入るとそのあと間違えるバグ
• 逆アセンブラが状態を持つとは思わなかった
時々間違えるobjdump (2.3.0)
% objdump -M x86-64 -D -b binary -m i386 vcvtpd2dq.bin
vcvtpd2dq.bin: file format binary
Disassembly of section .data:
00000000 <.data>:
0: 67 c5 fb e6 40 20 vcvtpd2dqx 0x20(%eax),%xmm0 ; (X)
6: 67 c5 ff e6 40 20 vcvtpd2dqy 0x20(%eax),%xmm0 ; (Y)
c: 67 62 f1 ff 18 e6 40 vcvtpd2dq 0x20(%eax){1to2},%xmm0 ; (Z)
13: 04
14: 67 c5 fb e6 40 20 vcvtpd2dq 0x20(%eax),%xmm0 ; (X')
1a: 67 c5 ff e6 40 20 vcvtpd2dq 0x20(%eax),%xmm0 ; (Y')
13 / 36
16. • スタックレイアウト
• スタックの割り当ては4KiBずつ
• 新しいスタックは4KiBずつ伸ばさなければならない
Windowsのstackは自動的に伸びる
←現在のスタック
←過去に使われたところのあるスタックの先端
小さいアドレス
大きいアドレス
ここから↓はまだ割り当てられていないページ
16 / 36
18. • 関数のプロローグ
• 元のコードが時々落ちていたのは
• 普段は他のコードがスタックを利用してスタック領域が伸び
ていた大丈夫
• 通常と異なるパスでスタックがあまり伸びてないときに突入
• 落ちる
専用の関数__chkstkがある
foo:
mov [rsp+8], ecx
mov eax, STACK_SIZE
call __chkstk
sub rsp, rax
...
18 / 36
21. • 何かのツールがchar [][8]のパースに失敗してる?
• でもなんで??? 詳しい人プリーズ
違い
// bls.cgo1.goのOKなとき
v := (_Cfunc_blsFunc)((*[8] _Ctype_char)(unsafe.Pointer(&buf[0])))
// ERRなとき
v := func() _Ctype_int{
_cgoIndex0 := &buf;
_cgo0 := (*[8]_Ctype_char)(unsafe.Pointer(&(*_cgoIndex0)[0]));
_cgoCheckPointer(_cgo0, *_cgoIndex0); ...
}()
// bls.cgo2.cのOKなとき
_cgo_..._Cfunc_blsFunc(void *v) {
struct { __typeof__(char const[8])* p0; ...
// ERRなとき
_cgo_..._Cfunc_blsFunc(void *v) {
struct { void* p0; ...
21 / 36
22. • gccのバージョンを上げたらある処理が4倍遅くなった
• gccが生成する関数のそれぞれのasm出力は問題なさそう
• バージョンが変わっても大差ない
• 謎のベンチマーク挙動
• ベンチマークの後ろに
exit()を挿入すると速度が変わる
• 挿入箇所と効果の関係は?
• (B)でexitすると遅いまま
• (C)でexitすると速い
• (D)でexitすると(C)より少し遅い
• (E)でexitすると(C)よりもう少し遅い
おかしな因果関係?
void bench() {
(A)ベンチマークコード
// (B)
}
int main() {
bench();
// (C)
unitTest1();
// (D)
unitTest2();
// (E)
unitTest3();
}
22 / 36
23. • gccはinline対象関数の総量を管理している
• --param max-inline-insns-single=N オプション
• この範囲内でinline化する関数を選んでいる
• 原因判明
• gccのバージョンが上がってinline対象となる関数が増えた
• bench内の関数がinline対象外となり遅くなった
• 単体でみるとそれは分からない
• exitすると速くなったり遅くなったりした理由
• mainの途中でexitした後のコードは生成されない
• inline対象が減るのでbenchがinline化されて速くなる
• bench内でexitしてもmain内でのinline対象は減らなかった
• benchは速くならない
• -Winlineで該当関数がinlineされているか確認
inlineされるかされないか
23 / 36
24. • Visual Studioでsinやexpが遅くなる現象
• 理由がさっぱり分からない
• とても苦労して見つけた再現コード
• struct A notUsedを作るとmainの中のsin等が遅くなる
• ループ回数の8を7にすると遅くならない
何故か遅くなる数学関数
const struct A {
float a[8];
A() {
const float x = log(2.0);
for (int i = 0; i < 8; i++) a[i] = x;
}
} notUsed;
int main() {
...
}
24 / 36
26. • レジスタの形
• SSEは128bitレジスタxmm
• AVXは256bitレジスタymm
• ymmの下位128bitがxmmレジスタ
• SSEの命令padddはxmmレジスタ同士の足し算
• AVXの命令vpadddはymmレジスタ同士の足し算
• SSE命令はymmの上位128bitの存在を知らない
• その部分(d7:d6:d5:d4)は変更されない
SSEとAVX
xmm0 [d3:d2:d1:d0] ; diは32bit
ymm0 [d7:d6:d5:d4:d3:d2:d1:d0]
26 / 36
30. • Intelシステムプログラミングガイド
• Performance Monitoring Events
• C1H:08H ; OTHER_ASSISTS.AVX_TO_SSE
• CPU内でAVX→SSEでペナルティを受けた回数を記録してる
• perf stat -e r08c1 -e r10c1 ./実行ファイル
• perfが動かない場合(VM上など)はsdeを使う
• https://software.intel.com/en-us/articles/intel-software-development-emulator
• sde -oast out.txt -- ./実行ファイル
• # AVX_to_SSE_transition_instances: 10000005
perfやsdeによる検出方法
Ice Lakeでは無くなった
30 / 36
32. • malloc/freeだけではない
• memalign, aligned_alloc, posix_memalignなど
• 偽陽性が高い?
• mallocしてないのにfreeに渡される知らないポインタ
• strdupもラップが必要だった
• -Dmalloc=my_malloc –Dfree=my_free ...
• 偽陽性消える
• 作業中にPHPのバグをいくつか見つける
• いろいろなmalloc/free
• Apache APR, MySQL由来のpstrdupなどの外部ライブラリ
• PHP内部のfree, interned_free, ...
• 整合性を保つよう調整
ラップが難しい(1/2)
32 / 36
33. • dlopen
• dlopenの中でもmalloc
• デバッグ用にfprintfするとfprintfで先にmallocされて順序が変
わる
• 戦略
• (コードレベルで)全てのmalloc, freeを置き換える
• LD_PRELOADで自前のmalloc/freeに置き換え
• これで普通のプログラムは落ちなくなった
• ASanで落ちていた(ASanの誤動作)の場所も判明
ラップが難しい(2/2)
33 / 36
34. • 泥臭い方法
• ASLRを無効化しておく
• sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"
• 実行
• おかしなfreeを受けたところでそのポインタを記録
• そのポインタをmallocした箇所でbreak & btで該当ソース
• 主な結論
• PHPのオプションのfast_shutdownが有効なときに
malloc/freeの不一致コードに突入
• コードがカオスなので修正時間は無い
• いくつかバグ報告したしまあいいか
• fast_shutdown=0で回避可能
当時の解決
34 / 36
35. • clang
• dlopenにRTLD_DEEPBINDをつけるとエラー
• 昔はこれで誤検知?
• typo発見 incompatibe → incompatible
• macOSのdlopenは最初からRTLD_DEEPBIND相当の挙動
• gccのdlopenにはこの制約はなさそう
最近のclang/gcc
shared library with RTLD_DEEPBIND flag which is
incompatibe with sanitizer runtime (see
https://github.com/google/sanitizers/issues/611 for
details).
35 / 36
36. • RTLD_DEEPBIND
• dlopenするライブラリのシンボルの参照領域を
グローバル領域よりも前に配置する
• 例
• RTLD_DEEPBINDなし ; clock()は123を返す
• RTLD_DEEPBINDあり ; clock()はオリジナルの値を返す
補足
main.c
h = dlopen("sub.so");
f = dlsym(h, "sub");
f();
sub.c // sub.so
void sub() {
int t = (int)clock();
printf("clock()=%d¥n", t);
}
pred.c // pred.so
clock_t clock() { return 123; }
LD_PRELOAD=./pred.so ./main
36 / 36