04. 함수 호출 규약
일단 함수 호출 규약에는 3가지 방식이 있다.
시작하기에 앞서 함수 호출 규약을 알아보기위해 사용한 코드이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include <stdio.h> int __cdecl test1(int a1, int a2, int a3) { int result = a1 + a2 + a3; return result; } int __stdcall test2(int a1, int a2, int a3) { int result = a1 + a2 + a3; return result; } int __fastcall test3(int a1, int a2, int a3) { int result = a1 + a2 + a3; return result; } int main(void) { test1(1, 2, 3); test2(1, 2, 3); test3(1, 2, 3); return 0; } | cs |
1. __cdecl
기본적으로 사용되는 호출 규약으로서 호출자가 스택을 정리한다.
인자는 오른쪽에서 왼쪽으로 전달되며 호출자가 피호출자를 호출할 때 전달되는
인자의 개수를 알기때문에 가변 인수 함수를 만들 수 있다.
그럼 코드 부분에서 __cdecl은 어느 부분일까?
1 2 3 4 | int __cdecl test1(int a1, int a2, int a3) { int result = a1 + a2 + a3; return result; } | cs |
test1의 어셈블리코드이다. 하나하나뜯어서 살펴보면
push 3 -> test1의 제일 오른쪽 인자로 전달된 3을 push한다.
push 2 -> test1의 중간 인자로 전달된 2를 push한다.
push 1 -> test1의 제일 왼쪽 인자로 전달된 1을 push한다.
여기서 __cdecl의 인자가 오른쪽에서 왼쪽으로 전달된다는 걸 알 수 있다.
call test1 (0D2104Bh) -> 전달받은 인자들로 test1을 호출한다.
add esp,0Ch -> 중요한 부분인데, 스택을 호출자가 정리하는 부분이다. 만약 피호출자, 즉 함수가 스택을 정리했다면
스택을 정리하는 이 부분이 함수내에서 나타나야한다.
2. __stdcall
Win32 API에서 사용하며 피호출자가 스택을 정리한다.
인자는 __cdecl과 마찬가지로 오른쪽에서 왼쪽으로 전달되고
매개변수의 개수가 고정적이어서 호출자쪽에서 스택을 정리하는것 보다는
피호출자쪽에서 스택을 정리하는게 더 효율적이다.
코드에서의 __stdcall 부분은
1 2 3 4 | int __stdcall test2(int a1, int a2, int a3) { int result = a1 + a2 + a3; return result; } | cs |
__cdecl과 달라진점이 보일것이다. 나머지 부분은 대부분 비슷하다.
push 3
push 2
push 1
식으로 인자를 오른쪽에서 왼쪽으로 전달하고 있고
call test2 (0D21343h)
인자 전달 후에 함수를 호출한다.
그런데 __cdecl에서는 보이던
add esp,0Ch
이런 비스무리한 코드조차 보이지가 않는다.
그 이유는 __stdcall은 스택을 피호출자, 즉 메인함수 혹은 이 함수를 쓰는 쪽에서 정리하고
__cdecl은 스택을 함수 자기 자신이 정리하기 때문에 스택을 정리하는 저 코드는
위 어셈 코드에서는 찾아볼 수 없고
test2 함수 자체를 어셈 코드로 나타내면 그 곳에서 찾아볼 수 있을것이다.
(위의 어셈 코드는 main함수 내의 test2 함수이다.)
3. __fastcall
__fastcall? 빠르게 호출한단다.
실제로 이 __fastcall 호출 규약은 스택이 아니라 가까운 레지스터를 사용해서 호출 속도가 빠르다.
피호출자가 스택을 정리하게끔 되어있지만 스택이 아니라 가까운 레지스터를 사용한다.
즉, 스택을 사용하지를 않으므로 정리할 필요가 없어서 정리를 하지않는다.
코드에서 __fastcall 부분은
1 2 3 4 | int __fastcall test3(int a1, int a2, int a3) { int result = a1 + a2 + a3; return result; } | cs |
흠칫 봤을때는 __stdcall과 별 다른게 없어보인다.
하지만, __fastcall의 특징은 스택이 아니라 가까운 레지스터를 사용하는 것이다.
push 3 -> 3을 push한다.
mov edx,2 -> edx에 중간 인자 값인 2를 mov 즉, 저장한다.
mov ecx,1 -> ecx에 제일 왼쪽 인자 값인 1을 mov 즉, 저장한다.
이렇게 레지스터를 사용해서 인자 값을 저장한다.
call test3 (0D21127h) -> 위의 인자 값들을 가지고 test3 함수를 호출한다.
레지스터를 사용했기 때문에 딱히 어느 부분에서 스택을 정리하지 않는다.
이상 함수 호출 규약에 대해 정리했다.
세개의 함수 호출 규약에 대한 어셈코드를 합쳐서 올린다.
이 어셈코드를 보고 각기 다른점을 알아챈다면 함수 호출 규약을 어느정도는 안다고 말할 수 있지않을까.
'Reversing' 카테고리의 다른 글
06. gdb 명령어 정리 (0) | 2017.09.29 |
---|---|
05. 스택, 스택프레임 (0) | 2017.08.04 |
03. 패킹 & 언패킹 (0) | 2017.05.26 |
02. IAT & EAT (0) | 2017.05.24 |
01. PE File Format (0) | 2017.05.24 |