스택 프레임(Stack Frame)
스택 프레임(Stack Frame)은 함수가 호출될 때, 그 함수만의 스택 영역을 구분하기 위해 생성되는 공간이다.
스택 프레임에는 함수와 관련된 지역변수, 매개변수가 저장되고, 함수 호출 시 할당되고, 함수 종료 시 소멸된다.
스택프레임에 대해서 이해하기 위해서, caller와 callee를 알아야 하는데,
caller는 호출한 함수이고, callee는 호출을 받은 함수이다.
예시로 코드를 하나 작성했는데, 여기서 main이 caller이고, func이 callee이다.
#include <stdio.h>
void func(int a, int b){
int arg1 = a;
int arg2 = b;
printf("arg1:%d, arg2:%d\n",arg1, arg2);
printf("Called func()\n");
}
int main(){
char str1[10];
printf("Called main()\n");
func(1,2);
return 0;
}
우선 main함수 안에서 func() 함수가 실행되는데,
인자값이 뒤에서부터 push 되고 난 후에 func함수가 call이 된다.
push 0x2
push 0x1
call 0x8049166
call func이 되면 다음 실행될 명령어 주소인 0x80491d9가 RET (return address)에 들어가게 된다.
func에 break를 걸고 stack을 확인하면, ret에 0x80491d9가 들어간 것을 확인할 수 있다.
이후, func에 대한 함수의 프롤로그(Prolog)가 진행된다.
함수 프롤로그(Prolog)
함수가 실행하게 되면 함수 영역을 설정하기 위해서 실행되는 2개의 명령어이다.
간단히 정리하면, 이전 함수의 ebp를 저장한 다음.(현재 함수가 끝나면 돌아가기 위해서) 현재 스택포인터를 베이스포인터로 지정하는 과정이다.
1. push ebp
2. mov ebp esp
함수가 시작되면,
push ebp로 이전 함수(caller)의 ebp값이 스택에 들어가고
그 값은 caller의 ebp로 함수 호출이 끝난 후에 돌아갈 위치이다.
그러면 esp는 -4가 된다. (0xffffcffc → 0xffffcff8)
이후 ebp의 위치를 esp의 위치와 동일하게 만든다.
이후 스택프레임이 끝날 때, 함수의 에필로그(Epilogue)가 시작된다.
함수 에필로그(Epilogue)
함수가 끝내기 위해 함수 영역에서 실행되는 2개의 명령어. 스택 프레임을 소멸시키는 역할을 수행한다.
간단히 정리하면, 기존의 함수로 복귀하기 위해서 ebp에 이전 함수의 ebp를 넣고, eip에는 이전함수에서 다음에 실행될 명령어를 넣는 과정이다.
1. leave
2. ret
여기서 leave는 아래와 같은 구조로 이루어져 있다.
1. mov esp ebp
2. pop ebp
함수의 스택프레임을 정리해 준다.
esp를 ebp와 같은 위치로 만든 다음에
ebp에는 기존에 저장한 이전 함수(caller)의 ebp값이 들어간다. 그러면, esp도 +4가 된다.
그리고 ret도 두 개의 명령어로 구성되어 있다.
1. pop eip
2. jmp eip
eip 레지스터에 Return Address인 0x80491d9가 들어가고 esp는 +4가 되고,
eip에 들어온 명령어가 실행된다.