X86 호출 규약

이 문서는 x86 아키텍처에 쓰이는 호출 규약에 대해 기술한다.

호출자 정리 
이 규약에서 호출자는 스택에서 인수를 정리하며 printf()와 같은 가변 인자 함수를 허용한다.

cdecl 
cdecl(←C declaration)은 C 프로그래밍 언어가 기원인 호출 규약으로서 x86 아키텍처용의 수많은 C 컴파일러가 사용한다.

아래의 C 소스 코드가 있다고 가정하면:
int callee(int, int, int);

int caller(void)
{
	register int ret;

	ret = callee(1, 2, 3);
	ret += 5;
	return ret;
}

x86에서는 다음의 어셈블리어를 만들어낸다. (AT&T 문법):
	.globl	caller
caller:
	pushl	%ebp
	movl	%esp,%ebp
	pushl	$3
	pushl	$2
	pushl	$1
	call	callee
	addl	$12,%esp
	addl	$5,%eax
	leave
	ret

호출 함수는 함수 호출이 반환된 뒤 스택을 정리한다.

리눅스/GCC에서 이중점/부동소수점의 값은 x87 의사(擬似) 스택을 통해 스택 위로 다음과 같이 푸시(push)된다.
	sub esp, 8	; make room for the double
	fld [ebp + x]	; load our double onto the floating point stack
	fstp [esp]	; push our double onto the stack
	call funct
	add esp, 8

이 방식을 통해 올바른 형식으로 스택 위에 push되었는지 확인한다.

cdecl 호출 규약은 일반적으로 x86 C 컴파일러의 기본 호출 규약이지만 수많은 컴파일러는 사용되는 호출 규약을 자동으로 변경하는 옵션을 제공한다.
수동으로 cdecl이 되는 함수를 정의할 목적으로 일부 컴파일러는 다음의 문법을 지원한다:

void _cdecl funct();

syscall 
인수들이 오른쪽에서 왼쪽으로 푸시(push)된다는 점에서 cdecl과 비슷하다. EAX, ECX, EDX는 보존되지 않는다. syscall은 32비트 OS/2 API의 표준 호출 규약이다.

optlink 
인수는 오른쪽에서 왼쪽으로 푸시된다. 맨 왼쪽의 세 개의 인수들이 EAX, EDX, ECX 안에 통과되며 최대 4개의 부동소수점 인수들이 ST(0)에서 ST(3)으로까지 통과된다. 그러나 이들을 위한 공간은 스택 위의 인수 목록 안에 보존된다. 결과는 EAX 또는 ST(0) 안에 반환된다. 레지스터 EBP, EBX, ESI, EDI는 보존된다.

optlink는 IBM 비주얼에이지 컴파일러에서 쓰인다.

피호출자 정리 
x86의 ret 명령의 예는 다음과 같다:
 ret 12

pascal 
파스칼 프로그래밍 언어의 호출 규약을 기반으로 매개변수들은 왼쪽에서 오른쪽 순으로 스택 위에 푸시된다. (cdecl과 반대) 피호출자는 반환 이전에 스택의 균형을 맞추는 일을 한다. 이 호출 규약은 다음의 16비트 API에서 일반적이다: OS/2 1/x, 마이크로소프트 윈도우 3.x, 볼랜드 델파이 버전 1.x.

register 
볼랜드 fastcall의 별칭이다.

stdcall 
stdcall 호출 규약은 마이크로소프트 Win32 API 및 오픈 왓콤 C++의 표준 호출 규약이다. 파스칼 호출 규약의 변형으로서 피호출자는 스택을 정리하는 일을 하지만 매개변수는 _cdecl 호출 규약에서처럼 오른쪽에서 왼쪽 순으로 스택 위로 푸시된다. 레지스터 EAX, ECX, EDX는 함수 내에 사용되도록 규정된다. 반환값은 EAX 레지스터에 저장된다.

fastcall 
fastcall은 표준화된 규약은 아니며 컴파일러 업체에 따라 다르게 처리된다. 일반적으로 fastcall 호출 규약은 레지스터 내 하나 이상의 인수를 통과시키며 호출에 필요한 메모리 접근의 수를 줄인다.

마이크로소프트 fastcall 
마이크로소프트나 GCC __fastcall 규약 (__msfastcall)은 처음 두 개의 인수 (왼쪽에서 오른쪽으로 평가)를 통과시켜 ECX, EDX에 맞춘다. 나머지 인수들은 오른쪽에서 왼쪽 순으로 스택 위로 푸시된다.

볼랜드 fastcall 
왼쪽에서 오른쪽으로 인수를 평가하며 세 개의 인수를 EAX, EDX, ECX를 통해 통과시킨다. 나머지 인수들은 방금과 동일한 방향으로 스택 위로 푸시한다.

엠바카데로 델파이 32비트 컴파일러의 기본 호출 규약으로서, "register"로 알려져 있다.

일부 리눅스 커널 버전은 i386에서 이 규약을 사용한다.

왓콤 레지스터 기반 호출 규약 
왓콤은 별칭을 null로 하는 것을 제외하고 __fastcall 키워드를 지원하지 않는다. 레지스터 호출 규약은 명령 줄 스위치로 선택할 수 있다. (그러나 IDA는 통일성을 위해 __fastcall을 사용한다)

최대 4개의 레지스터가 eax, edx, ebx, ecx 순으로 인수에 할당된다. 인수들은 왼쪽에서 오른쪽으로 레지스터로 할당된다. 어느 인수가 너무 크다는 이유 등으로 레지스터에 할당되지 못한다면 차후 모든 인수들은 스택에 할당된다. 스택에 할당된 인수들은 오른쪽에서 왼쪽으로 푸시된다.

왓콤 C/C++ 컴파일러 또한 #pragma aux를 사용하여 사용자가 자신만의 호출 규약을 지정할 수 있게 한다.

TopSpeed / Clarion / JPI 
처음 4개의 정수 매개변수들이 eax, ebx, ecx, edx 안에 통과된다. 부동소수점 매개변수들은 부동소수점 스택 - 레지스터 st0, st1, st2, st3, st4, st5, st6 위로 통과된다. 구조 매개변수들은 언제나 스택 위로 통과된다. 추가 매개변수들은 레지스터를 다 소진한 뒤 스택 위로 통과된다. 정수값들은 eax 안에, 포인터는 edx 안에, 부동소수점 자료형은 st0에 반환된다.

safecall 
엠파카데로 델파이, 프리 파스칼, 마이크로소프트 윈도우에서 safecall 호출 규약은 COM (컴포넌트 오브젝트 모델) 오류 관리를 요약한 것으로서, 예외들은 호출자에게 노출되지 않지만 COM/OLE에 의해 요구되면 HRESULT 반환값에 보고된다. safecall 함수를 델파이 코드에서 호출할 때 델파이는 자동으로 반환된 HRESULT를 검사하고 필요하면 예외를 발생시킨다.

호출자/피호출자 정리

thiscall 
이 호출 규약은 C++ 비정적 멤버 함수를 호출하는데 사용된다.
 GCC 컴파일러에서 thiscall은 cdecl과 거의 유사하다. 차이점은 this 포인터의 추가 여부이다.
 마이크로소프트 비주얼 C++ 컴파일러에서 this 포인터는 ECX 안에 통과되며 이는 스택을 정리하는 피호출자이다. 이 컴파일러와 윈도 API 함수에서 C에 사용되는 stdcall 규약을 있는 그대로 따른다.

thiscall 호출 규약은 마이크로소프트 비주얼 C++ 이상에서만 명시적으로 사용할 수 있다. 그 밖의 다른 컴파일러에서 thiscall은 키워드가 아니다.

이 호출 규약은 C++ 멤버 함수의 호출 규약으로 비정적 멤버 함수를 호출하는데 사용된다. 비정적이란 C++ 클래스의 멤버 함수를 만들 때 static 키워드를 주지 않고 만든 경우(일반적인 경우)를 의미함. 만약, C++ 멤버 함수에 static 키워드가 주어지면 독립함수가 되며(단위함수:쓰레드 단위로 작동이 가능한 함수) 모체를 필요로하지 않게된다. 여기서 모체란 C++ 클래스가 메모리에 인스턴스화되었을 때 가상함수 테이블 포인터 값을 갖고 있는 포인터인 this 포인터를 의미한다. 즉, static 키워드와 함께 멤버 함수를 만들면 일반 호출 규약을 따르는 독립함수가 되고 C++ 과 상관없는 독립함수가 되며 static 키워드가 없으면 모체에 종속적인 멤버 함수가 된다. 이때 모체종속 적인 함수들 즉, 종속적 비정적 멤버 함수들은 thiscall 호출 규약을 따르게 된다. thiscall 호출 규약은 this + call이며 this 포인터를 넘기고 call 을 한다는 의미이다. 이때, this 포인터를 MSVC(마이크로소프트 비주얼 스튜디오) 기준 컴파일러로 보면 ecx 레지스터에 담는다. thiscall 호출 규약은 일련의 어셈블리어로 표현할 경우에 다음과 같다.
 mov ecx, this 포인터
 call memberfunc
위와 같은 형태로 ecx 레지스터에 this 포인터(C++ 클래스의 인스턴스화된 포인터: 모체포인터)가 담기고 memberfunc 함수는 stdcall 표준호출 규약을 따른다. GCC의 경우 memberfunc 가 cdecl 을 따른다면(누군가 GCC는 확인후 정정 요망!) MSVC의 경우 memberfunc는 stdcall 호출 규약을 따른다. 이때, MSVC 의 컴파일러에 의하여 컴파일된 memberfunc 함수는 기본적으로 ecx 레지스터에 this 포인터가 있다고 가정한체 컴파일러에 의하여 함수가 제조된다. 그렇지만, memberfunc 함수 자체는 stdcall 호출 규약을 따르도록 되어있다.

간단히 종합하면, MSVC 컴파일러로 컴파일된 C++ 바이너리의 경우 thiscall 호출 규약은 ecx 레지스터에 this 포인터를 담은 stdcall 호출 규약으로 설명할 수 있다. ecx 레지스터에 this 포인터를 담을 수 있으면 멤버 함수를 그냥 stdcall 호출 규약 형태로 호출해도 아무 이상없이 작동한다. 이러한 호출 규약을 C++ 호출 규약 또는 thiscall 호출 규약이라 부른다. 주의할 점은 컴파일러마다 this 포인터를 어떤 레지스터에 담을지가 다르다. 일반적으로 통용되는 레지스터는 ecx 레지스터이다. 또한, stdcall 호출 규약을 사용할지 cdecl 호출 규약을 사용할지도 역시 컴파일러의 재량에 달려있다. thicall 호출 규약의 핵심 개념은 this 포인터를 레지스터를 통해서 넘긴다는 약속이며 암묵적(암시적)으로 레지스터에 박아서 넘겨주면 바로 다음에 이어서 호출되는 함수가 이 레지스터의 값을 C++ 인스턴스 값으로 이용한다는 약속이다. 그러므로 컴파일러 종속적인 구현이 될 수 있으나 전반적으로 널리 알려져 사용되는 방식은 MSVC 컴파일러의 ecx 레지스터를 사용하는 방법과 stdcall 호출 규약이 일반적이다.

x86 호출 규약 목록 
이 규약들은 주로 C/C++ 컴파일러용으로 고안되었다. 기타 언어는 구현에 따라 다른 형식과 규약을 갖출 수도 있다.

참조

더 읽기 
 
 
 
 

X86 아키텍처