System V ABI(System V Application Binary Interface)는 유닉스 계열 운영체제에서 실행 파일 형식, 데이터 타입 크기, 함수 호출 규약 등을 정의하는 규격이다.
현대의 64비트 UNIX 계열 (Linux, *BSD) 시스템에서는 주로 amd64(x86-64) System V ABI가 사용되기에 이 문서는 amd64(x86-64) System V ABI를 중심으로 설명한다.
amd64 System V ABI의 특징
데이터 모델 (LP64)
amd64 System V ABI에서는 다음과 같은 LP64 데이터 모델을 사용한다.
int: 32비트long,long long: 64비트- 포인터(
void *등) : 64비트
이는 C 코드에서 정수와 포인터 크기를 가정할 때 중요한 전제이다.
함수 인자 전달 규칙
정수형, 포인터형 등 대부분의 스칼라 인자는 다음 순서의 레지스터로 전달된다.
| 인자 번호 | 사용 레지스터 |
|---|---|
| 1번째 인자 | RDI |
| 2번째 인자 | RSI |
| 3번째 인자 | RDX |
| 4번째 인자 | RCX |
| 5번째 인자 | R8 |
| 6번째 인자 | R9 |
- 7번째 인자부터는 스택에 8바이트 단위로 저장된 뒤 전달된다.
- 부동소수점 인자(
float,double)는 XMM0–XMM7 레지스터를 통해 전달되며, 정수 인자와는 별도로 순서가 관리된다.
스택은 함수 호출 시 16바이트 정렬(alignment)을 유지해야 하며, 컴파일러가 이를 맞추도록 코드를 생성한다.
반환값 규칙
- 대부분의 정수/포인터 반환값: RAX
- 128비트까지의 일부 정수/구조체 반환: RAX와 RDX 조합
- 부동소수점 반환값: XMM0
더 큰 구조체 반환 등 특수한 경우에는 호출자가 반환 버퍼를 준비하고 포인터를 넘기는 방식 등이 사용된다.
레지스터 보존 규약
amd64 System V ABI는 어떤 레지스터를 누가 보존해야 하는지도 정의한다.
- 호출자 저장(caller-saved): 함수 호출 시 덮어써져도 되므로, 필요한 경우 호출하는 쪽이 미리 저장해야 하는 레지스터
- 피호출자 저장(callee-saved): 함수가 종료될 때 원래 값이 유지되어야 하므로, 호출되는 함수가 저장·복원해야 하는 레지스터
대표적인 규약은 다음과 같다.
| 구분 | 레지스터 |
|---|---|
| 호출자 저장 | RAX, RCX, RDX, RSI, RDI, R8, R9, R10, R11, XMM0–XMM15 |
| 피호출자 저장 | RBX, RBP, R12, R13, R14, R15 |
RSP(스택 포인터)는 호출 전후에 일관되게 유지되어야 한다.
C 언어 예시
다음 예시는 정수 인자가 어떻게 레지스터와 스택으로 전달되는지 보여준다.
int foo(int a, int b, int c, int d, int e, int f, int g);
int caller(void) {
return foo(1, 2, 3, 4, 5, 6, 7);
}
위 호출에서 foo는 amd64 System V ABI에 따라 다음과 같이 인자를 받는다.
a→ RDI (1번째 인자)b→ RSI (2번째 인자)c→ RDX (3번째 인자)d→ RCX (4번째 인자)e→ R8 (5번째 인자)f→ R9 (6번째 인자)g→ 스택 상의 슬롯 (7번째 인자)
foo가 int를 반환하면, 그 값은 RAX에 담겨 caller로 전달된다.
부동소수점 인자의 경우:
double add_double(double x, double y);
double caller2(void) {
return add_double(1.0, 2.0);
}
x→ XMM0y→ XMM1- 반환값(
double) → XMM0