armv 7命令セットC関数呼び出しスタックポインタSPの変化


まず説明すると、armにおける関数呼び出しの異なるコンパイラは大きく異なる可能性があり、arm-linuxのクロスコンパイラであっても差があり、r 7レジスタをスタックフレームレジスタ(fp)として、r 11レジスタをスタックフレームポインタ(fp)として、例えばarm-linux-gnueabihf-gcc用のr 7とarm-linux-gnueabi-gcc用のr 11として、また関数実行の先頭での処理も異なります
1.arm-linux-gnueabihf-gccコンパイラは、まず関数の変数にスタック空間を割り当て、その後、fpとspをスタックトップに向ける
2.arm-linux-gnueabi-gccコンパイラは、fpとspをスタックトップ(スタックボトム)に指向させ、その後、関数内の変数にスタック空間を割り当て、その後、spは新しいスタックトップを指向し、fpが指向する古いスタックトップは関数のスタックボトムになる.
個人的には2つ目が理解しやすいと思いますが、要するに、関数の実行が開始されるたびにfpポインタをスタック空間に保存する必要があります.ここで、保存されたfpポインタは前の関数のデータ(一般的にスタックデータ)で、すぐに本関数のスタックデータをfpポインタに保存し、関数呼び出しが終了し、fpのデータに基づいてspポインタを復元します.これにより、終了関数のスタックスペースが解放されます.
このように、関数呼び出しにスタックに入る必要があるデータは、前の関数のfpポインタのみであり、ジャンプ時にジャンプ命令blの次の命令アドレスをlrレジスタに保存し、関数呼び出しが終了して戻るのを便利にする.もちろん、関数のパラメータが多く、レジスタが足りない場合は、これらのパラメータもスタックに入る必要があります.
次の2つのコンパイラのCコードと逆アセンブリを貼ります.
Cプログラムを見てみましょう.
int m = 8;
int fun(int a,int b)
{
    int c = 0;
    c = a + b;
    return c;
}
int main()
{
    int i = 4;
    int j = 5;
    m = fun(i, j);
    return 0;
}

使用するコンパイラはarm-linux-gnueabihf-gccで、チップはI.Mx 6 ullで、対応する逆アセンブリコード:
Disassembly of section .text:

00010094 :
   10094:	b480      	push	{r7}
   10096:	b085      	sub	sp, #20
   10098:	af00      	add	r7, sp, #0
   1009a:	6078      	str	r0, [r7, #4]
   1009c:	6039      	str	r1, [r7, #0]
   1009e:	2300      	movs	r3, #0
   100a0:	60fb      	str	r3, [r7, #12]
   100a2:	687a      	ldr	r2, [r7, #4]
   100a4:	683b      	ldr	r3, [r7, #0]
   100a6:	4413      	add	r3, r2
   100a8:	60fb      	str	r3, [r7, #12]
   100aa:	68fb      	ldr	r3, [r7, #12]
   100ac:	4618      	mov	r0, r3
   100ae:	3714      	adds	r7, #20
   100b0:	46bd      	mov	sp, r7
   100b2:	f85d 7b04 	ldr.w	r7, [sp], #4
   100b6:	4770      	bx	lr

000100b8 
: 100b8: b580 push {r7, lr} 100ba: b082 sub sp, #8 100bc: af00 add r7, sp, #0 100be: 2304 movs r3, #4 100c0: 607b str r3, [r7, #4] 100c2: 2305 movs r3, #5 100c4: 603b str r3, [r7, #0] 100c6: 6839 ldr r1, [r7, #0] 100c8: 6878 ldr r0, [r7, #4] 100ca: f7ff ffe3 bl 10094 100ce: 4602 mov r2, r0 100d0: f240 03e4 movw r3, #228 ; 0xe4 m 100d4: f2c0 0302 movt r3, #2 100d8: 601a str r2, [r3, #0] 100da: 2300 movs r3, #0 100dc: 4618 mov r0, r3 100de: 3708 adds r7, #8 100e0: 46bd mov sp, r7 100e2: bd80 pop {r7, pc} Disassembly of section .data: 000200e4 : 200e4: 00000008 andeq r0, r0, r8 /* 00000008 m , andeq */

arm-linux-gnueabi-gccコンパイラ対応の逆アセンブリコードを使用する(自己参照)https://blog.csdn.net/melody157398/article/details/104454219あ、個人的にはこの文章が与えた図は少し間違っているかもしれないと思いますが、逆アセンブリコードは大丈夫です):
00010400 :
   10400:       e52db004        push    {fp}            ; (str fp, [sp, #-4]!)
   10404:       e28db000        add     fp, sp, #0
   10408:       e24dd014        sub     sp, sp, #20
   1040c:       e50b0010        str     r0, [fp, #-16]
   10410:       e50b1014        str     r1, [fp, #-20]  ; 0xffffffec
   10414:       e3a03000        mov     r3, #0
   10418:       e50b3008        str     r3, [fp, #-8]
   1041c:       e51b2010        ldr     r2, [fp, #-16]
   10420:       e51b3014        ldr     r3, [fp, #-20]  ; 0xffffffec
   10424:       e0823003        add     r3, r2, r3
   10428:       e50b3008        str     r3, [fp, #-8]
   1042c:       e51b3008        ldr     r3, [fp, #-8]
   10430:       e1a00003        mov     r0, r3
   10434:       e24bd000        sub     sp, fp, #0
   10438:       e49db004        pop     {fp}            ; (ldr fp, [sp], #4)
   1043c:       e12fff1e        bx      lr
 
 
00010440 
: 10440: e92d4800 push {fp, lr} 10444: e28db004 add fp, sp, #4 10448: e24dd008 sub sp, sp, #8 1044c: e3a03004 mov r3, #4 10450: e50b300c str r3, [fp, #-12] 10454: e3a03005 mov r3, #5 10458: e50b3008 str r3, [fp, #-8] 1045c: e51b1008 ldr r1, [fp, #-8] 10460: e51b000c ldr r0, [fp, #-12] 10464: ebffffe5 bl 10400 10468: e1a02000 mov r2, r0 1046c: e59f3010 ldr r3, [pc, #16] ; 10484
m 10470: e5832000 str r2, [r3] 10474: e3a03000 mov r3, #0 10478: e1a00003 mov r0, r3 1047c: e24bd004 sub sp, fp, #4 10480: e8bd8800 pop {fp, pc} 10484: 00021024 andeq r1, r2, r4, lsr #32

2番目の逆アセンブリコードを参照すると、スタックの変化過程が理解しやすいと思いますが、関数呼び出しは2つのポイントです.
1.関数自身のスタックベースアドレスを保存し、関数終了スタック空間が正常に解放されることを保証するが、fpポインタが1つしかないので、fpポインタを使用して本関数のスタックベースアドレスを保存する前に、前の関数のスタックベースアドレスをスタックに入れる、すなわち、fpをスタックに入れ、fpを使用して新しいスタックベースポインタを保存する.
2.blコマンドを使用して、関数呼び出しが終了してジャンプできることを保証します.bl命令の次のアドレスをlrレジスタに保存します.