source:http://blog.csdn.net/qq_29403077/article/details/53205010

一、地址空间与物理内存
(1)地址空间与物理内存是两个完全不同的概念,真正的代码及数据都存在物理内存中。
物理储存器是指实际存在的具体储存器芯片,CPU在操纵物理储存器的时候都把他们当做内存来对待,把他们看成由若干个储存单元组成的逻辑储存器,这个逻辑储存器就是我们所说的地址空间。
地址空间大小与逻辑储存器大小不一定相等。
(2)进程的地址空间分布
进程的地址空间包括:栈区(heap)、共享区、堆区(stack)、未初始化静态全局区、已初始化静态全局区、静态只读区、代码段。如图:

二、栈帧的建立
首先要明白几个地方:
每一个函数都有自己的栈帧空间,并且独占自己的栈帧空间,
当前正在运行的函数的栈帧总是在栈顶。Win32系统提供两个特殊的寄存器用于标识位于系统栈顶端的栈帧。

  (1)ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶,即栈顶寄存器。
  
  (2)EBP:基址指针寄存器(extended base pointer),即栈底寄存器。 
  
  (3)栈帧状态值:保存前栈帧的顶部和底部(实际上只保存前栈帧的底部,前栈帧的顶部可以通过栈帧平衡计算得到),用于在本栈被弹出后恢复出上一个栈帧。
  
  (4)函数返回地址:保存当前函数调用前的“断点”信息,也就是函数调用前的指令位置,以便在函数返回时能够恢复到函数被调用前的代码区中继续执行指令。
  
  注:函数栈帧的大小并不固定,一般与其对应函数的局部变量多少有关。函数运行过程中,其栈帧大小也是在不停变化的。除了与栈相关的寄存器外,我们还需要记住另一个至关重要的寄存器。
  
  EIP:指令寄存器(extended instruction pointer),其内存放着一个指针,该指针永远指向下一条等待执行的指令地址。
  
   首先栈是自高地址向低地址即向下生长的。
  我们通过如下程序了解栈帧建立过程:

int fun(int a,int b)
{
int sum=0;
sum=a+b;
return sum;
}
int main()
{
fun(3,4);
printf("haha");
return 0;
}

这里main是调用者(caller);fun是被调用者(Callee)在函数调用前,main正在用ESP和EBP寄存器指示它自己的栈帧。

main把EAX,ECX和EDX压栈。这是一个可选的步骤,只在这三个寄存器内容需要保留的时候执行此步骤。
接着,main把传递给fun的参数一一进栈,最后的参数最先进栈,这里也就解释了函数在压栈过程中是从右往左,即先压4,再压3。

(1)这里首先main函数建立自己的栈帧结构;main()函数是由—__tCRTStartup()函数调用的,所以mainCRTStratup()函数调用__tmainCRTStra()函数的时候就会从栈上为__tmainCRTStra()分配类似图中这么一块空间,因为我们现在要调用main()函数了,所以当然要先把__tmainCRTStartup()函数的运行状态保存下来,这样main()函数才能返回的时候才能找得到!。

然后继续执行下一条语句: mov ebp,esp
即把esp的值赋给ebp,这样,ebp也就指向了现在esp的位置
然后sub esp 0C0h 这样就为main函数开辟了一段空间然后将ebx、esi、edi寄存器压栈就形成如图所示:

紧接着将局部变量及实参压栈,并执行call指令,main用call指令调用子函数:
call fun
当call指令执行的时候,EIP指令指针寄存器的内容被压入栈中。因为EIP寄存器是指向main中的下一条指令,所以现在返回地址就在栈顶了。在call指令执行完之后,下一个执行周期将从名为fun的标记处开始.


当函数fun,也就是被调用者取得程序的控制权,它必须做3件事:建立它自己的栈帧,为局部变量分配空间,最后,如果需要,保存寄存器EBX,ESI和EDI的值。
首先fun必须建立它自己的栈帧。EBP寄存器现在正指向main的栈帧中的某个位置,这个值必须被保留,因此,EBP进栈。然后ESP的内容赋值给了
EBP。这使得函数的参数可以通过对EBP附加一个偏移量得到,而栈寄存器ESP便可以空出来做其他事情。如此一来,几乎所有的c函数都由如下两个指令开
始:
push ebp
mov ebp, esp
下一步,fun必须为它的局部变量分配空间,同时,也必须为它可能用到的一些临时变量分配
空间。比如,foo中的一些C语句可能包括复杂的表达式,其子表达式的中间值就必须得有地方存放。这些存放中间值的地方同城被称为临时的,因为他们可以为
下一个复杂表达式所复用
现在,局部变量和临时存储都可以通过基准指针EBP加偏移量找到了。
最后,如果fun用到EBX,ESI和EDI寄存器,则它必须在栈里保存它们。

fun的函数体现在可以执行了。这其中也许有进栈、出栈的动作,栈指针ESP也会上下移动,但EBP是保持不变的。这意味着我们可以一直用[EBP+…]找到第一个参数,而不管在函数中有多少进出栈的动作。
函数fun的执行也许还会调用别的函数,甚至递归地调用foo本身。然而,只要EBP寄存器在这些子调用返回时被恢复,就可以继续用EBP加上偏移量的方式访问实际参数,局部变量和临时存储。
紧接着当被调用者执行完毕时将消除栈帧结构,调用pop指令。
在把程序控制权返还给调用者前,被调用者foo必须先把返回值保存在EAX寄存器中。其次,foo必须恢复EBX,ESI和EDI寄存器的值。进栈和出栈操作的次数必须保持平衡。

  
  在程序控制权返回到调用者main)后,这时,传递给fun的参数通常已经不需要了。我们可以把参数一起弹出栈,这可以通过把栈指针实现:
add esp, 8
此时fun函数调用结束栈帧结构恢复至图一。
如果在函数调用前,EAX,ECX和EDX寄存器的值被保存在栈中,调用者main函数现在可以把它们弹出。这个动作之后,栈顶就回到了我们开始整个函数调用过程前的位置。
这样整个函数的调用就结束了

C语言函数调用及栈帧结构的更多相关文章

  1. Java虚拟机运行时栈帧结构--《深入理解Java虚拟机》学习笔记及个人理解(二)

    Java虚拟机运行时栈帧结构(周志明书上P237页) 栈帧是什么? 栈帧是一种数据结构,用于虚拟机进行方法的调用和执行. 栈帧是虚拟机栈的栈元素,也就是入栈和出栈的一个单元. 2018.1.2更新(在 ...

  2. 深入理解java虚拟机(十) Java 虚拟机运行时栈帧结构

    运行时栈帧结构 栈帧(Stack Frame) 是用于虚拟机执行时方法调用和方法执行时的数据结构,它是虚拟栈数据区的组成元素.每一个方法从调用到方法返回都对应着一个栈帧入栈出栈的过程. 每一个栈帧在编 ...

  3. Windows x64 栈帧结构

    0x01 前言 Windows 64位下函数调用约定变为了快速调用约定,前4个参数采用rcx.rdx.r8.r9传递,多余的参数从右向左依次使用堆栈传递.本次文章是对于Windows 64位下函数调用 ...

  4. 图解JVM字节码执行引擎之栈帧结构

    一.执行引擎      “虚拟机”的概念是相对于“物理机”而言的,这两种“机器”都有执行代码的能力.物理机的执行引擎是直接建立在硬件处理器.物理寄存器.指令集和操作系统层面的:而“虚拟机”的执行引擎是 ...

  5. 详细解析Java虚拟机的栈帧结构

    欢迎关注微信公众号:万猫学社,每周一分享Java技术干货. 什么是栈帧? 正如大家所了解的,Java虚拟机的内存区域被划分为程序计数器.虚拟机栈.本地方法栈.堆和方法区.(什么?你还不知道,赶紧去看看 ...

  6. 【转载】深入理解Java虚拟机笔记---运行时栈帧结构

    栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack)的栈元素.栈帧存储了方法的局部变量表,操作 ...

  7. 关于C语言函数调用压栈和返回值问题的疑惑

    按照C编译器的约定调用函数时压栈的顺序是从右向左,并且返回值是保存在eax寄存器当中.这个命题本该是成立的,下面用一个小程序来反汇编观察执行过程: #include<stdio.h> in ...

  8. C语言的函数调用过程(栈帧的创建与销毁)

    从汇编的角度解析函数调用过程 看看下面这个简单函数的调用过程: int Add(int x,int y) { ; sum = x + y; return sum; } int main () { ; ...

  9. c函数调用过程原理及函数栈帧分析

    转载自地址:http://blog.csdn.net/zsy2020314/article/details/9429707       今天突然想分析一下函数在相互调用过程中栈帧的变化,还是想尽量以比 ...

随机推荐

  1. Luogu P5352 Terrible Homework

    神仙@TheLostWeak出的题,因为他最近没时间所以我先写一下sol(其实我也没什么时间) 作为一道简单的数据结构题想必大家都能看出必须用LCT维护信息吧 一个朴素的想法就是直接维护四种操作的值, ...

  2. jQuery备忘录

    jquery 中遍历数组 var arr = [1,2,3,4,5] $.each(arr,function(i,j){ console.log(i,j) }) 结果 0 1 1 2 .... jQu ...

  3. Robot Framework(十三) 执行测试用例——创建输出

    3.5创建输出 执行测试时会创建几个输出文件,并且所有这些文件都与测试结果有某种关联.本节讨论创建的输出,如何配置它们的创建位置以及如何微调其内容. 3.5.1不同的输出文件 输出目录 输出文件 日志 ...

  4. 团队作业-Beta冲刺第一天

    这个作业属于哪个课程 <https://edu.cnblogs.com/campus/xnsy/SoftwareEngineeringClass1> 这个作业要求在哪里 <https ...

  5. ucosii(2.89)mutex 应用要点

    mutex 的创建在于共享资源打交道是可以可以保证满足互斥条件:1,必须保证继承优先级要高于可能与相应共享资源打交道的任务中优先级最高的优先级.2,不要将占有Mutex的任务挂起,也不要让占有mute ...

  6. Asp.Net Core 入门(八)—— Taghelper

    Taghelper是一个服务端的组件,可以在Razor文件中创建和渲染HTML元素,类似于我们在Asp.Net MVC中使用的Html Taghelper.Asp.Net Core MVC内置的Tag ...

  7. Jascript原型链以及Object和Function之间的关系

    先看一个简单的function变量 function fun1(name) { this.name = name; } console.log("fun1", fun1) 从结果可 ...

  8. python之for (循环)

    格式: for 循环 for i in s: print(i) # for 关键字 # i 变量 # in 关键字 # s 可迭代对象 int - bool pass和- # for a in &qu ...

  9. 单表操作ORM

    博客园 首页 新随笔 联系 管理 订阅 随笔- 0  文章- 339  评论- 29  Django基础五之django模型层(一)单表操作   本节目录 一 ORM简介 二 单表操作 三 章节作业 ...

  10. angstromctf -No libc for You

    0x00 syscall syscall函数原型为: int syscall(int number, ...) 其中number是系统调用号,number后面应顺序接上该系统调用的所有参数.大概意思是 ...