Virtual Memory 심화 이론
💡
Basic Assembly의 범위를 넘어선 학습내용 정리.
가상 메모리(VM)레이아웃의 상세 특성
1. 커널 영역 (Kernel Space)
- 주소 범위: 0xFFFF800000000000 ~ 0xFFFFFFFFFFFFFFFF
- 특징:
- 사용자 프로그램에서 직접 접근 불가
- 시스템 호출을 통해서만 간접 접근
- 모든 프로세스가 동일한 커널 공간을 공유
- 용도: 운영체제 코드, 디바이스 드라이버, 시스템 자료구조
2. 스택 (Stack)
- 주소 범위: 0x00007FFFFFFFFFFF부터 아래로 성장
- 특징:
- 함수 호출 시 자동으로 할당/해제
- LIFO (Last In, First Out) 구조
- 스택 포인터(RSP)가 현재 위치 가리킴
- 스택 오버플로우 시 세그멘테이션 폴트 발생
; 함수 호출 시 스택 구조
[RSP + 0] ← 지역변수
[RSP + 8] ← 이전 RBP (베이스 포인터)
[RSP + 16] ← 반환 주소
[RSP + 24] ← 매개변수
3. 힙 (Heap)
- 주소 범위: BSS 영역 위부터 위로 성장
- 특징:
- 동적 메모리 할당 (malloc, calloc, new)
- 프로그래머가 명시적으로 관리
- 메모리 누수 가능성 존재
- 단편화 문제 발생 가능
- 시스템 호출:
brk()
,sbrk()
,mmap()
4. BSS 영역 (Block Started by Symbol)
- 특징:
- 초기화되지 않은 전역/정적 변수
- 프로그램 로딩 시 0으로 초기화
- 실행 파일에는 크기 정보만 저장 (공간 절약)
int global_array[1000]; // BSS 영역
static int static_var; // BSS 영역
C Language
5. 데이터 영역 (.data)
- 특징:
- 초기화된 전역/정적 변수
- 읽기/쓰기 가능
- 실행 파일에 초기값이 포함됨
int global_var = 100; // 데이터 영역
static int static_var = 200; // 데이터 영역
C Language
6. 코드 영역 (.text)
- 특징:
- 실행 가능한 기계어 코드
- 읽기 전용 (Execute/Read, No Write)
- 코드 인젝션 공격 방지
- 여러 프로세스가 공유 가능
메모리 레이아웃의 심화
1. 현대의 메모리 구조가 생겨난 배경
역사적 배경
- Segmentation: 초기 8086에서 64KB 제한을 극복하기 위해
- Paging: 가상 메모리와 메모리 보호를 위해 도입
- ASLR (Address Space Layout Randomization): 보안 강화를 위한 현대적 기법
설계 원칙
분리의 원칙: 코드 ↔ 데이터 ↔ 스택을 물리적으로 분리
보안의 원칙: 실행 가능한 영역은 쓰기 금지 (NX bit)
효율의 원칙: 자주 사용되는 영역을 가까이 배치
2. 메모리 보호와 보안
NX bit (No eXecute or Never eXecute)
# 메모리 영역별 권한 확인
$ cat /proc/self/maps
400000-401000 r-xp ... /program # 코드: 실행가능, 쓰기불가
600000-601000 rw-p ... /program # 데이터: 읽기쓰기, 실행불가
7fff-7fff r--p ... [vvar] # 가상 변수 페이지
ASLR (Address Space Layout Randomization)
# ASLR 상태 확인
$ cat /proc/sys/kernel/randomize_va_space
2 # 모든 영역 랜덤화
# 같은 프로그램을 여러 번 실행해도 주소가 다름
$ ./program & echo "PID: $!"; cat /proc/$!/maps | head -5
$ ./program & echo "PID: $!"; cat /proc/$!/maps | head -5
3. 스택과 힙의 충돌
스택 오버플로우
recursive_function:
push rbp
mov rbp, rsp
sub rsp, 1000 ; 큰 지역변수 할당
call recursive_function ; 무한 재귀
add rsp, 1000
pop rbp
ret
; 결과: 스택이 힙 영역을 침범하여 세그폴트 발생
힙 고갈
// 메모리 누수로 인한 힙 고갈
while(1) {
void* ptr = malloc(1024*1024); // 1MB 할당
// free(ptr); 를 하지 않음 → 메모리 누수
}
4. 실무에서의 활용
성능 최적화
; 지역성 원리 활용 (Locality of Reference)
; 스택 변수들은 가까이 위치하므로 캐시 효율이 좋음
process_array:
sub rsp, 32 ; 지역변수들을 스택에 연속 배치
mov [rsp + 0], rax ; temp1
mov [rsp + 8], rbx ; temp2
mov [rsp + 16], rcx ; temp3
mov [rsp + 24], rdx ; temp4
; 이들은 같은 캐시 라인에 있을 가능성이 높음
디버깅과 프로파일링
# 메모리 사용량 실시간 모니터링
$ pmap -x PID
Address Kbytes RSS Dirty Mode Mapping
00400000 4 4 0 r-x-- /program
00600000 4 4 4 rw--- /program
...
5. 운영체제별 차이점
항목 | Linux x86-64 | Windows x64 | macOS x86-64 |
---|---|---|---|
코드 시작 | 0x400000 | 0x140000000 | 0x100000000 |
스택 크기 | 8MB (기본) | 1MB (기본) | 8MB (기본) |
ASLR | 기본 활성화 | 기본 활성화 | 기본 활성화 |
스택 방향 | 아래로 성장 | 아래로 성장 | 아래로 성장 |
핵심 포인트
- 메모리 영역은 각각 다른 목적과 특성을 가짐
- 보안을 위해 코드 영역은 읽기 전용으로 설정
- 스택과 힙이 서로 반대 방향으로 성장하여 공간을 효율적으로 사용
- ASLR과 NX bit 같은 현대적 보안 기법이 이 구조를 활용
- 어셈블리 프로그래밍 시 각 영역의 특성을 고려해야함
고급 주소 지정과 메모리 모델
; 세그먼트 레지스터 활용 (실제 시스템에서 중요)
mov ax, ds:[bx + si] ; 데이터 세그먼트 접근
mov ax, es:[di] ; 추가 세그먼트 접근
; 메모리 모델별 차이점 (small, medium, large, huge)
; 실제 보호모드 vs 리얼모드 차이점
고급 산술 연산
; 128비트 정밀도 연산
mov rax, 0xFFFFFFFFFFFFFFFF
mov rdx, 0xFFFFFFFFFFFFFFFF
add rax, 1 ; 다정밀도 덧셈
adc rdx, 0 ; 캐리 전파
; BCD 연산 (Binary Coded Decimal)
daa ; decimal adjust after addition
das ; decimal adjust after subtraction
고급 비트 조작
; 비트 스캔 연산
bsf rax, rbx ; bit scan forward
bsr rax, rbx ; bit scan reverse
popcnt rax, rbx ; population count (1비트 개수)
; 조건부 이동 (Pentium Pro+)
cmov rax, rbx ; conditional move
메모리 구조를 깊이 있게 이해할수록 버퍼 오버플로우, 메모리 누수, 성능 최적화 등의 다양한 이슈 발생시 골머리를 앓지 않아도 된다.