< 사전적 의미 >
프롤로그 : 책의 첫머리에 서문 대신 쓴 시.
에필로그 : 시가, 소설, 연극 따위의 끝나는 부분.
프롤로그와 에필로그의 사전적 의미이다.
이처럼 프로그래밍에도 함수가 실행되고, 종료될 때도 하나의 약속처럼 프롤로그와 에필로그가 있다.
함수의 프롤로그와 에필로그는 스택 프레임을 생성하고 제거하는 역할을 한다.
* 스택 : 데이터를 꺼낼 때, 마지막에 저장한 데이터가 먼저 꺼내진다. 후입선출(LIFO - Last In First Out) 구조
* 스택 프레임 : 함수가 호출될 때 스택 메모리가 할당되고, 종료 시 호출하기 전에 저장한 주소로 복귀한다.
< 스택 프레임 >
main 함수가 수행되는 과정에서 A 함수가 실행될 경우, 그림과 같이 A 함수의 스택 프레임이 생성된다.
이 사진은 A 함수의 프롤로그가 모두 진행 됐을 때의 스택상황이다.
esp와 ebp 값이 그림과 같이 설정 된다.
* esp : 스택의 가장 아래(낮은) 주소가 저장되어 있는 레지스터 (stack pointer). push 또는 pop은 이 esp에서 일어난다.
* ebp : 현재 스택 프레임에서 가장 위(높은) 주소가 저장되어 있는 레지스터 (base pointer).
* eip : cpu가 다음에 실행해야 할 명령어 주소가 저장되어 있는 레지스터
< 함수 프롤로그 / 에필로그 >
1. 함수의 프롤로그(prologue)
- 함수가 호출될 때, 스택 프레임을 할당한다.
1) push ebp
호출 된 함수의 새로운 프레임의 ebp 주소를 넣어주기 위해 이전에 사용했던 main 함수의 ebp 값을 스택에 push하여 저장해둔다.
2) mov ebp, esp
main 함수의 ebp 주소는 스택에 저장되었기 때문에, ebp의 값에 esp 값을 복사하여 현재 함수 스택 프레임의 base pointer를 설정한다.
이 두가지를 통해 함수 프롤로그는 끝이다. 함수가 끝나고 복귀해야 할 주소도 알고 있으며, 되돌아 갈 main 함수의 base pointer도 저장해두었기 때문이다.
이후, 함수 안의 지역변수를 사용하게 되면 esp가 더 낮은 주소로 따라 지역변수를 쌓는다. 이때 A 함수의 안에서 base pointer인 ebp는 고정이다.
2. 함수의 에필로그(epilogue)
- 스택 프레임의 할당을 해제한다.
* 'mov esp, ebp'와 'pop ebp'는 leave 명령어로 함축된다.
* ret 명령어는 'pop eip'와 jmp eip'라는 명령어로 구성된다.
1) mov esp, ebp
사용되었던 지역변수 만큼 esp의 주소가 낮아졌을 것이다. ebp의 값을 esp에 복사하여 esp를 데려온다.
2) pop ebp
esp를 기준으로 pop을 하게되면 스택의 제일 꼭대기(주소로는 낮음) 데이터를 뽑게 되고, 이를 ebp 레지스터 값에 넣는다.
esp 기준으로 제일 위에 있었던 데이터는 '이전 스택 프레임의 ebp'이고, 이것은 프롤로그에서 main 함수의 ebp를 저장해둔 값이다. 즉, 스택을 다 사용했으니 ebp(base pointer)를 main 함수의 ebp로 다시 설정해주는 것이다.
3) ret
위에서 설명한 것과 같이 ret 명령어는 'pop eip'와 jmp eip'라는 명령어로 구성된다.
'pop eip'를 하면 아까와 같이 esp 기준으로 한 칸을 pop하고, 이번엔 eip 레지스터의 값에 넣는다.
eip는 다음에 실행할 코드의 주소를 담는 레지스터이다.
이후, 'jmp eip'를 통해서 eip 레지스터 안에 들어있는 주소로 jump한다는 것이다.
이 두가지 명령어를 줄여서 ret라고 할 수 있다.
실제 예제코드로 이를 확인해보면 leave와 ret로 함축되어 있는 것을 확인할 수 있을 것이다.
ex) 다음 코드를 이용해서 A 함수가 실행 될 때를 확인해보자.
우리는 main 함수 안에 있는 A 함수가 시작되는 부분과 종료되는 부분을 확인 할 것이다.
먼저, <main+17> 에서 A함수(0x4ed)가 호출된다. 이 call이라는 명령어가 수행될 때 이 함수가 끝나고 복귀할 주소를 스택에 push하게 된다.
이후, A함수 주소(0x4ed)로 jmp하고, 위에서 설명했던 함수 프롤로그가 실행된다.
나머지 A 함수 코드들이 수행되고, 마지막 <A+30>과 <A+31> 부분에서 leave와 ret를 확인할 수 있다.
A 함수가 마무리되고 사용했던 스택 프레임을 해제하려고 하는 것이다.
여러 함수를 겹쳐 선언해도 모두 함수 프롤로그와 에필로그를 확인할 수 있을 것이다.
함수가 실행될 때 스택의 흐름을 알기 위해서는 이 프롤로그와 에필로그에 대한 이해가 필요할 것이다.