본문 바로가기
정보보안/Reversing

[Reversing] Calling Convention(호출 규약)

by Yoonsoo Park 2025. 4. 7.

Calling Convention

Calling Convention(함수 호출 규약)이란 '함수 호출 시 인자(parameter) 전달 및 반환값을 처리하는 방식에 관한 규약'이다. Calling Convention은 다음과 같은 것들을 정의한다. 

 

  1. 인수(argument)를 어떤 순서로 전달할지 - 오른쪽에서 왼쪽?
  2. 인수를 어디에 저장할지 - 레지스터에? 스택(stack)에?
  3. 함수가 반환값을 어디에 저장할지 - eax? 
  4. stack frame을 누가 정리할지 - Caller? Callee?
  5. 어떤 레지스터를 보존해야 할지 - 함수 호출 전에 저장하고, 호출 후 복구해야하는 레지스터

대표적인 Calling Convention들은 다음과 같다. 

 

Calling Convention들을 하나씩 살펴보자.

 

_cdecl 

  • 인자를 오른쪽 => 왼쪽으로 스택에 push
  • caller 가 스택을 정리하기 때문에 callee(호출되는 함수)가 몇 개의 인자가 들어왔는지 몰라도 된다
  • printf와 같은 가변 인자가 가능하다(인자의 개수가 바뀔 수 있음)

 

_cdecl의 예시를 보면, 오른쪽 -> 왼쪽 순으로 인자를 스택에 push하고, add 함수가 끝난 뒤 caller(main)이 ADD esp, 8 이라는 명령어를 통해 스택 프레임을 정리하는 것을 볼 수 있다. 

 

** dword ptr ss:[ebp+8]은 뭘까?

dword: double word = 4byte 크기의 데이터를 다룬다 

ss: stack segment(생략가능) - x86에서는 세그먼트가 기본적으로 정해져있기 때문이다

[ebp+8]: ebp를 기준으로 8byte만큼 떨어진(offset값) 메모리 주소

즉, dword ptr ss:[ebp+8] 은 ebp 기준으로 8만큼 떨어진 주소에 있는 4byte 값을 의미한다

 

_stdcall

  • WIN32 API 및 C++ 표준 호출 규약
  • callee가 RETN '인자크기' 형태로 stack frame을 정리하고 반환되기 때문에 _cdecl 보다 코드 크기가 작아진다
  • callee가 미리 인자의 크기를 알아야 하기 때문에 가변 인자를 전달할 수 없다
  • 인자는 오른쪽 => 왼쪽 순으로 스택에 push 된다

 

**RETN 정리 

retn n : n byte 만큼 스택에 추가하여 정리한 후 retn

ret / retn : pop eip => jmp eip

 

_fastcall

  • 표준화된 규약은 아니며, 컴파일러마다 다르게 처리한다
  • 인자를 오른쪽 => 왼쪽 / 왼쪽 => 오른쪽 둘 다 push 가능
  • 레지스터(eax/ecx/edx)를 먼저 사용하여 인자를 전달하고, 스택을 사용할 수도 있다
  • 레지스터를 통해 인자를 전달하여 메모리 접근 수를 줄인다
  • stack을 사용하는 경우 retn n 을 사용하여 callee가 스택 프레임을 정리한다

 

함수 결과값 반환 방식(x86)

함수가 호출된 후 어떤 값을 되돌려줄 때, x86에서는 주로 EAX 레지스터를 사용한다. 반환할 때, 함수 결과의 타입에 따라 반환 방식이 달라진다. 

 

Value 타입 반환 - 정수, 실수와 같이 4byte(int) 이하의 값일 경우 

  • EAX 레지스터에 해당 값을 직접 넣어서 반환
  • 만약 double과 같이 8byte의 경우 eax+edx 레지스터 조합을 이용하여 반환한다

 

Address 타입 반환 - 포인터, 배열, 구조체, 객체 등 상대적으로 큰 데이터를 반환하는 경우

  • EAX에 반환할 데이터의 주소(pointer)를 담아서 반환
  • 값 자체가 아닌 값을 담고 있는 메모리 주소를 전달하는 방식이다