c函数调用过程原理及函数栈帧分析
转载自地址:http://blog.csdn.net/zsy2020314/article/details/9429707
今天突然想分析一下函数在相互调用过程中栈帧的变化,还是想尽量以比较清晰的思路把这一过程描述出来,关于c函数调用原理的理解是很重要的。
1.关于栈
首先必须明确一点也是非常重要的一点,栈是向下生长的,所谓向下生长是指从内存高地址->低地址的路径延伸,那么就很明显了,栈有栈底和栈顶,那么栈顶的地址要比栈底低。对x86体系的CPU而言,其中
---> 寄存器ebp(base pointer )可称为“帧指针”或“基址指针”,其实语意是相同的。
---> 寄存器esp(stack pointer)可称为“ 栈指针”。
要知道的是:
---> ebp 在未受改变之前始终指向栈帧的开始,也就是栈底,所以ebp的用途是在堆栈中寻址用的。
---> esp是会随着数据的入栈和出栈移动的,也就是说,esp始终指向栈顶。
见下图,假设函数A调用函数B,我们称A函数为"调用者",B函数为“被调用者”则函数调用过程可以这么描述:
(1)先将调用者(A)的堆栈的基址(ebp)入栈,以保存之前任务的信息。
(2)然后将调用者(A)的栈顶指针(esp)的值赋给ebp,作为新的基址(即被调用者B的栈底)。
(3)然后在这个基址(被调用者B的栈底)上开辟(一般用sub指令)相应的空间用作被调用者B的栈空间。
(4)函数B返回后,从当前栈帧的ebp即恢复为调用者A的栈顶(esp),使栈顶恢复函数B被调用前的位置;然后调用者A再从恢复后的栈顶可弹出之前的ebp值(可以这么做是因为这个值在函数调用前一步被压入堆栈)。这样,ebp和esp就都恢复了调用函数B前的位置,也就是栈恢复函数B调用前的状态。
这个过程在AT&T汇编中通过两条指令完成,即:
leave
ret
这两条指令更直白点就相当于:
mov %ebp , %esp
pop %ebp
2.举个简单的实例,从汇编的视角看函数调用
2.1建立一个简单的程序,程序文件名为 main.c
开发测试环境:
Ubuntu 12.04
gcc版本:4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) (是Ubuntu自带的)
- <span style="font-size:18px;">/*main.c代码:*/
- void swap(int *a,int *b)
- {
- int c;
- c = *a;
- *a = *b;
- *b = c;
- }
- int main(void)
- {
- int a ;
- int b ;
- int ret;
- a =16;
- b = 64;
- ret = 0;
- swap(&a,&b);
- ret = a - b;
- return ret;
- }</span>
2.2编译
#gcc -g -o main main.c
#objdump -d main > main.dump
#gcc -Wall -S -o main.s main.c
这样大家可以看main.s也可以看main.dump,这里我们选择使用main.dump。
截取关键的部分,即_start, swap , main,为什么会有_start呢,因为ELF格式的入口其实是_start而不是main()。下面的图展示了main()函数调用swap()前后的栈空间的结构。右边的数字代表相对帧指针的偏移字节数。后面我们使用GDB调试就会发现栈的变化跟下图是一致的。
(!!!请注意,由于栈对齐的缘故,编译器分配栈空间时可能会有没用到的内存地址,而这些没使用到的内存地址就没在下图表示出来,所以下图只能当作示意图来了解函数栈帧结构!!具体的栈内存内容以下文的GDB调试的信息为准!!!)
下面是main.dump中_start的代码注释,比较重要的是对esp的栈对齐操作,esp是16字节对齐的,注意左边行号的右边的0x8048300一类的数字是指令地址。
下面是main.dump中swap()函数和main()函数的汇编代码,代码旁有详细的注释。
下面我们使用GDB调试main.c的代码,使用刚才编译好的main镜像。
# gdb start (启动gdb)
# (gdb) file main (加载镜像文件)
# (gdb) break main (把main()设置为断点,注意gdb并没有把断点设置在main的第一条指令,而是设置在了调整栈指针为局部变量保留空间之后)
# (gdb) run (运行程序)
# (gdb) stepi (单步执行,不熟悉gdb的童鞋要注意了,stepi命令执行之后显示出来的源代码行或者指令地址,都是即将执行的指令,而不是刚刚执行完的指令!)
c函数调用过程原理及函数栈帧分析的更多相关文章
- C函数调用过程原理及函数栈帧分析(转)
在x86的计算机系统中,内存空间中的栈主要用于保存函数的参数,返回值,返回地址,本地变量等.一切的函数调用都要将不同的数据.地址压入或者弹出栈.因此,为了更好地理解函数的调用,我们需要先来看看栈是怎么 ...
- Cortex-M3双堆栈MSP和PSP+函数栈帧
为了防止几百年以后找不到该文章,特此转载 ------------------------------------------------开始转载--------------------------- ...
- 计算机是如何计算的、运行时栈帧分析(神奇i++续)
关于i++的疑问 通过JVM javap -c 查看字节码执行步骤了解了i++之后,衍生了一个问题: int num1=50; num1++*2执行的是imul(将栈顶两int类型数相乘,结果入栈), ...
- 函数调用过程中,函数参数的入栈顺序,why?
C语言函数参数入栈顺序为从右至左.具体原因为:C方式参数入栈顺序(从右至左)的好处就是可以动态变化参数个数.通过栈堆分析可知,自左向右的入栈方式,最前面的参数被压在栈底.除非知道参数个数,否则是无法通 ...
- MIPS架构上函数调用过程的堆栈和栈帧
转载于CSDN:http://blog.csdn.net/do2jiang/article/details/5404566 在计算机科学中,Call stack是指存放某个程序的正在运行的函数的信息的 ...
- 图文并茂-超详解 CS:APP: Lab3-Attack(附带栈帧分析)
CS:APP:Lab3-ATTACK 0. 环境要求 关于环境已经在lab1里配置过了.lab1的连接如下 实验的下载地址如下 说明文档如下 http://csapp.cs.cmu.edu/3e/at ...
- 自己动手实现arm函数栈帧回溯【转】
转自:http://blog.csdn.net/dragon101788/article/details/18668505 内核版本:2.6.14 glibc版本:2.3.6 CPU平台:arm gl ...
- C语言的函数调用过程(栈帧的创建与销毁)
从汇编的角度解析函数调用过程 看看下面这个简单函数的调用过程: int Add(int x,int y) { ; sum = x + y; return sum; } int main () { ; ...
- [Android Pro] 深入理解函数的调用过程——栈帧
cp :http://blog.csdn.net/x_perseverance/article/details/78897637 每一个函数被调用时,都会为函数开辟一块空间,这块空间就称为栈帧. 首先 ...
随机推荐
- 简单的SocketExample
客户端//---------------VerySimpleClient.java package SocketExample; // Tue Nov 2 18:34:53 EST 2004 // / ...
- AndroidJNI 调用JAVA(转)
转自:http://www.cnblogs.com/likwo/archive/2012/05/21/2512400.html 1. JNIEnv对象 对于本地函数 JNIEXPORT ...
- WIN7建立网络映射磁盘
建立网络映射磁盘 如果需要经常访问网络中的同一个共享文件夹,则可以将这个共享文件夹直接映射为本地计算机中的一个虚拟驱动器.其具体操作如下. (1)双击桌面上"计算机"图标,打开&q ...
- mysql 行列动态转换(列联表,交叉表)
mysql 行列动态转换(列联表,交叉表) (1)动态,适用于列不确定情况 create table table_name( id int primary key, col1 char(2), col ...
- mkdir -p
git bash 或 mac terminal 我们可以使用 mkdir 命令来创建文件夹. 当前目录创建多个文件夹: $ mkdir a b c 会创建 a .b.c 三个文件夹 但是有时候我们需要 ...
- git push default
今天使用git push的时候出现了如下提示: warning: push.default is unset; its implicit value is changing in Git 2.0 fr ...
- int和integer;Math.round(11.5)和Math.round(-11.5)
int是java提供的8种原始数据类型之一.Java为每个原始类型提供了封装类,Integer是java为int提供的封装类.int的默认值为0,而Integer的默认值为null,即Integer可 ...
- UVa 11584 Partitioning by Palindromes
题意: 给出一个字符串,求最少能划分成多少个回文子串. 分析: d[i] = min{d[j] + 1 | s[j+1]...s[i]是回文串} d[i]表示前 i 个字符最少能分割的回文子串的个数 ...
- asp.net实现GZip压缩和GZip解压
最近在开发一个网站doc.115sou.com,使用到了GZip压缩技术,经过多次搜索找到asp.net中用GZip对数据压缩和解压缩非常方便,当我第一次拿到这个类的时候却感觉很迷茫,无从下手.主要是 ...
- BZOJ 1861 书架
(╯-_-)╯╧╧ 此处为错误代码. #include<iostream> #include<cstdio> #include<cstring> #include& ...