리얼 개발

[CS] 함수 호출 규약 (Function Calling Convention) 본문

Computer Science/기타

[CS] 함수 호출 규약 (Function Calling Convention)

econo-my 2025. 4. 7. 23:53

 

함수 호출 규약 ( Calling Convention ) 이란, 함수를 호출하는 방식에 대한 약속이다.

함수를 호출하는데 뭘 약속까지 하냐? 라고 생각할 수도 있지만 함수 호출에는 꽤나 많은 일들이 일어난다.

기본적인 레지스터 stack frame 등은 설명하지 않고 넘어가겠다.

 

함수 호출 규약으로 정하는건 다음과 같다.

  • 인자 전달 순서
  • 인자 전달 방법
  • Stack Frame을 정리하는 방법

 

먼저 함수 호출 규약을 보기 전에 함수가 호출되면 컴퓨터에선 어떤 일이 벌어지는지 확인해보자.

함수 호출 시 다음과 같은 일이 일어난다.

  1. call 명령어 실행
    1. push ip + jump [함수의 주소]
    2. atomic 명령어로 한 클락에 실행됨
  2. 함수 프롤로그
    1. 해당 함수의 stack frame을 만드는 일
    2. push ebp → mov ebp, esp
  3. 함수 에필로그
    1. 해당 함수의 stack frame을 없애는 일
    2. pop ebp → ret
  4. ret 명령어 실행
    1. pop ip + jump[ip]
    2. atomic 명령어로 한 클락에 실행됨

코드로 확인해보자.

컴파일 설정을 통해서 해당 코드를 컴파일 할때 dbg 툴로 보기 쉽게 만들었다.

 

빌드된 .exe 파일을 dbg 툴에 넣으면 코드가 실행될 때 내부적으로 어떤 기계 명령어들이 수행되는지 확인할 수 있다.

 

우리가 주목할 것은 call과 ret 명령어이다

call 부터 봐보자 함수가 호출되기 직전이다.

 

call 명령어가 실행되고 stack 맨 위에 어떤 값이 들어갔는데, 그 값을 보니 함수 호출 다음 줄의 명령어 주소이다. 이 값을 먼저 저장한 뒤 함수의 시작 주소로 ip가 이동한다.

 

그리고 push ebp와 mov ebp, esp 명령어를 통해서 호출한 함수의 bp를 정한 뒤 함수의 몸체에 있는 명령들을 실행한다.

함수의 몸체가 끝나면 pop ebp를 통해 함수를 부른 caller의 bp 주소를 bp 레지스터에 다시 넣어준다.

 

마지막으로 ret 명령어가 실행되면 stack 에 올라와있던 bp,ip 값이 사라져 있고 ip도 정확히 함수 호출 다음 줄을 가리키고 있는 것을 볼 수 있다.

 


 

이제 인자가 있는 경우의 함수를 알아보자. 코드는 다음과 같다.

 

dbg로 보면 다음과 같다.

변수를 정의하고 인자로 넣어주는 과정을 표시해두었다. 이때 인자를 반대로 넣어주고 있는데 그 이유는 함수 stack frame의 입장에서 인자들의 위치를 보면 아래로 내려다 보게 된다. 인자를 거꾸로 줘야 함수 stack frame 입장에선 순서대로 오는 것으로 보인다.

 

그리고 call 을 통해 함수를 호출하고( push ip, jump[func()] ), caller의 bp를 저장한다. 함수의 역할을 모두 마치고 caller의 bp를 꺼내며 ret를 통해 함수를 끝마친다.( pop ip, jump[ip] )

 

함수를 끝 마치고 나서 못 보던 명령어가 있다. add esp, c 인데, 이 명령어가 stack 을 정리하는 명령어이다. stack은 주소가 높은 곳에서 낮은 곳으로 자라기 때문에 sp에 add 명령어로 주소를 더 해줌으로써 스택을 정리한다.

 

이때 우리가 알던 쓰레기값의 진실이 나오는데, stack을 정리한다는 것은 단순히 stack pointer 를 이동시키는 것이다. stack에는 그대로 값이 남게 된다. 우리가 변수를 선언하고 초기화를 안해주었을 때 들어가는 쓰레기 값들이 이렇게 남아있는 값들이 들어가는 것이다.

 


 

이제 마지막으로 함수 호출 규약에 대해 알아볼 차례이다.

대표적으로 다음 3가지의 호출 규약이 있다.

  • cdecl
  • stdcall
  • fastcall

참고로 위에서 예시로 든 코드는 cdecl 방식을 따르고 있다.

 

 

cdecl 방식

c언어에서 주로 사용되는 방식으로 Caller에서 stack 을 정리한다. 위에서 본 것과 같이 add 명령어로 sp에 인자를 넣은 바이트만큼 더해준다.

 

장점은 가변 길이의 파라미터를 전달할 수 있다.

단점은 함수의 호출이 많아지면 코드가 길어진다.

 

 

stdcall 방식

Win32 API에서 사용되는 방식으로 Callee에서 stack 을 정리한다.

함수 앞에 명시적으로 함수 호출 규약 키워드를 붙여 원하는 방식으로 설정할 수 있다.

 

dbg로 보면

ret 옆에 C가 생기는데 10진수로 하면 12 이다. 따라서 int형 변수 3개를 인자로 넘겼을 때 스택에 쌓인 12바이트만큼 Callee에서 sp를 움직이도록 한다.

 

장점은 Caller 의 크기가 작아지며 함수를 여러번 호출해도 한 줄로 정리할 수 있다.

단점은 가변 인자를 사용할 수 없다.

 

 

fastcall 방식

stdcall 방식과 유사하지만 함수에 전달하는 파라미터를 스택 메모리가 아닌 레지스터를 이용하여 전달한다.

 

장점은 스택 메모리 대신 레지스터를 사용하기에 속도가 빠르다.

 

 

 

 

'Computer Science > 기타' 카테고리의 다른 글

[CS] 프로그램이 실행되기 까지 과정  (0) 2025.03.05