Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo

1

C/C++プログラマのための開発ツール
2016/9/8
光成滋生

2

• (主にLinux上における)プログラム開発、
デバッグ、不具合調査のためのツールの紹介
• 広く浅く
• 同じことを複数の手段で
• キーワードや「できること」を知っていれば後は自分で
概要と目的
2/30

3

• gcc, clang
• ソース読み
• ag, GNU GLOBAL, Doxygen, callgrind
• デバッグ
• gdb, objdump, c++filt, core dump, addr2line
• メモリチェック
• ASan, Valgrind, TCMalloc
• 静的解析
• cppcheck, scan-build
• 実行時解析
• SystemTap, perf
目次
3/30

4

• 警告系オプション
• -Wall -Wextra ; 必須
• コンパイラの警告を無視してはいけない
• https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Dialect-
Options.html
• -Wnon-virtual-dtor
• -Woverloaded-virtual ; virtualを同名の関数で隠してしまった
• 他に-Weffc++など変わりものも
• clangには全ての警告を表示する-Weverythingがある
• ときどき便利
• -pedantic ; C/C++標準でないGNU拡張を使っていないか
C/C++コンパイル時のオプション(1/2)
4/30

5

• 最適化オプション
• -Ofast ; 最適化最大
• -march=native ; コンパイル環境のCPUに合わせた最適化
• -DNDEBUG ; assert()マクロの無効化
• デバッグ用オプション
• -g ; シンボル情報がつく(速度に影響はない)
• -g3 ; マクロも見えるようになる
• -S ; アセンブリ出力(生成コードが意図通りか?)
• objdumpによる逆アセンブルも便利
• objdump -CSlw -M intel
• -C ; C++の関数名を見やすくする(demangle)
• -S ; ソースコードを併記
C/C++コンパイル時のオプション(2/2)
5/30

6

• <vector>のソースを見たいが場所はどこ?
• echo "#include <vector>"|gcc -x c++ -E -|lv
• プロファイルをとる
• -pgオプション
• ただし関数に介入するのでオーバーヘッドあり
• perfやVTune(後述)など別のものがおすすめ
その他
6/30

7

• GNU GLOBAL
• https://www.gnu.org/software/global/
• 関数が定義されている場所を行ったり来たりできる
• 使い方
• 見たいソースのトップディレクトリでgtags
• 各自のエディタに応じて設定すること
• あとはがんばって:-)
• ag
• 高速なgrep ; grep -Irw 単語 ./
• apt install silversearcher-ag
• highway(https://github.com/tkengo/highway)というのも
• Visual Studioなどの統合環境 一度は触ってみるとよい
ソースコード読み補助ツール
7/30

8

• ソースコードのドキュメント自動化ツール
• apt install doxygen
• ソースコードのコメントに書くとマニュアルを生成する
• doxygen -gで設定ファイルを作成
• doxygen Doxyfileでhtmlを作成
• コメントのつけ方
• http://www.doxygen.jp/docblocks.html
Doxygen
8/30

9

• Graphviz
• ものの依存関係を画像表示する
• apt install graphviz
• 関数の呼び出し関係の画像化
• DoxygenのDoxyfileでHAVE_DOT = yes
• callgrind
• 呼び出し回数などを可視化
• apt install kcachegrind
• Valgrind(後述)の一部
• valgrind –tool=callgrind <binary>
• kcachegrind callgrind.out.*
関数呼び出しの可視化
9/30

10

• 落ちるプログラムをデバッグする
• rで実行して落ちたところでbtでバックトレースをみる
gdb(デバッグツール)
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
int main(int argc, char *argv[]) {
char buf[32];
int n = argc == 1 ? 10 : atoi(argv[1]);
printf("n=%d¥n", n);
memset(buf, 'A', n);
printf("n=%d¥n", n);
printf("buf[%d-1]=%c¥n", n, buf[n - 1]);
}
gcc -g t.cpp
gdb --args ./aout 10000
r
...
Program received signal SIGSEGV, Segmentation fault.
10/30

11

• s ; ステップ実行(1行ずつ実行する)
• 最適化オプションありではコードの位置がずれることが多い
• 一度コマンドを実行したあと[return]で同じコマンドを実行
• fin ; 関数の中から外に出るまで一気に動く
• n ; 関数の中に入らないでステップ実行
• up/down スタックフレームを登ったり降りたり
• p 変数 ; 変数を表示する
gdbのコマンドいくつか
11/30

12

• gdbを使わず落ちる場所だけ調べる
• 落ちたときのレジスタ, ip, backtrace, メモリマップなど表示
libSegFault.so
env LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so ¥
SEGFAULT_SIGNALS=all ./a.out 10000
*** Segmentation fault
Register dump:
RAX: 00007ffeb2670fd0 RBX: 0000000000000000 RCX: 00007ffeb2671000
...
Backtrace:
/lib/x86_64-linux-gnu/libc.so.6(memset+0x5d)[0x7f4cf774050d]
./a.out[0x400699]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5)[0x7f4cf76d5f45]
./a.out[0x400569]
12/30

13

• https://github.com/herumi/misc/blob/master/dev/dea
d-lock.cpp
• ./a.outで返って来ない
• psでa.outのプロセスidをさがす
• sudo gdb -p <プロセスid>
• cat /proc/<プロセスid>/mapsなどで情報いろいろみえる
• info threadでthreadが2個あることがわかる
• t 2で2番目に行きbtでバックトレース
dead lockした実行ファイル(1/2)
(gdb) info thread
Id Target Id Frame
2 Thread 0x7f5b3883b700 (LWP 964) "a.out" ...
1 Thread 0x7f5b3984a780 (LWP 963) "a.out" ...
(gdb) t 2
[Switching to thread 2 (Thread 0x7f5b3883b700 (LWP 964))]
#0 0x00007f5b38f15f1c in __lll_lock_wait () from ...
13/30

14

• dead-lock.cppの12行目で止まっていた
• straceで呼ばれたシステムコールの確認
• ltraceで呼ばれたライブラリ関数の確認
• アドレスは毎回変わる
• sudo sysctl -w kernel.randomize_va_space=0で固定化
• セキュリティ低下につながるので本番環境では禁止
dead lockした実行ファイル(1/2)
gdb) bt
#0 ...5f1c in __lll_lock_wait () from ...libpthread.so.0
#1 ...1649 in _L_lock_909 () from ... libpthread.so.0
#2 ...1470 in pthread_mutex_lock () from ... libpthread.so.0
#3 ...0ecc in __gthread_mutex_lock (__mutex=0x7ffcec6c3e90) at ...
#4 ...12fa in std::mutex::lock (this=0x7ffcec6c3e90) at ...
#5 ...137e in std::lock_guard<std::mutex>::lock_guard ...
#6 ...0fbf in f (m=...) at dead-lock.cpp:12
#7 ...274f in std::_Bind_simple<void (*(std:...
14/30

15

• プログラムが異常終了したときの情報を保存したもの
• /proc/sys/kernel/core_patternで保存ファイル名を指定
• 例 : カレントディレクトリにcore.<プロセス名>
• shellでcoreサイズを制限していないか確認
• bashならulimit -a ; tcshならlimit
• 0ならcoreファイルは作られないので設定する
• ulimit -c unlimited / limit coredumpsize unlimited
• SEGVする実行ファイルを実行する
• gdb –c <coreファイル> ./a.outでいつものように操作
core dump
sudo sh -c 'echo core.%p > /proc/sys/kernel/core_pattern'
15/30

16

• coreファイルが無かったとき最低限の情報
• ip ; SEGVしたときに実行していたコードのアドレス
• libc-2.19.soのip番目をさがす
• 00007f55ac1c650d - 7f55ac13a000 = 0x8c50d
• libcの0x8c50d番目は何の関数か
• addr2lineを使う
• memsetのようだ ; 引数がおかしい?(と推測)
• objdump -Sでもわかる
dmesg
[609339.455455] a.out[1088]: segfault at 7fffa910f130 ip 00007f55ac1c650d
sp 00007fffa910ca08 error 6 in libc-2.19.so[7f55ac13a000+1ba000]
% addr2line -e /lib/x86_64-linux-gnu/libc-2.19.so 0x8c50d
/build/eglibc-oGUzwX/eglibc-2.19/string/../sysdeps/x86_64/memset.S:80
16/30

17

• Address Sanitizer(ASan)
• メモリ関係のエラーのチェック
• バッファオーバーフロー
• ヒープオーバーフロー
• スタックバッファーオーバーフロー
• メモリリーク
• -fsanitize=addressをつけてコンパイル
メモリ関係
17/30

18

• ./a.out 32 ; 問題なしだが33を指定すると
実行してみる
% ./a.out 33
n=33
==11277==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffdad25b7a0 at pc
0x0000004a691f bp 0x7ffdad25b650 sp 0x7ffdad25ae08
WRITE of size 33 at 0x7ffdad25b7a0 thread T0
#0 0x4a691e in __asan_memset
...
This frame has 5 object(s):
[32, 36) ''
[48, 52) ''
[64, 72) ''
[96, 128) 'buf'
[160, 164) 'n' <== Memory access at offset 128 partially underflows this variable
SUMMARY: AddressSanitizer: stack-buffer-overflow ??:0 __asan_memset
Shadow bytes around the buggy address:
0x100035a436a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100035a436b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100035a436c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100035a436d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100035a436e0: 00 00 00 00 f1 f1 f1 f1 04 f2 04 f2 00 f2 f2 f2
=>0x100035a436f0: 00 00 00 00[f2]f2 f2 f2 04 f3 f3 f3 00 00 00 00
...
18/30

19

• deleteしていない
メモリ解放し忘れ
% cat t.cpp
int main()
{
char *p = new char[10];
}
% clang++-3.6 -fsanitize=address no_free.cpp -g && ./a.out
=================================================================
==11320==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 10 byte(s) in 1 object(s) allocated from:
#0 0x4dc4f2 in operator new[](unsigned long) (/a.out+0x4dc4f2)
#1 0x4dd24f in main /no_free.cpp:3:12
#2 0x7fc2af5a4f44 in __libc_start_main /build/eglibc-oGUzwX/eglibc-
2.19/csu/libc-start.c:287
SUMMARY: AddressSanitizer: 10 byte(s) leaked in 1 allocation(s).
19/30

20

• delete p;
• cf. std::stringかstd::unique_ptr<char> p(new char[10])を使え
delete追加したけど間違ってる
% cat t.cpp
int main() {
char *p = new char[10];
delete p;
}
% clang++-3.6 -fsanitize=address no_free.cpp -g && ./a.out
==11464==ERROR: AddressSanitizer: alloc-dealloc-mismatch (operator new []
vs operator delete) on 0x60200000eff0
#0 0x4dc942 in operator delete(void*) (/a.out+0x4dc942)
#1 0x4dd30a in main /no_free.cpp:4:2
#2 0x7fc0cf759f44 in __libc_start_main /build/eglibc-oGUzwX/eglibc-
2.19/csu/libc-start.c:287
#3 0x435f46 in _start (/a.out+0x435f46)
20/30

21

• ASanとは別のメモリチェックツール
• ASanとは排他的(ASanありでビルドしたものは動かない)
• 特別なコンパイルオプションは不要
• 他の便利なオプション--leak-check=full, --tool=callgrind
Valgrind
% g++ no_free.cpp -g
% valgrind ./a.out
==11471== Memcheck, a memory error detector
==11471== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==11471== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright
info
==11471== Command: ./a.out
==11471==
==11471== Mismatched free() / delete / delete []
==11471== at 0x4C2C2BC: operator delete(void*) (in
/usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11471== by 0x40066E: main (no_free.cpp:4)
==11471== Address 0x5a22040 is 0 bytes inside a block of size 10 alloc'd
==11471== at 0x4C2B800: operator new[](unsigned long) (in...
21/30

22

• 高速なmalloc/free
• http://goog-perftools.sourceforge.net/doc/tcmalloc.html
• リンク時にライブラリを指定する
• LD_PRELOADで指定する
• デバッグ機能も持つ
• apt install libtcmalloc*
• 他にも同様のツールはいろいろある
TCMalloc
% g++ no_free.cpp -g
% env LD_PRELOAD=/usr/lib/libtcmalloc_debug.so ./a.out
memory allocation/deallocation mismatch at 0x13280e0: allocated with new
[] being deallocated with delete
Abort (core dumped)
22/30

23

• 実行しないでエラーを検出するツール
• ASan, Valgrindは実行時解析
• 商用のものが性能がよいことが多い(ex. Coverity)
• cppcheck ; apt install cppcheck
• Visual Studio の /analyzeとか
• cppcheckでも表示される
• clangに付属のscan-build(Xcodeの静的解析ツール)
静的解析ツール
% cppcheck -enable=all no_free.cpp
Checking no_free.cpp...
[no_free.cpp:4]: (error) Mismatching allocation and deallocation: p
int f(int a) {
if (a != 3 || a != 5) return 4;
return 2;
}
t.cpp(3) : warning C6289: 不適切な演算子です:|| を使用した相互排除は常に 0
でない定数となります。&& を使用しようとしましたか?
23/30

24

• Linux kernelの中身を調べるツール
• https://wiki.ubuntu.com/Kernel/Systemtap
• インストール
• sudo apt install systemtap
• シンボル付きのカーネルが必要
• バイナリのインストール方法
• https://wiki.ubuntu.com/Kernel/Systemtap#Where_to_get_de
bug_symbols_for_kernel_X.3F
• 自分でビルド
• https://wiki.ubuntu.com/Kernel/Systemtap#How_do_I_build_a
_debuginfo_kernel_if_one_isn.27t_available.3F
• sudo dpkg -i linux-*.ddeb linux-*.debのあとリブート
SystemTap
24/30

25

• kernel内の関数の場所を調べる
• write(2)を調べたい
• 内部的にはSyS_write
• fs/read_write.cの514行目にある
• sync(8)したときの流れをみたい
• ext4のwriteまわりの関数は何があるだろう
• なんとなくext4_writepagesを見てみる
使い方
% stap -l 'kernel.function("SyS_write")'
kernel.function("SyS_write@/*/fs/read_write.c:514")
% stap -l 'kernel.function("ext4_write*")'
kernel.function("ext4_write_begin@/*/fs/ext4/inode.c:958")
kernel.function("ext4_write_dquot@/*/fs/ext4/super.c:5032")
...
kernel.function("ext4_writepage_trans_blocks@/*/fs/ext4/inode.c:4888")
kernel.function("ext4_writepages@/*/fs/ext4/inode.c:2473")
25/30

26

• awkライクなスクリプト言語
• コンパイルして実行できる
• 裏でkernel moduleになってloadされる
• ext4_writepagesが呼ばれたときにpidが自分が指定したコマ
ンドなら関数名と引数とバックトレースを表示する
• .call ; 呼ばれたとき
• pid() ; 今のpid
• $$parms ; 引数
stapスクリプト
>cat a.stp
probe kernel.function("ext4_writepages").call
{
if (pid() != target()) exit()
printf("%s(%s)¥n", probefunc(), $$parms)
print_backtrace()
}
26/30

27

• -c <command>でコマンド実行
• いろいろ試してみる
syncコマンドを実行してみる
% sudo stap a.stp -c "sync"
ext4_writepages(mapping=0xffff880402e009b0 wbc=0xffff880405a9bc58)
0xffffffff81247f80 : ext4_writepages+0x0/0xdb0 [kernel]
0xffffffff8115e47e : do_writepages+0x1e/0x40 [kernel]
0xffffffff811eaae0 : __writeback_single_inode+0x40/0x2a0 [kernel]
0xffffffff811eb9ca : writeback_sb_inodes+0x26a/0x440 [kernel]
0xffffffff811ebc3f : __writeback_inodes_wb+0x9f/0xd0 [kernel]
0xffffffff811ebef3 : wb_writeback+0x283/0x320 [kernel]
0xffffffff811ed77c : bdi_writeback_workfn+0x11c/0x4a0 [kernel]
0xffffffff81086078 : process_one_work+0x178/0x470 [kernel]
0xffffffff81086e91 : worker_thread+0x121/0x410 [kernel]
0xffffffff8108dc79 : kthread+0xc9/0xe0 [kernel]
0xffffffff8173a3e8 : ret_from_fork+0x58/0x90 [kernel]
27/30

28

• g++ user-backtrace.cpp -g
• sudo stap user.stp –c “./a.out” | c++filt
• print_ubacktrace
でバックトレース
ユーザランドのバックトレース
probe process("./a.out").function("*").call {
printf("%s -> %s¥n", thread_indent(1), probefunc())
}
probe process("./a.out").function("*").return {
printf("%s <- %s¥n", thread_indent(-1), probefunc())
}
0 a.out(23897): -> main
10 a.out(23897): -> h(int)
26 a.out(23897): -> g(int)
30 a.out(23897): -> f(int)
40 a.out(23897): <- g(int)
42 a.out(23897): -> f(int)
45 a.out(23897): <- g(int)
47 a.out(23897): <- h(int)
48 a.out(23897): -> g(int)
51 a.out(23897): -> f(int)
54 a.out(23897): <- g(int)
56 a.out(23897): -> f(int)
59 a.out(23897): <- g(int)
60 a.out(23897): <- h(int)
62 a.out(23897): <- main
64 a.out(23897): <- 0x7fb394b0cf45
#include <stdio.h>
void f(int x) {
printf("f=%d¥n", x);
}
void g(int x) {
puts("g");
for (int i = 0; i < 2; i++) {
f(x + i);
}
}
void h(int x) {
puts("h");
for (int i = 0; i < 2; i++) {
g((x + i) * (x + i));
}
}
int main(int argc, char *[]) {
h(argc);
}
28/30

29

• SystemTap Beginners Guide
• https://sourceware.org/systemtap/SystemTap_Beginners_Guide/
• スクリプトの文法
• https://sourceware.org/systemtap/langref/Language_elements.html
• サンプルいろいろ
• https://sourceware.org/systemtap/examples/
• 関数いろいろ
• https://sourceware.org/systemtap/man/
• 例 addrからn個のデータを文字列化 kernel_string_n(addr, n)
• Ftraceというkernelのevent記録ツールもある
• cf. ファイルキャッシュクリアの謎
• http://www.slideshare.net/herumi/kernel-fcachebug
参考文献
29/30

30

• CPU内部のカウンタを使って詳細な情報を収集
• apt install linux-tools-common
• perf listで取得可能な一覧を表示
• VM上では取得可能なeventは極めて制限される
• perf stat -e <イベント> <実行ファイル>
• sudo perf top ; 現在のkernelの詳細なtopを表示
• IntelのVTuneはこれのGUI版(便利)
perf
List of pre-defined events (to be used in -e):
cpu-cycles OR cycles [Hardware event]
instructions [Hardware event]
cache-references [Hardware event]
cache-misses [Hardware event]
branch-instructions OR branches [Hardware event]
...
30/30

More Related Content

C/C++プログラマのための開発ツール

  • 2. • (主にLinux上における)プログラム開発、 デバッグ、不具合調査のためのツールの紹介 • 広く浅く • 同じことを複数の手段で • キーワードや「できること」を知っていれば後は自分で 概要と目的 2/30
  • 3. • gcc, clang • ソース読み • ag, GNU GLOBAL, Doxygen, callgrind • デバッグ • gdb, objdump, c++filt, core dump, addr2line • メモリチェック • ASan, Valgrind, TCMalloc • 静的解析 • cppcheck, scan-build • 実行時解析 • SystemTap, perf 目次 3/30
  • 4. • 警告系オプション • -Wall -Wextra ; 必須 • コンパイラの警告を無視してはいけない • https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Dialect- Options.html • -Wnon-virtual-dtor • -Woverloaded-virtual ; virtualを同名の関数で隠してしまった • 他に-Weffc++など変わりものも • clangには全ての警告を表示する-Weverythingがある • ときどき便利 • -pedantic ; C/C++標準でないGNU拡張を使っていないか C/C++コンパイル時のオプション(1/2) 4/30
  • 5. • 最適化オプション • -Ofast ; 最適化最大 • -march=native ; コンパイル環境のCPUに合わせた最適化 • -DNDEBUG ; assert()マクロの無効化 • デバッグ用オプション • -g ; シンボル情報がつく(速度に影響はない) • -g3 ; マクロも見えるようになる • -S ; アセンブリ出力(生成コードが意図通りか?) • objdumpによる逆アセンブルも便利 • objdump -CSlw -M intel • -C ; C++の関数名を見やすくする(demangle) • -S ; ソースコードを併記 C/C++コンパイル時のオプション(2/2) 5/30
  • 6. • <vector>のソースを見たいが場所はどこ? • echo "#include <vector>"|gcc -x c++ -E -|lv • プロファイルをとる • -pgオプション • ただし関数に介入するのでオーバーヘッドあり • perfやVTune(後述)など別のものがおすすめ その他 6/30
  • 7. • GNU GLOBAL • https://www.gnu.org/software/global/ • 関数が定義されている場所を行ったり来たりできる • 使い方 • 見たいソースのトップディレクトリでgtags • 各自のエディタに応じて設定すること • あとはがんばって:-) • ag • 高速なgrep ; grep -Irw 単語 ./ • apt install silversearcher-ag • highway(https://github.com/tkengo/highway)というのも • Visual Studioなどの統合環境 一度は触ってみるとよい ソースコード読み補助ツール 7/30
  • 8. • ソースコードのドキュメント自動化ツール • apt install doxygen • ソースコードのコメントに書くとマニュアルを生成する • doxygen -gで設定ファイルを作成 • doxygen Doxyfileでhtmlを作成 • コメントのつけ方 • http://www.doxygen.jp/docblocks.html Doxygen 8/30
  • 9. • Graphviz • ものの依存関係を画像表示する • apt install graphviz • 関数の呼び出し関係の画像化 • DoxygenのDoxyfileでHAVE_DOT = yes • callgrind • 呼び出し回数などを可視化 • apt install kcachegrind • Valgrind(後述)の一部 • valgrind –tool=callgrind <binary> • kcachegrind callgrind.out.* 関数呼び出しの可視化 9/30
  • 10. • 落ちるプログラムをデバッグする • rで実行して落ちたところでbtでバックトレースをみる gdb(デバッグツール) #include <stdio.h> #include <stdlib.h> #include <memory.h> int main(int argc, char *argv[]) { char buf[32]; int n = argc == 1 ? 10 : atoi(argv[1]); printf("n=%d¥n", n); memset(buf, 'A', n); printf("n=%d¥n", n); printf("buf[%d-1]=%c¥n", n, buf[n - 1]); } gcc -g t.cpp gdb --args ./aout 10000 r ... Program received signal SIGSEGV, Segmentation fault. 10/30
  • 11. • s ; ステップ実行(1行ずつ実行する) • 最適化オプションありではコードの位置がずれることが多い • 一度コマンドを実行したあと[return]で同じコマンドを実行 • fin ; 関数の中から外に出るまで一気に動く • n ; 関数の中に入らないでステップ実行 • up/down スタックフレームを登ったり降りたり • p 変数 ; 変数を表示する gdbのコマンドいくつか 11/30
  • 12. • gdbを使わず落ちる場所だけ調べる • 落ちたときのレジスタ, ip, backtrace, メモリマップなど表示 libSegFault.so env LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so ¥ SEGFAULT_SIGNALS=all ./a.out 10000 *** Segmentation fault Register dump: RAX: 00007ffeb2670fd0 RBX: 0000000000000000 RCX: 00007ffeb2671000 ... Backtrace: /lib/x86_64-linux-gnu/libc.so.6(memset+0x5d)[0x7f4cf774050d] ./a.out[0x400699] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5)[0x7f4cf76d5f45] ./a.out[0x400569] 12/30
  • 13. • https://github.com/herumi/misc/blob/master/dev/dea d-lock.cpp • ./a.outで返って来ない • psでa.outのプロセスidをさがす • sudo gdb -p <プロセスid> • cat /proc/<プロセスid>/mapsなどで情報いろいろみえる • info threadでthreadが2個あることがわかる • t 2で2番目に行きbtでバックトレース dead lockした実行ファイル(1/2) (gdb) info thread Id Target Id Frame 2 Thread 0x7f5b3883b700 (LWP 964) "a.out" ... 1 Thread 0x7f5b3984a780 (LWP 963) "a.out" ... (gdb) t 2 [Switching to thread 2 (Thread 0x7f5b3883b700 (LWP 964))] #0 0x00007f5b38f15f1c in __lll_lock_wait () from ... 13/30
  • 14. • dead-lock.cppの12行目で止まっていた • straceで呼ばれたシステムコールの確認 • ltraceで呼ばれたライブラリ関数の確認 • アドレスは毎回変わる • sudo sysctl -w kernel.randomize_va_space=0で固定化 • セキュリティ低下につながるので本番環境では禁止 dead lockした実行ファイル(1/2) gdb) bt #0 ...5f1c in __lll_lock_wait () from ...libpthread.so.0 #1 ...1649 in _L_lock_909 () from ... libpthread.so.0 #2 ...1470 in pthread_mutex_lock () from ... libpthread.so.0 #3 ...0ecc in __gthread_mutex_lock (__mutex=0x7ffcec6c3e90) at ... #4 ...12fa in std::mutex::lock (this=0x7ffcec6c3e90) at ... #5 ...137e in std::lock_guard<std::mutex>::lock_guard ... #6 ...0fbf in f (m=...) at dead-lock.cpp:12 #7 ...274f in std::_Bind_simple<void (*(std:... 14/30
  • 15. • プログラムが異常終了したときの情報を保存したもの • /proc/sys/kernel/core_patternで保存ファイル名を指定 • 例 : カレントディレクトリにcore.<プロセス名> • shellでcoreサイズを制限していないか確認 • bashならulimit -a ; tcshならlimit • 0ならcoreファイルは作られないので設定する • ulimit -c unlimited / limit coredumpsize unlimited • SEGVする実行ファイルを実行する • gdb –c <coreファイル> ./a.outでいつものように操作 core dump sudo sh -c 'echo core.%p > /proc/sys/kernel/core_pattern' 15/30
  • 16. • coreファイルが無かったとき最低限の情報 • ip ; SEGVしたときに実行していたコードのアドレス • libc-2.19.soのip番目をさがす • 00007f55ac1c650d - 7f55ac13a000 = 0x8c50d • libcの0x8c50d番目は何の関数か • addr2lineを使う • memsetのようだ ; 引数がおかしい?(と推測) • objdump -Sでもわかる dmesg [609339.455455] a.out[1088]: segfault at 7fffa910f130 ip 00007f55ac1c650d sp 00007fffa910ca08 error 6 in libc-2.19.so[7f55ac13a000+1ba000] % addr2line -e /lib/x86_64-linux-gnu/libc-2.19.so 0x8c50d /build/eglibc-oGUzwX/eglibc-2.19/string/../sysdeps/x86_64/memset.S:80 16/30
  • 17. • Address Sanitizer(ASan) • メモリ関係のエラーのチェック • バッファオーバーフロー • ヒープオーバーフロー • スタックバッファーオーバーフロー • メモリリーク • -fsanitize=addressをつけてコンパイル メモリ関係 17/30
  • 18. • ./a.out 32 ; 問題なしだが33を指定すると 実行してみる % ./a.out 33 n=33 ==11277==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffdad25b7a0 at pc 0x0000004a691f bp 0x7ffdad25b650 sp 0x7ffdad25ae08 WRITE of size 33 at 0x7ffdad25b7a0 thread T0 #0 0x4a691e in __asan_memset ... This frame has 5 object(s): [32, 36) '' [48, 52) '' [64, 72) '' [96, 128) 'buf' [160, 164) 'n' <== Memory access at offset 128 partially underflows this variable SUMMARY: AddressSanitizer: stack-buffer-overflow ??:0 __asan_memset Shadow bytes around the buggy address: 0x100035a436a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100035a436b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100035a436c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100035a436d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100035a436e0: 00 00 00 00 f1 f1 f1 f1 04 f2 04 f2 00 f2 f2 f2 =>0x100035a436f0: 00 00 00 00[f2]f2 f2 f2 04 f3 f3 f3 00 00 00 00 ... 18/30
  • 19. • deleteしていない メモリ解放し忘れ % cat t.cpp int main() { char *p = new char[10]; } % clang++-3.6 -fsanitize=address no_free.cpp -g && ./a.out ================================================================= ==11320==ERROR: LeakSanitizer: detected memory leaks Direct leak of 10 byte(s) in 1 object(s) allocated from: #0 0x4dc4f2 in operator new[](unsigned long) (/a.out+0x4dc4f2) #1 0x4dd24f in main /no_free.cpp:3:12 #2 0x7fc2af5a4f44 in __libc_start_main /build/eglibc-oGUzwX/eglibc- 2.19/csu/libc-start.c:287 SUMMARY: AddressSanitizer: 10 byte(s) leaked in 1 allocation(s). 19/30
  • 20. • delete p; • cf. std::stringかstd::unique_ptr<char> p(new char[10])を使え delete追加したけど間違ってる % cat t.cpp int main() { char *p = new char[10]; delete p; } % clang++-3.6 -fsanitize=address no_free.cpp -g && ./a.out ==11464==ERROR: AddressSanitizer: alloc-dealloc-mismatch (operator new [] vs operator delete) on 0x60200000eff0 #0 0x4dc942 in operator delete(void*) (/a.out+0x4dc942) #1 0x4dd30a in main /no_free.cpp:4:2 #2 0x7fc0cf759f44 in __libc_start_main /build/eglibc-oGUzwX/eglibc- 2.19/csu/libc-start.c:287 #3 0x435f46 in _start (/a.out+0x435f46) 20/30
  • 21. • ASanとは別のメモリチェックツール • ASanとは排他的(ASanありでビルドしたものは動かない) • 特別なコンパイルオプションは不要 • 他の便利なオプション--leak-check=full, --tool=callgrind Valgrind % g++ no_free.cpp -g % valgrind ./a.out ==11471== Memcheck, a memory error detector ==11471== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al. ==11471== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info ==11471== Command: ./a.out ==11471== ==11471== Mismatched free() / delete / delete [] ==11471== at 0x4C2C2BC: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==11471== by 0x40066E: main (no_free.cpp:4) ==11471== Address 0x5a22040 is 0 bytes inside a block of size 10 alloc'd ==11471== at 0x4C2B800: operator new[](unsigned long) (in... 21/30
  • 22. • 高速なmalloc/free • http://goog-perftools.sourceforge.net/doc/tcmalloc.html • リンク時にライブラリを指定する • LD_PRELOADで指定する • デバッグ機能も持つ • apt install libtcmalloc* • 他にも同様のツールはいろいろある TCMalloc % g++ no_free.cpp -g % env LD_PRELOAD=/usr/lib/libtcmalloc_debug.so ./a.out memory allocation/deallocation mismatch at 0x13280e0: allocated with new [] being deallocated with delete Abort (core dumped) 22/30
  • 23. • 実行しないでエラーを検出するツール • ASan, Valgrindは実行時解析 • 商用のものが性能がよいことが多い(ex. Coverity) • cppcheck ; apt install cppcheck • Visual Studio の /analyzeとか • cppcheckでも表示される • clangに付属のscan-build(Xcodeの静的解析ツール) 静的解析ツール % cppcheck -enable=all no_free.cpp Checking no_free.cpp... [no_free.cpp:4]: (error) Mismatching allocation and deallocation: p int f(int a) { if (a != 3 || a != 5) return 4; return 2; } t.cpp(3) : warning C6289: 不適切な演算子です:|| を使用した相互排除は常に 0 でない定数となります。&& を使用しようとしましたか? 23/30
  • 24. • Linux kernelの中身を調べるツール • https://wiki.ubuntu.com/Kernel/Systemtap • インストール • sudo apt install systemtap • シンボル付きのカーネルが必要 • バイナリのインストール方法 • https://wiki.ubuntu.com/Kernel/Systemtap#Where_to_get_de bug_symbols_for_kernel_X.3F • 自分でビルド • https://wiki.ubuntu.com/Kernel/Systemtap#How_do_I_build_a _debuginfo_kernel_if_one_isn.27t_available.3F • sudo dpkg -i linux-*.ddeb linux-*.debのあとリブート SystemTap 24/30
  • 25. • kernel内の関数の場所を調べる • write(2)を調べたい • 内部的にはSyS_write • fs/read_write.cの514行目にある • sync(8)したときの流れをみたい • ext4のwriteまわりの関数は何があるだろう • なんとなくext4_writepagesを見てみる 使い方 % stap -l 'kernel.function("SyS_write")' kernel.function("SyS_write@/*/fs/read_write.c:514") % stap -l 'kernel.function("ext4_write*")' kernel.function("ext4_write_begin@/*/fs/ext4/inode.c:958") kernel.function("ext4_write_dquot@/*/fs/ext4/super.c:5032") ... kernel.function("ext4_writepage_trans_blocks@/*/fs/ext4/inode.c:4888") kernel.function("ext4_writepages@/*/fs/ext4/inode.c:2473") 25/30
  • 26. • awkライクなスクリプト言語 • コンパイルして実行できる • 裏でkernel moduleになってloadされる • ext4_writepagesが呼ばれたときにpidが自分が指定したコマ ンドなら関数名と引数とバックトレースを表示する • .call ; 呼ばれたとき • pid() ; 今のpid • $$parms ; 引数 stapスクリプト >cat a.stp probe kernel.function("ext4_writepages").call { if (pid() != target()) exit() printf("%s(%s)¥n", probefunc(), $$parms) print_backtrace() } 26/30
  • 27. • -c <command>でコマンド実行 • いろいろ試してみる syncコマンドを実行してみる % sudo stap a.stp -c "sync" ext4_writepages(mapping=0xffff880402e009b0 wbc=0xffff880405a9bc58) 0xffffffff81247f80 : ext4_writepages+0x0/0xdb0 [kernel] 0xffffffff8115e47e : do_writepages+0x1e/0x40 [kernel] 0xffffffff811eaae0 : __writeback_single_inode+0x40/0x2a0 [kernel] 0xffffffff811eb9ca : writeback_sb_inodes+0x26a/0x440 [kernel] 0xffffffff811ebc3f : __writeback_inodes_wb+0x9f/0xd0 [kernel] 0xffffffff811ebef3 : wb_writeback+0x283/0x320 [kernel] 0xffffffff811ed77c : bdi_writeback_workfn+0x11c/0x4a0 [kernel] 0xffffffff81086078 : process_one_work+0x178/0x470 [kernel] 0xffffffff81086e91 : worker_thread+0x121/0x410 [kernel] 0xffffffff8108dc79 : kthread+0xc9/0xe0 [kernel] 0xffffffff8173a3e8 : ret_from_fork+0x58/0x90 [kernel] 27/30
  • 28. • g++ user-backtrace.cpp -g • sudo stap user.stp –c “./a.out” | c++filt • print_ubacktrace でバックトレース ユーザランドのバックトレース probe process("./a.out").function("*").call { printf("%s -> %s¥n", thread_indent(1), probefunc()) } probe process("./a.out").function("*").return { printf("%s <- %s¥n", thread_indent(-1), probefunc()) } 0 a.out(23897): -> main 10 a.out(23897): -> h(int) 26 a.out(23897): -> g(int) 30 a.out(23897): -> f(int) 40 a.out(23897): <- g(int) 42 a.out(23897): -> f(int) 45 a.out(23897): <- g(int) 47 a.out(23897): <- h(int) 48 a.out(23897): -> g(int) 51 a.out(23897): -> f(int) 54 a.out(23897): <- g(int) 56 a.out(23897): -> f(int) 59 a.out(23897): <- g(int) 60 a.out(23897): <- h(int) 62 a.out(23897): <- main 64 a.out(23897): <- 0x7fb394b0cf45 #include <stdio.h> void f(int x) { printf("f=%d¥n", x); } void g(int x) { puts("g"); for (int i = 0; i < 2; i++) { f(x + i); } } void h(int x) { puts("h"); for (int i = 0; i < 2; i++) { g((x + i) * (x + i)); } } int main(int argc, char *[]) { h(argc); } 28/30
  • 29. • SystemTap Beginners Guide • https://sourceware.org/systemtap/SystemTap_Beginners_Guide/ • スクリプトの文法 • https://sourceware.org/systemtap/langref/Language_elements.html • サンプルいろいろ • https://sourceware.org/systemtap/examples/ • 関数いろいろ • https://sourceware.org/systemtap/man/ • 例 addrからn個のデータを文字列化 kernel_string_n(addr, n) • Ftraceというkernelのevent記録ツールもある • cf. ファイルキャッシュクリアの謎 • http://www.slideshare.net/herumi/kernel-fcachebug 参考文献 29/30
  • 30. • CPU内部のカウンタを使って詳細な情報を収集 • apt install linux-tools-common • perf listで取得可能な一覧を表示 • VM上では取得可能なeventは極めて制限される • perf stat -e <イベント> <実行ファイル> • sudo perf top ; 現在のkernelの詳細なtopを表示 • IntelのVTuneはこれのGUI版(便利) perf List of pre-defined events (to be used in -e): cpu-cycles OR cycles [Hardware event] instructions [Hardware event] cache-references [Hardware event] cache-misses [Hardware event] branch-instructions OR branches [Hardware event] ... 30/30