기본적으로 r0-r3 4개의 register 함수 호출시 파라미터로 사용을 한다. 함수 파라미터가 4개 이상인 경우는 stack에 push를 해주게 된다.

#include<stdint.h>

void param_four (uint8_t one, uint16_t two, uint32_t three, uint32_t four);
void param_eight(uint8_t one, uint16_t two, uint32_t three, uint32_t four,
                 uint8_t five, uint16_t six, uint32_t seven, uint32_t eight);

void caller() {
  param_four (0xaa, 0xbbaa, 0xccbbaa, 0xddccbbaa);
  param_eight(0xaa, 0xbbaa, 0xccbbaa, 0xddccbbaa,
              0x11, 0x2211, 0x332211, 0x44332211);
}

void param_four(uint8_t one, uint16_t two, uint32_t three, uint32_t four) {
  return;
}

void param_eight(uint8_t one, uint16_t two, uint32_t three, uint32_t four,
                 uint8_t five, uint16_t six, uint32_t seven, uint32_t eight) {
  return;
}

4개의 파라미터 값을 받는 함수, param_four(),와 8개의 파라미터 값을 받는 함수, para_eight(). 파라미터는 8, 16, 32 bit 다양하게.

arm-none-eabi-gcc -g3 -O0 -c aapcs_parameters.c

컴파일을 해주고

$ objdump -t aapcs_parameters.o
aapcs_parameters.o:     file format elf32-little
SYMBOL TABLE:
...
00000000 g     F .text  0000007c caller
0000007c g     F .text  00000034 param_four
000000b0 g     F .text  00000034 param_eight

Object 파일에서 각 함수의 offset을 보면, 0x0부터 0xb0+0x34까지 필요한 코드가 있다는 걸 확인.

$ arm-none-eabi-objdump --start-address 0x00000000 --stop-address $((0x000000b0+0x00000034)) -dS aapcs_parameters.o

aapcs_parameters.o:     file format elf32-littlearm


Disassembly of section .text:

00000000 <caller>:

void param_four (uint8_t one, uint16_t two, uint32_t three, uint32_t four);
void param_eight(uint8_t one, uint16_t two, uint32_t three, uint32_t four,
                 uint8_t five, uint16_t six, uint32_t seven, uint32_t eight);

void caller() {
   0:   e92d4800        push    {fp, lr}
   4:   e28db004        add     fp, sp, #4
   8:   e24dd010        sub     sp, sp, #16
  param_four (0xaa, 0xbbaa, 0xccbbaa, 0xddccbbaa);
   c:   e59f3050        ldr     r3, [pc, #80]   ; 64 <caller+0x64>
  10:   e59f2050        ldr     r2, [pc, #80]   ; 68 <caller+0x68>
  14:   e59f1050        ldr     r1, [pc, #80]   ; 6c <caller+0x6c>
  18:   e3a000aa        mov     r0, #170        ; 0xaa
  1c:   ebfffffe        bl      7c <param_four>
  param_eight(0xaa, 0xbbaa, 0xccbbaa, 0xddccbbaa,
  20:   e59f3048        ldr     r3, [pc, #72]   ; 70 <caller+0x70>
  24:   e58d300c        str     r3, [sp, #12]
  28:   e59f3044        ldr     r3, [pc, #68]   ; 74 <caller+0x74>
  2c:   e58d3008        str     r3, [sp, #8]
  30:   e59f3040        ldr     r3, [pc, #64]   ; 78 <caller+0x78>
  34:   e58d3004        str     r3, [sp, #4]
  38:   e3a03011        mov     r3, #17
  3c:   e58d3000        str     r3, [sp]
  40:   e59f301c        ldr     r3, [pc, #28]   ; 64 <caller+0x64>
  44:   e59f201c        ldr     r2, [pc, #28]   ; 68 <caller+0x68>
  48:   e59f101c        ldr     r1, [pc, #28]   ; 6c <caller+0x6c>
  4c:   e3a000aa        mov     r0, #170        ; 0xaa
  50:   ebfffffe        bl      b0 <param_eight>
              0x11, 0x2211, 0x332211, 0x44332211);
}
  54:   e1a00000        nop                     ; (mov r0, r0)
  58:   e24bd004        sub     sp, fp, #4
  5c:   e8bd4800        pop     {fp, lr}
  60:   e12fff1e        bx      lr
  64:   ddccbbaa        .word   0xddccbbaa
  68:   00ccbbaa        .word   0x00ccbbaa
  6c:   0000bbaa        .word   0x0000bbaa
  70:   44332211        .word   0x44332211
  74:   00332211        .word   0x00332211
  78:   00002211        .word   0x00002211

0000007c <param_four>:

먼저 0c:부터가 param_four()를 호출하는 과정. 0c:에서 네번째 파라미터를 r3에 넣어주고, 밑에서 r2, r1에 각각 숫자를 넣어준다. 넣어줄 숫자는 instruction과 같이 있지 않고, pc로 부터 얼마나 떨어지 offset에 있는 값을 가지고 온다. 아마 instruction은 4bytes로 다 마추려고 그런듯. 18:에서는 마지막으로 첫 번째 파라미터(r0)을 0xaa로 설정해 주는데 여기서는 offset을 사용 안하고 0xaa를 바로 설정 해준다. instruction에 1byte 정도는 여유가 있나보다.

두번째 param_eight() 한수 호출에서 40:에서 4cparam_four()와 같고. 5~6번째 파라미터를 전달하는 방식만 조금 다르다. 20:을 보면 r3에다가 8번째 인자로 사용할 값을 70:에서 가져오고, r3을 다시 sp+12에 저장을 해준다. 비슷한 방법으로 7,6,5 번째 파라미터도 stack에 올려준다.

void param_four(uint8_t one, uint16_t two, uint32_t three, uint32_t four) {
  7c:   e52db004        push    {fp}            ; (str fp, [sp, #-4]!)
  80:   e28db000        add     fp, sp, #0
  84:   e24dd014        sub     sp, sp, #20
  88:   e50b200c        str     r2, [fp, #-12]
  8c:   e50b3010        str     r3, [fp, #-16]
  90:   e1a03000        mov     r3, r0
  94:   e54b3005        strb    r3, [fp, #-5]
  98:   e1a03001        mov     r3, r1
  9c:   e14b30b8        strh    r3, [fp, #-8]
  return;
  a0:   e1a00000        nop                     ; (mov r0, r0)
}
  a4:   e28bd000        add     sp, fp, #0
  a8:   e49db004        pop     {fp}            ; (ldr fp, [sp], #4)
  ac:   e12fff1e        bx      lr

그럼 받아온 파라미터는 어떻게 사용할까? 레지스터로 받아온 값은 그냥 register로 사용하면 되고, stack에 받아온 값은 stack을 읽어 들여서 사용하면 되겠지만 레지스터로 받아온 값은 다시 스텍에 넣어준다. 예제를 컴파일 할 때 -O0을 해줘서 그런 것도 있고, 진짜 유용한 일을 하는 함수였다면 r0-r3을 진짜 유용하게 사용하기 위해 파라미터로 묶여있지 않게 하기 위함.

000000b0 <param_eight>:

void param_eight(uint8_t one, uint16_t two, uint32_t three, uint32_t four,
                 uint8_t five, uint16_t six, uint32_t seven, uint32_t eight) {
  b0:   e52db004        push    {fp}            ; (str fp, [sp, #-4]!)
  b4:   e28db000        add     fp, sp, #0
  b8:   e24dd014        sub     sp, sp, #20
  bc:   e50b200c        str     r2, [fp, #-12]
  c0:   e50b3010        str     r3, [fp, #-16]
  c4:   e1a03000        mov     r3, r0
  c8:   e54b3005        strb    r3, [fp, #-5]
  cc:   e1a03001        mov     r3, r1
  d0:   e14b30b8        strh    r3, [fp, #-8]
  return;
  d4:   e1a00000        nop                     ; (mov r0, r0)
}
  d8:   e28bd000        add     sp, fp, #0
  dc:   e49db004        pop     {fp}            ; (ldr fp, [sp], #4)
  e0:   e12fff1e        bx      lr

원래 스텍으로 전달된 파라미터들은 이미 스텍에 있으니까 다시 스텍에 넣을 필요가 없다.