[ASM] 스택 메모리와 스택 프레임

👻 스택 메모리

스택 메모리(Stack Memory)는 메모리 내에 할당되어있는 한 영역으로 일반적인 메모리와 다른 저장 방식과 구조를 가진다. 함수가 사용하는 일종의 메모장이라고 생각하면 된다.

영화 인셉션 으로 비유해보면

  • 꿈이 유효한 동안에는 그 꿈을 유지시켜야 한다.
    • 유효 범위의 개념이 있다.
  • 꿈이 끝나면 그 꿈을 부셔버려도 된다.
    • 정리의 개념이 있다.
  • 꿈에서도 또 꿈을 꿀 수 있다는 것을 고려해야 한다.
    • 유동적으로 유효 범위가 확장이 가능하다.

이러한 조건을 모두 만족하는 게 바로 스택 메모리이다. 스택 메모리는 기본적으로 후입선출(LIFO : Last In First Out) 순으로 데이터가 이동되며 높은 주소부터 낮은 주소 순으로 데이터가 저장된다.

스택 메모리에 데이터를 넣을 땐 push, 꺼낼 땐 pop 명령어를 사용한다.

💡 다양한 용도로 사용되는 레지스터

  • a, b, c, d는 범용 레지스터이다.
  • 포인터 레지스터 : 위치를 가리키는, 스택과 관련된 레지스터이다.
    • ip (Instruction Pointer) : 다음 수행 명령어의 위치
    • sp (Stack Pointer) : 현재 스택 top 위치(일종의 cursor)
    • bp (Base Pointer) : 스택 상대주소 계산용
  • 64비트로 연산을 하면 앞에 r이 붙는다.

디버깅을 실행하면 mov rbp, rsp가 한 줄 추가되는데 바로 스택에서 사용하는 포인터를 의미한다.


🌱 데이터 이동 확인해보기

push 1
push 2
push 3

pop rax
pop rbx
pop rcx

위의 코드를 디버깅 해보자.

Alt Text

레지스터 창에서 이제 rbp, rsp, rip의 의미를 각각 알 수 있게 되었다. rsp가 현재 스택을 가리키고 있으니 해당 주소값을 메모리에 붙여넣어 확인해보자. 이제 Address를 체크해주어야한다.

Alt Text

현재 rsp의 위치는 0x60fe38이고 8 바이트씩 나타내고 있다. 스택 메모리는 높은 주소에서 낮은 주소 순으로 저장되므로 데이터가 어떻게 이동되는지 확인하기 위해 주소를 8 바이트씩 이동해서 전부 보려고 저렇게 설정해두었다. 여기까지 하고 F10을 눌러 한 줄씩 디버깅해보자.

pushx86(32비트 프로그램) 환경에선 4바이트(32비트),
x64(64비트 프로그램) 환경에선 8바이트(64비트) 단위로 움직인다.

Alt Text

현재 디버깅 코드의 위치는 push 2를 가리키고 있다. push 1이 실행되었다는 의미이다. 메모리 창을 살펴보면 8바이트 이전 주소값에 1이 들어간 것을 확인할 수 있다. 값의 저장은 리틀 엔디언 방식이다.

Alt Text

push 3까지 끝내면 값이 이렇게 잘 들어간 것을 알 수 있다. 이제 pop을 해보면 각각의 레지스터에 값이 차례로 들어갔다는 것을 알 수 있다.

Alt Text

가장 마지막에 들어간 값이 제일 먼저 나오므로 rax에는 마지막으로 들어갔던 3이 들어가게 된다. 메모리엔 값이 그대로 있는 걸 확인할 수 있는데, pop을 해도 메모리에 있는 값은 변경되지 않고 유효 범위만 바뀐다고 보면 된다.


🌱 스택 프레임

함수 시간에 해봤던 MAX 함수를 스택을 사용해 구현해보자.

    push 1
    push 2
    call MAX
    PRINT_DEC 8, rax
    NEWLINE
    add rsp, 16 ; 스택 초기화. 안 해주면 에러남 

    xor rax, rax
    ret

MAX:
    push rbp
    mov rbp, rsp

    mov rax, [rbp+16]
    mov rbx, [rbp+24]
    cmp rax, rbx
    jg L1
    mov rax, rbx
L1:
    pop rbp
    ret

Alt Text

위의 스택 메모리 이미지를 참고하면, 고정된 bp값을 이용해 push를 이용해 저장한 매개변수 1, 2의 상대주소를 구하게된다. 64비트 환경에서는 8바이트씩 움직이므로 rax에는 2의 값을, rbx에는 1의 값을 넣어줬다고 보면 된다. 이전 bp값의 주소가 매개변수 주소보다 낮으므로 각각 16과 24를 더해준다.

이렇게 함수의 연산이 끝나게 되면 함수에서 사용하려고 저장해뒀던 데이터들을 소멸시키게 되는데, 이렇게 함수 호출 시에 사용하고 종료 시에 소멸하게 되는 부분을 스택 프레임(Stack Frame)이라고 한다. 스택 사용이 완료되면 깔끔하게 정리 후에 종료시켜야 에러가 나지 않는다.
여기선 add rsp, 16으로 포인터를 변경해주는 것으로 초기화 해주었다.

스택 사용 후 정리를 하지 않으면 충돌 에러가 난다.
Alt Text

기존의 rax, rbx 값을 복원시키고 싶다면 함수 호출 부분 앞뒤로 push, pop을 사용해주면 간편하다.

    push rax
    push rbx
    push 1
    push 2
    call MAX
    PRINT_DEC 8, rax
    NEWLINE
    add rsp, 16 ; 스택 초기화. 안 해주면 에러남 
    pop rbx
    pop rax

👻 글을 마치며

이번 시간에는 스택 메모리와 스택 프레임에 대해 알아보았다. C++언어를 공부하면서 개념을 확실히 익혔기 때문에 이번 포스팅을 하면서는 복습 겸 마무리 정리를 하는 시간을 가졌다. 그래서 개념보단 추가적인 설명들을 적으면서 머릿속에 개념을 확립 시켰다. 공부하기 전엔 스택이라 함은 데이터 저장방식, 후입선출 밖에 알지 못했는데 이제는 코드에서 변수를 선언하거나 함수를 만들면 데이터가 어디에 저장되는 지를 확실히 알게 되었다.


소스코드 보러가기


출처
인프런 Rookies님 강의

Leave a comment