LoginSignup
13
8

More than 5 years have passed since last update.

ARM 関数呼び出し引数の渡し方

Last updated at Posted at 2017-05-13

ARMで関数呼び出しの引数の渡し方について調べてみました。

arm-none-eabi-gcc で簡単なC言語のサンプルコードをアセンブラ出力して調査しました。

arm-none-eabi-gcc -S test.c

関数呼び出しの仕様はAAPCSと呼ばれるドキュメントに記載があります。

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 規則の概要」を読むと良いです。

13
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
8