함수 호출 규약 ( cdelc, stdcall, fastcall, thiscall)

함수 호출 규약이란?

함수 호출 규약이란 서브 함수를 호출하는 측(호출자)과 호출되는 측(피호출자) 중 누가 해당 함수의 자산을 관리할 지 미리 정의한 방식입니다.

int __cdecl Add(int a, int b);
int __stdcall Add(int a, int b);
int __fastcall Add(int a, int b);
void __thiscall MyClass::Func();

Cdecl ( C Declarartion )

초창기 C 컴파일러가 사용하던 기본 함수 호출 규약입니다.

Cdecl는 편의상 “씨덱”이라고 읽으며, 호출자가 피호출자의 스택을 정리합니다.

이러한 방식은 서브함수에 가변 인자를 전달할 수 있게 해주며, 대표적인 함수는 C언의 printf 함수가 있습니다.

Cdecl은 함수 인자를 오른쪽부터 push하며, 함수가 마무리 된 이후 호출자 측에서 아래와 같은 코드를 실행합니다.

call 서브함수 
add esp, 인자 크기

Stdcall ( Standard Call )

스탠다드 콜은 WIN API에서 주로 사용합니다.

해당 함수 호출 규약은 사용할 인자 개수가 정해져 있기 때문에 고정된 형식에서만 사용 가능합니다.

StdCall은 인자 개수가 정해져 있기 때문에, 피호출자 측에서 코드를 정리합니다.

따라서 피호출자의 함수 코드 부분에서 아래와 같은 코드가 발견됩니다.

ret 인자 크기

Fastcall

패스트 콜은 레지스터를 통해 인자를 전달할 때 주로 사용합니다.

대표적으로 x86 MSVC(Microsoft Visual C++)에서 사용하며, GCC나 G++와 같은 다른 컴파일러에서

해당 옵션을 설정하여 컴파일할 시 사용할 수 있습니다.

ECX와 EDX 레지스터를 우선적으로 사용하며, 이후 인자는 스택에 넣어 전달합니다.

해당 호출 규약 또한 호출자가 사용한 스택을 정리합니다. (예 : ret 8)

Thiscall

디스 콜은 C++의 클래스 멤버함수를 호출할 때 사용하는 규약입니다.

해당 호출 규약은 멤버함수 호출 시, 첫번째 인자로 this 포인터가 전달되기 위해서 사용되는데요.

예를 들어 Class가 Person 안에 Talk라는 함수가 존재할 경우, 멤버함수는 아래와 같습니다.

Void Person::Talk(int arg1, int arg2) {}; // 개발자가 이와 같이 작성
Void Person::Talk(Person* this, int arg1, int arg2); // 아래와 같이 talk 함수에 this가 전달됨

첫 번째 함수로 해당 인스턴스의 포인터가 전달되는데 이때는 ECX를 사용합니다.

그 외 인자는 오른쪽부터 왼쪽으로 스택에 푸시되어 전달되고, 스택 정리는 피호출자에서 진행됩니다.

x64

64비트에서는 cdecl, stdcall, fastcall의 차이가 거의 사라지고 레지스터로 전달됩니다.

1 = RCX, 2 = RDX, 3 = R8, 4 = R9 , 이후는 스택으로 전달됩니다.

스택 정리는 호출자가 진행합니다.

마무리하며

x86 시절에는 함수 호출규약으로 cdecl, stdcall, fastcall, thiscall과 같이 여러 호출 규약을 써서

리버싱할 때 인자를 파악하는데 불편한 점이 있었습니다.

x86을 직접 리버싱할 때 참고 하시면 좋을 것 같습니다.

감사합니다.