스택 프레임(Stack Frame)은 함수가 호출될 때 스택 내에 새롭게 형성되는 독립적인 영역이다.
이 영역에는 함수가 실행되는 동안 필요한 매개변수(Arguments),
함수 실행 후 돌아갈 복귀 주소(Return Address),
그리고 함수 내부에서 선언된 지역 변수(Local Variables) 등이 저장된다.
프로그램은 한 번에 여러개의 함수를 호출할 수 있고, 재귀 호출도 가능하다.
이때, 각 함수의 매개변수, 복귀 주소, 지역 변수가 섞이지 않도록 함수마다 독립적인 저장 공간이 필요하다.
그 공간이 바로 스택 프레임이다.
CPU가 다음에 실행할 명령어의 메모리 주소를 가리키는 레지스터이다.
callcall명령은 두 가지 일을 동시에 수행한다.
1. 명령어 포인터(EIP)가 가리키는 주소값(복귀 주소)을 스택에 push한다.
2. EIP주소를 함수의 시작 주소로 바꾼다.
retret은 스택에 저장돼 있던 복귀 주소를 pop하여 EIP에 넣는다.
main:
call func
mov eax, 5
func:
mov ebx, 10
ret
- main에서 call 실행시 EIP가 가리키는 주소값(복귀 주소)을 스택에 push
- func의 시작 주소를 EIP로 바꿈 = func 시작
- ebx에 10이 저장
- 스택에 저장된 복귀주소를 pop하여 EIP에 넣음 = EIP에
mov eax, 5의 주소를 저장- eax = 5, ebx = 10이 저장됨
BP는 메모리의 주소값을 담는 레지스터이다.
BP는 함수가 호출될 때, 해당 함수의 스택 프레임의 기준점(base)로 사용된다.
즉, 함수 내부의 지역변수나 매개변수들에 접근할 때 "스택의 어디에 있는지"를 BP를 기준으로 계산할 수 있게 해준다.
예를 들어, 이러한 C코드가 있을때
void fn()
{
int a = 1, b = 2, c;
c = function(a, b, 3);
}
int function(int x, int y, int z)
{
int sum;
sum = x + y + z;
return sum;
}
fn() 함수가 실행되다가 function()을 호출하면,
CPU는 함수 호출을 위해 스택을 이용해 여러 가지 데이터를 차례로 저장한다.
이 과정을 단계별로 살펴보면 다음과 같다.
먼저 호출자(fn)는 피호출자(function)에게 전달할 인자 a, b, 3을
오른쪽에서 왼쪽 순서로 스택에 push한다.
이제 스택의 최상단(top)에는 x, y, z가 차례로 쌓여 있다.
push 3 ; z
push b ; y
push a ; x
call function ; 복귀 주소 push + EIP 변경
call 다음 명령의 주소를 스택에 pushfunction의 시작 주소로 변경z
y
x
return adress <--- SP
함수가 시작되면, 기존의 BP값을 저장하고 새로운 스택 프레임을 만든다.
push ebp ;이전 함수의 bp 저장
mov ebp, esp ;현재 sp를 새 bp로 설정
sub esp, 4 ;지역 변수(sum)를 위한 공간 확보
int sum
int는 4바이트이므로 top을 가리키는 주소에서 4만큼 낮은 주소로 이동시켜 그만큼의 공간을 마련해야한다.
z <---- ebp+16
y <---- ebp+12
x <---- ebp+8
return address <---- ebp+4
previous ebp <---- ebp
sum (local) <---- ebp-4
ESP(top) <---- esp
스택 프레임이 생성되면 함수의 본문이 실행된다.
함수의 본문에서는 EBP를 기준으로 지역 변수, 매개 변수에 접근할 수 있다.
sum = x + y + z
이 c코드는 어셈블리어로 다음과 같다
mov eax, [ebp+8]
add eax, [ebp+12]
add eax, [ebp+16]
mov [ebp-4], eax
함수 실행이 끝나면, 스택을 정리하고 저장해두었던 return address로 돌아가기 위해 SP와 BP를 복원한다.
mov esp, ebp ; 스택 포인터 복원 (지역 변수 제거)
pop ebp ; 이전 함수의 EBP 복원
ret ; 복귀 주소 pop → EIP로 이동
ret으로 복귀하면, 스택에는 여전히 호출할 때 push했던 인자(x, y, z)가 남아 있다.
스택을 그대로 두면 ESP가 함수 호출 전 위치로 돌아가지 않기 때문에, 다음 함수 호출 시 스택이 겹치거나 주소 계산이 꼬일 수 있다.
따라서 함수 호출이 끝나면 인자들이 차지했던 공간을 해제해 스택을 원래 상태로 되돌리는 과정이 필요하다.
이 과정이 바로 “스택 정리(Stack Cleanup)”이며, 누가 이 정리를 담당하느냐(호출자 or 피호출자)는 호출 규약(Calling Convention) 에 따라 달라진다.
(호출 규약은 다음 글에서 다룬다.)
호출규약관련된 모든것들