프로그래밍/C++

#12. 스택 메모리 & 스택 프레임 ( Stack Frame )

코딩하는상후니 2022. 7. 15. 22:10
 

*스택 메모리

 

=> 스택 메모리의 주소는 '높은주소' 부터 시작
 

 

http://www.tcpschool.com/c/c_memory_stackframe

 

 

 

 

* 스택 프레임

 

 

=> 함수에 대한 지역 변수와 매개변수를 일시적으로 보유하는 스택 메모리 영역
 
=> bp ( base pointer ) 와 sp ( stack pointer )를 기준으로
 
해당 함수가 스택 메모리에서 사용할 영역을 지정한다.
 
 
bp 와 sp 는 레지스터의 한 종류. ( = 링크 )

 

 

 


 

*포인터 레지스터
 
ip  (  Instruction Pointer  )  :  다음 수행 명령어의 위치 => 'Code 영역에 해당하는 줄 위치'
sp  (  Stack Pointer  )  :  현재 '스택 top' 위치.
bp  (  Base Pointer  )  :  함수(프로시저) 시작 위치.
 
 
 
 

 

*스택 메모리 동작 과정 ( VC++, 32bit 환경 , cdecl 호출규약 기준 )

 

 

int Func(int R, int V) 
{ 
	int Ret = R + V; 
	return Ret; 
} 
int main() 
{ 
	int A = 1, B = 10; 
	int Result = Func(A, B); 
	return 0; 
}
 
 
1. 오른쪽 매개변수부터 [ B ] , [ A ] 스택에 넣는다.

 

=> esp 가 8바이트 만큼 줄어든 것을 볼 수 있다.
=> 해당 매개변수가 있는 스택의 주소가 함수 안에서 쓰일 매개변수의 주소이다.
 

 

2. 되돌아올 주소 저장.

 

 
3. 함수 call Func (  )
 

 

 

4.  함수 시작 첫부분에서 이전의 ebp 저장.
 
 
5. 현재 esp -> ebp 에 저장.

 

 
6. 함수 내에서 쓰일 스택 메모리 공간 확보
=> sub esp, 0xcc
 
 
 
=> 컴파일러가 함수 내에서 쓰일 지역변수 크기를 판단 ( 0xC ) 해서
lea edi, [ ebp - 0xC ],  지역변수가 쓰일 주소를 edi 에 저장.

 

 
=> 이 때, Ret 위치  :  ebp - 0xC + 4  =  0xd9fcac
 
 

 

*현재까지 정리

 

 

esp  :  0xd9fbe8
ebp  : 0xd9fbe8 + 0xcc  =   0xd9fcb4
( ebp 가 있는 곳에서 0xcc 로 스택 메모리 영역을 잡고 sub 해준 곳이 esp )

 

 
=> ebp ( 0xd9fcb4 )  에 가서 확인해보면, 아래와 같이 저장되어있는걸 볼 수 있다.
 

0x00D9FCB4 :  ebp 주소.

0x00D9FCB8  :  함수가 끝나면 돌아갈 주소.

 

0x00D9FCBC  :  매개변수 1

 

0x00D9FCC0  :  매개변수 10 (=a)

 

 

 

이처럼 bp 를 기준으로 sp 를 빼면서 잡은 영역을
'스택 프레임' 이라고 한다. 이 영역의 크기는 컴파일러가 지역변수를 고려해 판단한다.
 
 
더 나아가,
호출되는 함수의 매개변수를 어떤 방식으로 처리할 것인지에 대해
컴파일 환경마다 여러가지 호출 규약이 존재한다.
 
=> 더 알아보고 싶다면 여기

 

 

 
 

*여기서 궁금한 것

*  지역변수를 저장하기 위해 쓰는 공간이 너무 작지 않나 ??
스택 메모리는 얼마의 크기을 지역변수로 받을 수 있을까??

 

int Func(int a, int b)
{
	int ret = a + b;
	int arr[100][100] = { 4, };
	return ret;
}
 
 

 

 
=> ary[100][100] 크기의 데이터만 잡아도 이전 상황과는 조금 다르게 동작하는데
sub esp 0x9c58 + @ 로 스택의 영역을 잡지 않는 것을 볼 수 있다.
 
 
하지만, 결과적으론 같다.
아마 call __alloca_probe ( ) 함수 내부적으로 처리를 해준 것으로 추측.
 
 
 
 
=> ebp - esp 계산 시, arr 를 선언한 크기 ( 4byte * 100 * 100 ) 보다 약간 많은 크기를
스택 메모리 영역으로 잡는다.
 
ebp - esp = A258 ( 41560 )
 
0x9c58  :  40,024
 
 
 
=> 디스어셈블리에서 보이는 것처럼
edi, [ ebp - 0x9c58 ] 로 선언한 배열의 크기만큼 떨어진 주소를 복사하는 것을 볼 수 있다.
 
지역 변수로 배열을 스택에 선언하기 위해
해당 배열의 크기 ( 4byte* 100 * 100 ) 만큼 떨어진 주소를 저장한 것으로 추측된다.
 
int Func(int a, int b)
{
	int ret = a + b;
	int arr[1000][500] = { 4, };
	return ret;
}
 
=> 실험 결과,
해당 크기의 Array ( 4byte * 1000 * 500 ) 를 지역변수로 선언 시,
StackOverFlaw 에러가 나는 것을 볼 수 있다.
 
 
 
즉,
 
컴파일러가 함수 안에 쓰일 지역변수의 크기를
 
알아서 판단해 충분한 스택 영역을 잡고
 
정의될 데이터 크기만큼 스택 포인터를 옮긴다고 볼 수 있다.
 
 

 

 

*다시 돌아가서,

 

 

7. 함수 종료 직전, 레지스터 (eax) 에 리턴 값 저장.
 

 

 
8. 함수가 종료되면 함수에서 사용된 모든 데이터를 pop
 
 
9. 해당 위치가 맞는지 cmp ebp, esp 를 통해서 확인.
 
 
10. pop ebp 와 동시에
esp = ebp 가 되고,  ebp 는 이전의 ebp 를 가져옴.
이 시점에서 ebp 는 함수 시작 위치 ( 5번 상황. )
 
즉, 0xd9fcb4 가 됨.

 

이 때,
 
이전의 ebp -> pop
 
돌아올 위치 -> pop
 
 
 
'2번' pop 했기 때문에
 
eip  :  0xc918b0
 
ebp  :  0xd9fdb5
 
esp  :  0xd9fcb4 + 4 + 4  =  0xd9fcbc
 

 
11. call 되고 나서 매개변수 개수만큼 위치 더해줌.
=> 이 상황은 A, B 두 개라  add esp, 8
 
최종적으로,
eip  :  0xc918b3
ebp  :  0xd9fdb5
esp  :  0xd9fcc4
 
 
12. 마지막으로 함수에서 받아온 eax 레지스터에 있는 값을 [Result] 에 넣어줌.

 

 

 

 

*결론

 

 

*함수 실행 과정
 
1. 실행 중 함수를 만났다.
2. 매개변수, 돌아올 위치 저장.
3. 함수 안에 들어와 bp 저장, 새로운 Newbp 생성
4. 함수 내에 쓰일 스텍 메모리 공간 확보
5. 함수 종료 직전, 리턴값 존재 시, 레지스터(eax) 에 리턴 값 저장.
6. 함수 종료 시, 데이터 pop 후, 공간 크기만큼 더하기.
7. Newbp 와 sp 위치 확인 ( 오류 확인 )
8. sp = bp 후, 이전의 bp, ip = 돌아올 위치 pop 후, 매개변수 크기만큼 스택 더하기.
 

 
참고 자료
 
 
 
 
 
 
 
더 알아보기
 
 
https://sanghoon23.tistory.com/28 ( 함수 호출 규약 )
 

'프로그래밍 > C++' 카테고리의 다른 글

#14. 부동소수점 & boolean  (0) 2022.07.16
#13. 정수  (0) 2022.07.16
#11. 함수 기초  (0) 2022.07.15
#10. 배열과 주소  (0) 2022.07.15
#9. 어셈블리 반복문  (0) 2022.07.15