はじめまして、プログラマーのイシドです。
バグを直したところに、ブログ担当が回ってきましたので、今回はこのバグを追ったときに使った方法を書こうと思います。
開発・実行環境
今回は環境に依存する方法なので、先立って簡単に環境を書いておきます。
- Windows10 64bit 1703
- VisualStudio2015 Update3
- 言語はC++
でビルド&実行しています。
バグの内容
今回のバグは、とあるポインタのアドレスが読み込めない!という例外で止まってしまうもので、アドレスを見るとあまり見慣れないアドレス(上位4バイトが使われている)値でした。
何度かそのバグに遭遇すると規則性が見えてきて、止まったときは毎回同じメンバ変数が変なアドレスを指していて、そのメンバ変数の上位4バイトが何かのIDっぽい数値になっているようでした。これはどこかから破壊されていそうです。
このバグは、複数台を3時間ほど動かしていると出るような感じで、その対象メンバ変数を保持しているインスタンスは数分で新しく生まれ変わります。
ですので、そのインスタンスのコンストラクタで、対象メンバ変数のアドレスにハードウェアブレークポイントを設定し、デストラクタで解除できないものかと考えました。
プログラムから設定
今回試したハードウェアブレークポイントを置く方法をわかりやすいように最小限にしたコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <stdio.h> #include <stdint.h> #include <Windows.h> void SetHardwareBreakpoint(uint64_t address) { CONTEXT context{}; context.ContextFlags = CONTEXT_DEBUG_REGISTERS; context.Dr0 = address; context.Dr7 |= 0x00000001; // 有効 context.Dr7 |= 0x00010000; // 書き込み時 context.Dr7 |= 0x00080000; // 8バイト ::SetThreadContext(::GetCurrentThread(), &context); } void main() { void* pointer{}; SetHardwareBreakpoint(reinterpret_cast<uint64_t>(&pointer)); pointer = nullptr; // ここで例外 } |
これを実行すると、
1 |
ハンドルされない例外が 0x00007FF7D49C1938 (hwbreakpoint.exe) で発生しました: 0x80000004: Single step。 |
という例外が発行されて、書き換えた瞬間に停止します。
これでプログラムからハードウェアブレークポイントが置けるようになったので、後は対象インスタンスのコンストラクタで設定して、デストラクタで無効を設定すればOKです。
今回参考にしたサイトです。
複数スレッド
Dr7レジスタを見るとGlobal Breakpointというフラグがあるようですが、LocalではなくGlobalの方を立てるとなにも起きなくなってしまいました。また、コンテキストはスレッドごとに設定するため、もれなく検知するためにすべてのスレッドに設定する必要があります。
本題とはずれるため割愛しますが、全スレッドを保持していおいて、ブレークポイントの設定は全スレッドに反映されるようにしたほか、設定後に新規で作ったスレッドについても適応されるようにしました。
この対応を入れて待つこと数時間・・無事?壊した犯人を見つけることができました。
気をつけてはいても、やはりバグは起きてしまうもの、、探し方はケースバイケースだと思いますが、固定オフセットで壊すパターンには今後も使えそうです。
それでは今回はこのへんで。