armv 7命令セットC関数呼び出しスタックポインタSPの変化
5029 ワード
まず説明すると、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プログラムを見てみましょう.
使用するコンパイラはarm-linux-gnueabihf-gccで、チップはI.Mx 6 ullで、対応する逆アセンブリコード:
arm-linux-gnueabi-gccコンパイラ対応の逆アセンブリコードを使用する(自己参照)https://blog.csdn.net/melody157398/article/details/104454219あ、個人的にはこの文章が与えた図は少し間違っているかもしれないと思いますが、逆アセンブリコードは大丈夫です):
2番目の逆アセンブリコードを参照すると、スタックの変化過程が理解しやすいと思いますが、関数呼び出しは2つのポイントです.
1.関数自身のスタックベースアドレスを保存し、関数終了スタック空間が正常に解放されることを保証するが、fpポインタが1つしかないので、fpポインタを使用して本関数のスタックベースアドレスを保存する前に、前の関数のスタックベースアドレスをスタックに入れる、すなわち、fpをスタックに入れ、fpを使用して新しいスタックベースポインタを保存する.
2.blコマンドを使用して、関数呼び出しが終了してジャンプできることを保証します.bl命令の次のアドレスをlrレジスタに保存します.
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レジスタに保存します.