ELF란?
ELF (Executable and Linkable Format)
리눅스에서 사용되는 실행 파일, 목적 파일 (.o), 공유 라이브러리 (.so)의 표준 포맷
- 목적: 프로그램을 효율적으로 연결(link), 실행(load) 하도록 정보 구조화
- 사용처: 링커(ld), 로더(execve), 디버거(gdb)
구체적 설명
오브젝트 파일은 “반쪽짜리 실행파일”
주소가 확정되지 않았기 때문에 재배치가 가능하고, 링크 과정을 통해 최종 주소가 정해짐.
구조

ELF Header
- ELF 파일임을 알리는 시작 지점
- 파일 전체의 구조를 정의
- 어떤 CPU용인지, 32비트/64비트인지, 실행파일인지 목적파일인지 등을 기록
| **섹션** | **설명** |
| --- | --- |
| .text | **기계어 코드**가 들어가는 곳 (함수 본체 등) |
| .data | **초기화된 전역/정적 변수** |
| .bss | **초기화되지 않은 전역/정적 변수** (int x;) → 실행 시 0으로 초기화됨 |
| .rodata | 읽기 전용 데이터 (ex: "hello" 문자열 상수) |
예: ELF64, x86-64, Type: REL (Relocatable)
코드 & 데이터 섹션
프로그램의 실질적 실행 내용을 구성
심볼 테이블 (.symtab) & 문자열 테이블 (.strtab)
- .symtab에는 함수와 변수의 이름, 주소, 크기, 소속 섹션 같은 메타정보가 담김
- 이름 자체는 .strtab이라는 문자열 테이블에 저장되고, .symtab이 인덱스로 참조함
int x = 10; // → 심볼: 이름=x, 위치=.data, 크기=4바이트
링커와 디버거가 변수나 함수 이름을 이해하는 핵심 요소
재배치 정보 (.rel.text, .rela.text 등)
- 아직 주소가 확정되지 않은 함수 호출/전역 변수 접근 등을 나중에 채워 넣기 위한 정보
- .o 파일에는 주소가 확정되지 않으므로 이걸 통해 링커가 주소를 확정함
printf("hi");
// → printf의 실제 주소를 모름 → 링커가 이 위치에 진짜 주소를 채워줌
디버깅 정보 (선택적)
- .debug_info, .debug_line, .line 등은 gdb 같은 디버거가 소스와 ELF를 연결할 때 사용
- 변수 이름, 줄 번호, 파일 이름 등의 정보가 포함됨
필수는 아니지만, 디버깅을 할 때 매우 중요
섹션 헤더 테이블 (Section Header Table)
- ELF 파일 내의 모든 섹션의 위치, 크기, 이름을 정의
- 이걸 통해 readelf -S 같은 도구가 섹션을 찾아냄
- 섹션 이름은 .shstrtab에 저장됨
한 장 요약
| 이름 | 역할 | 필수? |
|---|---|---|
| .text | 함수 코드 저장 | ✅ |
| .data / .bss | 전역 변수 저장 | ✅ |
| .rodata | 상수 문자열 등 읽기 전용 데이터 | ✅ |
| .symtab / .strtab | 함수/변수 이름과 메타데이터 | ✅ |
| .rel.* / .rela.* | 주소 채워넣기 정보 | ✅ |
| .debug_*, .line | 소스코드 정보 (gdb용) | ⛔ 선택 |
| .shstrtab | 섹션 이름들 저장소 | 내부적으론 필수지만 학습 시는 간략화 가능 |
예시 (코드)
int g = 10; // .data
int h; // .bss
const char *s = "hi"; // .rodata
int f() { return g + h; } // .text
- .data : g = 10 → 값이 이미 지정됨
- .bss : h → 초기화 안 됨 → 실행 시 0으로 채워짐
- .rodata : "hi"는 읽기 전용 상수 → .rodata에 들어가고, s는 .data에서 .rodata 주소를 참조
- .text : f() 함수 → 기계어 코드
- .rel.text 정보로
링커가 g와 h의실제 주소를 코드에 삽입
.data와 .bss가 나뉘는 이유
| 구분 | .data | .bss |
|---|---|---|
| 초기화 여부 | 초기화된 변수 | 초기화되지 않은 변수 |
| 실행 파일 내 저장 여부 | 있음 | 없음 (공간만 기록됨) |
| 목적 | 값 저장 | 공간 절약 |
예제
int g = 10; // → .data
int h; // → .bss
- g는 값을 10으로 초기화했으므로, 실행파일에 10이라는 값이 저장됨.
- h는 초기화 안 됐으므로, 실행파일에 값은 저장하지 않고 크기만 기록함.
- → 실행 시 메모리에 0으로 자동 초기화됨.
그래서 왜?
1. 실행 파일 크기를 줄이기 위해
- .bss 섹션은 실제 데이터 없이 “여기 몇 바이트 필요해요”만 기록함
- 초기화된 변수(.data)는 값까지 기록해야 하므로 실행파일 용량이 커짐
예를 들어:
char huge_array[1000000]; // .bss
→ 이걸 .data에 넣으면 실행파일이 수 MB 커짐
2. 메모리 초기화 방식이 다름
- .data: 값이 있으므로 파일에서 읽어서 메모리에 복사
- .bss: OS가 0으로 자동 초기화 (실제로 값이 파일에 없음)
링커 입장에서도 유리
- 링커는 .bss는 그냥 “여기 메모리만 잡아줘“라고 OS에 알려주면 되고,
- .data는 파일에 저장된 값들을 메모리에 올려야 함
ELF 파일에서도 이렇게 표시됨
readelf -S a.out 했을 때:
[ 3] .data PROGBITS 0000000000601020 ...
[ 4] .bss NOBITS 0000000000601030 ...
- .data는 PROGBITS →
실제 데이터있음 - .bss는 NOBITS → 데이터 없이
크기만 표시
요약
- .bss는 공간은 필요하지만 초기화값이 없는 전역/정적 변수용
- 실행 파일 크기를 줄이고, OS가 0으로 초기화하도록 맡긴다
- .data는 초기값이 있는 변수만 담는다
728x90
'CS:APP' 카테고리의 다른 글
| 예외상황 (0) | 2025.08.10 |
|---|---|
| 실행 가능 목적파일의 로딩 (0) | 2025.08.09 |
| 컴파일러 드라이버 (0) | 2025.08.04 |
| 함수는 추상화의 결과다 - CS:APP 3.7 프로시저 호출 (0) | 2025.04.08 |
| 스택은 사실 아래로 자란다(push,pop) - CS:APP 3.4.4 (0) | 2025.04.07 |