System V ABI

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번째 인자)

fooint를 반환하면, 그 값은 RAX에 담겨 caller로 전달된다.

부동소수점 인자의 경우:

double add_double(double x, double y);

double caller2(void) {
    return add_double(1.0, 2.0);
}
  • x → XMM0
  • y → XMM1
  • 반환값(double) → XMM0