gdb hacks - ハードウェアのデバッグ機能を使う (前編)
gdb hacks 第 5 回。プロセッサの中にはデバッグ支援機能をハードウェアで持つものがあり、例えば IA-32 アーキテクチャでは 8 本のデバッグレジスタ (DR0-DR7) というものが用意されています。gdb はこれをどのように使用しているかを見てみます。
IA-32 のデバッグ機能については Intel のマニュアル IA-32 Intel® Architecture Software Developer's Manual, Volume 3B: System Programming Guide, Part 2 の CHAPTER 18 Debugging and Performance Monitoring に完全な解説があります。
はしょって説明すると、DR0-DR3 の 4 つのレジスタで指定したリニアアドレスの示すメモリ領域にプロセッサのアクセスがあると、INT 1 のデバッグ例外を発生させてくれるというものです。残りの 4 つのレジスタは予約か、デバッグ支援機能の制御用となっています。
さて、Linux ではデバッグレジスタはプロセスごとにその値を設定できます。設定には例によって ptrace システムコールを使います。ヘッダファイル asm/user.h を見ると struct user に int u_debugreg[8]; というメンバが定義されているのがわかります。
今回の実験に使うプログラムはこれです。実験環境は例によって i386 の Debian GNU/Linux (sid) で、gdb 6.4 を使用しています。
#include <stdio.h> int a, b, c, d, e; int main(void) { int i; for (i = 0; i < 1000; i++); a = b = c = d = e = i; printf("%d %d %d %d %d\n", a, b, c, d, e); return 0; }
gdb にはハードウェアによるブレークポイントを設定する hbreak という命令があります。まずはこれを使ってみます。
% gcc -g -Wall -O0 hoge.c % gdb a.out (gdb) start Breakpoint 1 at 0x80483a2: file hoge.c, line 6. Starting program: /home/yaegashi/tmp/a.out main () at hoge.c:6 6 for (i = 0; i < 1000; i++); (gdb) set debug target 1 (gdb) set debug lin-lwp 1 (gdb) hbreak 7 Hardware assisted breakpoint 2 at 0x80483b9: file hoge.c, line 7. (gdb) c Continuing. child:target_xfer_partial (2, (null), 0x830eae8, 0x0, 0xb7f724a0, 1) = 1, bytes = 55 child:target_xfer_partial (2, (null), 0x0, 0xbfd68270, 0xb7f724a0, 1) = 1, bytes = cc CW: waitpid 16844 received トレース/ブレイクポイント トラップ (stopped) child:target_xfer_partial (2, (null), 0x0, 0xbfd68260, 0xb7f724a0, 1) = 1, bytes = 55 Breakpoint 2, main () at hoge.c:7 7 a = b = c = d = e = i; (gdb) info symbol 0xb7f724a0 _dl_debug_state in section .text _dl_debug_state in section .text
ターゲットデバッグ出力によればアドレス 0xb7f724a0 に 0xcc (INT3) を書き込んでいますが、これは gdb が勝手に設定した別のソフトウェアブレークポイントによるものです。hbreak 命令で設定した main() 関数内のブレークポイントにはアクセスしていないことがわかります。
ハードウェアブレークポイントの利点は、このようにブレークポイント命令 (IA-32 の場合 0xcc (INT3)) を挿入するためにメモリを書き換える必要がないということです。Linux プロセスがデバッグターゲットの場合あまり関係ありませんが、ROM に格納されたプログラムをデバッグするときなどは大いに役に立ちます。
さて、もっと多くのハードウェアブレークポイントを設定するとどうなるでしょうか。
% gdb a.out (gdb) hbreak 6 Hardware assisted breakpoint 1 at 0x80483a2: file hoge.c, line 6. (gdb) hbreak 7 Hardware assisted breakpoint 2 at 0x80483b9: file hoge.c, line 7. (gdb) hbreak 8 Hardware assisted breakpoint 3 at 0x80483e9: file hoge.c, line 8. (gdb) hbreak 9 Hardware assisted breakpoint 4 at 0x8048426: file hoge.c, line 9. (gdb) hbreak 10 Hardware assisted breakpoint 5 at 0x804842b: file hoge.c, line 10. (gdb) run Starting program: /home/yaegashi/tmp/a.out Warning: Cannot insert hardware breakpoint 5. Could not insert hardware breakpoints: You may have requested too many hardware breakpoints/watchpoints.
実行できませんでした。5 つ目のハードウェアブレークポイントを設定しようとしたところでデバッグレジスタが尽きてしまったためです。ウォッチポイントについても同様で、
% gdb a.out (gdb) watch a+b+c+d+e Hardware watchpoint 1: a + b + c + d + e (gdb) run Starting program: /home/yaegashi/tmp/a.out Hardware watchpoint 1: a + b + c + d + e warning: Could not remove hardware watchpoint 1. Warning: Could not insert hardware watchpoint 1. Could not insert hardware breakpoints: You may have requested too many hardware breakpoints/watchpoints.
このように 4 つより多くの変数の値をハードウェアでウォッチしようとすると失敗します。
ハードウェアを使わないように set can-use-hw-watchpoint 0 という指定をすることでこの制限を回避できますが、この場合シングルステップ実行しつつ値を調べるということをやるため、デバッグプロセスの速度が極端に遅くなってしまいます。
ハードウェアウォッチポイントに関しては他にも注意事項があり、たとえば最適化によってレジスタに割り当てあてられた変数を、ハードウェアでウォッチすることはできないということがあります。
IA-32 で gdb がシングルステップを繰り返さざるを得ない (つまり遅い) 命令に step や next があります。これはソースコード上で新しい行に到達するまで実行するという命令で、よく使われるのですが、これもデバッグ出力を有効にして試してみればどういうことが起きているかがわかります。
% gdb a.out (gdb) start Breakpoint 1 at 0x80483a2: file hoge.c, line 6. Starting program: /home/yaegashi/tmp/a.out main () at hoge.c:6 6 for (i = 0; i < 1000; i++); (gdb) set debug infrun 1 (gdb) next infrun: proceed (addr=0xffffffff, signal=144, step=1) infrun: resume (step=1, signal=0) infrun: wait_for_inferior infrun: infwait_normal_state infrun: TARGET_WAITKIND_STOPPED infrun: stop_pc = 0x80483a9 infrun: stepping inside range [0x80483a2-0x80483b9] infrun: resume (step=1, signal=0) .... (以下 24000 行ほど略)
このように非常に単純な for 文ひとつから抜けるために gdb-ターゲット間で膨大なやりとりが発生することがわかります。これは特にリモートデバッグをしている場合には深刻です。
ここで gdb が着目しているのは stepping inside range [0x80483a2-0x80483b9] というアドレス範囲で、要するに EIP がここを抜けた時点で止まるような機能がハードウェアにあればよいのですが、IA-32 には残念ながらありません。
筆者が知る限りでは SH-3/4 プロセッサにはそのような機能が内蔵されているものがあり、PC が指定した範囲に到達したときに例外を起こすことができます。このようなプロセッサではカーネルや gdb が対応していれば、より効率のよいデバッグができるでしょう。
つづく。
カテゴリ
gdb hacksトラックバック(0)
このブログ記事を参照しているブログ一覧: gdb hacks - ハードウェアのデバッグ機能を使う (前編)
このブログ記事に対するトラックバックURL: http://www.keshi.org/mt/mt-tb.cgi/36
コメントする