[Stack] Stack Frame 공부하기 - 1

두비니

·

2021. 3. 1. 23:26

 

 

 

 

 


Stack Frame

-1-


 

 

오늘은 Stack Frame에 대해서 배울 예정입니다. 

앞으로 2~3개의 글을 통해서 기본적으로 Stack Frame이 무엇인지, 시스템 보안을 하는 사람의 입장에서는 어떤 것들을 알고 있어야 하는지, 그리고 나아가 Stack Frame을 분석할 수 있는 툴 중 하나인 gdb 사용방법을 작성할 예정입니다.

 

 

 

1. Stack Frame이란?

 

사실 Stack Frame이라는 말보다는 그냥 Stack, Stack 자료구조정도로 더 많이 쓰이는 것 같습니다. 

기본적으로 프로그램이라고 함은 컴퓨터의 메모리를 사용해서 이런저런 연산/작동을 수행하는 것을 칭하죠. 그럼 이런 프로그램들이 수십개가 실행되고 있는 컴퓨터에서는 메모리를 효율적으로, 잘 쓰는게 중요하겠죠?

 

효율적으로!

그래서 이 프로그램들이 사용하는 특별한 자료구조의 형태 중 하나가 Stack입니다. 잠시 위키에서 사전적인 의미를 한 번 봅시다.

 

스택(stack)은 제한적으로 접근할 수 있는 나열 구조이다. 그 접근 방법은 언제나 목록의 끝에서만 일어난다. 끝먼저내기 목록(Pushdown list)이라고도 한다.
스택은 한 쪽 끝에서만 자료를 넣거나 뺄 수 있는 선형 구조(LIFO - Last In First Out)으로 되어 있다. 
...(중략)
컴퓨터에서 포인터라고 하는 자료의 위치 표시자와 넣고 빼는 명령어를 사용해서 스택을 이용한다. 주로 함수를 호출할 때 인수의 전달 등에 이용된다. LIFO의 특징을 이용하여 역폴란드 표기법을 이용한 프로그래밍 언어 포스(Forth) 등에서도 이용된다.

-Wikipedia-

 

네. 뭐 아무튼 컴퓨터에서 많이 쓰인다는 내용입니다.

이 Stack구조의 경우에는 가장 특징적인 부분은 LIFO(Last In First Out)구조를 따른다는 것입니다. 글자 자체에서도 알 수 있듯이, 마지막으로 들어온 게 처음으로 나간다는 개념입니다. 보통 컵 안에 원반같은 걸 넣는 걸로 비유를 하곤 합니다.

 

 

다음은 LIFO구조를 간략하게 표현한 그림입니다. 이제 순서대로 보면 1, 2, 3번이 차례로 들어오고, 나갈때는 마지막으로 들어온 3번부터 나가는걸 볼 수 있죠?

이렇게 들어오고 나가는 것들이 프로그램에서는 변수들이나 다른 메모리들이 될 것이고, 변수들이 쌓이는 과정은 push, 그리고 변수들이 나가는 과정은 pop이라고 합니다. 앞으로는 위의 용어들을 사용할 것이기 때문에 용어정도는 알아두고 갑시다.

 

 

 

2. 프로그램 상에서의 Stack

 

프로그램을 직접 실행하면 stack에서는 함수가 호출하는 다양한 정보들을 저장하는 공간을 저장하는 용도로 사용됩니다. 바로 위에 있는 사진처럼 평화롭게 123정도만 오가는 수준이였으면 좋겠지만, 안타깝게도 그렇게 간단하진 않습니다😥

일단은 크게크게 stack의 구조를 한번 파악해 봅시다.

 

//gcc -o ex1 ex1.c
#include <stdio.h>

int func1();
int func2();

int main()
{
	printf("main함수가 호출되었습니다\n");
	func1();  // func1() 호출
	return 0;
}
void func1()
{
	printf("func1함수가 호출되었습니다\n");
	func2();  // func2() 호출
}
void func2()
{
	printf("func2함수가 호출되었습니다\n");
}

 

 

다음은 간단히 작성을 한 코드입니다. 당연히 저 코드를 실행시킨다면

 

 

main() >> func1() >> func2() 순서로 호출되기 때문에, 당연히 저런 식으로 출력이 되겠죠?

이걸 stack으로 표현하면 다음과 같이 표현됩니다.

 

 

각 과정에서 함수들이 호출되어서 stack에 쌓이는 것을 push당한다라고 이해할 수 있고, 위 사진에서는 표현되진 않았지만 함수가 해야할 일을 모두 하고 난 뒤에는 func2(), func1(), main()의 순서대로 stack에서 pop이 될 것도 예상할 수 있을 것 같습니다.

 

한가지만 더 짚고 넘어갑시다. 각 stack을 보면 옆에 Low addr, High addr이라고 쓰여 있는 것을 확인할 수 있습니다. 뜻은 당연히 높은 주소낮은 주소라는 뜻인데, 상식적으로 생각한다면 당연히 위쪽에 높은 주소라고 쓰고, 낮은 쪽에 낮은 주소라고 쓰는게 맞겠져

일단 결론적으로 하고 싶은 이야기는 stack은 쌓이면 쌓일수록 주소가 낮아집니다. 이게 정말 중요해요. 왜 주소가 낮아지는지에 대한 부분은 컴퓨터의 메모리 구조에 대한 전체적인 이해가 필요합니다. 이건 나중에 배우는 걸로 하고, 일단은 외우고 가는게 정신건강에 더 이롭습니다ㅎ.

 

 

3. ESP와 EBP에 대하여

저는 개인적으로 stack구조에서 ebp와 esp가 가장 중요하다고 생각합니다.

너무 개념이 복잡해지기 전에 설명을 하고 넘어가는게 좋을 것 같아 마지막에 삽입하였습니다.

 

(1) ESP (Stack Pointer Register)

ESP 레지스터는 스택의 최상단을 가리키는 레지스터입니다. 즉 어떤 값이 stack에 push되서 쌓일 때 마다 ESP 레지스터가 가리키고 있는 주소는 낮아집니다.(왜 낮아지는지 이해가 안간다면 위에 2번내용을 다시 읽어봅시다.) 그럼 반대로 어떤 값이 pop되면 ESP 레지스터의 주소는 반대로 높아지겠죠?

ESP레지스터는 이런식으로 프로그램을 실행시키면서 프로그램이 진행된 정도를 기록하고 관리합니다.

 

참고로 ebp는 32bit 운영체제 안에서의 이름이고, 64bit 운영체제의 경우 rbp라고 불리웁니다. 추가로 모든 레지스터들도 32bit에서는 e로 시작하는 레지스터이고, 64bit에서는 r로 시작하는 레지스터입니다.

하지만 대부분 처음 스택을 배우시면 32bit 운영체제 안에서의 개념들을 배울 거기 때문에 편의상 ebp라고 작성해놓았습니다. 

 

(2) EBP (Base Pointer Register)

EBP 레지스터는 스택의 최하단을 가리키는 레지스터입니다. EBP가 ESP와 다른 가장 큰 특징은 함수가 종료되기 전까지 EBP는 한번 할당받은 주소가 바뀌지 않습니다. 따라서 이런 점을 이용해 어떤 지역변수의 값을 확인하거나 접근할 때는 대부분 ebp의 기준으로 쓰여져 있는 것도 확인할 수 있습니다.

 

각각 ebp와 esp가 어떻게 쓰이고, 어떤 자리에 있을지는 다음 실습에서 같이 확인해보도록 하겠습니다.

지금은 각 개념에 대해서 모든걸 완벽히 이해할 필요는 없으며, 대충 이런거구나~ 하고 넘어가면 좋을 것 같습니다.

 

 

일단은 여기서 글을 마무리하는걸로 하겠습니다.

여기서 내용을 더 쓰면 너무 길어질 것 같아서 다음 글에서 이어서 작성하도록 하겠습니다.

감사합니다!