函数调用原理

来源:互联网 发布:mysql case when else 编辑:程序博客网 时间:2024/06/02 19:43

1. EIP、EBP、ESP

  • EIP寄存器里存储的是CPU下次要执行的指令的地址。
  • EBP寄存器里存储的是栈的栈底地址,通常叫栈基址。
  • ESP寄存器里存储的是栈的栈顶地址,始终指向栈顶。 

2. 函数调用

  • 源程序

int foo(int a, int b)

{

        int c = a + b;

        return c;

}

int main()

{

        foo(2, 3);

        return 0;

}

  • 对应的汇编程序

$gcc –g main.c

$objdump –D a.out

 

080482f4 <foo>:

 80482f4:      55                  push   %ebp

 80482f5:      89 e5               mov    %esp,%ebp

 80482f7:      83 ec 04            sub    $0x4,%esp

 80482fa:      8b 45 0c            mov    0xc(%ebp),%eax

 80482fd:      03 45 08            add    0x8(%ebp),%eax

 8048300:      89 45 fc            mov    %eax,0xfffffffc(%ebp)

 8048303:      8b 45 fc            mov    0xfffffffc(%ebp),%eax

 8048306:      c9                  leave

 8048307:      c3                  ret

                                                                                                                            

08048308 <main>:

 8048308:       55                   push   %ebp

 8048309:       89 e5                mov    %esp,%ebp

 804830b:       83 ec 08             sub    $0x8,%esp

 804830e:       83 e4 f0             and    $0xfffffff0,%esp

 8048311:       b8 00 00 00 00       mov    $0x0,%eax

 8048316:       29 c4                sub    %eax,%esp

 8048318:       83 ec 08             sub    $0x8,%esp

 804831b:       6a 03                push   $0x3

 804831d:       6a 02                push   $0x2

 804831f:       e8 d0 ff ff ff       call   80482f4 <foo>

 8048324:       83 c4 10             add    $0x10,%esp

 8048327:       b8 00 00 00 00       mov    $0x0,%eax

 804832c:       c9                   leave

 804832d:       c3                   ret

  • 函数调用过程

1) 调用foo

 804831f:       e8 d0 ff ff ff       call   80482f4 <foo>

main函数中的call指令调用函数foo,此时EIP压栈,存下函数返回时要执行的下一条指令的地址(这里也就是存下8048324)。

call addr指令所做的操作相当于push eip然后 jmp addr机器指令4字节。 

jmp addr执行所做的操作相当于 mov addr, eip机器指令2字节。

2) 进入foo

 80482f4:      55                  push   %ebp

 80482f5:      89 e5               mov    %esp,%ebp

 80482f7:      83 ec 04            sub    $0x4,%esp

ebp压栈,即将main函数栈帧的栈底地址压栈。

esp的值传给ebp,此时的ebp指向foo函数栈帧的栈底地址。

foo栈帧分配栈空间

3) 离开foo

 8048306:      c9                  leave

 8048307:      c3                  ret

恢复到main函数的栈底地址(ebp)、栈顶地址(esp)、下一条指令地址(eip.

leave 指令所做的操作相当于mov ebp, esp然后 popebp机器指令1字节。

ret 指令所做的操作相当于pop eip机器指令1字节。 

 

3. 验证- gdb调试

$gdb a.out

 

 (gdb) disassemble

Dump ofassembler code for function foo:

0x080482f4 <foo+0>:    push   %ebp

0x080482f5 <foo+1>:    mov    %esp,%ebp

0x080482f7 <foo+3>:    sub    $0x4,%esp

0x080482fa <foo+6>:    mov    0xc(%ebp),%eax

0x080482fd <foo+9>:    add    0x8(%ebp),%eax

0x08048300 <foo+12>:   mov    %eax,0xfffffffc(%ebp)

0x08048303 <foo+15>:   mov    0xfffffffc(%ebp),%eax

0x08048306 <foo+18>:   leave

0x08048307 <foo+19>:   ret

End ofassembler dump.

(gdb) nexti

0x080482fd      3               int c = a + b;

(gdb) info registers

ebx            0x42130a14       1108544020

esp            0xbfffeb14       0xbfffeb14

ebp            0xbfffeb18       0xbfffeb18

eip            0x80482fd       0x80482fd

 (gdb) x/20 $esp

0xbfffeb14:    0x42130a14     0xbfffeb38     0x08048324    0x00000002

0xbfffeb24:    0x00000003     0xbfffeb38     0x0804833a    0x42130a14

0xbfffeb34:    0x40015360     0xbfffeb58     0x42015574   0x00000001

0xbfffeb44:    0xbfffeb84     0xbfffeb8c     0x4001582c    0x00000001

0xbfffeb54:    0x08048244     0x00000000     0x08048265    0x08048308


0xbfffeb14:   0x42130a14    0xbfffeb38(ebp)    ---- foo栈帧

   0x08048324    0x00000002

0xbfffeb24:   0x00000003    0xbfffeb38     0x0804833a    0x42130a14

0xbfffeb34:   0x40015360    0xbfffeb58(ebp)     ---- main栈帧