x86 호출 규약

(Cdecl에서 넘어옴)

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

호출자 정리 편집

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

cdecl 편집

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

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

fastcall 편집

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

마이크로소프트 fastcall 편집

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

볼랜드 fastcall 편집

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

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

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

왓콤 레지스터 기반 호출 규약 편집

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

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

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

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++ 컴파일러용으로 고안되었다. 기타 언어는 구현에 따라 다른 형식과 규약을 갖출 수도 있다.

아키텍처 호출 규약 이름 운영 체제, 컴파일러 레지스터 내 매개변수 스택 위 매개변수 순서 스택 정리 주체
8086 cdecl RTL (C) 호출자
파스칼 LTR (파스칼) 피호출자
fastcall 마이크로소프트 (비 멤버) AX, DX, BX LTR (파스칼) 피호출자
fastcall 마이크로소프트 (멤버 함수) AX, DX LTR (파스칼) 피호출자
fastcall 볼랜드 컴파일러[8] AX, DX, BX LTR (파스칼) 피호출자
왓콤 컴파일러 AX, DX, BX, CX RTL (C) 피호출자
IA-32 cdecl GCC RTL (C) 호출자
cdecl 마이크로소프트 RTL (C) 호출자
stdcall RTL (C) 피호출자
GCC RTL (C) 호출자/피호출자
fastcall 마이크로소프트 ECX, EDX RTL (C) 피호출자
fastcall GCC ECX, EDX RTL (C) 피호출자
fastcall 볼랜드/엠바카데로 컴파일러 EAX, EDX, ECX LTR (파스칼) 피호출자
thiscall 마이크로소프트 ECX RTL (C) 피호출자
왓콤 컴파일러 EAX, EDX, EBX, ECX RTL (C) 피호출자
x86-64 마이크로소프트 x64 호출 규약[9] 윈도우 (마이크로소프트 비주얼 C++, 인텔 C++ 컴파일러, 엠바카데로 컴파일러), UEFI RCX/XMM0, RDX/XMM1, R8/XMM2, R9/XMM3 RTL (C) 호출자
시스템 V AMD64 ABI[10] GNU/리눅스, BSD, OS X (GCC, 인텔 C++ 컴파일러) RDI, RSI, RDX, RCX, R8, R9, XMM0–7 RTL (C) 호출자

참조 편집

  1. Agner Fog (2010년 2월 16일). “Calling conventions for different C++ compilers and operating systems” (PDF). 
  2. http://msdn2.microsoft.com/en-us/library/zxk0tw93(vs.71).aspx
  3. Ohse, Uwe. “gcc attribute overview: function fastcall”. ohse.de. 2010년 9월 27일에 확인함. 
  4. “__fastcall”. msdn.microsoft.com. 2010년 9월 27일에 확인함. 
  5. “Program Control: Register Convention”. docwiki.embarcadero.com. 2010년 6월 1일. 2010년 9월 27일에 확인함. 
  6. “Add CONFIG for -mregparm=3”. 
  7. “Calling_Conventions: Specifying_Calling_Conventions_the_Watcom_Way”. openwatcom.org. 2010년 4월 27일. 2006년 7월 20일에 원본 문서에서 보존된 문서. 2010년 9월 27일에 확인함. 
  8. 《Borland C/C++ version 3.1 User Guide》 (PDF). Borland. 1992. 158,189–191쪽. 2011년 4월 28일에 원본 문서 (PDF)에서 보존된 문서. 2013년 2월 10일에 확인함. 
  9. “x64 Software Conventions: Calling Conventions”. msdn.microsoft.com. 2010. 2010년 9월 27일에 확인함. 
  10. Michael Matz, Jan Hubicka, Andreas Jaeger, and Mark Mitchell (2012년 5월 15일). “System V Application Binary Interface AMD64 Architecture Processor Supplement” (PDF). 0.99.6. 2011년 7월 16일에 원본 문서 (PDF)에서 보존된 문서. 2010년 11월 16일에 확인함. 

더 읽기 편집