ARMの関数呼び出しのルールは、 『ARMシステム開発者ガイド』のATPCSの部分で詳細に定義されており、fpやspなどのレジスタの関数は、主に関数呼び出し中に関数スタックによって説明されます。ATPCSの詳細については、ドキュメントにアクセスしてください。
fpはスタックポインターレジスタであるフレームポインターレジスタと呼ばれ、spはスタックポインターレジスタであるスタックポインターレジスタと呼ばれます。それで、彼らの特定の役割は何ですか?
まず最初に、各プロセスには独自のスタックスペースがあることを誰もが知っています。プロセスには数千万の関数呼び出しがあり、これらの関数はプロセスのスタックスペースを共有します。問題は、関数の実行プロセスに多くの関数があることです。スタックとスタックのプロセスで、関数がバックトレースを返すときに、戻りアドレスを正確に見つけるにはどうすればよいですか?サブ機能によって保存された一部のレジスターの内容は何ですか?このように、スタックフレームの概念があります。つまり、各関数が使用するスタックスペースはスタックフレームであり、すべてのスタックフレームがこのプロセスの完全なスタックを形成します。また、fpはスタックベースアドレスレジスタであり、現在の関数スタックフレームのスタックの一番下を指し、spは現在の関数スタックフレームのスタックの一番上を指します。親関数のスタックフレームは、sp、fpなどで指摘されたスタックフレームで復元でき、すべての関数の呼び出しシーケンスをバックトレースできます。
その関数のスタックフレームの具体的なスコープは何ですか?fpとspは正確にどこを指すべきですか?下の写真をご覧ください:
写真1
上の図は、ARMのスタックフレームレイアウトを示しています。メインスタックフレームは呼び出し元の関数のスタックフレームであり、func1スタックフレームは現在の関数(呼び出し先)のスタックフレームであり、スタックの最下部は上位アドレスにあり、スタックは下に向かって成長します。この画像はインターネット上の画像であり、理論的には上の画像の形式である必要があります。4つのレジスタfp、sp、lr、pcは非常に特殊なレジスタであり、現在実行中の機能に関する重要な情報を記録しています。実行開始の関数が前の関数の情報を保存するとき、それらをスタックに置いて保存する必要があります。これは非常に重要です!これらはATPCSでは定義されていません。ATPCSは、関数が呼び出されたときにパラメーターがどのように渡されるかを規定し、関数の戻り値が保存されます。上記は、シーンの保存と復元の機能を定義する暗黙の合意であると個人的に感じています。ATPCSを含むこれらの暗黙の合意は、人為的な制約です。目的は、プログラムが失敗しないことを確認することです。実装方法は別のコンパイラである必要があります同じではありません。
armのgccコンパイラーの実装を検証するために、私は小さな実験プログラムを自分で作成しました。
main.c:
#include <stdio.h>
int func(int i);
int main(void)
{
int i = 25;
func(i);
return 0;
}
func.c
int func(int i)
{
int a = 2;
return a * i;
}
func関数はmain.cで呼び出され、func関数はfunc.cファイルで実装されます。以下は、arm-linux-androideabi-gccでコンパイルされた実行ファイルによって逆アセンブルされたコードです。
Disassembly of section .text:
0000822c <func>:
822c: e52db004 push {fp} ; (str fp, [sp, #-4]!)
8230: e28db000 add fp, sp, #0
8234: e24dd014 sub sp, sp, #20
8238: e50b0010 str r0, [fp, #-16]
823c: e3a03002 mov r3, #2
8240: e50b3008 str r3, [fp, #-8]
8244: e51b3008 ldr r3, [fp, #-8]
8248: e51b2010 ldr r2, [fp, #-16]
824c: e0030392 mul r3, r2, r3
8250: e1a00003 mov r0, r3
8254: e24bd000 sub sp, fp, #0
8258: e49db004 pop {fp} ; (ldr fp, [sp], #4)
825c: e12fff1e bx lr
00008260 <main>:
8260: e92d4800 push {fp, lr}
8264: e28db004 add fp, sp, #4
8268: e24dd008 sub sp, sp, #8
826c: e3a03019 mov r3, #25
8270: e50b3008 str r3, [fp, #-8]
8274: e51b0008 ldr r0, [fp, #-8]
8278: ebffffeb bl 822c <func>
827c: e3a03000 mov r3, #0
8280: e1a00003 mov r0, r3
8284: e24bd004 sub sp, fp, #4
8288: e8bd8800 pop {fp, pc}
上記のアセンブリコードを見るとわかるように、上の図で何が描かれているかは考えていません。すべてのfp、sp、lr、pcをスタックに配置しましたが、スタック内の4つのレジスタのみが変更されています。fpは確実に保存され、各関数スタックフレームのスタックベースアドレスを指します。通常、値はfpに格納されるため、spをスタックにプッシュする必要はありません。関数を入力すると、最後の関数がfpがスタックに保存された後、現在の関数のスタックスペースは空になり、fpはspと同じ位置を指すはずです。その後、一時変数を保存するために、spを減算してスタックスペースを割り当てます。現在の関数で他の関数の呼び出しがない場合、lrレジスタは変更されないため、保存する必要はありません。
ただし、上記のmain関数とfunc関数のfpポインターの位置は完全に同じではなく、main関数のfpは最後のfpによって保存されたメモリアドレスを指し、funcのfpはspの同じ位置を指します。ただし、リカバリにエラーがない限り、これらのATPCSの規定は、バックトレース中に正しいコンテンツをレジスタに復元できるようにするための人工的な制約であると言われています。これを達成する方法に特に厳密さはありません。定義。
もう1つの重要なことは、スタックの順序です。ARM 命令システムでは、アドレスの減少スタックです。スタック操作のパラメータのスタック順序は右から左です。パラメータのスタック順序は左からあなたは右側を操作します。プッシュ/ポップおよびLDFMD / STMFDを含みます。
たとえば、push {fp、sp、lr、pc}命令の実行結果は、図1のスタックのようになります。pcは最初にスタックにプッシュされ、右から左に上位アドレスがあり、fpは下位アドレスに存在します。
これらはより詳細で基本的なものですが、明確にする必要もあります。
参照リンク:
1. http://www.linuxidc.com/Linux/2013-03/81247.htm
2. http://www.cnblogs.com/chyl411/p/4579053.html
3. http://www.cnblogs.com/fanzhidongyzby/p/5250116.html