스택 프롤로그(Stack Prologue)란?
함수가 시작될 때 자기만의 작업 공간을 만드는 과정
함수마다 자기만의 지역 변수 공간이 필요합니다. 이 공간을 스택에 만드는 게 바로 프롤로그입니다.
main() 함수 실행 중
↓
func() 함수 호출
↓
func()는 자기 공간이 필요함
↓
스택 프롤로그 실행!
전형적인 3단계 과정
assembly
push %rbp # (1) 호출자의 RBP를 스택에 저장
mov %rsp, %rbp # (2) 현재 RSP를 새 RBP로: 기준점 설정
sub $N, %rsp # (3) 지역변수용 공간 N바이트 확보
실제 예시로 이해하기
C 코드
c
// demo.c
int f(void) {
int x = 5;
return x;
}
컴파일
bash
gcc -S -O0 -fno-omit-frame-pointer -masm=att demo.c
어셈블리 출력
assembly
f:
push %rbp # (1) 호출자 RBP 저장
mov %rsp, %rbp # (2) 새 프레임 기준점 설정
sub $16, %rsp # (3) 지역변수용 공간 확보 (16바이트 정렬)
movl $5, -4(%rbp) # (4) x = 5 저장
mov -4(%rbp), %eax # (5) return x → EAX에 로드
leave # (6) 프레임 정리
ret # (7) 복귀
단계별 스택 변화
호출 직후 (프롤로그 전)
높은 주소
┌─────────────────────┐
│ caller의 데이터 │
├─────────────────────┤
│ return address │ ← call이 push한 복귀 주소
└─────────────────────┘ ← %rsp
낮은 주소
(1) push %rbp
효과: rsp -= 8; *(rsp) = rbp
높은 주소
┌─────────────────────┐
│ caller의 데이터 │
├─────────────────────┤
│ return address │
├─────────────────────┤
│ old %rbp │ ← 이전 함수의 기준점 저장
└─────────────────────┘ ← %rsp
낮은 주소
왜 저장? 함수가 끝나면 이전 함수로 돌아가야 하니까!
(2) mov %rsp, %rbp
현재 스택 꼭대기를 프레임 기준점으로 고정
높은 주소
┌─────────────────────┐
│ caller의 데이터 │
├─────────────────────┤
│ return address │
├─────────────────────┤ ← %rbp, %rsp (함께 위치)
│ old %rbp │ 새 함수의 기준점
└─────────────────────┘
낮은 주소
%rbp의 역할: 현재 함수의 고정된 기준점
- 지역 변수는 %rbp - 4, %rbp - 8 같은 오프셋으로 접근
(3) sub $16, %rsp
지역변수 공간 확보 - 스택은 아래(주소 감소)로 자람
높은 주소
┌─────────────────────┐
│ caller의 데이터 │
├─────────────────────┤
│ return address │
├─────────────────────┤ ← %rbp (고정된 기준점)
│ old %rbp │
├─────────────────────┤
│ │
│ 지역변수 영역 │
│ (16바이트) │
│ │
└─────────────────────┘ ← %rsp (16바이트 내려감)
낮은 주소
왜 16바이트? SysV AMD64 규약의 스택 16바이트 정렬 요구사항 때문
(4) movl $5, -4(%rbp)
지역 변수 x에 5 저장
%rbp (고정)
↓
┌─────────────────────┐
│ old %rbp │
├─────────────────────┤
│ (여유 공간) │
├─────────────────────┤
│ x = 5 │ ← -4(%rbp)
└─────────────────────┘
↑
%rsp
스택 프레임 전체 구조
Stack Frame = 각 함수의 전용 작업 공간
높은 주소
┌───────────────────────────┐
│ main()의 Stack Frame │
│ - main의 지역 변수 │
│ - main의 매개 변수 │
├───────────────────────────┤ ← 저장된 main의 %rbp
│ func()의 Stack Frame │
│ - func의 지역 변수 │
│ - func의 매개 변수 │
├───────────────────────────┤ ← 저장된 func의 %rbp
│ ... │
└───────────────────────────┘
낮은 주소
각 함수는 자기 프레임 안에서만 작업하며, 다른 함수 영역은 건드리지 못합니다.
스택 에필로그 (함수 종료)
프롤로그의 반대 과정
assembly
leave # mov %rbp, %rsp; pop %rbp 와 동일
ret # 호출한 곳으로 돌아감
분해하면:
assembly
mov %rbp, %rsp # (1) rsp를 rbp로: 지역 변수 공간 버림
pop %rbp # (2) 저장했던 이전 rbp 복원
ret # (3) return address로 점프
왜 %rbp를 사용하나?
고정된 기준점이 필요하기 때문
- %rsp는 계속 변함: push/pop/sub/add 때마다 이동
- %rbp는 고정: 함수 실행 내내 같은 값 유지
- 결과: 지역 변수 접근이 쉬워짐
c
int x; → %rbp - 4
int y; → %rbp - 8
int z; → %rbp - 12
// %rbp가 고정이니까 항상 같은 offset으로 접근 가능!
메모리 오프셋 규칙
지역 변수/임시 공간
- (%rbp - 양수) 형태
- 예: -4(%rbp), -8(%rbp)
저장된 값/함수 인자
- (%rbp + 양수) 형태
- 예: 8(%rbp), 16(%rbp)
AT&T 문법 표기법
assembly
$0x3 # 즉값(상수)
%edi # 레지스터
-4(%rbp) # 메모리 참조 (rbp - 4 주소의 값)
추가 예시: return x + 3
c
int f(void) {
int x = 5;
return x + 3;
}
어셈블리
assembly
movl $5, -4(%rbp) # x = 5
mov -4(%rbp), %eax # eax = x
addl $0x3, %eax # eax += 3 ← 즉값 더하기
leave
ret
차이점:
- addl $0x3, %edi: 인자 a가 %edi로 들어왔을 때 사용
- addl $0x3, %eax: 지역변수를 %eax로 가져와서 사용
핵심 정리
스택 프롤로그 3단계
- push %rbp: 이전 함수의 기준점 저장
- mov %rsp, %rbp: 새 함수의 기준점 설정
- sub $N, %rsp: 지역 변수 공간 확보
스택 프레임의 구성
- 저장된 이전 함수의 RBP
- 반환 주소 (return address)
- 지역 변수 / 임시 저장 공간
- 필요 시 저장된 레지스터
개념 정리 - %rsp: 동적으로 변하는 스택 포인터
- %rbp: 고정된 프레임 기준점
- 스택 방향: 높은 주소 → 낮은 주소로 성장
- 16바이트 정렬: x86-64 호출 규약 요구사항
'concept' 카테고리의 다른 글
| 운영체제 프로세스 개념 정리 (0) | 2025.10.17 |
|---|---|
| x86-64 레지스터의 자동 Zero-Extension 규칙 (0) | 2025.10.05 |
| [임시] 강의자료 정리: 함수 호출 과정 - Virtual Memory (1) | 2025.10.05 |
| x86-64 어셈블리 명령어 (0) | 2025.10.05 |
| [Virtual Memory] x86-64 함수 호출 & Virtual Memory 기반 스택 프레임 정리 (SysV ABI) (0) | 2025.10.05 |