직전 포스팅에서, 레지스터의 여러가지 사용 규칙을 알아보았습니다.
함수 인자의 전달이나 반환값에 대한 레지스터 규칙과, caller 나 callee 저장 레지스터 등이 활용되는 레지스터 보존 규칙 등이 있었죠. 이번엔 마지막으로, 스택 프레임 규칙을 공부해보겠습니다.
먼저 여기서 말하는 스택 프레임은, 메모리의 스택 영역을 말합니다. 해당 스택 영역에는, 지역변수나 매개변수, 모든 명령어의 동작을 마치고, 반환해야하는 포인터의 주소값이 저장됩니다. 또한 스택 자체는, 함수 할당과 함께 생성되며, 함수 호출이 완료되었을 때 소멸합니다. 정확히는 쓰레기 값이 남게되는거지만.. 해당 공간이 다시 누군가에게 할당되었을 때, 덮어씌워지니 사실상 소실됨을 의미합니다.
본격적인, 어셈블리 코드에 들어가기에 앞서, 레지스터 규칙을 공부하는 겸 어떻게 어셈블리 코드가 동작하며 레지스터가 어떤 방식으로 값이 이용되는지 예습하기 좋은 파트라고 생각합니다.
스택 프레임 규칙
어떤 함수에 진입하게 되었을 때, 다음과 같은 패턴의 기초작업이 우선적으로 수행됩니다.
push %rbp
mov %rsp, %rbp
sub $n, %rsp
먼저 push, %rbp의 값이 push 명령어를 통해 스택에 저장됩니다. 이 rbp는 스택의 기준점(BasePoint)을 의미합니다. 따라서 해당 명령어는, 지금 실행될 함수 이전의, 해당 함수 기준점을 백업해놓는 과정을 수행하고 있는 겁니다. 그리고, 스택을 사용하는 것이니, 이 과정에서 %rsp도 8바이트 감소합니다.(스택이 증가하여, 스택 포인터가 하강합니다.) 이 push는, sub $8, %rsp 와 mov %rbp, (%rsp) 두 명령을 수행하는 것을 말합니다.
이후 mov를 통해, 현재의 %rsp의 값을 %rbp에 저장합니다. 즉, 현재의 스택 포인터 값을, rbp에 저장하여 현재 진입한 함수 스택의 기준점으로 rbp를 갱신합니다. 그리고 sub 명령어를 통해 rsp를 강제로 n 바이트 만큼 내립니다. 해당 n 바이트는, 지역변수들이 요구하는 총 공간 크기입니다.
해당 프롤로그 과정을 거친 뒤, 그제서야 함수가 동작합니다. 그리고 모든 함수 실행이 마무리되었다면, 비슷한 패턴으로, 종료작업이 수행됩니다.
mov %rbp, %rsp
pop %rbp
ret
먼저, mov 명령어를 통해, %rsp를 %rbp 값으로 복구시킵니다. 즉, 지역변수의 크기만큼 하강해있는 %rsp를 %rbp 주소값으로 강제 복구시키는 겁니다. 따라서, %rsp가 해당 지역변수들의 값을 조회할 수 없게되면서(스택의 영역밖으로 나가게 만들어버려서) 지역변수 공간을 반납하게됩니다. 이는 다르게 말하면, 지역변수들을 제거하는 것과 같은 효과를 보입니다.
이후 pop을 통해, %rbp 주소값에 저장된 값을 가져옵니다. %rbp의 주소 자체는 언급했듯 해당 스택의 기준점, Base Point입니다. 그리고 그 내부에 저장된 값은, 이전 함수의 %rbp값이 저장되어 있죠. 이는 즉, 현재 함수의 기준점이 이전 caller 함수의 %rbp 백업 역할을 겸임함을 의미합니다. 그리고 덕분에 pop을 통해서, 즉시 기존 함수의 %rbp 값을 추출할 수 있게됩니다. 그럼 이전 함수의 종료차례가 됐을 때도, mov를 통해 rsp를 즉시, 해당 백업 주소값으로 끌어올려, 효율적으로 종료시킬 수 있게 됩니다. 그리고 push 때처럼, 이 과정에서 %rsp는, 8바이트 증가하게 되죠.
그리고 최종적으로 ret, 리턴을 통해, 다음 명령어 실행 주소로 점프합니다.
한번에 예시를 보여드리자면,
long foo(long x) {
long a = x + 1;
return a * 2;
}
다음과 같은 함수가 실행된다 했을 경우, 아래와 같은 어셈블리 코드가 동작하게 됩니다.
push %rbp
mov %rsp, %rbp
sub $8, %rsp ; 지역 변수 a 공간 확보
mov %rdi, -8(%rbp) ; a = x (x는 %rdi에 들어옴)
addq $1, -8(%rbp) ; a += 1
mov -8(%rbp), %rax ; return a * 2
add %rax, %rax
mov %rbp, %rsp
pop %rbp
ret
해당 어셈블리 코드에 대한 자세한 설명은, 이후 포스팅에서 다루겠습니다.
스택 오버플로우
지금까지 이야기한 스택 공간은, 결국 메모리 내에 존재합니다. 이는 공간이 한정되어 있음을 의미하죠. 우리가 함수를 너무 많이 실행하고, 이들이 닫히지 않아버리면(예를 들자면 브레이크가 고장난 8톤 재귀함수와 같이) %rsp 값이 증가하지 않고 계속 감소하여(=스택이 무한적으로 증가하여) 할당된 공간을 초과하게 되고, 이는 오류로 이어집니다. 해당 오류가 바로, 우리가 알고리즘에 지긋지긋하게 보게 된, 스택 오버플로우로 인한, OOM(Out Of Memory) 에러입니다.

'CS' 카테고리의 다른 글
| [CSAPP] 3챕터 - 프로그램의 기계수준 표현(5) - 데이터 이동 인스트럭션 (1) | 2025.09.09 |
|---|---|
| [CSAPP] 3챕터 - 프로그램의 기계수준 표현(4) - 오퍼랜드 (0) | 2025.09.06 |
| [CSAPP] 3챕터 - 프로그램의 기계수준 표현(2) - 레지스터의 종류 (2) | 2025.08.10 |
| [CSAPP] 3챕터 - 프로그램의 기계수준 표현(1) - 인스트럭션 셋 아키텍처(ISA) (5) | 2025.07.30 |
| [CSAPP] 1챕터 - 컴퓨터 시스템에게로 떠나는 여행 (3) - 운영체제 (5) | 2025.07.29 |