흰싸라기 2022. 3. 13. 23:36

(흐름상 5->6보다 5->7이 이해에 좋은 것 같아 업로드 순서 수정)

7.1 스택 프레임

  • ESP(스택 포인터)가 아닌 EBP(베이스 포인터) 레지스터를 사용하여 스택 내의 로컬 변수, 파라미터, 복귀 주소에 접근하는 기법
  • ESP 값은 프로그램 내에서 수시로 변경되기 때문에 스택에 저장된 변수, 파라미터에 접근하기 어렵고 CPU가 정확한 위치를 참고하기 어렵다
  • ESP 값을 EBP에 저장하고 함수 내에서 유지하면, 안전하게 해당 함수의 변수, 파라미터, 복귀 주소에 접근할 수 있다.
  • 스택 프레임의 구조
PUSH EBP        ; 함수 시작(기존의 값을 스택에 저장)
MOV EBP, ESP    ; 현재의 ESP를 EBP에 저장
...
MOV ESP, EBP    ; ESP 정리(함수 시작했을 때 값으로 복원)
POP EBP         ; 저장해 놓았던 원래 EBP 값으로 복원
RETN            ; 함수 종료
  • 최신 컴파일러는 최적화 옵션을 가지고 있어 간단한 함수의 경우 스택 프레임을 생성하지 않음

 

7.2 stackframe.exe

7.2.1 StackFrame.cpp

#include "stdio.h"

long add(long a, long b)
{
    long x = a, y = b;
    return (x + y);
}

int main(int argc, char* argv[])
{
    long a = 1, b = 2;
    
    printf("%d\\n", add(a, b));

    return 0;
}

7.2.2 main() 함수 시작 & 스택 프레임 생성

int main(int argc, char* argv[])
{

1) main() 함수 살펴보기 (main 함수 찾기 : 2장 참고)

  • main() 함수에 BP 설치 후 실행

2) 함수 시작 시 스택 상태

  • ESP = 19FF2C, EBP =19FF70
  • ESP에 저장된 값 4012A8은 main() 함수의 실행이 끝난 후 돌아갈 리턴 주소

3) PUSH EBP

  • 의미 : EBP 값을 스택에 집어 넣어라
  • 이전에 가지고 있던 값을 스택에 백업해두기 위한 용도

4) MOV EBP,ESP

  • 의미 : ESP의 값을 EBP로 옮겨라
  • 명령 후 EBP는 현재 ESP와 같은 값을 가지게 됨
  • main() 함수가 종료될 때까지는 EBP 값 고정됨

5) EBP 위치 확인 (스택 창 마우스 우측 - Address - Relative to EBP)

  • 현재 EBP, ESP 값이 동일함
  • 19FF28 주소에는 19FF70(EBP 초기 값)이라는 값이 저장되어있음

7.2.3 로컬 변수 세팅

long a = 1, b = 2;

1) 메모리 공간 확보

  • 의미 : ESP에서 8을 빼라
    • 8을 빼는 이유? ‘a’와 ‘b’는 long 타입이므로 각각 4바이트 크기를 가짐. 총 8바이트의 메모리 공간을 확보하기 위해

2) 값 저장

  • 의미 : [EBP-8]에는 1을 넣고, [EBP-4]에는 2를 넣어라
  • 실행 후 스택 상태

7.2.4 add() 함수 파라미터 입력 및 add() 함수 호출

    printf("%d\\n", add(a, b));

1) 함수 호출 과정

  • [EBP-4] = b, [EBP-8] = a
  • 입력 순서와 반대로 스택에 저장됨
    • 변수 b가 먼저 들어가고 변수 a가 나중에 들어감

2) add() 함수 복귀주소

  • 함수로 들어가기 전 CPU는 무조건 해당 함수가 종료될 때 복귀할 주소(return address)를 스택에 저장해야함
  • 4010BC 주소의 CALL 명령어 실행 후 스택

  • 4010C1은 add() 함수의 복귀 주소
  • 4010C1은 add() 함수를 호출한 4010BC 다음 명령어

7.2.5 add() 함수 시작 & 스택 프레임 생성

long add(long a, long b)
{

1) 스택 프레임 생성

  • add() 함수가 시작되면 자신만의 스택프레임 생성

  • main() 함수와 동일한 코드 (7.2.2)
  • 원래 EBP 값(main() 함수의 base pointer)을 저장하고 현재의 ESP 값을 EBP에 입력

7.2.6 add() 함수의 로컬 변수(x, y) 세팅

long x = a, y = b;
  • 로컬 변수 x, y에 파라미터 a, b 대입

1) 로컬 변수 x, y 스택 메모리 영역(8바이트) 확보

2) 로컬 변수에 파라미터 값 대입

  • 스택

7.2.7 ADD 연산

return (x + y);

1) add 연산

  • 401092 명령 : 변수 x 값을 EAX에 넣음
  • 401095 : EAX에 변수 y값을 더함
    • ADD는 덧셈 연산 명령어
  • 함수가 리턴하기 직전에 EAX에 넣은 값은 그대로 리턴 값이 된다

7.2.8 add() 함수의 스택 프레임 해제 & 함수 종료(리턴)

return (x + y);
}

1) 스택 프레임 해제 및 복원

  • EBP 값을 ESP에 대입
  • add() 함수 시작할 때의 ESP값을 EBP에 넣어두었다가 함수가 종료될 때 ESP를 원래대로 복원

EBP값 복원

  • 스택
    • 4010C1은 add() 함수의 복귀 주소

2) 복귀 주소로 리턴

  • RETN 명령어 실행 시 스택에 저장된 복귀 주소로 리턴
  • 스택 : (7.2.4 파라미터 입력 후와 동일)

add() 함수 리턴 후

7.2.9 add() 함수 파라미터 제거(스택 정리)

  • ESP에 8을 더하는 이유?
    • 함수에게 넘겨준 파라미터 a, b를 제거하기 위해
  • 스택

7.2.10 printf() 함수 호출

printf("%d\\n", add(a, b));

1) 프린트 함수 호출 코드

  • EAX에는 add() 함수 리턴 값이 저장되어 있음
  • printf() 함수의 파라미터는 8비트, 따라서 ADD ESP, 8 명령으로 함수 파라미터 정리

7.2.11 리턴 값 세팅

return 0;

  • XOR : 같은 값끼리 XOR하면 0됨
  • MOV EAX, 0 명령어보다 실행 속도가 빨라 사용함

7.2.12 스택 프레임 해제 & main() 함수 종료

return 0;
}

  • add() 함수와 마찬가지로 스택프레임 해제 후 리턴 (7.2.8)