浅析函数调用栈过程

 

先列出栈调用的基本原则

  • 栈是通过rsp(栈顶指针)、rbp(栈底指针)两个指针来标识的
  • 对于栈上的访问都是通过栈底指针($rbp)的偏移来访问的
  • 函数调用跳转时(callq)在新帧的栈首8Bytes存放上一帧的指令地址($rip的值)
  • 通常函数的起始操作为push $rbp,将上帧的栈底地址8Bytes压入栈中
  • 在保存完指令地址和栈底地址后,会进行一次sub xxx,$rsp,为当前函数内所有在栈上的局部变量都申请好需要的栈空间
  • 函数调用前将需要保存的寄存器值和超过6个的参数都压入栈中
  • 函数结束ret指令干了两件事:先出栈;再将出栈的值放到CPU的IP寄存器中。(因为IP寄存器中永远放的是下一次执行指令的地址,所以就顺理成章的在函数调用完之后依旧接着原来的代码继续执行)

以下面简单函数为例,解析函数调用栈的过程。

#include <stdio.h>

int sum(int a, int b)
{
    int tmp = 0;
    tmp = a+b;
    return tmp;
}
 
int main(void)
{
    int a=1;
    int b=2;
    int ret = 0;
 
    ret = sum(a,b);
    return ret;
}

汇编语言如下:

(gdb) disassemble main
Dump of assembler code for function main:
   0x00000000004004f1 <+0>:     push   %rbp
   0x00000000004004f2 <+1>:     mov    %rsp,%rbp
   0x00000000004004f5 <+4>:     sub    $0x10,%rsp-----------设置main函数需要的栈空间
   0x00000000004004f9 <+8>:     movl   $0x1,-0x4(%rbp)-------放入局部变量
   0x0000000000400500 <+15>:    movl   $0x2,-0x8(%rbp)
   0x0000000000400507 <+22>:    movl   $0x0,-0xc(%rbp)
   0x000000000040050e <+29>:    mov    -0x8(%rbp),%edx-------reg中写入sum函数入参
   0x0000000000400511 <+32>:    mov    -0x4(%rbp),%eax
   0x0000000000400514 <+35>:    mov    %edx,%esi
   0x0000000000400516 <+37>:    mov    %eax,%edi
   0x0000000000400518 <+39>:    callq  0x4004d0 <sum>--------调用时,在$rsp 0x7fffffffe8e0
压栈下一次执行命令的地址 0x 040051d,
   0x000000000040051d <+44>:    mov    %eax,-0xc(%rbp)
   0x0000000000400520 <+47>:    mov    -0xc(%rbp),%eax
   0x0000000000400523 <+50>:    leaveq 
   0x0000000000400524 <+51>:    retq   
End of assembler dump.
(gdb) disassemble sum 
Dump of assembler code for function sum:
   0x00000000004004d0 <+0>:     push   %rbp-------压栈main函数rbp指针,0x7fffffffe8f0
   0x00000000004004d1 <+1>:     mov    %rsp,%rbp-------设置sum函数栈底指针,参数入栈从右向左(原因时因为不定长参数)
   0x00000000004004d4 <+4>:     mov    %edi,-0x14(%rbp)-----读入参数放入sum栈空间
   0x00000000004004d7 <+7>:     mov    %esi,-0x18(%rbp)
   0x00000000004004da <+10>:    movl   $0x0,-0x4(%rbp)
   0x00000000004004e1 <+17>:    mov    -0x18(%rbp),%eax
   0x00000000004004e4 <+20>:    mov    -0x14(%rbp),%edx
   0x00000000004004e7 <+23>:    add    %edx,%eax
   0x00000000004004e9 <+25>:    mov    %eax,-0x4(%rbp)
   0x00000000004004ec <+28>:    mov    -0x4(%rbp),%eax
   0x00000000004004ef <+31>:    pop    %rbp--------出栈main函数的指针
   0x00000000004004f0 <+32>:    retq   ----出栈(callq压栈的main函数下一次执行命令的地址0x 040051d),并将值放入rip指针当中

在调用函数sum时,单步执行,查看frame 信息和栈信息如下

(gdb) frame
#0  sum (a=1, b=2) at text.c:6
6               tmp = a+b;
(gdb) info frame
Stack level 0, frame at 0x7fffffffe8e0:
 rip = 0x4004e1 in sum (text.c:6); saved rip 0x40051d
 called by frame at 0x7fffffffe900
 source language c.
 Arglist at 0x7fffffffe8d0, args: a=1, b=2
 Locals at 0x7fffffffe8d0, Previous frame's sp is 0x7fffffffe8e0
 Saved registers:
  rbp at 0x7fffffffe8d0, rip at 0x7fffffffe8d8

(gdb) x/100wx 0x7fffffffe8a0
0x7fffffffe8a0: 0x00400540      0x00000000      0x00400343      0x00000000
0x7fffffffe8b0: 0x00000001      0x00c30000      0x00000002      0x00000001
0x7fffffffe8c0: 0x00000000      0x00000000      0x52e1cbc0      0x00000000
0x7fffffffe8d0: 0xffffe8f0      0x00007fff      0x0040051d      0x00000000
0x7fffffffe8e0: 0xffffe9d0      0x00000000      0x00000002      0x00000001
0x7fffffffe8f0: 0x00000000      0x00000000      0x5301d9f4      0x00000037
0x7fffffffe900: 0x00400370      0x00000000      0xffffe9d8      0x00007fff
0x7fffffffe910: 0x00000000      0x00000001      0x004004f1      0x00000000
0x7fffffffe920: 0x52e1cbc0      0x00000037      0x7d1a95d2      0x03d7b1a3
0x7fffffffe930: 0x00000000      0x00000000      0xffffe9d0      0x00007fff
0x7fffffffe940: 0x00000000      0x00000000      0x00000000      0x00000000

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值