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 int
64비트의 세번째 파라미터는 분할되지 않고 스택에 완전히 전달된다.
기본 타입
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(서브루틴 호출)