Armアセンブリスタディノート(6)-関数呼び出しスタックスペースとfpレジスタ

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

元の記事を60件公開 44のよう 訪問数340,000以上

おすすめ

転載: blog.csdn.net/beyond702/article/details/52228683