스택 프레임이란 함수가 호출 될 때마다 함수 호출을 위해 할당 받는 메모리 덩어리다. 스텍 프레임은 스택 세그먼트 안에 잡힌다.
스텍프레임을 사용해서 얻는 이점 : 복귀주소(여기서 복귀 주소는 함수 호출 직후의 주소, 즉 다음 주소를 이야기 한다) 전달이 가능해짐. (스택의 크기가 충분하다면), 함수의 인자전달,
* 고정된 레지스터나 고정된 메모리 주소에 값을 쓰면 이전값이 사라지니까.
그리고 복귀 주소를 쌓아둔 메모리(스텍프레임이지..)중 가장 윗부분을 가리키는 레지스터가 SP이다.
함수내에서는 자동으로 함수가 종료되면 함수내에서 스택세그먼트의 주소를 4바이트 해제한다 그 이유는 스택 포인터 레지스터(4바이트)만큼 증가시켜둔걸 해제하기 위함이다..(함수내라는걸 잊지 말도록..) 그뒤에 함수 호출이 끝난고 난뒤에 나머지 인자 만큼 또 다시 줄이는것임..
여기서 등장하는 개념이 함수 호출규약이다.
__cdcel은 함수의 인자는 함수 밖에서 하게 된다. 그래서 약간 코드 사이즈가 더 길다;
__stdcall에서는 인자의 대한 스택 해지까지 호출된 함수 내부에서 하게 된다. 그래서 함수 호출이 끝난 다음에도 별도의 작업을 할 필요가 없어
함수의 독립성이 뛰어나다
둘의 차이가 또 있는데 cdcel은 가변인자 가능 stdcall은 불가능이다 왜냐면..가변인자의 크기를 모르니까..
(매번 printf를 실행해봐..그러면 함수 내부에서는 도대체얼만큼의 크기인지 모른다는거야..
왜냐면 printf해서 하나 출력할수도 2개 출력할수도..모르잖어 ..)
함수의 리턴값은 스택이 아니라 레지스터를 통해 전달이 된다. eax! eax !
ebp는 esp(스택포인터)레지스터 대신 인자값을 엑서스 하기 위한 사용밖에 없다는..
push와 pop 어셈블리어
push의 사용법을 보면 레지스터 이름이 와도 되고 메모리 주소나 상수 값이 와도 됩니다. 레지스터의 값이나 메모리에 있는 값을 스택에 저장합니다.
push ax
push ds
push [bx]
pop 명령에는 레지스터와 메모리가 사용됩니다. 스택에서 값을 읽어서 레지스터나 메모리에 저장합니다.
(ebp : extended base pointer)
push ebp - 이전 스택의 base주소를 저장
mov ebp, esp - 현재 스택의 꼭대기를 새로운 스택의 base설정
C 함수 호출
먼저 스택을 살펴보자. 값이 저장되면 스택 포인터 위치(ESP)가 감소한다. 즉, 4Byte가 저장(push)되면 스택 포인터(ESP)에서 4가 감소한다. 그리고 값이 가져오면(pop) 가져온 바이트 수 만큼 더해진다. 간혹 이부분이 헤갈릴 수 있는데 주의가 필요하다.
ESP(Extended Stack Pointer) 32bit용 Stack Pointer. 스택의 최상위 주소. 시스템에 의해 관리.
EBP(Extended Base Pointer) 32bit용 Base/Frame pointer. 현재 함수에서 stack의 시작 위치 저장(지역변수 영역 시작위치).
일단 함수 호출 전에 스택은 아래와 같이 비어 있다고 하자. 물론 이전에 처리하면서 저장해둔 데이터에 의해서 스택 포인터(ESP)가 변경될 수 있지만, 여기서는 새로운 함수가 호출되기 때문에 현재 스택 포인터(ESP)가 처음 스택 위치라고 보면 된다. 그래서 스택은 비어있는 상태라고 할 수 있다.
[main]스택에 인자 저장
함수 호출 전에 함수에 넘겨지는 인자 값이 스택에 저장된다. 저장되는 순서는 오른쪽에서 왼쪽이다.
앞에서 호출한 함수 포인터와 아래 함수 시작 포인터 주소가 틀리다. 이는 윈도우에서 중간 jump에 의해서 이동했기 때문에 함수 포인터 위치가 틀리다. 여기서는 중요한 것이 아니기 때문에 패스~
현재 스택 시작 포인터(EBP)를 스택에 저장해 둔다. 나중에 함수 종료시 이전에 사용했던 스택을 돌려주기 위해서이다.
그리고, EBP에 ESP를 저장해둔다. 바꿔 말하면 새로운 함수 영역으로 왔기 때문에 EBP의 위치를 현재 스택 포인터(ESP)로 지정했다고 보면 된다.
ESP에서 0C0h를 뺀 것은 0C0h 바이트 만큼 스택을 비어둔 것이며, 이 영역은 함수 로켤변수를 위한 영역으로 할당한 것이다.
int TestAPI someFunc(int p1, int p2, int p3)
{
00F81390 push ebp // 스택 베이스 포인터 보전
00F81391 mov ebp,esp // 현재 스택 포인터를 새로운 베이트 포인터로 설정(새로운 스택시작)
00F81393 sub esp,0C0h // 로컬변수할당 영역 설정(C0h=192B)
그 결과 스택의 모양은 다음과 같다. RA는 Return Address로 함수 호출한 곳으로 복원하는 위치다. 즉, 함수 호출한 곳이 00F83574h이므로 그 다음 위치로 복원하기 때문에 RA는 00F83578h가 된다.
중요한 사실인데..esp는 인수등등.이 push pop되면 변경된다 위처럼 esp는 스택 프레임의 끝을 가리켜야 하기 때문에..