스택구조
스택은 한쪽 끝에서만 넣고 뺄 수 있는 LIFO형식의 자료구조이다. LIFO란 Last In First Out의 줄임말로 가장 최근에 추가된(push) 자료가 가장 먼저 제거될(pop)될 항목이다.
또한, 스택에는 지역변수와 함수 인자 등의 내용이 들어가기도 한다.
그리고 스택은 함수 1개당 만들어 진다는 점이 특징이다.
스택을 그린 후
만약에 이런 명령어를 실행한다고 생각해보자
push 1
push 2
1
|
2
|
|
|
|
push는 값을 추가하는 것이기 때문에
맨 위에서부터 값이 하나씩 채워지게 될 것이다.
그렇다면 제거하는 명령어인 pop을 실행한다면 어떻게 될까?
이전의 스택에서 한번 pop이 실행되었을 때의 스택의 모습은 이렇다.
pop
1
|
|
|
|
|
pop이 한번 실행된 모습이다.
스택은 LIFO형식의 자료구조이기 때문에 가장 먼저 들어온 1이 빠지지 않고
가장 최근에 들어온 2가 pop 되었다.
이를 통해 스택은 LIFO 구조이고 LIFO 구조가 뭔지도 알 수 있을 것이다.
레지스터
CPU가 여러 명령을 신속하게 처리하기 위해서는 메모리보다 더 빠른 연산이 필요할 것이다. 레지스터는 CPU가 일을 수행하는 데 있어서 필요한 데이터들을 일시적으로 저장하는 역할을 하고
공간은 작아도 CPU와 직접 연결되어 있으므로 속도가 매우 빠른 것이 특징이다.
또한, 레지스터는 32bit에서 4byte이고 64bit에서는 8byte이다.
일단 기본적인 32bit 상에서만 살펴보도록 하겠다.
포인터 레지스터.
명령어 포인터 레지스터 (EIP)
EIP는 현재 실행 중인 코드 세그먼트를
ESP는 맨 마지막에 스택이 쌓아둔 자료의 주소를 저장한다.
또한, 스택에 자료를 push, pop 하면 ESP 값이 증감되는 것이 특징이다.
EBP는 스택의 맨 밑바닥의 주소를 가리키는 포인터이다,
새로운 함수가 호출될 때, 새로운 스택을 시작한다.
또한, 새로운 함수호출 시, 또는 실행 중인 함수의 종료 시에는 값의 변화가 있기도 하다.
함수의 프롤로그와 에필로그
함수의 프롤로그와 에필로그는 스택에 관여하는 작업인데,
프롤로그는 함수가 호출(CALL)될 때, 스택을 구성해 주는 작업을 일컫는 말이다.
프롤로그는
push ebp
이전 함수의 ebp를 저장한다.
move ebp esp
esp값을 ebp에 복사한다.
이 덕분에 함수의 ebp외 esp가 같은 지점을 가리키게 된다.
ebp를 스택에 저장한 후 esp를 ebp에 저장한다.
에필로그는
leave ret로 구성되어 있다.
-leave-
move ebp esp
esp에 ebp를 복사하여 둘을 똑같게 해준다.
pop ebp
esp가 가리키고 있는 값을 ebp에 넣으며 esp를 감소시킨다.
-ret-
pop eip
jmp eip
.
간단하게
esp를 ebp로 복구한 후에 ebp를 복구하고 ret를 통해 다음에 가야 할 주소로 이동한다.
SFP와 RET
기본적인 메모리 구조는
buffer[n] |
SFP |
RET |
SFP
Saved Frame Pointer의 약자로
실행될 때 이전 EBP의 값을 가지고 있으며 32bit에서는 4byte, 64bit에서는 8byte이다.
한 함수 A에서는 그 함수 A만의 지역변수가 존재할 것이다.
따라서 새로운 A의 ebp를 정해줘야 한다. 그래야 A의 지역변수 접근이 쉽기 때문이다.
그런데 A가 끝난 뒤에는 다시 이전이 함수의 ebp가 필요하게 된다.
이 때문에 이전 함수의 ebp를 저장하는 공간이 필요하게 되고 그 역할을 하는 것이 바로 sfp다.
RET
sfp와 똑같이 32bit는 4byte, 64bit에서는 8byte인 것을 명심하자.
아까 설명했었던 eip를 pop 해서 eip에는 ret의 주소가 들어간다.
ret 는 pop eip와 jmp eip를 수행하는데, 이는 다음 수행할 명령을 eip에 넣고
그 주소로 가서 코드를 진행하는 것이다.
리틀 엔디안과 빅 엔디안
엔디안이란 컴퓨터의 메모리 같은 1차원의 공간 안에 연속된 대상을 배열하는 방법을 뜻한다.
또한, 이런 바이트를 배열하는 방법을 바이트 순서라고도 한다.
엔디언은 크게 빅 엔디안과 리틀 엔디안 둘로 나뉜다.
빅 엔디안은 최상위 바이트부터 차례로 저장하는 방식이고
리틀 엔디안은 최하위 바이트부터 차례대로 저장되는 방식이다.