函數調用分析

轉自 http://blog.csdn.net/blackbillow/archive/2009/01/20/3839586.aspx

 

測試環境:Red Hat Linux 7.2

註解 :
eip 寄存器內容式當前執行指令的下一條指令的地址;
mov eax, ebx 將寄存器eax內容移到ebx; 機器指令2字節。
leave 指令所做的操作相當於mov ebp, esp 然後 pop ebp; 機器指令1字節。
ret 指令所做的操作相當於pop eip; 機器指令1字節。
call addr 指令所做的操作相當於push eip 然後 jmp addr; 機器指令4字節。
jmp addr 執行所做的操作相當於 mov addr, eip; 機器指令2字節。

注意:我們所說的”相當於”是在功能上的等價,並不是實際機器指令的等價。

1. 編寫測試程序,如下:
//file name : cc.c
#include

int foo(int fi,int fj)
{
int fk;
fk=3;
return(0);
}
int main()
{
int mi;
int mj;
mi=1;
mj=2;
foo(mi,mj);

return(0);
}

2. 對代碼進行編譯:
gcc –g –o cc cc.c

3. 用gdb進行debug:gdb cc
(1) 查看源程序:
(gdb) list
4 {
5 int fk;
6 fk=3;
7 return(0);
8 }
9 int main()
10 {
11 int mi;
12 int mj;
13 mi=1;
(gdb)
14 mj=2;
15 foo(mi,mj);
16
17 return(0);
18 }

(2)查看彙編代碼:
(gdb) disass main
Dump of assembler code for function main:
0x8048444 : push %ebp
0x8048445 : mov %esp,%ebp
0x8048447 : sub $0x8,%esp
0x804844a : movl $0x1,0xfffffffc(%ebp)
0x8048451 : movl $0x2,0xfffffff8(%ebp)
0x8048458 : sub $0x8,%esp
0x804845b : pushl 0xfffffff8(%ebp)
0x804845e : pushl 0xfffffffc(%ebp)
0x8048461 : call 0x8048430
0x8048466 : add $0x10,%esp
0x8048469 : mov $0x0,%eax
0x804846e : leave
0x804846f : ret
End of assembler dump.
(gdb) disass foo
Dump of assembler code for function foo:
0x8048430 : push %ebp
0x8048431 : mov %esp,%ebp
0x8048433 : sub $0x4,%esp
0x8048436 : movl $0x3,0xfffffffc(%ebp)
0x804843d : mov $0x0,%eax
0x8048442 : leave
0x8048443 : ret
End of assembler dump.

(3)在主函數設置斷點,並執行程序,讓程序在main函數剛開始時暫停:
(gdb) break 9
Breakpoint 1 at 0x8048444: file c1.c, line 9.
(gdb) run
Starting program: /home/syf/p/cc

Breakpoint 1, main () at c1.c:10
10 {

(4)查看關鍵寄存器內容:
(gdb) i reg esp
esp 0xbffffaec 0xbffffaec
(gdb) i reg ebp
ebp 0xbffffb28 0xbffffb28
(gdb) i reg eip
eip 0x8048444 0x8048444

(5)查看棧空間內容:
(gdb) x/32xw 0xbffffae0
0xbffffae0: 0x080494e8 0x080495e4 0xbffffaf8 0x40046507
0xbffffaf0: 0x00000001 0xbffffb54 0xbffffb5c 0x080482d2
0xbffffb00: 0x080484b0 0x00000000 0xbffffb28 0x400464f1
0xbffffb10: 0x00000000 0xbffffb5c 0x4015ec3c 0x40016300
0xbffffb20: 0x00000001 0x08048330 0x00000000 0x08048351
0xbffffb30: 0x08048444 0x00000001 0xbffffb54 0x080482bc
0xbffffb40: 0x080484b0 0x4000dc14 0xbffffb4c 0x40016944
0xbffffb50: 0x00000001 0xbffffc53 0x00000000 0xbffffc62

注意:ebp內容是:0xbffffb28,這個地址對應的內容是0x00000000;

(6)執行一條機器指令(0x8048444 : push %ebp),即將ebp壓棧:
(gdb) si
0x08048445 10 {
(gdb) i reg esp
esp 0xbffffae8 0xbffffae8
(gdb) i reg ebp
ebp 0xbffffb28 0xbffffb28
(gdb) i reg eip
eip 0x8048445 0x8048445
(gdb) x/12xw 0xbffffae0
0xbffffae0: 0x080494e8 0x080495e4 0xbffffb28 0x40046507
0xbffffaf0: 0x00000001 0xbffffb54 0xbffffb5c 0x080482d2
0xbffffb00: 0x080484b0 0x00000000 0xbffffb28 0x400464f1

可以看到,ebp(內容是0xbffffb28)被壓到0xbffffae8處。

(7)執行一條機器指令(0x8048445 : mov %esp,%ebp),即將esp->ebp,查看寄存器內容和堆內容:
(gdb) si
0x08048447 in main () at c1.c:10
10 {
(gdb) i reg ebp
ebp 0xbffffae8 0xbffffae8
(gdb) i reg esp
esp 0xbffffae8 0xbffffae8
(gdb) i reg eip
eip 0x8048447 0x8048447
(gdb) x/12xw 0xbffffae0
0xbffffae0: 0x080494e8 0x080495e4 0xbffffb28 0x40046507
0xbffffaf0: 0x00000001 0xbffffb54 0xbffffb5c 0x080482d2
0xbffffb00: 0x080484b0 0x00000000 0xbffffb28 0x400464f1

可以看到,棧空間內容沒變,只是寄存器ebp變成了esp的值。

(8) 執行三條機器指令,爲mi,mj賦值。
三條指令是:
0x8048447 : sub $0x8,%esp
0x804844a : movl $0x1,0xfffffffc(%ebp)
0x8048451 : movl $0x2,0xfffffff8(%ebp)
這三條指令的作用是爲mi,mi賦值。

(gdb) si
13 mi=1;
(gdb) si
14 mj=2;
(gdb) si
15 foo(mi,mj);
(gdb) i reg esp
esp 0xbffffae0 0xbffffae0
(gdb) i reg ebp
ebp 0xbffffae8 0xbffffae8
(gdb) i reg eip
eip 0x8048458 0x8048458
(gdb) x/12xw 0xbffffae0
0xbffffae0: 0x00000002 0x00000001 0xbffffb28 0x40046507
0xbffffaf0: 0x00000001 0xbffffb54 0xbffffb5c 0x080482d2
0xbffffb00: 0x080484b0 0x00000000 0xbffffb28 0x400464f1

可以看到,系統爲mi,mj在棧內分配了空間(對應的地址分別是:0xbffffae4,0xbffffae0),並進行了賦值(分別是:0x00000001,0x00000002)。

(9)執行下一條指令(0x8048458 : sub $0x8,%esp),將堆棧棧空間加8個字節的空間,沒有意義,可以略去,在優化代碼時,將被略去。
注:優化代碼的方法是:gcc –O –o cc cc.c
(gdb) si
0x0804845b 15 foo(mi,mj);

(10)執行下兩條指令,堆將要調用的函數foo(int, int)的參數壓棧。兩條指令是:
0x804845b : pushl 0xfffffff8(%ebp)
0x804845e : pushl 0xfffffffc(%ebp)
這兩條指令的作用是對調用的函數foo(int fi, int fj)的參數壓棧。

(gdb) si
0x0804845e 15 foo(mi,mj);
(gdb) si
0x08048461 15 foo(mi,mj);
(gdb) i reg esp
esp 0xbffffad0 0xbffffad0
(gdb) i reg ebp
ebp 0xbffffae8 0xbffffae8
(gdb) i reg eip
eip 0x8048461 0x8048461
(gdb) x/12xw 0xbffffad0
0xbffffad0: 0x00000001 0x00000002 0xbffffaf8 0x08048411
0xbffffae0: 0x00000002 0x00000001 0xbffffb28 0x40046507
0xbffffaf0: 0x00000001 0xbffffb54 0xbffffb5c 0x080482d2

(11)執行下一條指令:0x8048461 : call 0x8048430 。
這條call指令可以分解成兩條指令:push eip;jmp 0x8048430。
其作用是保存main函數的foo(int, int)函數調用後的下一條指令的地址(0x8048466),以便foo(int ,int)函數調用返回後在main()函數繼續指令,同時還要跳轉到foo(int, int)的地址(0x8048430),以便執行函數foo(int ,int);

注意:此時的eip爲0x8048466,對應的指令是:0x8048466 : add $0x10,%esp

(gdb) si
foo (fi=1, fj=-1073743020) at c1.c:4
4 {
(gdb) i reg esp
esp 0xbffffacc 0xbffffacc
(gdb) i reg ebp
ebp 0xbffffae8 0xbffffae8
(gdb) i reg eip
eip 0x8048430 0x8048430
(gdb) x/12xw 0xbffffacc
0xbffffacc: 0x08048466 0x00000001 0x00000002 0xbffffaf8
0xbffffadc: 0x08048411 0x00000002 0x00000001 0xbffffb28
0xbffffaec: 0x40046507 0x00000001 0xbffffb54 0xbffffb5c

(12)執行下兩條指令,並查看寄存器和堆內容:
這兩條指令是:
0x8048430 : push %ebp
0x8048431 : mov %esp,%ebp

這兩條指令的作用是對main函數中的 ebp進行壓棧,以便返回時用(和第15步對應),同時將esp賦值爲ebp(ebp->esp),一般本函數返回(和第16步對應)。

(gdb) si
0x08048431 4 {
(gdb) si
0x08048433 in foo (fi=1, fj=2) at c1.c:4
4 {
(gdb) i reg esp
esp 0xbffffac8 0xbffffac8
(gdb) i reg ebp
ebp 0xbffffac8 0xbffffac8
(gdb) i reg eip
eip 0x8048433 0x8048433
(gdb) x/12xw 0xbffffac8
0xbffffac8: 0xbffffae8 0x08048466 0x00000001 0x00000002
0xbffffad8: 0xbffffaf8 0x08048411 0x00000002 0x00000001
0xbffffae8: 0xbffffb28 0x40046507 0x00000001 0xbffffb54

可以看到ebp的內容被壓棧,壓在地址0xbffffac8處。

(13)執行下兩條指令,並查看寄存器和堆內容:
這兩條指令是:
0x8048433 : sub $0x4,%esp
0x8048436 : movl $0x3,0xfffffffc(%ebp)
其作用是爲fk賦值。

(gdb) si
7 return(0);
(gdb) i reg esp
esp 0xbffffac4 0xbffffac4
(gdb) i reg ebp
ebp 0xbffffac8 0xbffffac8
(gdb) i reg eip
eip 0x804843d 0x804843d
(gdb) x/12xw 0xbffffac4
0xbffffac4: 0x00000003 0xbffffae8 0x08048466 0x00000001
0xbffffad4: 0x00000002 0xbffffaf8 0x08048411 0x00000002
0xbffffae4: 0x00000001 0xbffffb28 0x40046507 0x00000001

可以看到系統在堆中爲fk分配空間(0xbffffac4),並賦值(0x00000003)。

(14)執行下一條指令(0x8048469 : mov $0x0,%eax),其作用是爲eax清空,即使返回值爲0。
(gdb) i reg esp
esp 0xbffffac4 0xbffffac4
(gdb) i reg ebp
ebp 0xbffffac8 0xbffffac8
(gdb) i reg eip
eip 0x8048442 0x8048442
(gdb) x/12xw 0xbffffac4
0xbffffac4: 0x00000003 0xbffffae8 0x08048466 0x00000001
0xbffffad4: 0x00000002 0xbffffaf8 0x08048411 0x00000002
0xbffffae4: 0x00000001 0xbffffb28 0x40046507 0x00000001

(15)執行下一條指令(0x8048442 : leave),這條指令可以分解爲mov ebp esp和pop ebp,其作用使彈出12步中保存的main函數中的ebp,爲返回做準備.
(gdb) si
0x08048443 in foo (fi=1, fj=-1073743020) at c1.c:8
8 }
(gdb) i reg esp
esp 0xbffffacc 0xbffffacc
(gdb) i reg ebp
ebp 0xbffffae8 0xbffffae8
(gdb) i reg eip
eip 0x8048443 0x8048443
(gdb) x/12xw 0xbffffacc
0xbffffacc: 0x08048466 0x00000001 0x00000002 0xbffffaf8
0xbffffadc: 0x08048411 0x00000002 0x00000001 0xbffffb28
0xbffffaec: 0x40046507 0x00000001 0xbffffb54 0xbffffb5c

(16)執行下一條指令(0x8048443 : ret),即pop eip,返回11步中保存的返回地址:0x8048466 : add $0x10,%esp。

(gdb) si
0x08048466 in main () at c1.c:15
15 foo(mi,mj);
(gdb) i reg esp
esp 0xbffffad0 0xbffffad0
(gdb) i reg ebp
ebp 0xbffffae8 0xbffffae8
(gdb) i reg eip
eip 0x8048466 0x8048466
(gdb) x/12xw 0xbffffad0
0xbffffad0: 0x00000001 0x00000002 0xbffffaf8 0x08048411
0xbffffae0: 0x00000002 0x00000001 0xbffffb28 0x40046507
0xbffffaf0: 0x00000001 0xbffffb54 0xbffffb5c 0x080482d2

(17)此時,eip爲:0x8048466 : add $0x10,%esp,
其後的指令還有:
0x8048466 : add $0x10,%esp
0x8048469 : mov $0x0,%eax
0x804846e : leave
0x804846f : ret

至此函數調用完成,我們不對下面的內容進行分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章