
위 그림은 프로세스 메모리 구조이다
간혹 주소 0번지가 가장 위에 표현되어서 스택이 아래에 있는 자료도 있는데,
개인적으로, '스택이 아래로 자란다' 라는 표현 때문에 (높은주소->낮은주소) 를 의미해서 그림에서도 위에서 아래로 자라는 표현 방식이 더 직관적으로 와닿았다
프로세스가 실행될 때 각 프로세스는 독립된 5개의 메모리 공간(세그먼트)을 할당받는다
이 메모리 공간은 각각 목적에 따라 여러 영역으로 나뉜다
각 영역은 높은 주소부터 차례로
스택, 힙, BSS, 데이터, 코드 세그먼트로 나뉜다
'낮은 주소(코드) 에서 가장 높은 주소(스택) 로 갈수록 휘발성이 강하다' 고 생각하면 이해하기 좋다
왜 나누냐면, 용도에 맞게 각 영역에 권한을 부여하기 위해서 (rwx)
CPU 는 각 영역의 권한에 맞는 행위만 할 수 있다
예를 들어, 코드가 저장되는 세그먼트는 임의로 접근해서 WRITE 을 해서는 안되고, 변수가 저장되는 세그먼트는 EXECUTE를 제한해서 임의 코드가 실행되는 것을 막아야 한다
좀더 자세한 내용을 알아보자
코드 세그먼트 (=텍스트 세그먼트)
코드 세그먼트는 기계어 코드를 저장하고, CPU가 프로그램 실행 시 여기에 있는 기계 코드를 메모리에 올린다.
코드는 읽고 실행을 해야하지만 직접 접근해서 변경해서는 안되기 때문에 쓰기 권한은 제한되어 있다
읽기 권한 READ (O)
쓰기 권한 WRITE (X) <- 코드 위변조 방지
실행 권한 EXECUTE (O)
section .data
message db "Hello, World!", 0xA
message_len equ $ - message
section .text // 이 아래에 어셈블리어 코드 입력하면 컴파일 시 텍스트 세그먼트에 기계어 코드가 옴
global _start
_start:
mov rax, 1
mov rdi, 1
mov rsi, message
mov rdx, message_len
syscall
mov rax, 60
xor rdi, rdi
syscall
데이터 세그먼트
컴파일 시점에 값이 정해진 전역변수, 전역상수가 저장된다
쓰기가 가능한 data 세그먼트와 쓰기가 불가능한 rodata (read-only) 세그먼트가 있는데,
실행 도중 변경 가능한 전역 변수는 data 세그먼트에, 그렇지 않은 전역 상수는 rodata 세그먼트에 저장된다
읽기 권한 READ (O)
쓰기 권한 WRITE (O/X)
실행 권한 EXECUTE (X)
int data_num = 31337; // 전역변수 (data)
char data_rwstr[] = "writable_data"; // 전역변수 (data)
const char data_rostr[] = "readonly_data"; // const 전역상수 (rodata)
char *str_ptr = "readonly"; // 포인터 전역변수 (data), 문자열 상수 (rodata)
int main() { ... }
BSS 세그먼트 (Block Started by Symbol 세그먼트)
컴파일 시점에 값이 정해지지 않은 전역변수가 위치하는 메모리 영역이다
(e.g., 개발자가 선언만 하고 초기화하지 않은 전역 변수)
프로그램 실행 시 모두 초기값 0으로 초기화된다
읽기 권한 READ (O)
쓰기 권한 WRITE (O)
실행 권한 EXECUTE (X)
힙 세그먼트
실행 중에 동적으로 할당되는 메모리이며, 스택과는 반대방향으로 자란다
Linux의 sys_brk, mmap 시스템콜로 힙 세그먼트를 관리할 수 있다
C언어에서 malloc(), calloc() 과 같은 동적 할당 함수를 통해 할당받는 메모리에 해당한다
(이 동적 할당 함수도 결국 내부적으로 위 Linux 시스템콜을 사용하는 것이다)
읽기 권한 READ (O)
쓰기 권한 WRITE (O)
실행 권한 EXECUTE (X)
int main() {
int *heap_data_ptr =
malloc(sizeof(*heap_data_ptr)); // 동적 할당한 힙 영역의 주소를 가리킴
*heap_data_ptr = 31337; // 힙 영역에 값을 씀
printf("%d\n", *heap_data_ptr); // 힙 영역의 값을 사용함
return 0;
}
스택 세그먼트
함수의 매개변수, 지역변수등 임시로 할당되는 변수들이 실행 중에 저장되는 공간이다
이 변수들은 함수가 호출될 때 생성되었다가 반환될 때 해제된다
함수가 언제 호출되고 해제될 지 실행 흐름을 예측하는 것은 불가능하다
즉, 스택 프레임 크기는 미리 계산할 수 없다
그러므로, 프로세스 시작 시 스택 세그먼트를 최소 할당하고, 부족하면 이를 확장해준다
높은주소에서 낮은 주소로 확장된다 (스택은 '아래로 자란다' 라는 표현이 이 의미)
어셈블리어에서는 함수의 에필로그와 프롤로그에서 각각 직접 스택 프레임을 만들고 해제한다
프로세스 동작 중에 할당되고 사용되므로 READ, WRITE 권한이 모두 허용된다
다만, 실행 권한은 제한된다
초기에는 실행권한이 허용되었으나, 임의 코드 실행을 막기 위해 NX(Non-Executable) 보호기법이 등장한 이후 실행 권한을 없앴다
읽기 권한 READ (O)
쓰기 권한 WRITE (O)
실행 권한 EXECUTE (X) <- NX 보호기법 적용
// GCC Compiler, x86 assembly
// 함수 프롤로그 (스택 프레임 생성)
push ebp // 베이스 포인터를 스택에 push
mov ebp, esp // 베이스 포인터를 현재 스택 포인터의 주소 (esp, stack 상단) 으로 설정
sub esp, 0x100 // 현재 스택 포인터의 주소로부터 일정 크기만큼 감소 (공간 확보)
// 함수 에필로그 (스택 프레임 정리)
mov esp, ebp // 베이스 포인터를 esp로 옮김 (프롤로그 호출 시점의 스택 포인터)
pop ebp // 베이스 포인터 pop
ret // 함수 호출 전으로 리턴
// -> leave; ret; 도 동일한 동작 수행
힙과 스택이 반대로 자라는 이유?
두 세그먼트 모두 확장성있어 프로세스 실행 도중 가변적으로 크기가 변한다
만약 같은 방향으로 자라면 확장 과정에서 서로 영역을 침범하여 충돌할 수 있다
각각 시작을 반대쪽 끝에 위치시키고 자라게 하면 최대한 충돌을 피하면서 자유롭게 확장할 수 있다
최종 정리
| 세그먼트 | 역할 | 권한 | 저장 데이터 |
| 스택 | 임시 변수 | R, W | 지역변수, 매개변수 |
| 힙 | 실행 중 동적 할당 영역 | R, W | 동적 할당 함수 (malloc, calloc)로 할당받는 메모리 |
| BSS | 초기화되지 않은 데이터 | R, W | 초기화되지 않은 전역변수 |
| 데이터 | 초기화된 데이터 | R, (W - 전역변수만) | 초기화된 전역변수, 전역상수 |
| 코드 | 실행가능한 코드 | R, E | 함수 코드 |
참고 자료
the structure of the virtual memory of a Linux process
I'm reading a textbook which shows virtual memory as: Linux also maps a set of contiguousvirtual pages (equal in size to the total amount of DRAM in the system) to the corresponding set of contiguous
unix.stackexchange.com
- https://www.geeksforgeeks.org/dsa/stack-vs-heap-memory-allocation/
Stack vs Heap Memory Allocation - GeeksforGeeks
Your All-in-One Learning Portal: GeeksforGeeks is a comprehensive educational platform that empowers learners across domains-spanning computer science and programming, school education, upskilling, commerce, software tools, competitive exams, and more.
www.geeksforgeeks.org
- 드림핵 강의
'ComputerScience & Embedded > Pwnable' 카테고리의 다른 글
| x86 Assembly - 데이터 이동, 산술연산, 논리연산 (0) | 2025.10.26 |
|---|