X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

main函数及其子函数之间的栈

1 工具及实验程序

本文的实验在一个虚拟机中进行,虚拟机模拟的cpu是x86-64(Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz),运行的是64bit ubuntu,安装了ARM64的工具链:linux

$sudo apt-get install gcc-aarch64-linux-gnu
$sudo apt-get install gcc-arm-linux-gnueabi

实验使用的程序为:ubuntu

#include<stdio.h>
#include <stdlib.h>

int func_C(int x1, int x2, int x3, int x4, int x5, int x6){
        int sum  = 0;
        sum = x1 + x2;
        sum = sum + x3 + x4;
        sum = sum + x5 + x6;
        return sum;
}

int func_B(int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8, char x9){
        int sum = 0;
        sum = func_C(x1, x2, x3, x4, x5,x6);
        sum = sum + x7;
        sum = sum + x8;
        sum += x9;
        return sum;
}

void func_A(void){
        int sum = 0;
        int x1 = 1;
        int x2 = 2;
        int x3 = 3;
        int x4 = 4;
        int x5 = 5;
        int x6 = 6;
        int x7 = 7;
        int x8 = 8;
        char x9 = 'c';
        sum = func_B(x1, x2, x3, x4, x5, x6, x7, x8, x9);
        printf("sum = %d\n", sum);
}

int main(int argc, char *argv[], char *envp[])
{
        int count = argc;
        char **p_argv = argv;
        char **p_env = envp;

        func_A();

        return 0;
}

2 x86-64

2.1 main函数的栈

int main(int argc, char *argv[], char *envp[])
{
        int count = argc;
        char **p_argv = argv;
        char **p_env = envp;

        func_A();

        return 0;
}

首先,编译源程序 $gcc test.c -o test -fno-stack-protector,而后反汇编出main函数 $gdb test:ide

(gdb) disas main
Dump of assembler code for function main:
   0x0000000000000790 <+0>:     push   %rbp
   0x0000000000000791 <+1>:     mov    %rsp,%rbp
   0x0000000000000794 <+4>:     sub    $0x40,%rsp
   0x0000000000000798 <+8>:     mov    %edi,-0x24(%rbp)
   0x000000000000079b <+11>:    mov    %rsi,-0x30(%rbp)
   0x000000000000079f <+15>:    mov    %rdx,-0x38(%rbp)
   0x00000000000007a3 <+19>:    mov    -0x24(%rbp),%eax
   0x00000000000007a6 <+22>:    mov    %eax,-0x4(%rbp)
   0x00000000000007a9 <+25>:    mov    -0x30(%rbp),%rax
   0x00000000000007ad <+29>:    mov    %rax,-0x10(%rbp)
   0x00000000000007b1 <+33>:    mov    -0x38(%rbp),%rax
   0x00000000000007b5 <+37>:    mov    %rax,-0x18(%rbp)
   0x00000000000007b9 <+41>:    callq  0x6fd <func_A>
   0x00000000000007be <+46>:    mov    $0x0,%eax
   0x00000000000007c3 <+51>:    leaveq
   0x00000000000007c4 <+52>:    retq
End of assembler dump.

从上面对main函数栈的分析能够知道,一个函数栈大体会作如下几件事:函数

  • 保存上一个栈帧的信息。
    0x0000000000000790 <+0>:     push   %rbp
    0x0000000000000791 <+1>:     mov    %rsp,%rbp

    在x86-64上rbp和rsp彻底能够描绘出一个栈帧,rbp被称为帧指针(frame pointer),rsp被称为栈指针(stack pointer);rbp+0x08指向当前栈帧的底部,rsp指向栈帧的顶部。下面的第一句是将上一个栈帧的帧指针rbp压栈保存起来;rbp保存起来后,紧接着下一句汇编就为rbp赋予一个新值,将栈指针rsp的值赋给帧指针rbp,让rbp指向当前栈帧的底部,其实rbp+0x08才是当前栈帧的底部,只不过rbp+0x08处是上一个函数运行call指令时硬件自动存放的rip,对软件不可见。工具

X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

  • 开栈。
    0x0000000000000794 <+4>:     sub    $0x40,%rsp

    也就是开辟当前栈帧的空间,开辟的栈帧空间主要用于接收参数、存放局部变量以及运算的场所,下面的汇编开辟0x40个字节的空间。布局

X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

  • 接收参数。
    0x0000000000000798 <+8>:     mov    %edi,-0x24(%rbp)
    0x000000000000079b <+11>:    mov    %rsi,-0x30(%rbp)
    0x000000000000079f <+15>:    mov    %rdx,-0x38(%rbp)

    前面也说过,x86-64参数传递的规则是rdi传递第一个参数、rsi传递第二个参数、rdx传递第三个参数....。mian函数的一个形参是argc,第二个形参是argv,第三个形参是envp。从下面的汇编也能够看出,实参在rdi、rsi和rdx中,而后分别放到rbp - 0x24 、rbp - 0x30和rbp -0x38形参的位置处。3d

X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

  • 栈上运算。
    0x00000000000007a3 <+19>:    mov    -0x24(%rbp),%eax
    0x00000000000007a6 <+22>:    mov    %eax,-0x4(%rbp)
    0x00000000000007a9 <+25>:    mov    -0x30(%rbp),%rax
    0x00000000000007ad <+29>:    mov    %rax,-0x10(%rbp)
    0x00000000000007b1 <+33>:    mov    -0x38(%rbp),%rax
    0x00000000000007b5 <+37>:    mov    %rax,-0x18(%rbp)
    0x00000000000007b9 <+41>:    callq  0x6fd <func_A>

    栈的最大做用就是做为运算场所,main的栈上并无安排太多的运算,仅仅作了三次赋值,而后主要工做就就转给子函数了。下面的一二两句的做用是将argc的值赋值给count;三四句的做用是将argv的值赋给p_argv;五六两句的做用是将envp赋值给p_envl;最后一句就是调用子函数,咱们也把他看作是运算的一部分。指针

X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

  • 设置返回值。x86-64的函数返回值使用rax传递。从return 0可知函数的返回值是0,所以将0赋给eax。code

    0x00000000000007be <+46>:    mov    $0x0,%eax
  • 恢复上一个栈帧。先看一下相反的操做保存栈帧的两个步骤:一是将帧指针(rbp)压栈;二是将栈指针(rsp)的值赋值给帧指针(rbp),可知,上一个栈帧结构能够由当前的帧指针rbp推导出,也即下面汇编语句leaveq的做用,该语句的做用有两个:一是将帧指针(rbp)的值赋值给栈指针(rsp),即mov %rbp, %rsp;二是将帧指针(rbp)出栈,即pop %rbp。正好和保存上一个栈帧结构的操做相反。leaveq指令执行完后,帧指针(rbp)已经切换回caller的栈了,也即数据存储区已经切换完成,只差函数控制切换。
    0x00000000000007c3 <+51>:    leaveq

    为了更加深刻的了解如何恢复栈帧,画了一个以下所示的图,rsp和rbp指向了current frame,也就是说寄存器rbp和rsp并无指向任何"previous frame",恢复上一个栈帧的核心问题在于如何让rbp和rsp指向上一个栈帧,答案的钥匙就是rbp寄存器。rbp指向的就是上一个rbp,rbp-16就是上一个rsp, 直觉上恢复上一个栈帧就是将rbp-16赋值给rsp,并将rbp指向的值赋值给rbp,显示上述方法能够恢复rsp和rbp,可是事实上并无使用上述方法,而是利用栈天然而然的恢复上一个栈帧,所谓的天然而然就是怎么来怎么回去。首先让rbp赋值给rsp,而后pop一次栈便可恢复rbp,再pop栈一次便可恢复rsp。“首先让rbp赋值给rsp,而后pop一次栈便可恢复rbp”是人类发明的指令leaveq干的,"再pop栈一次便可恢复rsp"是人类发明的指令ret干的。
    X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈ip

  • 函数控制转移。完成函数控制切换,说白了也就是让CPU接着执行caller函数中callee函数后面的指令。下面的指令使用栈指针指向的值恢复rip,功能能够按照mov (%sp), %rip; addq $8,%rsppop %rip来理解。该指令执行完后,函数的控制以及栈指针(rsp)切换完成。 retq指令改变了rsp 和 rip的值。
    0x00000000000007c4 <+52>:    retq

    X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

2. 2 子函数的栈

2.2.1 func_A的栈

void func_A(void){
        int sum = 0;
        int x1 = 1;
        int x2 = 2;
        int x3 = 3;
        int x4 = 4;
        int x5 = 5;
        int x6 = 6;
        int x7 = 7;
        int x8 = 8;
        char x9 = 'c';
        sum = func_B(x1, x2, x3, x4, x5, x6, x7, x8, x9);
        printf("sum = %d\n", sum);
}

首先,编译源程序 $gcc test.c -o test -fno-stack-protector,而后反汇编出func_A函数 $gdb test:

(gdb) disas func_A
Dump of assembler code for function func_A:
   0x00000000000006fd <+0>:     push   %rbp
   0x00000000000006fe <+1>:     mov    %rsp,%rbp
   0x0000000000000701 <+4>:     sub    $0x30,%rsp
   0x0000000000000705 <+8>:     movl   $0x0,-0x4(%rbp)
   0x000000000000070c <+15>:    movl   $0x1,-0x8(%rbp)
   0x0000000000000713 <+22>:    movl   $0x2,-0xc(%rbp)
   0x000000000000071a <+29>:    movl   $0x3,-0x10(%rbp)
   0x0000000000000721 <+36>:    movl   $0x4,-0x14(%rbp)
   0x0000000000000728 <+43>:    movl   $0x5,-0x18(%rbp)
   0x000000000000072f <+50>:    movl   $0x6,-0x1c(%rbp)
   0x0000000000000736 <+57>:    movl   $0x7,-0x20(%rbp)
   0x000000000000073d <+64>:    movl   $0x8,-0x24(%rbp)
   0x0000000000000744 <+71>:    movb   $0x63,-0x25(%rbp)
   0x0000000000000748 <+75>:    movsbl -0x25(%rbp),%edi
   0x000000000000074c <+79>:    mov    -0x1c(%rbp),%r9d
   0x0000000000000750 <+83>:    mov    -0x18(%rbp),%r8d
   0x0000000000000754 <+87>:    mov    -0x14(%rbp),%ecx
   0x0000000000000757 <+90>:    mov    -0x10(%rbp),%edx
   0x000000000000075a <+93>:    mov    -0xc(%rbp),%esi
   0x000000000000075d <+96>:    mov    -0x8(%rbp),%eax
   0x0000000000000760 <+99>:    push   %rdi
   0x0000000000000761 <+100>:   mov    -0x24(%rbp),%edi
   0x0000000000000764 <+103>:   push   %rdi
   0x0000000000000765 <+104>:   mov    -0x20(%rbp),%edi
   0x0000000000000768 <+107>:   push   %rdi
   0x0000000000000769 <+108>:   mov    %eax,%edi
   0x000000000000076b <+110>:   callq  0x699 <func_B>
   0x0000000000000770 <+115>:   add    $0x18,%rsp
   0x0000000000000774 <+119>:   mov    %eax,-0x4(%rbp)
   0x0000000000000777 <+122>:   mov    -0x4(%rbp),%eax
   0x000000000000077a <+125>:   mov    %eax,%esi
   0x000000000000077c <+127>:   lea    0xd1(%rip),%rdi        # 0x854
   0x0000000000000783 <+134>:   mov    $0x0,%eax
   0x0000000000000788 <+139>:   callq  0x520 <printf@plt>
   0x000000000000078d <+144>:   nop
   0x000000000000078e <+145>:   leaveq
   0x000000000000078f <+146>:   retq
End of assembler dump.

上述汇编作的事情也是那几个,保存上一个栈帧的信息、开栈、接受参数、栈上运算....,下面将进行分析。

  • 保存上一个栈帧的信息
    0x00000000000006fd <+0>:     push   %rbp
    0x00000000000006fe <+1>:     mov    %rsp,%rbp

保存上一个栈帧的做用就是为了该函数被调用完成后还能再回到caller函数的栈帧继续执行,须要把caller的栈帧保存起来,保存地点就是callee的栈帧上。上述汇编的第一句就是把帧指针rbp入栈保存在栈上,第二句把栈指针rsp保存在帧指针rbp中。上述两句执行完后,func_C的栈布局以下图所示。
X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

  • 开栈
    0x0000000000000701 <+4>:     sub    $0x30,%rsp

开栈的操做比较简单,就是把rsp的值减少,开辟出一片连续的内存区域用做接收参数,存放局部变量以及栈上运算。不过func_C没有参数和栈上运算。执行完上述汇编后,栈的布局以下图所示。
X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

  • 接受参数。func_A不涉及。
  • 栈上运算
    0x0000000000000705 <+8>:     movl   $0x0,-0x4(%rbp)
    0x000000000000070c <+15>:    movl   $0x1,-0x8(%rbp)
    0x0000000000000713 <+22>:    movl   $0x2,-0xc(%rbp)
    0x000000000000071a <+29>:    movl   $0x3,-0x10(%rbp)
    0x0000000000000721 <+36>:    movl   $0x4,-0x14(%rbp)
    0x0000000000000728 <+43>:    movl   $0x5,-0x18(%rbp)
    0x000000000000072f <+50>:    movl   $0x6,-0x1c(%rbp)
    0x0000000000000736 <+57>:    movl   $0x7,-0x20(%rbp)
    0x000000000000073d <+64>:    movl   $0x8,-0x24(%rbp)
    0x0000000000000744 <+71>:    movb   $0x63,-0x25(%rbp)

    这里的栈上运算比较简单,只是对局部变量进行赋值。局部变量赋值事后的栈空间以下图所示:

X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

  • 参数传递
    0x0000000000000748 <+75>:    movsbl -0x25(%rbp),%edi
    0x000000000000074c <+79>:    mov    -0x1c(%rbp),%r9d
    0x0000000000000750 <+83>:    mov    -0x18(%rbp),%r8d
    0x0000000000000754 <+87>:    mov    -0x14(%rbp),%ecx
    0x0000000000000757 <+90>:    mov    -0x10(%rbp),%edx
    0x000000000000075a <+93>:    mov    -0xc(%rbp),%esi
    0x000000000000075d <+96>:    mov    -0x8(%rbp),%eax
    0x0000000000000760 <+99>:    push   %rdi
    0x0000000000000761 <+100>:   mov    -0x24(%rbp),%edi
    0x0000000000000764 <+103>:   push   %rdi
    0x0000000000000765 <+104>:   mov    -0x20(%rbp),%edi
    0x0000000000000768 <+107>:   push   %rdi
    0x0000000000000769 <+108>:   mov    %eax,%edi

前面也说过,在X86-64平台,当参数小于7个时使用寄存器传参 。当参数个数大于等于7时,参数arg1~arg6分别使用寄存器rdi,rsi, rdx, rcx, r8 and r9传参,其他参数使用栈传递。以下图所示,实参x1~x6使用寄存器rdi,rsi, rdx, rcx, r8 and r9传参,实参x7~x9使用栈传递。

X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

2.2.2 func_B的栈

int func_B(int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8, char x9){
        int sum = 0;
        sum = func_C(x1, x2, x3, x4, x5,x6);
        sum = sum + x7;
        sum = sum + x8;
        sum += x9;
        return sum;
}

首先,编译源程序 $gcc test.c -o test -fno-stack-protector,而后反汇编出func_B函数 $gdb test:

(gdb) disas func_B
Dump of assembler code for function func_B:
   0x0000000000000699 <+0>:     push   %rbp
   0x000000000000069a <+1>:     mov    %rsp,%rbp
   0x000000000000069d <+4>:     sub    $0x30,%rsp
   0x00000000000006a1 <+8>:     mov    %edi,-0x14(%rbp)
   0x00000000000006a4 <+11>:    mov    %esi,-0x18(%rbp)
   0x00000000000006a7 <+14>:    mov    %edx,-0x1c(%rbp)
   0x00000000000006aa <+17>:    mov    %ecx,-0x20(%rbp)
   0x00000000000006ad <+20>:    mov    %r8d,-0x24(%rbp)
   0x00000000000006b1 <+24>:    mov    %r9d,-0x28(%rbp)
   0x00000000000006b5 <+28>:    mov    0x20(%rbp),%eax
   0x00000000000006b8 <+31>:    mov    %al,-0x2c(%rbp)
   0x00000000000006bb <+34>:    movl   $0x0,-0x4(%rbp)
   0x00000000000006c2 <+41>:    mov    -0x28(%rbp),%r8d
   0x00000000000006c6 <+45>:    mov    -0x24(%rbp),%edi
   0x00000000000006c9 <+48>:    mov    -0x20(%rbp),%ecx
   0x00000000000006cc <+51>:    mov    -0x1c(%rbp),%edx
   0x00000000000006cf <+54>:    mov    -0x18(%rbp),%esi
   0x00000000000006d2 <+57>:    mov    -0x14(%rbp),%eax
   0x00000000000006d5 <+60>:    mov    %r8d,%r9d
   0x00000000000006d8 <+63>:    mov    %edi,%r8d
   0x00000000000006db <+66>:    mov    %eax,%edi
   0x00000000000006dd <+68>:    callq  0x64a <func_C>
   0x00000000000006e2 <+73>:    mov    %eax,-0x4(%rbp)
   0x00000000000006e5 <+76>:    mov    0x10(%rbp),%eax
   0x00000000000006e8 <+79>:    add    %eax,-0x4(%rbp)
   0x00000000000006eb <+82>:    mov    0x18(%rbp),%eax
   0x00000000000006ee <+85>:    add    %eax,-0x4(%rbp)
   0x00000000000006f1 <+88>:    movsbl -0x2c(%rbp),%eax
   0x00000000000006f5 <+92>:    add    %eax,-0x4(%rbp)
   0x00000000000006f8 <+95>:    mov    -0x4(%rbp),%eax
   0x00000000000006fb <+98>:    leaveq
   0x00000000000006fc <+99>:    retq
End of assembler dump.

一个函数的汇编作的仍是那几件事,下面分析:

  • 保存上一个栈帧信息

X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

0x0000000000000699 <+0>:     push   %rbp
   0x000000000000069a <+1>:     mov    %rsp,%rbp

上一个栈帧的帧指针rbp保存在当前栈帧上, 上一个栈帧的栈指针rsp保存在当前栈帧的帧指针rbp寄存器中。

  • 开栈
    0x000000000000069d <+4>:     sub    $0x30,%rsp

    在栈上开辟一块连续的地址用于接收参数,局部变量,栈上运算。

X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

  • 接收参数
    0x00000000000006a4 <+11>:    mov    %esi,-0x18(%rbp)
    0x00000000000006a7 <+14>:    mov    %edx,-0x1c(%rbp)
    0x00000000000006aa <+17>:    mov    %ecx,-0x20(%rbp)
    0x00000000000006ad <+20>:    mov    %r8d,-0x24(%rbp)
    0x00000000000006b1 <+24>:    mov    %r9d,-0x28(%rbp)
    0x00000000000006b5 <+28>:    mov    0x20(%rbp),%eax
    0x00000000000006b8 <+31>:    mov    %al,-0x2c(%rbp)

    函数总共有9个参数,寄存器传递6个参数,栈传递三个参数。

X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

  • 传递参数
    0x00000000000006c2 <+41>:    mov    -0x28(%rbp),%r8d
    0x00000000000006c6 <+45>:    mov    -0x24(%rbp),%edi
    0x00000000000006c9 <+48>:    mov    -0x20(%rbp),%ecx
    0x00000000000006cc <+51>:    mov    -0x1c(%rbp),%edx
    0x00000000000006cf <+54>:    mov    -0x18(%rbp),%esi
    0x00000000000006d2 <+57>:    mov    -0x14(%rbp),%eax
    0x00000000000006d5 <+60>:    mov    %r8d,%r9d
    0x00000000000006d8 <+63>:    mov    %edi,%r8d
    0x00000000000006db <+66>:    mov    %eax,%edi

    调用函数有6个参数,这6个参数都使用寄存器传递。

X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

  • 栈上运算
    0x00000000000006dd <+68>:    callq  0x64a <func_C>
    0x00000000000006e2 <+73>:    mov    %eax,-0x4(%rbp)
    0x00000000000006e5 <+76>:    mov    0x10(%rbp),%eax
    0x00000000000006e8 <+79>:    add    %eax,-0x4(%rbp)
    0x00000000000006eb <+82>:    mov    0x18(%rbp),%eax
    0x00000000000006ee <+85>:    add    %eax,-0x4(%rbp)
    0x00000000000006f1 <+88>:    movsbl -0x2c(%rbp),%eax
    0x00000000000006f5 <+92>:    add    %eax,-0x4(%rbp)

    运算第一步就是将func_A的返回值%eax赋值给sum;第二步是将x7的值和sum相加放在sum中;第三步是将x8的和sum相加结果放在sum中;第三步是将x9的值扩展成32bit,和sum相加结果放在sum中。

X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

2.2.3 func_C的栈

int func_C(int x1, int x2, int x3, int x4, int x5, int x6){
        int sum  = 0;
        sum = x1 + x2;
        sum = sum + x3 + x4;
        sum = sum + x5 + x6;
        return sum;
}

首先,编译源程序 $gcc test.c -o test -fno-stack-protector,而后反汇编出func_A函数 $gdb test:

(gdb) disas func_C
Dump of assembler code for function func_C:
   0x000000000000064a <+0>:     push   %rbp
   0x000000000000064b <+1>:     mov    %rsp,%rbp
   0x000000000000064e <+4>:     mov    %edi,-0x14(%rbp)
   0x0000000000000651 <+7>:     mov    %esi,-0x18(%rbp)
   0x0000000000000654 <+10>:    mov    %edx,-0x1c(%rbp)
   0x0000000000000657 <+13>:    mov    %ecx,-0x20(%rbp)
   0x000000000000065a <+16>:    mov    %r8d,-0x24(%rbp)
   0x000000000000065e <+20>:    mov    %r9d,-0x28(%rbp)
   0x0000000000000662 <+24>:    movl   $0x0,-0x4(%rbp)
   0x0000000000000669 <+31>:    mov    -0x14(%rbp),%edx
   0x000000000000066c <+34>:    mov    -0x18(%rbp),%eax
   0x000000000000066f <+37>:    add    %edx,%eax
   0x0000000000000671 <+39>:    mov    %eax,-0x4(%rbp)
   0x0000000000000674 <+42>:    mov    -0x4(%rbp),%edx
   0x0000000000000677 <+45>:    mov    -0x1c(%rbp),%eax
   0x000000000000067a <+48>:    add    %eax,%edx
   0x000000000000067c <+50>:    mov    -0x20(%rbp),%eax
   0x000000000000067f <+53>:    add    %edx,%eax
   0x0000000000000681 <+55>:    mov    %eax,-0x4(%rbp)
   0x0000000000000684 <+58>:    mov    -0x4(%rbp),%edx
   0x0000000000000687 <+61>:    mov    -0x24(%rbp),%eax
   0x000000000000068a <+64>:    add    %eax,%edx
   0x000000000000068c <+66>:    mov    -0x28(%rbp),%eax
   0x000000000000068f <+69>:    add    %edx,%eax
   0x0000000000000691 <+71>:    mov    %eax,-0x4(%rbp)
   0x0000000000000694 <+74>:    mov    -0x4(%rbp),%eax
   0x0000000000000697 <+77>:    pop    %rbp
   0x0000000000000698 <+78>:    retq
End of assembler dump.

func_A的栈结构和前几个函数类型,可是编译器识别到该函数时叶子函数(leaf function),其栈指针rsp再也不被使用,就会少一修改rsp的指令和一个恢复rsp的指令。

  • 保存上一个栈帧信息

X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

0x0000000000000699 <+0>:     push   %rbp
   0x000000000000069a <+1>:     mov    %rsp,%rbp

不会有开栈的操做,即不会修改rsp指针。

  • 接收参数
    0x000000000000064e <+4>:     mov    %edi,-0x14(%rbp)
    0x0000000000000651 <+7>:     mov    %esi,-0x18(%rbp)
    0x0000000000000654 <+10>:    mov    %edx,-0x1c(%rbp)
    0x0000000000000657 <+13>:    mov    %ecx,-0x20(%rbp)
    0x000000000000065a <+16>:    mov    %r8d,-0x24(%rbp)
    0x000000000000065e <+20>:    mov    %r9d,-0x28(%rbp)

X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

  • 栈上运算
    0x0000000000000662 <+24>:    movl   $0x0,-0x4(%rbp)
    0x0000000000000669 <+31>:    mov    -0x14(%rbp),%edx
    0x000000000000066c <+34>:    mov    -0x18(%rbp),%eax
    0x000000000000066f <+37>:    add    %edx,%eax
    0x0000000000000671 <+39>:    mov    %eax,-0x4(%rbp)
    0x0000000000000674 <+42>:    mov    -0x4(%rbp),%edx
    0x0000000000000677 <+45>:    mov    -0x1c(%rbp),%eax
    0x000000000000067a <+48>:    add    %eax,%edx
    0x000000000000067c <+50>:    mov    -0x20(%rbp),%eax
    0x000000000000067f <+53>:    add    %edx,%eax
    0x0000000000000681 <+55>:    mov    %eax,-0x4(%rbp)
    0x0000000000000684 <+58>:    mov    -0x4(%rbp),%edx
    0x0000000000000687 <+61>:    mov    -0x24(%rbp),%eax
    0x000000000000068a <+64>:    add    %eax,%edx
    0x000000000000068c <+66>:    mov    -0x28(%rbp),%eax
    0x000000000000068f <+69>:    add    %edx,%eax
    0x0000000000000691 <+71>:    mov    %eax,-0x4(%rbp)

汇编语言描述的和C语言一致。

X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

2.3.4 调用关系以及红区

X86-64和ARM64用户栈的结构 (5) --- mian()函数和子函数之间的栈

相关文章
相关标签/搜索