ARMで関数呼び出しの引数の渡し方について調べてみました。
arm-none-eabi-gcc で簡単なC言語のサンプルコードをアセンブラ出力して調査しました。
arm-none-eabi-gcc -S test.c
関数呼び出しの仕様はAAPCSと呼ばれるドキュメントに記載があります。
- Procedure Call Standard for the ARM® Architecture
- http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ihi0042f/index.html
int 1個の場合
void arg1(unsigned int arg1);
void test(void)
{
arg1(1);
}
mov r0, #1
bl arg1
mov で r0 に引数を代入します。
bl で関数にジャンプします。
int 2個の場合
void arg2(unsigned int arg1, unsigned int arg2);
void test(void)
{
arg2(1,2);
}
mov r0, #1
mov r1, #2
bl arg2
mov で2個目の引数をr1に代入します。
ARMではintの場合は引数4個までレジスタで渡します。
int 5個の場合
void arg5(unsigned int arg1, unsigned int arg2, unsigned int arg3, unsigned int arg4, unsigned int arg5);
void test(void)
{
arg5(1,2,3,4,5);
}
sub sp, sp, #8
mov r3, #5
str r3, [sp]
mov r0, #1
mov r1, #2
mov r2, #3
mov r3, #4
bl arg5
sp は 4バイトしか使わないけど8バイト伸ばします。
mov r3, #5 で5個目の引数をr3にレジスタに代入します。
str r3, [sp] で、それをメモリ上のスタックにストアします。
float 1個の場合
void argf(float arg1);
void test(void)
{
argf(1.0);
}
mov r0, #1065353216
bl argf
floatは32bitなので、intの場合と同じです。
double 1個の場合
void argd(double arg1);
void test(void)
{
argd(1.1);
}
adr r1, .L2
ldmia r1, {r0-r1}
bl argd
.L2:
.word 2576980378
.word 1072798105
double は 64bitなので r0, r1 に数値を入れます。
adr で、ラベル L2のアドレスをr1に代入し、ldmiaでr0, r1にロードします。
dobule 3個の場合
double 2個まではレジスタ渡しできます。3個目からはスタック渡しです。
void argd3(double arg1, double arg2, double arg3);
void test(void)
{
argd(1.1, 2.2, 3.3);
}
sub sp, sp, #12
adr r4, .L2
ldmia r4, {r3-r4}
stmia sp, {r3-r4}
adr r1, .L2+8
ldmia r1, {r0-r1}
adr r3, .L2+16
ldmia r3, {r2-r3}
bl argd
.L2:
.word 1717986918
.word 1074423398
.word 2576980378
.word 1072798105
.word 2576980378
.word 1073846681
スタックを12バイト伸ばしている理由が不明。mod 8 == 0でないけど大丈夫なのでしょうか?
4バイトの構造体
struct s4{
int i1;
};
void args(struct s4 st);
void test(void)
{
struct s4 st;
args(st);
}
sub sp, sp, #8
ldr r0, [fp, #-8]
bl args
スタックに4バイトの構造体を確保する。AAPCSではpublic interfaceの場合spは mod 8 == 0というルールなので、4バイトだけ使うが、スタックを8バイト伸ばす。
ldr で r0 に構造体をロードします。
16バイトの構造体
struct s16{
int i1;
int i2;
int i3;
int i4;
};
void args(struct s16 st);
void test(void)
{
struct s16 st;
args(st);
}
sub sp, sp, #16
sub r3, fp, #20
ldmia r3, {r0, r1, r2, r3}
bl args
スタックに16バイトの構造体を確保する。
ldmia で r0 - r3に構造体をロードします。
20バイトの構造体
16バイト以上の構造体は、16バイトまではレジスタで渡して、16バイト以降はスタックで渡します。
struct s20{
int i1;
int i2;
int i3;
int i4;
int i5;
};
void args(struct s20 st);
void test(void)
{
struct s20 st;
args(st);
}
sub sp, sp, #32
ldr r3, [fp, #-8]
str r3, [sp]
sub r3, fp, #24
ldmia r3, {r0, r1, r2, r3}
bl args
スタックに20バイトの構造体を確保する。引数渡しで4バイト使うので、mod 8 == 0になるように、 24 + 8 の32バイト スタックを伸ばす。
スタックに16バイト以降の4バイトをロードする。
ldmiaで r0 - r3に構造体をロードします。
document
AAPCSを読む前に、Microsoftによる「ARM ABI 規則の概要」を読むと良いです。
- ARM ABI 規則の概要
- https://msdn.microsoft.com/ja-jp/library/dn736986.aspx