함수가 호출되는 과정



1. 인자를 스택에 집어넣는다

호출된 함수가 인자를 받을 수 있도록 스택에 집어넣습니다.


2. 함수를 호출합니다

Call 명령이 실행되면 CPU는 EIP 레지스터, 즉 함수 호출이 종료된 뒤 돌아와야 할 코드의 주소를 스택에 집어 넣은 뒤,

Call 명령 뒤에 이어지는 인자를 EIP 레지스터에 로드한다.

스택에 집어넣은 EIP 레지스터 값은 이후 함수가 종료된 뒤에 돌아올 코드 위치가 된다.


3. 프레임 포인터를 설정한다

호출된 함수는 호출한 함수의 프레임 포인터를 스택에 보관하고, 그 시점의 스택 포인터 ESP를 프레임 포인터로 설정한다.

설정된 프레임 포인터는 스택 프레임 정보를 참조하는 기준 값으로 사용하게 된다.


4. 로컬 변수를 위한 공간을 할당한다

호출된 함수에서 사용할 로컬 변수를 저장할 공간을 준비한다.

이 작업은 스택 포인터를 이동시키는 것으로 완료된다.

즉 이 함수에서 로컬 변수는 EBP와 ESP 사이의 공간을 사용하게 되는 것으로,

프레임 포인터를 기준으로 첫 번째 로컬 변수는 [ebp-4], 두 번째 로컬 변수는 [ebp-8]과 같은 식으로 참조하게 된다.


5. 호출한 함수의 실행 상태를 보존한다

호출된 함수에서 범용 레지스터를 사용하는 경우, 이전 함수에서 범용 레지스터를 저장해 둔 값이 지워져서

원래 함수로 돌아간 뒤 정상적으로 실행을 재개할 수 없게 되니, 안전하게 이전 함수의 실행 내역을 보존하기 위해서

범용 레지스터의 내용을 스택에 보존한다.


6. 함수를 실행한다

함수의 실행에 필요한 준비과 되었으므로 실제 함수의 연산을 수행한다.


7. 호출한 함수의 실행 상태를 복구한다

5번 과정에서 저장한 레지스터를 복구한다.

⚠︎ 스택을 사용하고 있기 때문에 넣은 순서와 반대의 순서로 꺼내고 있다는 점에 주의해야 한다.


8. 스택을 정리하고, 프레임 포인터를 복구한다

로컬 변수 할당 등으로 사용한 원래대로 복구 한다.

이 과정은 함수에 진입할 시점의 스택 포인터를 복구하는 것으로 완료된다.

그 후 현재 프레임 포인터 위치에 있는 이전 함수의 프레임 포인터를 복구한다.


9. 함수로부터 돌아간다

ret 명령을 수행하면 스택에 저장해 둔 리턴 주소를 꺼내서 EIP에 로드 한다.

이로써 호출된 함수에서 완전히 빠져나와 원래 함수로 돌아온 상태가 된다.


10. 스택에 집어넣은 인자를 정리한다

cdecl 호출 규약에서는 호출자가 인자를 정리해야 하므로, 

스택 포인터에 (인자당 바이트 수) * ( 인자의 개수) 만큼 이동시켜서 인자를 꺼내는 처리를 수행한다.

이것으로 함수 호출 과정이 끝나며 리턴값을 갖는다면 eax 레지스터에 함수 호출의 결과값이 들어있게 된다.




함수 호출 규약이나 예외 처리를 사용했는가의 여부에 따라서 스택 프레임의 구조는 약간씩 달라질 수 있다.

위의 과정에서 3 ~ 5 번에 해당하는 함수 실행 준비 과정을 프롤로그 (Prolog) 라고 부르며,

7 ~ 9번의 함수 실행을 마무리 하는 프롤로그의 반대 과정을 에필로그 (Epilog) 라고 부른다.