Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

7,471 views

Published on

研修資料

Published in: Technology
  • Be the first to comment

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

  1. 1. C/C++プログラマのための開発ツール 2016/9/8 光成滋生
  2. 2. • (主にLinux上における)プログラム開発、 デバッグ、不具合調査のためのツールの紹介 • 広く浅く • 同じことを複数の手段で • キーワードや「できること」を知っていれば後は自分で 概要と目的 2/30
  3. 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. 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. 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. 6. • <vector>のソースを見たいが場所はどこ? • echo "#include <vector>"|gcc -x c++ -E -|lv • プロファイルをとる • -pgオプション • ただし関数に介入するのでオーバーヘッドあり • perfやVTune(後述)など別のものがおすすめ その他 6/30
  7. 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. 8. • ソースコードのドキュメント自動化ツール • apt install doxygen • ソースコードのコメントに書くとマニュアルを生成する • doxygen -gで設定ファイルを作成 • doxygen Doxyfileでhtmlを作成 • コメントのつけ方 • http://www.doxygen.jp/docblocks.html Doxygen 8/30
  9. 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. 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. 11. • s ; ステップ実行(1行ずつ実行する) • 最適化オプションありではコードの位置がずれることが多い • 一度コマンドを実行したあと[return]で同じコマンドを実行 • fin ; 関数の中から外に出るまで一気に動く • n ; 関数の中に入らないでステップ実行 • up/down スタックフレームを登ったり降りたり • p 変数 ; 変数を表示する gdbのコマンドいくつか 11/30
  12. 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. 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. 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. 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. 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. 17. • Address Sanitizer(ASan) • メモリ関係のエラーのチェック • バッファオーバーフロー • ヒープオーバーフロー • スタックバッファーオーバーフロー • メモリリーク • -fsanitize=addressをつけてコンパイル メモリ関係 17/30
  18. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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

×