gdbによるデバッグのチュートリアル その2

前回の続きです。
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 上記の例だとsum()関数が使っているスタックは0xbffff858-0xbffff848で、0x10ぶんのサイズとなります。

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)


関連記事

コメントを残す

メールアドレスが公開されることはありません。