一個函數調用另一個函數,需先將被調用函數參數實參)准備好。然後執行call指令,完成了兩個任務:
1.將調用函數下一條指令入棧,被調函數返回後將取這條指令繼續執行.
2.修改指令指針寄存器eip的值,使其指向被調函數的執行位置.
一個函數被調用,首先要完成以下動作建立新棧幀):
將調用函數的棧幀棧底地址入棧,即將bp寄存器的值壓入調用棧中,將來返回時用。
設置被調函數的棧幀棧底地址ebp=esp。
函數調用和返回過程的規則:該規則與操作系統和編譯器相關)
參數壓棧傳遞,並且是從右向左;
ebp總是指向棧幀的棧底;
返回值通過eax寄存器傳遞;
被調用函數棧幀的棧底保存著調用函數棧幀的棧底;
被調用函數棧幀的棧底上一個存儲單元保存著該函數返回後下一條指令的地址。
用下面的代碼來解釋以上規則。
int bar(int c, int d)
{
int e = c +d;
return e;
}
int foo(int a, int b)
{
return bar(a, b);
}
int main(void)
{
foo(2, 3);
return 0;
}
在編譯時加上-g選項,用objdump反匯編時可以把C代碼和匯編代碼穿插起來顯示,這樣C代碼和匯編代碼的對應關系看得更清楚,這裡只列出我們關心的部分:指令地址每次編譯都不同只作參考。)
080483b4 <bar>:
#include <stdio.h>
int bar(int c, int d)
{
80483b4:55 push %ebp
80483b5:89 e5 mov %esp,%ebp
80483b7:83 ec 10 sub $0x10,%esp
int e = c +d;
80483ba:8b 45 0c mov 0xc(%ebp),%eax
80483bd:8b 55 08 mov 0x8(%ebp),%edx
80483c0:8d 04 02 lea (%edx,%eax,1),%eax
80483c3:89 45 fc mov %eax,-0x4(%ebp)
return e;
80483c6:8b 45 fc mov -0x4(%ebp),%eax
}
80483c9:c9 leave
80483ca:c3 ret
080483cb <foo>:
int foo(int a, int b)
{
80483cb:55 push %ebp
80483cc:89 e5 mov %esp,%ebp
80483ce:83 ec 08 sub $0x8,%esp
return bar(a, b);
80483d1:8b 45 0c mov 0xc(%ebp),%eax
80483d4:89 44 24 04 mov %eax,0x4(%esp)
80483d8:8b 45 08 mov 0x8(%ebp),%eax
80483db:89 04 24 mov %eax,(%esp)
80483de:e8 d1 ff ff ff call 80483b4 <bar>
}
80483e3:c9 leave
80483e4:c3 ret
080483e5 <main>:
int main(void)
{
80483e5:55 push %ebp
80483e6:89 e5 mov %esp,%ebp
80483e8:83 ec 08 sub $0x8,%esp
foo(2, 3);
80483eb:c7 44 24 04 03 00 00 movl $0x3,0x4(%esp)
80483f2:00
80483f3:c7 04 24 02 00 00 00 movl $0x2,(%esp)
80483fa:e8 cc ff ff ff call 80483cb <foo>
return 0;
80483ff:b8 00 00 00 00 mov $0x0,%eax
}
(gdb) info registers bar函數的棧幀)
esp 0xbffff718
ebp 0xbffff728
foo函數的棧幀
esp 0xbffff730
ebp 0xbffff738
main函數的棧幀
esp 0xbffff740
ebp 0xbffff748
以上是函數調用過程中的棧幀框架,現在看如何返回:
bar函數的返回過程:
mov -0x4(%ebp),%eax;將e的值賦給eax。
leave;它是push %ebp和mov %esp,%ebp的逆操作。把ebp的值賦給esp,現在esp的值是0xbffff728,ebp和esp都指向bar的棧底;棧底存放的正是foo函數的ebp。彈出ebp,現在ebp指向了foo函數的棧底,同時 esp加4.esp指向了foo函數的棧頂。foo函數的棧頂存放的是foo 函數中call指令的下一條指令地址80483e3。到此為止,現在已經返回到foo函數的棧幀。
ret;該指令是call指令的逆操作;把esp的值恢復給eip,同時esp加4。修改了程序計數器eip,現在跳轉到 地址80483e3,該處指令是leave ;ret;重復上述過程即返回到main函數。
main函數調用foo,foo調用bar,bar返回到foo,foo返回到main整個調用過程結束。
本文出自 “note” 博客,請務必保留此出處http://gcfred.blog.51cto.com/7948335/1302721