也谈栈和栈帧

article/2025/10/18 19:34:39

一个码农要是没遇见过coredump,那他就是神仙了。core file(coredump的转储文件)中保存的最重要内容之一,就是函数的call trace。还原这部分内容 (栈回溯) ,并与原代码对应上,尽快找出程序崩溃的位置和原因,是码农们一生的责任。当然,你如果有良好的开发环境和开发习惯,保留了现场环境(core file and lib file等)和unstrip的原程序,那么恭喜,也许你不用太费神,直接用GDB的backtrace功能,就可以找到症结所在。当然如果栈被冲掉了一部分, backtrace出来的就是一堆问号,要找 出call trace就不容易了。这在缓冲区溢出时经常碰到。
    好了废话少说,切入正题,先谈与call trace密切相关的栈和栈帧概念。

1.  栈和栈帧
     栈(stack)相对整个系统而言,调用栈(Call stack)相对某个进程而言,栈帧(stack frame)则是相对某个函数而言,调用栈就是正在使用的栈空间,由多个嵌套调用函数所使用的栈帧组成。 具体来说,Call stack就是指存放某个程序的正在运行的函数的信息的栈。Call stack 由 stack frames 组成,每个 stack frame 对应于一个未完成运行的函数。
    在当今多数计算机体系架构中,函数的参数传递、局部变量的分配和释放都是通过操纵栈来实现的。栈还用来存储返回值信息、保存寄存器以供恢复调用前处理机状态。每次调用一个函数,都要为该次调用的函数实例分配栈空间。为单个函数分配的那部分栈空间就叫做 stack frame,或者这样说,stack frame 这个说法主要是为了描述函数调用关系的。
    Stack frame 组织方式的重要性和作用体现在两个方面:
    第一,它使调用者和被调用者达成某种约定。这个约定定义了函数调用时函数参数的传递方式,函数返回值的返回方式,寄存器如何在调用者和被调用者之间进行共享;
    第二,它定义了 被调用者如何使用 它自己的 stack frame 来完成局部变量的存储和使用。

2.    压栈和出栈 
    在 RISC 计算机中
主要参与计算的是寄存器 ,saved registers 就是指在进入一个函数后,如果某个保存原函数信息的寄存器会在当前函数中被使用,就应该将此寄存器保存到堆栈上,当函数返回时恢复此寄存器值。而且由于 RISC 计算机大部分采用定长指令或者定变长指令,一般指令长度不会超过32个位。而现代计算机的内存地址范围已经扩展到 32 位甚至64位,这样在一条指令里就不足以包含有效的内存地址,所以RISC计算机一般借助于一个返回地址寄存器 RA(return address) 来实现函数的返回。几乎在每个函数调用中都会使用到这个寄存器,所以在很多情况下 RA 寄存器会被保存在堆栈上以避免被后面的函数调用修改,当函数需要返回时,从堆栈上取回 RA 然后跳转。
    移动 SP 和保存寄存器的动作一般处在函数的开头,这个压栈过程也叫做 function prologue;恢复这些寄存器状态的动作一般放在函数的最后,出栈过程也叫做 function epilogue。 压栈出栈指令各个CPU也不相同。
     Stack Frame 中所存放的内容和存放顺序,则由目标体系架构的调用约定(calling convention)定义。 下面,我们看看图形化的函数栈 栈向下增长为例 了解几种常用CPU的 stack frame 组织方式。
 
3.  MIPS栈帧
     先看MIPS的 帧布局图
    
    此图描述的是一种典型的MIPS stack frame 组织方式。在这张图中,sp(stack pointer)/s8 (栈基址,又称fp)  就是当前函数的栈指针,它指向栈顶的位置。Callee Frame 所示即为当前函数(被调用者)的 frame,Caller Frame 是当前函数的调用者的 frame。general register area根据需要保存ra、gp、s8等 caller的寄存器信息 ,保存位置和顺序暂时没有发现明确的规律。
    在MIPS这种 没有BP(base pointer) 寄存器的目标架构 中,进入一个函数时需要将当前栈指针向下移动 n个byte ( 字节 ) ,这个大小为nbyte  的存储空间就是此函数的 stack frame 的存储区域。此后栈指针便不再移动,只能在函数返回时再将栈指针加上这个偏移量恢复栈现场。由于不能随便移动栈指针,所以寄存器压栈和出栈都必须指定偏移量,这与 x86 架构的计算机对栈的使用方式有着明显的不同。所以, MIPS一般是sp跟s8一致,只有调用alloca或动态数组后fp才会移到新的frame边界。
    另外提一下两个重要的寄存器。MIPS有个寄存器 t9, 专门用来跳转子函数时预装子函数地址 跳转子函数的汇编命令是:jalr t9。这条命令后面8个 byte 的地址就是子函数栈帧中保留的ra值。 MIPS的寄存器gp则用来 存放某些变量或GOT信息,它的获取是在函数的开头三条语句完成,计算公式:gp = x <<16 + y   + t9 。三条语句是:
        lui        gp, x
        addiu   gp, gp, y 
        addu    gp, gp, t9          #t9就是当前函数的地址,它永远保留着最后一个被调用函数的地址
     MIPS的 压栈出栈指令分别是ld和sd。

谈x86的栈帧之前,补充一下堆和栈的认识。
1.  堆和栈的关系
    我们平时说的堆栈其实是指栈,而实际上堆和栈是两种不同的内存分配。简单罗列一下各方面的异同点。
    1).堆需要用户在程序中显式申请,栈不用,由系统自动完成。申请/释放堆内存的API,在C中是malloc/free,在C++中是new/delete。申请与释放一定要配对使用,否则会造成内存泄漏(memory leak),久而久之系统就无内存可用了,出现OOM(Out Of Memory)错误。一般在return/exit或break/continue等语句时容易忘记释放内存,所以检查内存泄漏的代码时要关注这些语句,看它们前面是否有必要的释放语句free/delete。
    2).堆的空间比较大,栈比较小。所以申请大的内存一般在堆中申请;栈上不要有较大的内存使用,比如大的静态数组;而且除非算法必要,否则一般不要使用较深的迭代函数调用,那样栈消耗内存会随着迭代次数的增加飞涨。
    3).关于生命周期。栈较短,随着函数退出或返回,本函数的栈就完成了使用;堆就要看什么时候释放,生命周期就什么时候结束。
    说了这么多,我们发现解析Coredump还是跟栈的关系相对紧密,跟堆的关系是有一种产生Coredump的原因是访问堆内存出错。

2.  x86的栈帧
    继续谈栈帧布局,这次说说x86的栈帧布局和操作方法,见栈帧布局图:
    
    上图描述的是一般x86的stack frame栈帧布局方式,当前帧为当前函数(被调用者)的stack frame,调用者的帧为调用函数(调用者)的stack frame。栈底在高地址,栈向下增长。图中,ebp(base pointer)就是栈基址,它指向当前函数的栈帧起始地址;esp(stack pointer)则是当前函数的栈指针,它指向栈顶的位置。压栈的顺序依次为栈基址ebp、其它寄存器、本地变量和临时变量。注意所传递的参数和返回地址lr一般放在调用者的栈帧中,当然按照实际的压栈过程,也有人认为previous ebp也在调用者栈帧内,这个暂时影响不大。
    相比于MIPS,x86比较可爱的地方是刚刚提到的两点,就是可以用栈基址和栈指针明确标示栈帧的位置,栈指针esp一直移动,同时压栈的顺序有一定的规律,一个栈空间内的地址前面,必然有一个代码地址(lr)明确标示着调用函数位置内的某个地址

3.  x86的栈操作
    x86有一对表示栈底和栈顶的寄存器,寄存器ebp是栈基址指针,指栈帧的底部(高地址),寄存器esp是栈指针,指栈帧的顶部(地址地)。
    函数的返回结果是通过寄存器eax传递的,因此在函数退出前会将计算结果拷贝到eax中,然后再出栈返回调用者。
    再来关注几个常用的函数内外跳转指令:
    call: 调用一个函数。以寄存器和偏移量来调用函数和c++里的虚函数调用很类似
    ret:  从一个函数返回。_stdcall调用规范要求如果有返回值,就要将返回值从堆栈的栈顶弹出
    leave:是move ESP EBP/pop EBP的简写,用来退出函数,同时释放了为局部变量分配的空间
    jmp:  无条件跳转
    je:   如果相等则跳转
    jne:  如果不等则跳转
    顺便提一下数据的传递操作mov和movl,mov是将右操作数复制到左操作数,而movl传递方向相反,是将左操作数复制到右操作数。装入有效地址指令(即用来得到局部变量和函数参数的指针)lea和leal也一样。
    最后,x86的压栈出栈指令众人皆知,分别是push和pop,或者其各种变体,比如puchad/popad是对所有通用寄存器的栈操作。
这次讲ARM的栈帧布局和操作方法。
1.  ARM的栈帧
    先来看看ARM的栈帧布局图:
   
    上图描述的是ARM的栈帧布局方式,main stack frame为调用函数的栈帧,func1 stack frame为当前函数(被调用者)的栈帧,栈底在高地址,栈向下增长。图中FP就是栈基址,它指向函数的栈帧起始地址;SP则是函数的栈指针,它指向栈顶的位置。ARM压栈的顺序很是规矩(也比较容易被黑客攻破么),依次为当前函数指针PC、返回指针LR、栈指针SP、栈基址FP、传入参数个数及指针、本地变量和临时变量。如果函数准备调用另一个函数,跳转之前临时变量区先要保存另一个函数的参数。
    ARM也可以用栈基址和栈指针明确标示栈帧的位置,栈指针SP一直移动,相比于x86,ARM更为鲜明的特点是,两个栈空间内的地址(SP+FP)前面,必然有两个代码地址(PC+LR)明确标示着调用函数位置内的某个地址。
2.  ARM的汇编指令和栈操作
    ARM微处理器共有37个寄存器,其中31个为通用寄存器,6个为状态寄存器。但是这些寄存器不能被同时访问,具体哪些寄存器是可编程访问的,取决于微处理器的工作状态及具体的运行模式。但在任何时候,通用寄存器R0~R15、一个或两个状态寄存器都是可访问的。有三个特殊的通用寄存器:
  寄存器R13:在ARM指令中常用作堆栈指针SP
  寄存器R14:也称作子程序连接寄存器(Subroutine Link Register)即连接寄存器LR
  寄存器R15:也称作程序计数器PC
    ARM进行函数内压栈和出栈往往使用如下的语句:
  stmfd sp!, {r0-r9, lr}    ; 满递减入栈,给寄存器r0-r9,lr压栈,sp不断减4
  ldmfd sp!, {r0-r9, pc}    ; 满递减出栈,给寄存器r0-r9出栈,并使程序跳转回函数的调用点,sp不断增4
    常用的函数内外跳转指令有mov和BL,ARM有两种跳转方式:
  (1)mov pc, <跳转地址〉
    这种向程序计数器PC直接写跳转地址,能在4GB连续空间内任意跳转。
  (2)通过 B BL BLX BX 可以完成在当前指令向前或者向后32MB的地址空间的跳转(为什么是32MB呢?寄存器是32位的,此时的值是24位有符号数,所以32MB?后面再查查看)。B是最简单的跳转指令。要注意的是,跳转指令的实际值不是绝对地址,而是相对地址——是相对当前PC值的一个偏移量,它的值由汇编器计算得出。BL很常用,它在跳转之前会在寄存器LR(R14)中保存PC的当前内容。BL的经典用法如下:
    bl NEXT       ; 跳转到NEXT
    ……
    NEXT
    ……
    mov pc, lr    ; 从子程序返回。
这次来看看PowerPC体系架构CPU的栈帧布局和操作方法。PowerPC用得不多,有不对的地方大家拍砖啊~~

1.  PowerPC的栈帧
    先来看看PowerPC的栈帧布局图:
    
    上图描述的是PowerPC的栈帧布局方式,PowerPC的栈生长方向也是由高到低,caller是调用者,current是被调用者。压栈的顺序依次是FPR、GPR、CR、Local Variable、Function Parameters、Padding、LR和Back Chain Word。具体涵义如下:
  (1)函数参数域FPR(Function Parameter Register):这个区域的大小是变化的,当调用者传递给被调用者的参数少于8个时,用GPR3-GPR10这8个寄存器就行,被调用者的栈帧中就可不要这个区域;但如果传递的参数多于8个时就需要这个区域。
  (2)通用寄存器GPR(General Parameter Register):当需要保存GPR寄存器中的一个寄存器GPRx时,就需要把从GPRx-GPR31的值都保存到堆栈帧中。
  (3)CR寄存器:即使修改了CR寄存器的某一个段CRx(x=0至7),都要保存这个CR寄存器的内容。
  (4)局部变量域(Local Variables Area):同上FPR所示,如果临时寄存器的数量不足以提供给被调用者的临时变量使用时,就会使用这个区域。
  (5)Function Parameters:跟第一个FPR重复了?暂时不知。
  (6)Padding:是补齐字节数,让当前栈帧的长度保持8Bytes的倍数。
  (7)LR:也就是ra寄存器,是指返回时的函数指针
  (8)Back Chain Word:是调用者函数帧的栈顶esp,即上一个栈帧的低地址,当前函数栈帧的基址ebp
    跟x86和ARM一样,压栈的顺序有一定的规律,一个栈空间内的地址前面,必然有一个代码地址明确标示着调用函数位置内的某个地址。而且很容易发现,跟x86一样(如果x86中ebp算是调用者栈帧的话),栈帧的最后两个位置存储的也是ra和ebp。所以可以考虑向x86学习,根据当前ebp的值回溯出整个任务的调用栈,如图中蓝箭头所示,具体操作后面再专门讲述。
 
2.  PowerPC的寄存器
    PowerPC的ABI规定的寄存器的使用规则如下:
  (1)GPR0:属于易失性寄存器,ABI规定普通用户不能使用此寄存器。GCC编译器用此寄存器来保存LR寄存器,Linux PowerPC用此寄存器来传递系统调用号码。
  (2)GPR1:属于专用寄存器,ABI规定用次寄存器来保存堆栈的栈顶指针。注:PowerPC构架没有独立的栈顶指针,这一点和X86体系结构是不同的
  (3)GPR2:属于专用寄存器,ABI规定普通用户不使用才寄存器,Linux PowerPC用此寄存器来保存当前进程的进程描述符地址。
  (4)GPR3-GPR4:属于易失性寄存器,ABI使用这两个寄存器来保存函数的返回值,或者用来传递参数。
  (5)GPR5-GPR10:也属于易失性寄存器,加上GPR3和GPR4共8个寄存器用来传递函数的参数。当函数的参数超过八个时使用堆栈来传递。
  (6)GPR11-GPR12:属于易失性寄存器,ABI规定普通用户不使用该寄存器,Linux PowerPC有时用这两个寄存器来存放临时变量,但是GCC编译器没有使用这两个寄存器。
  (7)GPR13:属于专用寄存器,ABI规定该寄存器sdata段的基地址指针。Linux PowerPC在系统初始化时使用该寄存器来存放临时变量。GCC有时会根据某些规则将一些常用的数据放入sdata或者sbss段中。应用程序对sdata或者sbss段数据的访问与对data和bss段数据的访问机制不同,访问sdata段的数据速度更快。
  (8)GPR14-GPR31:属于非易失性寄存器。ABI使用这些寄存器来存放一些临时变量,在应用程序中可以自由使用这些变量。
 
3.  PowerPC的汇编指令和栈操作
    PowerPC寄存器没有专用的push和pop指令来执行堆栈操作,所以PowerPC构架使用存储器访问指令stwu、lwzu来代替push和pop指令。
    下面我们通过一个例子来说明堆栈帧的建立、使用和移除过程:
    func1中开始几行汇编会为自己建立栈帧:
func1:    mflr %r0                ;Get link register
          stwu %r1,-88(%r1)       ;Save back chain then move sp
          stw %r0,+92(%r1)        ;Save link register
          stmw %r28,+72(%r1)      ;Save 4 non-volatiles r28-r31

    func1的结尾几行,会移除前面建立的栈帧,并使得SP(即GPR1)寄存器指向上一个栈帧的栈顶(即栈帧的最低地址处,也就是back chair)
    如下所示:
          lwz %r0,+92(%r1)       ;Get saved link register
          mtlr %r0               ;Restore link register
          lmw %r28,+72(%r1)      ;Restore non-volatiles
          addi %r1,%r1,88        ;Remove frame from stack
          blr                    ;Return to caller function
程序发生Crash时,一般会coredump出转储文件core file。Crash调查的最直接目标是根据core file进行栈回溯或还原栈帧, 即find call trace。同时根据寄存器和出错处汇编代码,分析Crash的深层次原因,并提出解决方法。

 

1.  coredump设置
    要使coredump时产生合适的core file,需正确设置corefile format,这个在procfs中可以定制:
/proc/sys/kernel/core_pattern  
    core_pattern包含全路径,比如是/var/core/%e-%p-%t,则表示:/var/core/进程名-进程PID-CrashTime
    顺便关注一下内核源码中,core file及其名字的生成过程:
do_signal                            # arch/x86/kernel/signal.c
->  get_signal_to_deliver            # kernel/signal.c
->    do_coredump
->      format_corename              # fs/exec.c

    为了使生成的core file发挥更为直观的作用,要注意编译exec file(executed-file)时加上-g选项,这样通过gdb工具查找到的信息才更有价值。关于core file和exec file,有几个注意点。

  • MUST have exec file together, the unstrip is better;
  • MUST have shared lib file together, if some code is in lib;
  • MUST accordant with core file and exec file!

 

2.  Crash调查
    如果做到了上面说的几点,发生coredump时能保留现场环境,那么正常情况下gdb的backtrace命令就能找到call trace。也就不用下面那么费劲。
    但总会出现一些不好处理的异常情况。比如在x86结构CPU的程序crash时,也许会出现一堆问号,如下所示:
(gdb) bt
#0  0xb5ff6106 in raise () from /lib/libc.so.6
#1  0xb6112ff4 in ?? () from /lib/libc.so.6
#2  0xb459f900 in ?? ()
#3  0xbfed551c in ?? ()
#4  0xb5ff7be1 in abort () from /lib/libc.so.6
#5  0x00000004 in ?? ()
Previous frame inner to this frame (corrupt stack?)

    出现异常的可能原因是exec file的symbol不存在或不匹配。根据之前对x86栈帧布局图的分析(http://blog.chinaunix.net/uid-16459552-id-3328601.html),bp-ra(即栈帧基址-返回地址)必定在栈帧顶端的固定位置,可以利用这个分布特点进行栈回溯。从当前的bp0开始,找到上一个bp1=*bp0,ra1=*(bp0 + 4),ra1就是调用函数的地址。继续回溯,bp2=*bp1,ra2=*(bp1 + 4),ra2应该是再上一级调用函数的地址。如此循环,同时用info symbol $ra* 打印出来每一级函数,就找到实际的call trace信息了。

 

3.  栈回溯示例
    获取pc(即eip)和bp(即ebp)的值,为回溯第一步作准备。执行gdb命令info register:
(gdb) i r
eax            0x0 0
ecx            0x3e4a 15946
edx            0x6 6
ebx            0x3e4a 15946
esp            0xbfed53f8     0xbfed53f8
ebp            0xbfed5400 0xbfed5400
esi            0xbfed54a0 -1074965344
edi            0xb6111ff4 -1240391692
eip            0xb5ff6206 0xb5ff6206 <raise+70>
eflags         0x202 [ IF ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x0 0
gs             0x33 51

    实现上述栈回溯的shell脚本代码关键部分如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
echo "Call func is: "
info symbol $pc        #打印当前函数的符号(即名字)
x/4x $ebp              #显示上一个栈帧顶部 4 个内存地址中的值,首先是上一个栈帧的bp和调用函数ra。保存在文件call trace .log中最后一行
#下面是循环迭代部分
bp_now=$(tail -l "calltrace.log" | awk -F: '{print$1}' )     #当前栈帧基址
bp_up=$(tail -l "calltrace.log" | awk '{print$2}' )          #上一个栈帧基址
func_up=$(tail -l "calltrace.log" | awk '{print$3}' )        #上一个栈帧对应函数
if [ $bp_up = "Cannot" -o $bp_up = "can't" ]; then
     exit
else
     gdb -q "$exec_file" "$core_file" << EOF
     set loggint redirect on
     set prompt
     set logging file "calltrace.log"
     set height 0
     echo Call function in :
     print/x $func_up
     info symbol $func_up          #打印上一个栈帧对应函数的符号(即名字),形成call trace !
     echo Upper frame bp is :
     print/x $bp_up
     x/4x $bp_up                   #为下一次回溯作准备!
     quit
EOF
fi  


    一次回溯的打印结果如下所示:
Call func is:
raise + 70 in section .text    #当前函数
0xbfed54000xbfed552c  0xb5ff7bd1  0x00000006  0xbfed54a0

Call function in:$1 = 0xb5ff7bd1
abort + 257 in section .text   #上一个调用函数
Upper frame bp is:$2 = 0xbfed552c
0xbfed552c0xbfed5b78  0xb602f2ab  0x00000004  0xbfed5664

......

 

    x86的backtrace异常还有一种情况是只能回溯一二级栈帧,如下所示:

(gdb) bt
#0  0xb5ff6106 in raise () from /lib/libc.so.6
(gdb) x $ebp
0x0:  Cannot acess memory at adderss 0x0

    这种情况一般是因为栈溢出,最外层的部分栈帧被冲掉了,所以无法进行栈回溯。这时,还是可以利用bp-ra在固定位置的分布特点来调查,从当前sp开始找“栈地址-函数地址”对,找到后就开始按上面的方法回溯。

 

4.  PowerPC和ARM的栈回溯
    鉴于PowerPC和ARM结构的CPU也有类似的栈帧布局,也可以采用上面的方法解决backtrace异常的问题。PowerPC栈帧布局特点见http://blog.chinaunix.net/uid-16459552-id-3459993.html ,ARM栈帧布局特点见http://blog.chinaunix.net/uid-16459552-id-3364761.html 。由于暂时没有target环境,也未找到合适的虚拟硬件场景技术,所以无法为PowerPC和ARM类型的CPU程序Crash调查提供栈回溯例子,以后有机会补上。



http://chatgpt.dhexx.cn/article/rVPjVEld.shtml

相关文章

(栈帧和函数调用一)栈帧,函数调用与栈的关系

&#xff08;栈帧和函数调用一&#xff09;栈帧&#xff0c;函数调用与栈的关系 一&#xff0c;栈帧的介绍二&#xff0c;函数调用与栈的关系三&#xff0c;汇编演示四&#xff0c;总结 在计算机科学中&#xff0c;栈是一个特殊的容器&#xff0c;用户可以将数据压入栈中&#…

理解栈帧和栈的运行原理

栈中的数据都是以栈帧&#xff08;Stack Frame&#xff09;的格式存在&#xff0c;栈帧是一个内存区块&#xff0c;是一个数据集&#xff0c;是一个有关方法 (Method) 和运行期数据的数据集&#xff0c;当一个方法A被调用时就产生了一个栈帧 F1&#xff0c;并被压入到栈中&…

函数栈帧的形成与释放

✅作者简介&#xff1a;嵌入式入坑者&#xff0c;与大家一起加油&#xff0c;希望文章能够帮助各位&#xff01;&#xff01;&#xff01;&#xff01; &#x1f4c3;个人主页&#xff1a;rivencode的个人主页 &#x1f525;系列专栏&#xff1a;玩转C语言 &#x1f4ac;推荐一…

【函数栈帧的创建和销毁】(超详细图解)

想必大家在学完C语言函数章节之后&#xff0c;是否有这样的困惑&#xff1a; 局部变量是怎么创建的 &#xff1f; 为什么局部变量的值是随机值 &#xff1f; 函数是怎么传参的&#xff1f;传参的顺序又是什么样的 &#xff1f; 形参和实参是什么关系 &#xff1f; 函数调用…

C语言函数栈帧详解

系列文章目录 前言 最近正在学习栈帧方面的知识&#xff0c;由于本人对汇编不太熟悉&#xff0c;对其中频繁出现的ESP寄存器和EBP寄存器一直没搞清楚&#xff0c;在查找资料后&#xff0c;在此进行整理&#xff0c;方便以后温故知新。 一、预备知识 要清楚理解栈帧的概念&…

详解栈帧结构

https://www.1024do.com/?p367 栈帧结构 含义&#xff1a;C语言中&#xff0c;每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。栈帧也叫过程活动记录&#xff0c;是编译器用来实现过程函数调用的一种数据结构。 从逻辑上讲&#xff0c;栈帧就是…

函数栈帧详解

目录 一.什么是函数栈帧 1.寄存器&#xff1a; 2.函数栈帧 3.栈帧的作用和维护 4.栈帧结构 二.函数栈帧的创建 1.汇编代码 2.main函数函数栈帧的创建 1.汇编语言讲解&#xff1a; 2.栈帧创建&#xff1a; 3.详细步骤 3.ADD函数栈帧的创建 栈帧创建&#xff1a; 3.函…

栈帧

首先应该明白,栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(地址地)。下图为典型的存取器安排,观察栈在其中的位置 入栈操…

什么是函数栈帧

函数栈帧的创建与销毁 一、函数栈帧的创建1.寄存器2.函数栈帧3.函数中调用函数 二、函数栈帧的销毁总结 一、函数栈帧的创建 1.寄存器 一般来说&#xff0c;计算机中的寄存器有六种 分别是&#xff1a;eax, ebx, ecx,edx,ebp,esp 而ebp,esp这两个寄存器中存放的是地址&#…

栈帧 stack frame

栈帧 stack frame 每一次函数调用都会维护一个栈帧&#xff08;stack frame&#xff09;&#xff0c;栈帧主要用于传递参数、保存返回地址、保存局部变量等。先直接上一个《深入理解计算机系统》上的原图。 其中&#xff0c;%rsp 指向栈顶位置&#xff0c;%rbp 指向栈底位置。…

C/函数栈帧

&#x1f331;博客主页&#xff1a;大寄一场. &#x1f331;系列专栏&#xff1a;C语言学习笔记 &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 ​ 目录 前言 寄存器 1. 寄存器的种类与功能 C语言汇编指令介绍 函数栈帧的创建与销毁过程 1.函数栈帧的…

浅谈函数栈帧(Stack Frame)

&#x1f499;作者&#xff1a;阿润菜菜 &#x1f4d6;专栏&#xff1a;C 本文目录 什么是栈帧 在调试中观察 总结 什么是栈帧 那我们先来看看什么是栈&#xff1a; 栈(stack)是限定仅在表尾进行插入或者删除的线性表。栈是一种数据结构&#xff0c;它按照后进先出的原则存储…

栈和栈帧

栈 堆栈(stack)又称为栈或堆叠&#xff0c;是计算机科学里最重要且最基础的数据结构之一&#xff0c;它按照FILO&#xff08;First In Last Out&#xff0c;后进先出&#xff09;的原则存储数据。 栈的相关概念&#xff1a; 栈顶和栈底&#xff1a;允许元素插入与删除的一端…

函数栈帧的创建和销毁(图解)

目录 基础知识介绍1. 寄存器的种类与功能2. 常用汇编指令3. 内存模型 演示函数栈帧的创建销毁过程1. 为main()函数开辟栈帧2. 在main()函数中创建变量3. 调用Add()函数前的准备4. 为Add()函数开辟栈帧5. 在Add()函数中创建变量并运算6. Add()栈帧的销毁7. 返回main()函数栈帧 总…

运行时栈帧结构是怎样的?

写在前面 本文隶属于专栏《100个问题搞定Java虚拟机》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和文献引用请见100个问题搞定Java虚拟机 解答 栈帧(Stack Frame)是J…

函数栈帧(详细图解)

目录 一、栈 二、常用寄存器及简单汇编指令 三、理解栈帧 3.1 main函数栈帧创建 3.1.1 main函数栈帧创建动态演示 3.2 局部变量创建 3.2.1 局部变量创建动态演示 3.3 函数传参与调用 3.3.1 函数传参 3.3.2 函数传参动态演示 3.3.3 函数调用 3.3.4 函数返回 四、END…

栈帧详解——C语言进阶

目录 传统艺能&#x1f60e;过渡区&#x1f923;正片开始&#x1f440;寄存器&#x1f44f;main函数创建&#x1f44f;局部变量创建&#x1f44f;函数部分&#x1f44f;形参与实参&#x1f44f; 传统艺能&#x1f60e; 小编是大一菜鸟不赘述&#xff0c;欢迎大佬指点江山&…

栈帧结构详解

前言 Java虚拟机以方法作为基本的执行单位&#xff0c;“栈帧”是用于支持虚拟机进行方法调用和执行的数据结构&#xff0c;每一个方法从调用开始到执行结束&#xff0c;都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程&#xff0c;栈帧也是虚拟机运行时数据区中虚拟机栈的栈…

浅谈栈帧

一、 什么是栈帧&#xff1f; 什么是栈帧&#xff0c;首先引用百度百科的经典解释&#xff1a;“栈帧也叫过程活动记录&#xff0c;是编译器用来实现过程/函数调用的一种数据结构。 实际上&#xff0c;可以简单理解为&#xff1a;栈帧就是存储在用户栈上的&#xff08;当然内…

栈帧(Stack Frame)

0x01.栈在计算机中的应用 在计算机系统中&#xff0c;栈也可以称之为栈内存是一个具有动态内存区域,存储函数内部&#xff08;包括main函数&#xff09;的局部变量和方法调用和函数参数值&#xff0c;是由系统自动分配的&#xff0c;一般速度较快&#xff1b;存储地址是连续且…