AAPCS를 따르는 Arm 아키텍처에서는 서브루틴 호출을 위한 명령어를 사용하여 함수를 호출하고 복귀하는 방식을 취한다.
서브루틴 호출 명령어
Arm 명령어 세트에는 링크 포함 분기 작업을 수행하는 기본 서브루틴 호출 명령어 인 BL이 포함되어 있다. BL은 “Branch with Link”의 약자로 그 효과는 프로그램 카운트의 다음 값인 반환 주소를 링크 레지스터(LR)에 저장하고, 대상 주소를 프로그램 카운터(PC)로 설정한다.
; call subroutine_function
BL subroutine_function
; return from subroutine_function
서브루틴 호출은 다음과 같은 명령 시퀀스를 따른다.
LR[31:1] ← return address
LR[0] ← code type at return address (0 Arm, 1 Thumb)
PC ← subroutine address
...
return address:
인수 전달
서브루틴 호출에 들어가기 전에 레지스터는 caller-saved 레지스터와 callee-saved 레지스터로 구분된다.
- 범용 레지스터 R0~R3 는 함수에 인수를 전달하고 값을 반환하는 데 사용된다. callee가 보존할 필요가 없으며 caller가 저장한다.
- R4~R8, R10, R11 은 함수 내에서 지역 변수를 유지하는데 사용된다. caller는 호출된 함수가 반환될 때 변경되지 않을 것으로 예상할 수 있따. callee에 의해 저장된다.
다음은 AAPCS에서 정한 파라미터를 함수에 전달하기 위한 규정들이다.
- 파라미터는 선언된 대로 왼쪽에서 오른쪽으로 전달된다.
- 인수는 레지스터와 스택에서 전달된다.
- 복합 타입은 레지스터와 스택 간에 분할될 수 있다. 기본 타입은 분할되지 않는다.
- 처음 4개의 파라미터는 R0~R3에 전달된다. 다음 파라미터들은 모두 스택으로 전달된다.
- 하나의 인수가 32비트보다 큰 경우(
long long int) 파라미터는 R0 및 R1에서 R3까지 연속 레지스터로 분할된다. - 기본 타입은 레지스터와 스택 간에 분할 할 수 없다. 따라서 R0-R2가 함수의 처음 두 파라미터에 대해 이미 예약되어 잇는 경우
long long int64비트의 세번째 파라미터는 분할되지 않고 스택에 완전히 전달된다.
기본 타입
extern void callee(int a, int b, int c, long long int d);
void caller(){
callee(0,1,2,3);
}
00000000 <caller>:
0: b507 push {r0, r1, r2, lr}
2: 2300 movs r3, #0
4: 2203 movs r2, #3
6: 2101 movs r1, #1
8: e9cd 2300 strd r2, r3, [sp]
c: 2000 movs r0, #0
e: 2202 movs r2, #2
10: f7ff fffe bl 0 <callee>
14: b003 add sp, #12
16: f85d fb04 ldr.w pc, [sp], #4
정수형 타입 인자 0,1,2은 각각 r0, r1, r2로 전달되고, long long int 타입인 네번째 인자 3은 “strd r2, r3, [sp]” 에 의해 스택으로 전달된다.
복합 타입
struct myStruct1Word {
int a;
};
struct myStruct7Word{
int a;
int b;
int c;
int d;
int e;
int f;
char g;
};
extern void callee1(struct myStruct1Word);
extern void callee7(struct myStruct7Word);
void caller(){
struct myStruct1Word s;
s.a = 1111;
callee1(s);
struct myStruct7Word t;
t.a = 2222;
t.g = 0xFF;
callee7(t);
}
위 코드에서는 myStruct1Word, myStruct7Word 두개의 구조체가 선언되어 있으나, struct myStruct1Word 는 멤버가 int 하나이므로 기본 타입으로 취급된다. myStruct7Word 멤버는 int 6개와 char 1개를 멤버로 한다.
00000000 <caller>:
0: b500 push {lr}
2: f240 4057 movw r0, #1111 ; 0x457
6: b08d sub sp, #52 ; 0x34
8: f7ff fffe bl 0 <callee1>
c: f640 03ae movw r3, #2222 ; 0x8ae
10: 9305 str r3, [sp, #20]
12: 23ff movs r3, #255 ; 0xff
14: f88d 302c strb.w r3, [sp, #44] ; 0x2c
18: ab0c add r3, sp, #48 ; 0x30
1a: e913 0007 ldmdb r3, {r0, r1, r2}
1e: e88d 0007 stmia.w sp, {r0, r1, r2}
22: ab05 add r3, sp, #20
24: cb0f ldmia r3, {r0, r1, r2, r3}
26: f7ff fffe bl 0 <callee7>
2a: b00d add sp, #52 ; 0x34
2c: f85d fb04 ldr.w pc, [sp], #4
line 2: myStruct1Word 구조체는 하나의 멤버가 기본 타입인 int 기 때문에 기본 타입이기 때문에, “s.a = 1111” 은 "movw r0, #1111” 형태로 레지스터로만 처리된다. 하지만, myStruct7Word 는 4개의int 까지는 R0~R3으로 전달되고, 나머지는 스택으로 전달된다.
다음은 일반적인 스택 레이아웃이다.

짙은 회색 부분은 현재 실행중인 함수가 소유한 메모리 영역이다. callee에게 전달되는 인수는 맨 아래에 있고, 중간 회색 부분은 지역 변수로 사용횐다.
밝은 회색 부분은 다음과 같다.
- 이전 함수 상태 저장소: caller에 대해 보존된 callee 값이다.저장된 LR 및 callee-saved 레지스터는 스택 맨 아래에 있다.
- 다음 함수 호출을 위해 준비된 인수, 즉 스택 맨 위에 있는 인수
반환 함수 값
AAPCS에는 함수에서 값이 반환되는 방식은 인수 전달과 매우 유사하지만, 몇가지 세부 사항이 다르다.
- 기본 타입 및 복합 타입의 경우 32비트보다 작거나 같으면 R0가 사용된다.
- 기본 타입에만 해당하는 반환 값이 32비트보다 큰 경우 매개 변수는 연속 레지스터로 분할된다.
- 32비트보다 큰 복합 데이터 타입의 경우 caller는 자신이 예약한 메모리에 대한 참조를 R0에 배치한 하여 callee가 값을 반환하는데 사용하도록 한다.
struct myStruct1Word {
int a;
};
struct myStruct2Word {
int a;
int b;
};
struct myStruct7Word{
int a;
int b;
int c;
int d;
int e;
int f;
char g;
};
extern struct myStruct1Word callee1();
extern struct myStruct2Word callee2();
extern struct myStruct7Word callee7();
int caller(){
struct myStruct1Word s;
struct myStruct2Word t;
struct myStruct7Word u;
s = callee1();
t = callee2();
u = callee7();
return s.a + t.a + u.a;
}
00000000 <caller>:
0: e92d4010 push {r4, lr}
4: e24dd028 sub sp, sp, #40 ; 0x28
8: ebfffffe bl 0 <callee1>
c: e1a04000 mov r4, r0
10: e28d0004 add r0, sp, #4
14: ebfffffe bl 0 <callee2>
18: e28d000c add r0, sp, #12
1c: ebfffffe bl 0 <callee7>
20: e59d0004 ldr r0, [sp, #4]
24: e0844000 add r4, r4, r0
28: e59d000c ldr r0, [sp, #12]
2c: e0840000 add r0, r4, r0
30: e28dd028 add sp, sp, #40 ; 0x28
34: e8bd4010 pop {r4, lr}
38: e12fff1e bx lr
line c: callee1이 호출되고 난 뒤 r0로 return 된 값을 r4에 저장한다.
line 10: callee2가 호출되기 전에 r0에 sp+4 한 값을 줘서 r0(sp+4) 에서 리턴값을 참조할 수 있도록 한다.
line 18 : callee7가 호출되기 전에 r0에 sp+12한 값을 줘서 r0(sp+12) 에서 리턴값을 참조할 수 있도록 한다.
서브루틴 반환
서브 루틴에서는 복귀 주소를 이용하여 호출한 함수로 돌아가야 할때 서브루틴 종료 후 ‘BX LR’ 명령어를 사용하여 복귀한다.
subroutine_function:
; ...
BX LR
서브루틴 호출 예제
; R0, R1을 더하는 서브루틴
add_numbers:
ADD R0, R0, R1 ; R0 = R0 + R1
BX LR ; 복귀
; 메인 함수
main:
MOV R0, #10 ; 첫 번째 인자로 10 설정
MOV R1, #20 ; 두 번째 인자로 20 설정
BL add_numbers ; 서브루틴 호출
; 서브루틴이 끝난 후 이곳으로 돌아옴
참고
AAPCS 소개
AAPCS와 EABI의 관계
AAPCS: 데이터 타입과 정렬
AAPCS: 레지스터 사용
AAPCS: 프로세스 메모리 및 스택
AAPCS: Subroutine Calls(서브루틴 호출)