图解栈帧运行过程

打印 上一主题 下一主题

主题 656|帖子 656|积分 1968

通用的栈帧结构

C语言在调用过程(函数)的时候使用了栈数据结构提供的后进先出的内存管理原则。
当Q 在执行时, p 以及所有在向上追溯到P 的调用链中的过程,都是暂时被挂起的。
当x86-64 过程需要的存储空间超出寄存器能够存放的大小时,就会在栈上分配空间,这个部分称为过程的栈帧(stack fram)。
下图给出了运行时栈的通用结构,包括把它划分为栈帧。当前正在执行的过程的帧总是在栈顶。
我们可以看到P调用过程Q时,两过程的栈帧时相邻的,且P到Q地址依次减小(往下)。
 
                                                                                   
 
这里关于通用栈帧构造不在赘述,详情看CSAPP中3.7.1章节。
栈帧运行过程

 以简单的main函数调用add函数为例进行讲解。
运行环境:Linux Ubuntu 1404LTS i386 +gcc
  1. #include<stdio.h>
  2. int add(int a,int b)
  3. {
  4.     return a+b;
  5. }
  6. int main()
  7. {
  8. int res=add(1,2);
  9. return 0;
  10. }
复制代码
gcc对其进行编译
  1. gcc -S hello.c hello.s
复制代码
使用vscode打开hello.s汇编文件。去掉注释信息。
  1. add:
  2.     pushl    %ebp         //将ebp压入栈【假设ebp=addr2】,保存main函数的栈帧(栈底)
  3.     movl    %esp, %ebp    //将当前栈顶值赋值给%ebp,此时%ebp是新函数(add)的栈底
  4.     movl    12(%ebp), %eax //将12+M(%ebp)内存处的值移动到%eax寄存器,该内存处的值其实是在main函数的栈帧上
  5.     movl    8(%ebp), %edx  //与上类似
  6.     addl    %edx, %eax    //将两寄存器的值相加保存在%eax寄存器上(存储返回值)
  7.     popl    %ebp          //栈弹出,将此时addr2值存入寄存器%ebp,此时栈底指针指向了addr2即为原main函数的栈底
  8.     ret                   //把main函数的返回地址值赋值给rip
  9. main:
  10.     pushl    %ebp        //将main函数上一个函数的ebp压入栈
  11.     movl    %esp, %ebp   //将上一个函数的esp栈顶指针存入%ebp寄存器,此时%esp,%ebp指向同一位置,但%ebp此时的含义是新栈帧(main函数的栈底)
  12.     subl    $24, %esp    //为main函数开辟栈空间24byte
  13.     movl    $2, 4(%esp)  //立即数2存入M(%esp)+4的内存   cdecl方式(实参从右往左先入栈)
  14.     movl    $1, (%esp)   //立即数1存入M(%esp)的内存
  15.     call    add          //调用call时,执行两个动作:将main函数的返回地址压入栈;将add函数的入口地址EntryPoint赋值给rip,随即执行add函数
  16.     movl    %eax, -4(%ebp)  //将%eax寄存器的值移动到内存M(%ebp)
  17.     movl    $0, %eax       //eax赋值为0
  18.     leave
  19.     ret
复制代码
图解如下所示:


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

勿忘初心做自己

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表