函数调用栈的过程

本文分析函数的调用过程中,栈的变化过程。

X86寄存器简介

寄存器名称注释
eax累加器,是很多加法、乘法的默认寄存器
ebx基地址,内存寻址时,存放基地址
ecx计数器,是重复前缀指令和LOOP指令的内定计数器
edx存放整数除法产生的余数
esi源索引寄存器,因为在很多字符串操作指令中,DS:ESI指向源串,而ES:EDI指向目标串
ebp基址指针,常被用作高级语言函数调用时的“帧指针”(frame pointer)
esp专门用作堆栈指针,被形象地称为栈顶指针,堆栈的顶部是地址小的区域,压入堆栈的数据越多,ESP也就越来越小。

文章常用汇编指令的理解

0x4(%esp)

把寄存器esp中的值取出,然后加上4,得到的值作为地址,间接寻址得到需要的数据
例如,

pushl -0x4(%ecx)

该指令的含义是取出寄存器ecx的值,减去4,将得到的值作为地址,在内存找到该地址对应的值,将其压入栈中。

lea 0x4(%esp), %ecx

该指令的作用是,取出esp寄存器里的值,加上4,不再继续寻址,而是将得到值直接传递给ecx;如果是其他指令,则还需进行间接寻址,再传值。

call

首先将eip设置为本函数中call后的那条指令的地址(所有指令都有这步)。然后push %eip。(esp自减4,将eip的值存入esp指向的地址,最后子函数ebp+4出即存储了这一步保存的eip值)。然后将eip的值设置为被调用函数的第一调指令的地址。(最后cpu继续执行eip所指向地址的指令,虽然这不是call干的事)

leave

首先将esp指向ebp。然后pop %ebp.(将esp指向内存的值拷到ebp,esp自减4)。

ret

pop%eip(即先将esp指向的内存块的值拷贝到eip中,然后esp自减4)

函数栈帧结构

Figure 15-5-2

举例演示

简单的源代码:

#include<stdio.h>

int sum(int x, int y)
{
    int tmp = 0;
    
    tmp = x+y;
    return tmp;
}
 
int main()
{
    int a=10;
    int b=20;
    int ret=0;
 
    ret=sum(a,b);
    printf("ret=%d\n",ret);
    return 0;
}

利用反汇编工具 objdump 将执行文件生成汇编文件,其内容如下:

......
0804840b <sum>:
 804840b:   55                      push   %ebp                 // 把ebp寄存器的内容压入栈中,也就是保存栈帧寄存器
 804840c:   89 e5                   mov    %esp,%ebp            // 将esp中的内容拷贝到ebp中,也就是将帧指针指向栈指针
 804840e:   83 ec 10                sub    $0x10,%esp           // 调整栈指针,存放局部变量(栈顶增加了4*4字节的空间)
 8048411:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%ebp)      // 局部变量tmp(0)的值存入栈底      
 8048418:   8b 55 08                mov    0x8(%ebp),%edx       // 取出x的值
 804841b:   8b 45 0c                mov    0xc(%ebp),%eax       // 取出y的值
 804841e:   01 d0                   add    %edx,%eax            // x+y的结果存入%eax寄存器中
 8048420:   89 45 fc                mov    %eax,-0x4(%ebp)      // x+y的结果存入局部变量tmp的地址处
 8048423:   8b 45 fc                mov    -0x4(%ebp),%eax      // x+y的结果存入%eax寄存器中
 8048426:   c9                      leave  
 8048427:   c3                      ret    

08048428 <main>:
 8048428:   8d 4c 24 04             lea    0x4(%esp),%ecx
 804842c:   83 e4 f0                and    $0xfffffff0,%esp
 804842f:   ff 71 fc                pushl  -0x4(%ecx)
 8048432:   55                      push   %ebp                 // 把ebp寄存器的内容压入栈中,也就是保存栈帧寄存器
 8048433:   89 e5                   mov    %esp,%ebp            // 将esp中的内容拷贝到ebp中,也就是将帧指针指向栈指针
 8048435:   51                      push   %ecx                 // 保护ecx寄存器的内容
 8048436:   83 ec 14                sub    $0x14,%esp           // 调整栈指针,存放局部变量(栈顶增加了5*4字节的空间)
 8048439:   c7 45 ec 0a 00 00 00    movl   $0xa,-0x14(%ebp)     // 局部变量a(10)的值存入栈底-20
 8048440:   c7 45 f0 14 00 00 00    movl   $0x14,-0x10(%ebp)    // 局部变量b(20)的值存入栈底-16地址处
 8048447:   c7 45 f4 00 00 00 00    movl   $0x0,-0xc(%ebp)      // 局部变量ret(0)的值存入栈底-12地址处
 804844e:   ff 75 f0                pushl  -0x10(%ebp)          // 向函数sum()的参数x传实参a(10),并压栈
 8048451:   ff 75 ec                pushl  -0x14(%ebp)          // 向函数sum()的参数y传实参b(20),并压栈
 8048454:   e8 b2 ff ff ff          call   804840b <sum>        // 调用sum()函数
 8048459:   83 c4 08                add    $0x8,%esp            // 调整栈顶位置(+8),
 804845c:   89 45 f4                mov    %eax,-0xc(%ebp)      // 相加结果(30)存入局部变量ret(0)所在(栈底-12)地址中
 804845f:   83 ec 08                sub    $0x8,%esp            // 调整栈顶位置(-8)
 8048462:   ff 75 f4                pushl  -0xc(%ebp)           // 
 8048465:   68 00 85 04 08          push   $0x8048500
 804846a:   e8 71 fe ff ff          call   80482e0 <printf@plt>
 804846f:   83 c4 10                add    $0x10,%esp
 8048472:   b8 00 00 00 00          mov    $0x0,%eax
 8048477:   8b 4d fc                mov    -0x4(%ebp),%ecx
 804847a:   c9                      leave  
 804847b:   8d 61 fc                lea    -0x4(%ecx),%esp
 804847e:   c3                      ret    
 804847f:   90                      nop

下面我们通过图示的方法,展示函数调用的过程:

Figure 15-5-3

这里我们有几个假设条件:

  1. 我们假设这儿的栈实现方式是 满栈,所谓的 满栈就是在压栈的时候,地址先自加4,然后再存储数值。另外一种方式就是 空栈,而空栈就是先存储值,然后再把地址加4。
  2. 本文只研究 mainsum之间的函数栈帧,对于 printf以及 main函数之前和之后的启动和结束代码不作研究;

相关文章:栈缓存溢出

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值