從彙編視角看函式呼叫
C語言函式如下
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, 5);
return 0;
}
讓我們從彙編的角度跟蹤函式的執行,main對應的彙編函式如下:
Dump of assembler code for function main:
12 {
0x0000000000400529 <+0>: 55 push %rbp
0x000000000040052a <+1>: 48 89 e5 mov %rsp,%rbp
13 foo(2, 5);
=> 0x000000000040052d <+4>: be 05 00 00 00 mov $0x5,%esi
0x0000000000400532 <+9>: bf 02 00 00 00 mov $0x2,%edi
0x0000000000400537 <+14>: e8 ce ff ff ff callq 0x40050a <foo>
14 return 0;
0x000000000040053c <+19>: b8 00 00 00 00 mov $0 x0,%eax
15 }
0x0000000000400541 <+24>: 5d pop %rbp
0x0000000000400542 <+25>: c3 retq
End of assembler dump.
剛剛進入main函式,而且已經建立呼叫stack時,對應的暫存器狀態為:
(gdb) info registers rbp rsp
rbp 0x7fffffffdc80 0x7fffffffdc80
rsp 0x7fffffffdc80 0x7fffffffdc80
執行三條指令(si 3),進入foo函式之後,彙編指令如下:
Dump of assembler code for function foo:
8 {
=> 0x000000000040050a <+0>: 55 push %rbp
0x000000000040050b <+1>: 48 89 e5 mov %rsp,%rbp
0x000000000040050e <+4>: 48 83 ec 08 sub $0x8,%rsp
0x0000000000400512 <+8>: 89 7d fc mov %edi,-0x4(%rbp)
0x0000000000400515 <+11>: 89 75 f8 mov %esi,-0x8(%rbp)
9 return bar(a, b);
0x0000000000400518 <+14>: 8b 55 f8 mov -0x8(%rbp),%edx
0x000000000040051b <+17>: 8b 45 fc mov -0x4(%rbp),%eax
0x000000000040051e <+20>: 89 d6 mov %edx,%esi
0x0000000000400520 <+22>: 89 c7 mov %eax,%edi
0x0000000000400522 <+24>: e8 c9 ff ff ff callq 0x4004f0 <bar>
10 }
0x0000000000400527 <+29>: c9 leaveq
0x0000000000400528 <+30>: c3 retq
End of assembler dump.
此時的函式堆疊如下:此時,尚未切換棧基地址,但是棧頂地址已經發生變化(因為call將main中的返回地址入棧)
(gdb) info registers rbp rsp
rbp 0x7fffffffdc80 0x7fffffffdc80
rsp 0x7fffffffdc78 0x7fffffffdc78
接下來兩句,建立新函式的堆疊;50e~515是取得函式實參的過程,這三句彙編等價於push edi, push esi. 指令執行到0518行,暫存器的狀態
(gdb) info registers rbp rsp
rbp 0x7fffffffdc70 0x7fffffffdc70
rsp 0x7fffffffdc68 0x7fffffffdc68
輸入命令si 9, 進入到新的函式呼叫棧。
(gdb) disassemble /rm
Dump of assembler code for function bar:
3 {
=> 0x00000000004004f0 <+0>: 55 push %rbp
0x00000000004004f1 <+1>: 48 89 e5 mov %rsp,%rbp
0x00000000004004f4 <+4>: 89 7d ec mov %edi,-0x14(%rbp)
0x00000000004004f7 <+7>: 89 75 e8 mov %esi,-0x18(%rbp)
4 int e = c + d;
0x00000000004004fa <+10>: 8b 45 e8 mov -0x18(%rbp),%eax
0x00000000004004fd <+13>: 8b 55 ec mov -0x14(%rbp),%edx
0x0000000000400500 <+16>: 01 d0 add %edx,%eax
0x0000000000400502 <+18>: 89 45 fc mov %eax,-0x4(%rbp)
5 return e;
0x0000000000400505 <+21>: 8b 45 fc mov -0x4(%rbp),%eax
6 }
0x0000000000400508 <+24>: 5d pop %rbp
0x0000000000400509 <+25>: c3 retq
End of assembler dump.
此時的堆疊狀態是:
(gdb) info registers rbp rsp
rbp 0x7fffffffdc70 0x7fffffffdc70
rsp 0x7fffffffdc60 0x7fffffffdc60
此時f0~f1仍然是建立新的函式呼叫stack。注意,接下來的幾句,並沒有進行函式stack的擴充套件,因為沒有更深的函式呼叫。(另外,和對fun函式的呼叫不同,這裡多了508對應的pop操作, 實際上,在fun函式中,是leave操作,這個操作等價於mov esp,ebp; pop ebp. 這裡為什麼沒有將ebp的值傳遞給esp呢?因為ebp和esp是相同的)
Enter的作用相當==push ebp和mov ebp,esp
這後面兩句大家很熟悉吧?函式開始一般都是這兩句
Leave的作用相當==mov esp,ebp和pop ebp