其中 ESP:堆栈指针寄存器,存放执行函数对应栈帧的栈顶地址,且始终指向栈顶。 EBP:栈帧基址指针寄存器,存放执行函数对应栈帧的栈底地址,用于 C 运行库访问栈中的局部变量和参数。 0x02 栈帧 函数调用经常是嵌套的,在同一时刻,堆栈中会有多个函数的信息。每个未完成运行的函数占用一个独立的连续区域,称作栈帧 (Stack Frame)。 栈帧是堆栈的逻辑片段,当调用函数时逻辑栈帧被压入堆栈, 当函数返回时逻辑栈帧被从堆栈中弹出。栈帧存放着函数参数,局部变量及恢复前一栈帧所需要的数据等。 栈帧的边界由栈帧基地址指针 EBP 和堆栈指针 ESP 界定 (指针存放在相应寄存器中)。 EBP 指向当前栈帧底部 (高地址),在当前栈帧内位置固定;ESP 指向当前栈帧顶部 (低地址),当程序执行时 ESP 会随着数据的入栈和出栈而移动。
每个方法被执行的时候,java虚拟机都会同步创建一个栈帧,栈的基本单位为栈帧,每个线程都有自已的栈,每个执行方法对应一个栈帧,也叫当前栈帧。 每一个栈帧都包括了局部变量表,操作数栈,动态连接,方法返回地址和一些额外的附加信息。 栈的特点就是后进先出,类似于坐电梯,后面进来的先出去。 特点: 局部变量的生命周期与栈帧一致:随着方法栈的销毁,局部变量随着销毁。 Maximum local variables 是3. 注意:若调用方法返回的时候带了返回值,其返回会被压入当前栈帧的操作数中,并更新PC寄存器中一条需要执行的字节码指令。
栈帧是用于支持虚拟机进行方法调用和方法执行背后的数据结构。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址信息。 操作数栈 操作数栈,也称操作栈,是一个后入先出栈。 当一个方法刚刚开始执行的时候, 该方法的操作数栈也是空的, 在方法的执行过程中, 会有各种字节码指令往操作数栈中写入和提取内容, 也就是出栈与入栈操作。 动态连接 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用, 持有引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。
.调用者的返回地址入栈,这个是用 前指令地址++ 作返回地址的3.调用者的帧指针入栈保护(本次调用的基址指针就指向这里)4.调用者的寄存器入栈保护5.被调用的函数的局部变量的分配依cpu不同会有细小的差别函数调用时的内存布局 当A调用B时,函数A的返回地址(调用返回后继续执行的指令地址)被压入栈中,栈中该位置也明确指明了A栈帧的结束处。而B的栈帧则从随后的栈部分开始,即图中保存帧指针(ebp)的地方开始。 这两个函数的栈帧结构如图3-5所示。可以看出,函数swap()从调用者main()的栈帧中获取其参数。图中的位置信息相对于寄存器ebp中的帧指针。栈帧左边的数字指出了相对于帧指针的地址偏移值。 1 .text2 _swap:3 pushl %ebp # 保存原ebp值,设置当前函数的帧指针。 32 ret33 这两个函数均可以划分成三个部分:"设置",初始化栈帧结构;"主体",执行函数的实际计算操作;"结束",恢复栈状态并从函数中返回。对于swap()函数,其设置部分代码是3~5行。
一、函数栈帧是什么? 在程序运行过程中,函数栈帧(Function Stack Frame) 是栈内存中为单个函数调用分配的一块独立内存区域。 其中放入乒乓球被称为压栈(push) 其中拿出乒乓球被称为出栈(pop) 1.压栈 2.出栈 3.维护栈的指针(两个寄存器) ①EBP(栈底指针):固定指向当前栈帧的 “基地址”,用于定位栈帧内的参数 步骤 2.1:压入 add 的第二个参数 如下图所示: 执行 push 3(将b的值压栈) ESP 向下移动 4 字节(int 占 4 字节),指向新栈顶 步骤 2.2:压入 add 的第一个参数 阶段3: 进入 add 函数 创建 add 的栈帧,此时 CPU 跳转到 add 函数的代码,开始初始化 add 的栈帧 —— 核心是 “保存 main 的 EBP” 和 “设置 add 四、总结: 函数栈帧的核心要点: ①创建栈帧:调用者压参数→压返回地址→被调用者压旧 EBP→设新 EBP→分配局部变量。
提示:以下是本篇文章正文内容,下面案例可供参考 一、函数栈帧 1.1函数栈帧的概念 函数栈帧是指在函数被调用时,系统为该函数在栈(Stack)区域中开辟的一段存储空间。 1.2函数栈帧的作用 函数栈帧是程序执行过程中用来进行内存管理的必备工具。当函数被调用时,系统为该函数分配栈帧空间,将函数的返回地址、帧指针、局部变量、参数等信息保存在栈帧中。 2.1减少栈帧的大小 由于函数栈帧的大小直接影响程序内存的使用效率,因此我们可以通过一些优化手段减少栈帧的大小,从而提升程序的性能。 减少栈帧的深度 由于栈帧的深度直接影响栈的大小和内存的使用效率,因此我们可以通过减少栈帧的深度来提升程序的性能。 三、函数栈帧的调试与问题排查 调试和排查函数栈帧相关的问题是在开发过程中常见的任务。
,至此main函数的栈帧保护工作完成,然后通过mov指令更新栈帧基准线,与栈顶水位线齐平。 所谓的保护栈帧恢复栈帧,不过是在保存和恢复寄存器esp和ebp的值。 至于return address是用来做:函数返回的。 随着函数的调用,函数的栈帧会逐层堆叠,但互不重合。 随着函数的逐层返回函数的栈帧会被就地放弃,但不会清理内存。 2 正括号{用来保护上层主调函数(main)的栈帧,并设置被调函数(func)的栈帧,反括号}用来放弃被调函数的栈帧,同时恢复主调函数的栈帧,这样被调函数执行完后,主调函数就能正常执行。 3 ebp寄存器作为当前函数的:栈帧基地址,配合一定的偏移就可以读写函数体里的:临时变量。 如果一个变量是通过ebp寄存器,间接访问的,那么它往往是临时变量,也叫栈变量。
一般来讲,栈帧之间都是独立的,但是大多虚拟机都会做优化,使局部变量表和操作数栈之间有重叠,以达到共用的目的,这样能节省额外的参数复制等工作,重叠过程类似下图。 动态连接每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,Class 文件的常量池中存有大量的符号引用。字节码中的方法调用指令就以常量池中方法的符号引用为参数。 方法返回时可能会在栈帧中保存一些信息,用来恢复上层方法的执行状态。一般方法正常退出的时候,调用者的pc计数器的值可以作为返回地址,帧栈中很有可能会保存这个计数器的值作为返回地址。 方法退出的过程就是栈帧在虚拟机栈上的出栈过程,因此退出时的操作可能有:恢复上层方法的局部变量表和操作数栈,把返回值压入调用者的操作数栈每条整pc计数器的值指向调用该方法的后一条指令。 如果异常退出的话,返回地址是通过异常表来确定,栈帧中一般不会保存这部分信息。这两个出口的区别就在于,异常完成出口退出是不会给上一层调用者产生任何返回值的。
开发环境 Ubuntu 14.04(32bits) GCC 编辑器 Cmd Markdown 画图工具 Processon 1,函数调用过程 今天先介绍下基本的函数调用过程,即栈帧。 1.1栈帧 每个函数调用都对应一个栈帧。每个栈帧由ESP和EBP寄存器来确定。每个函数执行时,其局部变量都是在自己对应的栈帧内分配内存。 -main .globl test .type test, @function test: .LFB1: .cfi_startproc pushl %ebp //ebp压栈,即old ebp ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2" .section .note.GNU-stack,"",@progbits 当main函数调用test函数时,对应的栈帧见下图 当函数test返回后,main函数的栈帧如下图
前置知识 JVM运行时数据区 栈帧的组成 虚拟机栈 与 栈帧 虚拟机栈(JVM Stack),由 栈帧 Frame 组成。 Frame - 每个方法对应一个栈帧, 包括以下部分: Local Variable Table (局部变量表) ? Table(局部变量表)和Operand Stack(操作数栈) 将返回的变量压入 上一个方法(调用者的栈帧)的Operand Stack(操作数栈) 调整 Program Counter Register (PC, 程序计数器) 的值为 当前帧的返回地址 当前栈帧弹出JVM Stack 栈, 执行Program Counter Register (PC, 程序计数器)指向的指令 理解JVM栈帧 用两个代码来帮助理解 3 iload_1 ? 4 iinc 1 by 1 --> 完成 i++ ? 7 istore_1 --> i = i++; ? i = ++i ? 前面都是一样。
本篇介绍 本篇介绍下汇编中的函数,栈帧内容。 栈帧 对于intel处理器,在调用函数的时候需要保证rsp是16字节对齐的,这样设计是为了更好的支持SIMD。那体现到代码上是怎样呢? .14f",10,0 pi dq 3.14159265358979 section .bss section .text func3: call printf ; print a float pop rbp ret func2: push rbp call func3 本来在调用main函数之前rsp是16字节对齐的,可是在调用main时候,由于会将返回地址压栈,这时候rsp就不是16字节对齐了,就需要prologue中再次执行一个进栈操作,就可以保证是对齐的了。
这些信息保存在栈帧中,并且栈帧被压入调用栈。 什么是栈帧? 栈帧是调用栈中的基本单元,每个函数调用都会在调用栈中创建一个新的栈帧。栈帧保存了函数执行所需的所有信息,包括局部变量、返回地址、参数等。 函数调用过程 当程序执行到 main 函数时,会首先在调用栈中创建一个栈帧以保存 main 函数的执行状态。然后,main 函数调用 A 函数,系统会在调用栈中为 A 函数创建一个新的栈帧。 随着 A 函数调用 B 函数,调用栈中会继续创建新的栈帧。最终,B 函数调用 C 函数,调用栈中创建了 C 函数的栈帧。 2. 调用栈示意图 为了更直观地展示上述过程,我们可以使用 UML 创建一个调用栈的示意图: 栈帧在错误处理中的应用 栈帧在错误处理和调试过程中也非常有用。 结论 程序调用栈和栈帧是理解程序执行原理的重要概念。调用栈管理函数调用的顺序,而栈帧则保存每个函数调用的详细信息。通过掌握这些概念,开发者可以更好地进行调试、错误处理和性能优化。
通常情况下,返回地址是存储在栈帧中的。 3. 分配局部变量的存储空间 在函数调用时,所有的局部变量都会被分配出来存储空间。局部变量的数量和大小决定了栈帧的大小。 二、函数栈帧的销毁 函数栈帧的销毁是在函数返回时进行的。在函数返回之前,需要将栈帧中的信息恢复并将其从栈中弹出。具体的销毁过程如下: 1. 销毁局部变量 在函数返回之前,需要将栈帧中的局部变量的值恢复到原来的状态,并释放它们所占用的内存。这样可以确保下一次函数调用时,重新分配的局部变量地址不会与先前的栈帧冲突。 3. 栈帧的大小限制: 栈帧的大小是由局部变量、函数参数和其他信息所占用的内存大小决定的。在设计函数时,我们应该合理估计局部变量的大小和数量,避免栈帧过大导致栈溢出。 当函数返回时,局部变量的内存会被释放,所以在函数栈帧创建期间,不要将局部变量的指针返回给调用函数使用。如果需要返回局部变量的值,可以通过参数传递或者使用动态内存分配来实现。 3.
---- 函数栈帧的创建和销毁:: ebp,esp这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的,edp被称为栈底指针,esp被称为栈顶指针。push:压栈:给栈顶放一个元素。 pop:出栈:给栈顶删一个元素,lea:加载有效地址。dword=4byte。 答:首先为此次函数调用创建函数栈帧,在函数栈帧找空间存放局部变量值。 2.为什么局部变量的值是随机值? 随机值是系统开辟完函数栈帧后系统随机放进去的。 3.函数是怎么传参的? (i % 3 == 0) printf("%d ", i); } return 0; } 代码2 int main() { int i = 0; for (i = 3; i <= 100 ; i += 3) { printf("%d ", i); } return 0; } 3.编写程序求两个数的最大公约数 #include<stdio.h> 求两个数的最大公约数 代码1
---- 1.什么是函数栈帧 函数栈帧( stack frame )就是函数调用过程中在程序的调用栈( call stack )所开辟的空间,这些空间是用来存放: 函数参数和函数返回值 3.函数栈帧的创建和销毁解析 3.1栈 栈( stack )是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,没有栈就没有函数,没有局部变量,也就没有我们如今看到的所有的计算机语言。 转入目标函数 jump :通过修改 eip ,转入目标函数,进行调用 ret :恢复返回地址,压入 eip ,类似 pop eip 命令 3.3解析函数栈帧的创建和销毁 3.3.1 基本知识 1.每一次函数调用,都要为本次函数调用开辟空间,就是函数栈帧的空间。 2.这块空间的维护是使用了两个寄存器:esp ebp,ebp记录的是栈底的地址,esp记录的是栈顶的地址。 3.函数栈帧的创建和销毁过程,在不同的编译器上实现的方法大同小异。
函数栈帧的创建和销毁 函数栈帧的介绍 函数栈帧是计算机程序运行时在调用栈(Call Stack)上为每个函数调用分配的一块内存区域,用于存储该函数执行所需的各种信息。 栈帧的关键寄存器 在x86架构中,有两个关键寄存器与栈帧相关: ESP (Extended Stack Pointer):指向栈顶(当前可用的最低地址) EBP (Extended Base Pointer ):指向当前栈帧的基址 VS中的调用堆栈 栈帧的基本结构 一个典型的函数栈帧包含以下几个主要部分(从高地址向低地址生长): 函数参数:调用者传递给被调用函数的参数 返回地址:函数执行完毕后应该返回的指令地址 前一个栈帧的基址(EBP/RBP):保存调用者的栈帧基址 局部变量:函数内部定义的变量 临时空间:用于表达式计算等临时存储 栈帧的重要性 实现函数调用和返回:保证程序能正确地在函数间跳转和返回 隔离函数状态:每个函数有自己的栈帧,互不干扰 支持递归调用:每次递归调用都会创建新的栈帧 调试信息:栈帧包含调试器回溯调用链所需的信息 作者寄语 对于函数栈帧,我们只需了解一些运行原理即可
那么通过学习函数栈帧的创建和销毁,以上困惑就会迎刃而解。 注: 本次讲解使用的是vs2013,不要使用太高级的编译器,越高级的编译器,越不容易学习和观察;同时,在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现。 为了讲清楚函数栈帧,我们需要先做一些铺垫: 寄存器: eax ebx ecx edx ebp esp ebp、esp这2个寄存器中存放的是地址,这2个地址是用来维护函数栈帧的 每一个函数调用,都要在栈区创建一个空间 接下来,就正式开始介绍函数栈帧的创建和销毁 push ebp mov ebp,esp sub esp,0E4h push ebx push esi push 总结: 局部变量在函数的栈帧里被分配了一些空间进行创建 局部变量不初始化的时候是随机值(比如上述过程中不初始化之前是cccccccc) 函数在调用之前就把参数从右向左进行压栈;真正进入函数后通过指针的偏移量找到形参
活动记录(Activation Record),常称栈帧(stack frame)。 ---- 嵌套过程 静态链(Static Link) 嵌套函数中,内部函数调用的栈帧可见外部函数调用的栈帧中的变量。 以frame pointer作为第一个参数(不一定是当前的栈帧,而是callee的上层)传递给callee作为static link,可以通过static link回溯上一层、上上层的栈帧,最终获得外部的变量 如果儿子1调用儿子2,那么事实上儿子1是通过父亲访问到的儿子2,因此不能直接传儿子1的栈帧,而是先回溯到父亲的栈帧,再把父亲的栈帧指针作为第一个参数传递给儿子2. F_allocLocal在栈帧上分配局部变量。
相信在学习的过程中,你对上面的问题或多或少都会有些困惑,今天的博客--函数栈帧的创建和销毁就可以帮助你解决这些困惑; 这些都是和函数的栈帧的创建和销毁有关,这个函数栈帧在不同版本的编译器有关,略有差异但是大致相同 再使用低地址;我们的main函数开始执行之后,就会开辟main函数的函数栈帧,ebp esp分别指向的就是main函数的函数栈帧的边界(如图所示);我们可以把这个函数栈帧创建的过程理解为一个盖房子的过程 ,那么把esp赋值给ebp就相当于是ebp指向esp的位置,也就是说我们的ebp指向了新的栈底 (3)第三行的esp减去oE4h这个就是在给main函数创建栈帧,我们esp减去一个值就是向上移动,移动到一个新的位置 ,移动的距离就是0E4h,这个时候ebp和esp各自指向了新的栈底和新的栈顶,我们这个时候就完成了main函数栈帧的创建; (4)接下来反汇编里面是3个push,这三个都是进行压栈的操作,压栈完成之后的栈帧情况如下图所示 ebp的意思就是把ebp的值给esp相当于是esp直接来到了栈底,这样就可以把这个add函数的空间回收掉了; 下面的就是add栈帧回收之后压栈情况: (3)接下来我们的栈顶ebp是main函数的,这个时候
、理解栈帧 首先,什么是栈帧? 引用百度百科:C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。从这句话中,可以提炼以下几点信息: 栈帧是一块因函数运行而临时开辟的空间。 每调用一次函数便会创建一个独立栈帧。 栈帧中存放的是函数中的必要信息,如局部变量、函数传参、返回值等。 当函数运行完毕栈帧将会销毁。 下面进入主题,图解函数栈帧的创建与销毁过程。 3.esp减去0E4h:由于栈先使用高地址后使用低地址,减去一个值意味着esp指针向低地址移动了0E4h个地址,此处便开辟了main函数的栈帧。 4.压入ebx,esp指向ebx顶部。 在函数拿到返回值后,开始出栈: PLAINTEXT 00AA13F1 pop edi 00AA13F2 pop esi 00AA13F3 pop