1. 程式人生 > >從彙編視角看函式呼叫

從彙編視角看函式呼叫

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