X86-64和ARM64用戶棧的結構 (5) --- mian()函數和子函數之間的棧

main函數及其子函數之間的棧

1 工具及實驗程序

本文的實驗在一個虛擬機中進行,虛擬機模擬的cpu是x86-64(Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz),運行的是64bit ubuntu,安裝了ARM64的工具鏈:

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

實驗使用的程序爲:

#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:

(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形參的位置處。

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。

    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()函數和子函數之間的棧

  • 函數控制轉移。完成函數控制切換,說白了也就是讓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()函數和子函數之間的棧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章