前回の続きです。
gdbによるデバッグのチュートリアル その1
今回は、レジスタ値の確認方法と、Intelアーキテクチャにおけるスタックの使用方法をみていきます。
レジスタ一覧はinfo registerで確認できます。
(gdb) info register eax 0x14 20 ecx 0xbffff924 -1073743580 edx 0xbffff8b4 -1073743692 ebx 0x2a8ff4 2789364 esp 0xbffff860 0xbffff860 ebp 0xbffff888 0xbffff888 esi 0x0 0 edi 0x0 0 eip 0x80483ff 0x80483ff <main+27> eflags 0x200216 [ PF AF IF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 |
intelのCPUだとeipレジスタがプログラムカウンタ(PC)になります。
現在、0x80483ff番地の命令で止まっている事が分かるので、アドレス指定でlistコマンドを実行すると、11行目にいることが分かります。
(gdb) list *0x80483ff 0x80483ff is in main (test01.c:11). 6 { 7 8 int a = 10; 9 int b = a * 2; 10 11 int ans = sum( a, b ); 12 printf( "%d\n", ans ); 13 return 0; 14 } 15 |
もう一回stepを行うとsum()関数の中に入ります。
ちなみに、nextコマンドを打つと関数の中に入らず、次の行に行きます。(VisualStudioのステップオーバーと同じ機能です)
(gdb) step sum (a=10, b=20) at test01.c:18 18 int result = a + b; |
backtraceで、関数のコールスタックを確認します。
(gdb) backtrace #0 sum (a=10, b=20) at test01.c:18 #1 0x08048413 in main () at test01.c:11 |
info registerコマンドで再度レジスタ値を見ます。
レジスタ名を指定することで、該当のものだけが表示されます。
intelのCPUの場合、FP(frame pointer)はebpレジスタに保持されています。
また、SP(stack pointer)はespです。
(gdb) info register eip ebp esp eip 0x8048439 0x8048439 <sum+6> ebp 0xbffff858 0xbffff858 esp 0xbffff848 0xbffff848 |
SPで指し示すアドレスがスタックの一番上で、FPが現在実行中の関数の情報がある底になります。
スタックは大きな番地から若い方に進んでいくので、SP
xコマンドでメモリの値をダンプできます。
アドレスは16進で直接指定するほかに、レジスタを指定することも可能です。
(gdb) x 0xbffff848 0xbffff848: 0x00000001 (gdb) x $esp 0xbffff848: 0x00000001 |
xの後に”/数字”で、指定サイズ分のメモリダンプが出来ます。
(gdb) x/20 $esp 0xbffff848: 0x00000001 0x080482dd 0x0012f918 0x0804824c 0xbffff858: 0xbffff888 0x08048413 0x0000000a 0x00000014 0xbffff868: 0x00163bdb 0x002a9324 0x002a8ff4 0x0000000a 0xbffff878: 0x00000014 0x002a8ff4 0x08048450 0x00000000 0xbffff888: 0x00000000 0x0014a113 0x00000001 0xbffff924 |
説明のために、4byteづつ行を変えて整形します。
これからスタック領域に積まれた情報を解析していきます。
0xbffff848: 0x00000001 <= $esp 0x080482dd 0x0012f918 0x0804824c 0xbffff858: 0xbffff888 <= $ebp 0x08048413 0x0000000a sum関数 引数aの値 0x00000014 sum関数 引数bの値 0xbffff868: 0x00163bdb 0x002a9324 0x002a8ff4 0x0000000a 0xbffff878: 0x00000014 0x002a8ff4 0x08048450 0x00000000 0xbffff888: 0x00000000 0x0014a113 0x00000001 0xbffff924 |
まず、sum()に渡したパラメータa,bはそれぞれ10,20でしたが、これは$ebp+4, $ebp+8の場所に入っています。
次にsum関数のリターンアドレスがその次に有ります。
backtraceを見て分かるように、戻り先は0x08048413番地です。
(gdb) bt #0 sum (a=10, b=20) at test01.c:18 #1 0x08048413 in main () at test01.c:11 |
その次の0xbffff888は、上位関数であるmainのFPです。
フレームポインタは1個しかないので、関数がコールされるたびに1つ前の関数のFPを、このようにスタックへ乗せていきます。
次の0x0804824cは、sum関数内の自動変数であるint resultの格納場所です。
これは、print命令(p)でresultのアドレス/値をチェックすると分かります。
print命令は後ろに”/x”をつけると16進表記になります。
(gdb) p result $3 = 134513228 (gdb) p/x result $4 = 0x804824c (gdb) p/x &result $5 = 0xbffff854 |
さらにその上にある3word文の領域には、それぞれ値0x0012f918,0x080482dd,0x00000001が入っていますが、これはsum関数が使用するワークエリアです。
というわけで分かった情報を書き加えると、以下の構造になっています。
0xbffff848: 0x00000001 sum関数 作業領域 <= $esp 0x080482dd sum関数 作業領域 $esp+4 0x0012f918 sum関数 作業領域 $esp+8 0x0804824c sum関数 変数result $esp+12 0xbffff858: 0xbffff888 main関数のFPアドレス <= $ebp 0x08048413 sum()のリターンアドレス 0x0000000a sum関数 引数aの値 0x00000014 sum関数 引数bの値 0xbffff868: 0x00163bdb 0x002a9324 0x002a8ff4 0x0000000a 0xbffff878: 0x00000014 0x002a8ff4 0x08048450 0x00000000 0xbffff888: 0x00000000 main関数のFP位置($ebp) 0x0014a113 0x00000001 0xbffff924 |
試しに、もう1命令実行させてresultに値を代入すると、予想通り$esp+12の値が30(0x1e)になりました。
(gdb) next 19 return result; (gdb) x/4 $esp 0xbffff848: 0x00000001 0x080482dd 0x0012f918 0x0000001e <- |
main関数から戻ると、SPである$EIPレジスタの値が、0xbffff888に変わっています。
(gdb) next 20 } (gdb) next main () at test01.c:12 12 printf( "%d\n", ans ); (gdb) info register eip ebp esp eip 0x8048417 0x8048417 <main+51> ebp 0xbffff888 0xbffff888 esp 0xbffff860 0xbffff860 |
continueでプログラムの最後まで一度に実行させてしまいます。
途中で,printfの出力の”30″が表示されています。
(gdb) continue Continuing. 30 [Inferior 1 (process 26593) exited normally] (gdb) |
関連記事
コメントを残す