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.

センパイ!このプログラムクラッシュするんですけど。。。

1,959 views

Published on

講師:@y_jono
担当:Sapporo.cpp (札幌C++勉強会)
対象者:プログラムをクラッシュさせたことがある人
前提知識:コマンドラインの操作方法、C++プログラムのコンパイル方法の知識がある方。

後輩からメモリアクセス違反やデータ競合が起こるプログラムの相談を受けた時、あなたならどう対処しますか?

このセッションでは、それらのバグに対して
1.バグを特定する方法(デバッグ)
2.バグを検出する方法(テスト)
3.バグを予防する方法(レビュー)
を紹介します。
これらの手法によってプログラムの品質を高めることができます。

C++は使用できるコンパイラ、デバッガ、プロファイラなどの解析ツールが
OSやCPUアーキテクチャごとに異なります。
商用ツール以外には選択肢がないこともあります。
このセッションではLinux, Android, OSX, iOS を想定してお話しますが、
Windows環境についても時間の許す限りで紹介できればと思います。

Published in: Software
  • Be the first to comment

センパイ!このプログラムクラッシュするんですけど。。。

  1. 1. セミナー講師:@y_jono イラスト:@masshirohuyu Sapporo.cpp OSC Hokkaido 2015
  2. 2. 自己紹介 • Twitter : @y_jono • Github : y-jono • Sapporo.cpp (札幌C++勉強会)
  3. 3. 登場するキャラクター 知識豊富! 長い経験! 頼れる! 最近ようやく 開発ができる ようになって きた。新人。 今日の主役? 虫らしい
  4. 4. レジュメ •はじめに • デバッグってなんだろう? •コウハイ君の事件簿 Part1 •時間がたつとプログラムが落ちる!? •コウハイ君の事件簿 Part2 •たまにだけどプログラムが落ちることがある!? •まとめ
  5. 5. センパイ!質問が! なに?
  6. 6. センパイ!質問が! え・・・? この子なにものです か?
  7. 7. バグとは? こいつは業界で なんと呼ばれているか? 知ってる? ばぎー??
  8. 8. バグ関連用語
  9. 9. バグ関連用語
  10. 10. とりあえず・・ 今日はバギー ・・・。
  11. 11. バギーについてもっと詳しく バグ 欠陥 不正なプログラ ムコード 感染 不正なプログラ ム状態(メモリ) 障害 不正なプログラ ムの動作 * 引用:デバッグの理論と実践 難しい よう・・
  12. 12. つまり? 不正な (意図しない) 動作 不正な (意図しない) コード 不正な (意図しない) メモリ 「欠陥」 「感染」 「障害」
  13. 13. センパイ!そろそろ・・・ デバッグ すりゃいい じゃん 勉強になります! バギーのとりかた おしえてください!
  14. 14. 記録 • 障害、仕様誤認などの問題を記録 再現 • 障害を再現 • テストケースの自動化と単純化 特定 • 感染源の疑いがある箇所を特定 • 感染の連鎖を分離 修正 • 欠陥を修正 デバッグの進め方 * 引用:デバッグの理論と実践 こんな かんじかな
  15. 15. ・・・ やれやれだぜ デバッグ難しそうなので 手伝ってもらえませんか?
  16. 16. ケーススタディ デバッグ事例を2ケース紹介 • メモリリーク • データ競合 一緒に考えましょう • どうしてデバッグが難しいのか • どう特定するか • どう予防するか
  17. 17. センパイ! このプログラム、時間が経つと クラッシュするんですけど。。。
  18. 18. どんな障害? メモリ確保で、例外が出てクラッシュ してしまいます!!! たぶんメモリが 枯渇したんだな。
  19. 19. どんな障害? たぶんメモリが 枯渇したんだな。 枯渇??メモリはたくさん 載ってるはずですよね? メモリ確保で、例外が出てクラッシュ してしまいます!!! メモリリークが原因だからさ
  20. 20. 記録 • 障害、仕様誤認などの問題を記録 再現 • 障害を再現 • テストケースの自動化と単純化 特定 • 感染源の疑いがある箇所を特定 • 感染の連鎖を分離 修正 • 欠陥を修正 メモリリークのデバッグ
  21. 21. 記録 • メモリ確保時に例外でクラッシュ 再現 • 数時間放置で再現 • 数週間、数ヶ月経過で再現 特定 • クラッシュ箇所とリーク箇所が不一致 • ヒントがクラッシュダンプにない 修正 • 解放処理の追加で済むことが多い メモリリークのデバッグ 主にリーク箇所の特定が問題になる →難しい →簡単 →難しい →難しい →簡単
  22. 22. メモリリークの特定方法の例 解析ツール (過度に信用しないように注意!) • リーク特定用のアロケータ(例えばnew/deleteの再定義) • メモリ確保/解放時にメモリアドレス、 ファイル名、行番号などを記録。 • MFCでは_DEBUGディレクティブで有効になる。 • Valgrind • Address Sanitizer • clang static analyzer  コードレビュー • new, deleteされた不正ポインタの依存関係の解析
  23. 23. コウハイ君にデバッグさせるには • プロジェクトで実績のある特定用のツールが必要 • リーク特定用のアロケータ • 解析ツール • 用意できなければ、コウハイにデバッグさせるのは止めよう • センパイだってツールがなければ大変な作業 • それより、メモリー枯渇を予防する方法を考えよう
  24. 24. センパイによるリーク箇所の特定 • デモ • Address Sanitizer (clang, gcc) • Valgrind (memcheck)
  25. 25. Address Sanitizer
  26. 26. Valgrind(memcheck)
  27. 27. メモリリークの予防 デバッグはとても大変。 だからメモリリークが 発生しにくいコードを 書くことが大切なんだ
  28. 28. メモリーリークはどうして埋め込まれるの? オブジェクト共有方法の設計に不備 •オブジェクトの所有者が不明確 •ポインタ、参照の使い方にルールがない •new, delete を理由なく直接使うべきでない レガシーなC++の使用 •生ポインタやauto_ptrでメモリを管理している •Boostを使いたいが使えない環境だ
  29. 29. メモリリークを未然に防ぐには スマートポインタの利用 • std:: unique_ptr : 所有権を占有する • std:: shared_ptr : 所有権を共有する • C++03以前の環境ならBoostのスマートポインタを採用する スマートポインタって? • 自動でメモリの解放処理を行うクラス。 • ポインタのように振る舞うように設計されている。 • このクラスをポインタのように使用するとメモリリークを防げる。
  30. 30. メモリリークを未然に防ぐには コードレビュー • コウハイ君がnew, deleteを使っている箇所を指摘し、 スマートポインタを使うよう指導しよう。 • どうしてもnew, delete使うときは、対応関係を確認しよう。 • 静的解析ツール(Clang Static Analyzer)を使うと楽
  31. 31. Clang Static Analyzer
  32. 32. センパイ! このプログラム、たまにクラッ シュするんですけど。。。
  33. 33. メモリリークを解決したコウハイ君ですが・・・ またバギーくっつい てしまいました!! え・・・? コウハイ君の新たな問題とは?
  34. 34. おさらい 記録 • 障害、仕様誤認などの問題を記録 再現 • 障害を再現 • テストケースの自動化と単純化 特定 • 感染源の疑いがある箇所を特定 • 感染の連鎖を分離 修正 • 欠陥を修正 まずは手順 を確認しよう
  35. 35. 障害の確認 それで、 どんな障害なの?
  36. 36. 障害の確認 クラッシュするんですけど、 • なかなか再現しなくて・・ • 再現手順がよくわからなく て・・ • 再現環境は複数あって・・ • プログラム開始後からの 発生時間もさまざま・・ • デバッグビルドでもリリース ビルドでも発生します Oh… それで、 どんな障害なの?
  37. 37. 想定される欠陥 たしか並列処理する プログラムだったよね? はい。はじめて並列プロ グラム担当しています。 データ競合による クラッシュじゃないかな?
  38. 38. データ競合によるクラッシュとは どんな障害? • 1つのデータを複数のスレッドが 同時に読み書きすると起こる • 障害の表れ方はさまざま • 障害の再現が難しいことが多い 今回は再現も問題だね。
  39. 39. データ競合の例 #include <pthread.h> int global; void *thread1(void *x) { global++; return NULL; } void *thread2(void *x) { global--; return NULL; } (continued..) int main() { pthread_t t[2]; pthread_create(&t[0], NULL, thread1, NULL); pthread_create(&t[1], NULL, thread2, NULL); pthread_join(t[0], NULL); pthread_join(t[1], NULL); }
  40. 40. データ競合の例 #include <pthread.h> int global; void *thread1(void *x) { global++; return NULL; } void *thread2(void *x) { global--; return NULL; } (continued..) int main() { pthread_t t[2]; pthread_create(&t[0], NULL, thread1, NULL); pthread_create(&t[1], NULL, thread2, NULL); pthread_join(t[0], NULL); pthread_join(t[1], NULL); }
  41. 41. データ競合による障害のデバッグ難易度 難しそうなのはどの工程かな? 再現 • スレッド実行は非決定的。 再現性が低いこともある。 特定 • クリティカルセクションで 守られていない変数の特定 修正 • デグレードしない設計を考え、 適用する どれも難しそう
  42. 42. データ競合による障害のデバッグ難易度 そうだね 再現 • スレッド実行は非決定的。 再現性が低いこともある。 特定 • クリティカルセクションで 守られていない変数の特定 修正 • デグレードしない設計を考え、 適用する 難しい 難しさは 再現性次第 難しい つまり 全部難しいと
  43. 43. どうやって再現するのか 「障害」を再現するにはプログラムへの様々な 「入力」を再現しなければならないんだ。 例えば… 障害発生時と同じ環境 端末、OS、製作中のソフトウェア プログラム実行 データ、ユーザ入力、コミュニケーション、時間、乱数、動作環境 プロセスとスレッドのスケジューリング(再現できない) 物理現象、デバッグツール
  44. 44. どうやって特定するのか •解析ツール • (デモ:Thread Sanitizer, Helgrind) •コードレビュー(のちほど紹介) 「特定」する 方法を見せよう!
  45. 45. Thread Sanitizer
  46. 46. Helgrind
  47. 47. Helgrind
  48. 48. データ競合はどうして埋め込まれるの? なんでデータ競合を埋め込ん でしまったか、わかる? 意識してなかったから ですかね。 オイオイ
  49. 49. データ競合はどうして埋め込まれるの? 初めてききました。 じゃあ、データ競合が発生す る条件は知ってる? それだ!
  50. 50. 1. 対象がatomic変数でないとき、 2. 同一メモリ位置に対するアクセスにおいて、 3. 少なくとも一方が変更(modify)操作であり、 4. 異なるスレッド上から同時に行われるとき。 データ競合が発生する条件 参考:http://yohhoy.hatenablog.jp/entry/2013/12/15/204116#fn-cb31e5fd 同時に「読む」のは問題ないよ これをしらなかったのが 原因かぁ
  51. 51. 未然に防ぐ方法(性能要件確認) • 時間あたりのデータ処理量について早めに決定する • データ処理時間の最大時間は? • データ処理量の最低量は? • 例えば • FPS(frame per second)の平常値・最低値は? • ユーザリクエストからレスポンスまでの間隔は最大何ミリ秒? 性能要件が早めに判明しないと、プロジェクト後期や リリース前に複雑怪奇なコードが生まれるリスクが高まる
  52. 52. 未然に防ぐ方法(コードレビュー) 1. どのデータを並行アクセスから守るの? 2. どんな方法でそのデータを確実に守るの? 3. このソースコードのうち、複数のスレッドが同時にアクセスできる箇所は どこ? 4. このスレッドが保有するのは、これらのmutexのうちのどれ? 5. これらのmutexのうち、他のスレッドが保有する可能性があるのはどれ? 参考:C++ Concurrency in Action
  53. 53. 未然に防ぐ方法(コードレビュー) 6. このスレッドの処理完了と、他のスレッドの処理完了の順番に暗黙の要 求があるか? 7. このスレッドで読み込まれたそのデータはこの時点でまだ有効ですか? 他のスレッドからの修正ができてしまいますか? 8. 他のスレッドがそのデータを変更できると仮定する。その変更はどういっ た意味を持つのか?その変更が起こらないことを保証するにはどうすれ ばよいですか? 参考:C++ Concurrency in Action
  54. 54. 未然に防ぐ方法(テスト) テスト • パフォーマンステスト • ヒューリスティックテスト(意図的に障害が発生しそうなタイミングを 狙うテスト) • 耐久・高負荷テスト(高負荷時にのみ起きるバグを狙う) *参考:C++ Concurrency in Action
  55. 55. おわりに 担当するデバッグ作業の難しさについて考えましょう • 「バグを埋め込んでしまったのはなぜだろう」 • 「再現も特定もなんでこんなに難しいんだろう」 • 「どうやったらバグを防げただろう」 デバッグが難しい理由を考えることで • プログラマは成長できる • プログラマは助けあえる
  56. 56. 付録
  57. 57. std:: unique_ptr オブジェクト1 up1 up2 オブジェクト2 1つのオブジェクトを独占的に所有して管理したいとき用
  58. 58. std::unique_ptr #include <memory> int main() { auto a= new int( 123); // `new`したら明示的に`delete` delete a; } #include <memory> int main() { auto a = std::make_unique<int>( 123 ); } // `std::unique_ptr`は // スコープの終わりで自動的に開放される
  59. 59. std:: shared_ptr • 1つのオブジェクトを複数の所有者で管理したいとき用 • 何箇所から指しているかをカウントしている sp1 sp2 オブジェクト
  60. 60. std:: shared_ptr int main() { std::shared_ptr<Hoge> sp1(nullptr); { auto sp2 = make_shared<Hoge>(); sp1 = sp2; assert(sp1.use_count() == 2); } // sp2が解放され、参照カウントが1下がる assert(sp1.use_count() == 1); } // sp1が解放され、ヒープに確保したオブジェクトが解放される
  61. 61. weak_ptr sp1 sp2 オブジェク ト 参照カウンタ 弱い参照カウン タ wp デリータ • weak_ptrはshared_ptrと 似ているが、weak_ptrか ら参照した回数はカウン トされない。 • shared_ptrから参照され なくなったオブジェクトは weak_ptrから参照されて いても破棄される。 * 図引用:プログラミング言語C++第4版
  62. 62. weak_ptr weak_ptr<Hoge> wp(nullptr); { shared_ptr<Hoge> sp1(nullptr); { auto sp2 = make_shared<Hoge>(); sp1 = sp2; wp = sp1; assert(sp1.use_count() == 2); }; assert(sp1.use_count() == 1); }; assert(wp.expired());

×