본문 바로가기

Programming Language/Assembly

호출 규약(calling convention) 정리

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

 함수의 호출규약은 함수 호출 시 전달된 매개변수를 함수가 종료될 때 정리하는 방법으로서 cdecl, stdcall, fastcall, thiscall의 4가지가 존재합니다. 이 중 thiscall은 C++언어의 멤버함수에서 쓰이고 나머지는 C언어에서 사용됩니다.

cdecl 호출 규약


 먼저 cdecl은 C 함수 라이브러리에서 표준으로 쓰이는 호출규약으로 스택으로 전달한 매개변수를 함수를 호출한 위치에서 정리하는 방식입니다. 예를 들어 아래와 같은 C함수가 있습니다.

int __cdecl _fn_cdecl(int a, int b)
{
	return a+b;
}

 이를 어셈블리어로 다시 작성해봅니다. 시스템 기본 정수는 4바이트로 가정합니다.

_fn_cdecl:
	sub esp, 4 ; 함수 종료 후 돌아갈 위치가 있으므로 1워드만큼 뒤로 물림
	pop eax ; b 매개변수
	pop edx ; a 매개변수
	add esp, 12 ; 스택 포인터 원래대로
	; 스코프 시작
	push ebp
	mov ebp, esp
	add eax, edx ; eax = eax + edx; 스코프 끝
	mov esp, ebp
	pop ebp
	ret ; 함수 끝. 결과는 eax로 전달

 이 함수를 실행해보겠습니다. a = 10과 b = 5를 전달해 15를 얻겠습니다.

; fn_cdecl(10, 5);
	push dword ptr 10 ; 4 바이트 정수를 스택에 넣기
	push dword ptr 5 ; 다시 4바이트 정수를 스택에 넣기
	call _fn_cdecl ; 결과인 15가 eax에 기억
	sub esp, 8 ; 스택에 넣은 2개의 4바이트 정수를 정리하여 없앰

 위와 같이 cdecl로 만든 함수는 매개변수 전달을 위해 사용한 스택을 call 명령 다음에 정리합니다. 함수를 호출한 위치에서는 매개변수의 총 크기를 정확히 알고 있으므로 스택을 안전하게 정리할 수 있는 특징이 있습니다.

 

stdcall 호출 규약


 stdcall은 스택의 정리를 함수 내부에서 처리합니다. 함수를 호출하는 위치에서는 함수를 호출만하고 스택정리를 하지 않는 방식입니다. Windows API들이 이 방식을 채택합니다.

 두 수를 합을 반환하는 함수를 stdcall로 다시 작성해보겠습니다.

int __stdcall fn_stdcall(int a, int b)
{
	return a+b;
}

 이것을 어셈블리어로 다시 작성하면

_fn_stdcall@8:
	sub esp, 4 ; 돌아갈 위치는 건너뛰고
	pop eax ; b 매개변수를 eax에 기억
	pop edx ; a 매개변수를 edx에 기억
	add esp, 12 ; 스택포인터를 원래대로
	; 스코프 시작
	push ebp
	mov ebp, esp
	add eax, edx ; eax = eax + edx ; 스코프 종료
	mov esp, ebp
	pop ebp ; 8 바이트만큼의 스택을 CPU가 알아서 정리
	ret 8

 cdecl 함수와 달리 stdcall 함수는 이름 뒤에 @ 기호를 붙인 뒤 정리할 스택의 크기를 명시합니다. 이 크기는 고정되어 있기 때문에 가변인수를 받는 함수에는 stdcall 규약을 사용할 수 없습니다.

 이를 실행시켜보겠습니다.

; fn_cdecl(10, 5);
	push dword ptr 10 ; 4 바이트 정수를 스택에 넣기
	push dword ptr 5 ; 다시 4바이트
	정수를 스택에 넣기
	call _fn_stdcall@8 ; 결과인 15가 eax에 기억

 

fastcall 호출 규약


 fastcall은 처음 n개의 매개변수를 스택 대신 레지스터로 전달하는 방식입니다. 매개변수의 수가 n개 이하면 스택 정리 과정이 아예 생략되므로 실행이 빠르다는 특징이 있습니다. 함수 본문은 컴파일러마다 다르게 번역이 되므로 여기에서는 호출 하는 방식만 보겠습니다.

; int __fastcall fn_fastcall(int a, int b);
	; MSVC는 ecx, edx의 2개의 레지스터를 사용합니다.
	mov ecx, 10
	mov edx, 5
	call _fn_fastcall@8
\

 

thiscall 호출 규약


 thiscall은 ecx에 인스턴스의 주소가 기억된 후 호출되는 함수입니다. 그 외에는 stdcall과 동일하고 가변인수를 사용하는 함수에 한해 cdecl 규약으로 처리됩니다.

 C++ 클래스 내에 foo 함수가 있다면

; eax = this->foo(10, 5);
	mov ecx, (this)의 주소
	call foo