__cdecl, __stdcall, __fastcall x86 호출 규약(Calling Convention)
Calling Convention
X86에선 호출 규약이 __cdecl, __stacll, __fastcall 등으로 나누어있지만 x64에선 __fastcall 하나의 호출 규약만을 사용하도록 정의 되어있습니다. (__fastcall은 x86 전용이라고 합니다. x64는 추후 다시 정리하겠습니다!)
오늘은 x86 호출 규약에 대해서만 정리를 해보겠습니다.
함수 호출규약을 아무것도 입력하지 않으면 기본값은 __cdecl
입니다.
해당 테스트는 Visual Studio 2015 Community 에서 이루어졌습니다.
__cdecl (c declaration)
이 규약에서 호출자는 스택에서 인수를 정리하며, printf() 와 같은 가변 인자 함수를 지원합니다.
함수의 호출시 함수 호출 전과 함수가 끝난 후의 ESP의 위치는 같아야 합니다. 그렇기때문에 함수를 사용하기 위해 사용한 스택을 누군가는 정리를 해주어야 한다는 것 입니다. 누군가는 caller 혹은 callee 가 되겠네요. (직접 하는일은 아니고 내부적으로 처리됩니다.)
__cdecl sample code
int cdecl_test1(int a, int b, int c) {
int result = a + b + c;
return result;
}
int __cdecl cdecl_test2(int a, int b, int c) {
int result = a + b + c;
return result;
}
int main()
{
cdecl_test1(1, 2, 3);
cdecl_test2(1, 2, 3);
return 0;
}
__cdecl sample asm code
cdecl_test1(1, 2, 3);
00E53C3E push 3
00E53C40 push 2
00E53C42 push 1
00E53C44 call cdecl_test1 (0E5134Dh)
00E53C49 add esp,0Ch
cdecl_test2(1, 2, 3);
00E53C4C push 3
00E53C4E push 2
00E53C50 push 1
00E53C52 call cdecl_test2 (0E51352h)
00E53C57 add esp,0Ch
2개의 asm 코드가 일치합니다.
위의 코드는 호출 규약을 입력하지 않은 코드이고
아래 코드는 __cdecl을 입력한 코드 입니다.
위에서 설명드린 대로 아무것도 입력하지 않으면 __cdecl이네요
asm code를 보게되면 push 3, push 2, push 1
을 하고있습니다.
즉, 오른쪽부터 왼쪽으로 인자를 스택에 넣고 있는 것 입니다.
그리고나서 call cdecl_test 함수를 호출하네요
그런데 다음줄에 add esp, 0Ch
는 뭘까요??
함수 내부에서가 아닌 함수 외부에서 stack이 정리되고 있습니다. Caller에서 정리를 한다는 이야기가 되는것이죠
정리
- 전달 인자는 오른쪽에서 왼쪽으로 전달 된다.
- cdecl 방식은 caller 에서 전달 인자에 대한 Stack을 정리한다. (함수 외부)
- 호출 규약을 입력하지 않으면 기본값은 cdecl 이다
__stdcall
Win32 API 등의 표준 규약입니다.
Cdecl 방식과는 거의 비슷하게 동작하며, 어떤부분이 다르게 동작하는지 한번 봐보겠습니다.
__stdcall sample code
int __stdcall stdcall_test(int a, int b, int c) {
int result = a + b + c;
return result;
}
int main()
{
stdcall_test(1, 2, 3);
return 0;
}
__stdcall sample asm code
stdcall_test(1, 2, 3);
00E53C5A push 3
00E53C5C push 2
00E53C5E push 1
00E53C60 call stdcall_test (0E51357h)
같은듯 다른 무언가가 있네요
스택에 전달인자를 넣는건 똑같고 함수 call 하는것까진 똑같네요
하지만 call 이후에 뭔가가 없습니다.
add esp,0Ch
의 부분이 여기엔 없네요
그것이 __cdecl과 __stdcall의 차이점 입니다.
바로 __cdecl은 caller가 stack을 정리하고 __stdcall은 callee가 직접 정리를 한다는것이죠
정리
- 전달 인자는 오른쪽에서 왼쪽으로 전달 된다.
- stdcall 방식은 callee 에서 전달 인자에 대한 Stack을 정리한다. (함수 내부)
__fastcall
이름부터 속도가 빠를 것 같은 호출 규약 입니다.
해당 규약은 표준화된 규약은 아니기때문에 컴파일러마다 다르게 처리됩니다.
일반적인 fastcall 호출 규약은 레지스터 내 하나 이상의 인수르 통과시키며 호출에 필요한 메모리 접근의 수를 줄입니다.
VisualStudio, GCC의 fastcall 은 처음 두 개의 인수(왼쪽에서 오른쪽)를 통과시켜 ECX
, EDX
에 맞추고 나머지 인수들은 오른쪽에서 왼쪽 순으로 스택으로 PUSH 하게 됩니다.
__fastcall sample code
int __fastcall fastcall_test(int a, int b, int c) {
int result = a + b + c;
return result;
}
int __fastcall fastcall_test2(int a, int b, int c, int d, int e) {
int result = a + b + c + d + e;
return result;
}
int main()
{
fastcall_test(1, 2, 3);
fastcall_test2(1, 2, 3, 4, 5);
return 0;
}
__fastcall sample asm code
fastcall_test(1, 2, 3);
00E53C65 push 3
00E53C67 mov edx,2
00E53C6C mov ecx,1
00E53C71 call fastcall_test (0E5135Ch)
fastcall_test2(1, 2, 3, 4, 5);
00E53C76 push 5
00E53C78 push 4
00E53C7A push 3
00E53C7C mov edx,2
00E53C81 mov ecx,1
00E53C86 call fastcall_test2 (0E51361h)
여기서 fastcall이 왜 빠른지 나옵니다.
모든 전달 인자를 스택에 쌓는게 아니라 레지스터에 직접 넣어버림으로 인해서 속도를 향상시킨 방식입니다.
실제로 전달인자를 우측에서 좌측으로 입력하고,
처음 두개의 인자는 ECX, EDX 레지스터에 입력하는 모습을 볼 수 있습니다.
더불어 call 이후엔 스택을 정리하지 않는것으로 보아 이건 callee 즉 함수 내부에서 스택이 정리된다는것을 확인할 수 있습니다.
정리
- 전달 인자는 처음 두개는 ECX, EDX 레지스터에 직접 넣고 나머지에 대해선 오른쪽에서 왼쪽으로 전달 된다.
- fastcall은 레지스터를 직접 이용하기때문에 속도가 빠르다.
- fastcall 방식은 callee 에서 전달 인자에 대한 Stack을 정리한다. (함수 내부)
Disassembly는 지난번 소개해드렸던 방법으로 확인 및 테스트가 가능합니다.
2015/12/31 - [Development/Debugging] - visual studio 디버깅시 디스어셈블리 확인하기
'⌨ DEVELOPMENT > C++' 카테고리의 다른 글
[C/C++] convert string, wstring, utf-8 (9) | 2019.07.06 |
---|---|
[C/C++] 작업표시줄 아이콘 깜빡이게 하기 (FlashWIndow) (0) | 2019.07.03 |
[C++] std::string to std::wstring 서로 변환하기 (3) | 2016.01.31 |
[C/C++] DLL injection. 다른 Process에 내 DLL Load 하기 (5) | 2016.01.03 |
[C/C++] FormatMessage 윈도우 GetLastError를 메시지로!! (0) | 2015.12.29 |
[C/C++] IPC - Pipe client simple example (0) | 2015.12.28 |
[C/C++] IPC - Pipe server simple example (1) | 2015.12.21 |
[C/C++] string replace all 문자열 모두 치환 (0) | 2015.12.11 |
댓글
이 글 공유하기
다른 글
-
[C++] std::string to std::wstring 서로 변환하기
[C++] std::string to std::wstring 서로 변환하기
2016.01.31 -
[C/C++] DLL injection. 다른 Process에 내 DLL Load 하기
[C/C++] DLL injection. 다른 Process에 내 DLL Load 하기
2016.01.03 -
[C/C++] FormatMessage 윈도우 GetLastError를 메시지로!!
[C/C++] FormatMessage 윈도우 GetLastError를 메시지로!!
2015.12.29 -
[C/C++] IPC - Pipe client simple example
[C/C++] IPC - Pipe client simple example
2015.12.28