ELF는 리눅스에서 실행 파일, 공유 라이브러리(.so), 오브젝트 파일(.o)의 디스크 저장 구조를 정의하는 "표준 바이너리 포맷"
- 실행 파일 : /usr/bin/git, /bin/ls
- 공유 라이브러리(.so) : /lib/x86_64-linux-gnu/libc.so.6
- 오브젝트 파일(.o) : 컴파일 중간 산출물
- (자주) core dump도 ELF 형태

EC2 Ubuntu Instance - ELF Header
1. ELF Hedaer(readelf -h)
- 파일의 신분증
- Class : ELF32/ELF64
- Data : little/big endian
- Machine : x64-64, AArch64(CPU 정보)
- Type : EXEC / DYN
- Entry point : 시작 지점
2. Program Header Table(readelf -l) - 실행에 핵심
- 커널이 보는 적재 지도
- PT_LOAD : 코드/데이터 등 메모리에 매핑할 구간
- PT_DYNAMIC : 동적 링크 정보
- PT_INTERP : 동적 링커 경로(ld.so) - 있으면 동적 링크
3. Section Header Table(readelf -S) - 링킹/디버깅가 참조
- .text : 코드
- .data/.bss : 데이터
- .dynsym/.dynstr : 동적 심볼 테이블
- .got/.plt : 동적 링킹 테이블
execve(커널 syscall)
- ELF Header를 파싱해서 실행 파일 타입 확인(ET_EXEC / ET_DYN)
- Program Header의 "PT_LOAD" 세그먼트를 "mmap()"으로 메모리에 매핑
- 정적 바이너리면 "entry point"로 바로 점프
ld.so(동적 링커, /lib64/ld-linux-x86-64.so.2)
- 동적 링크 바이너리일 경우 "execve"가 "ld.so를 먼저 실행"
- "DT_NEEDED" 태그를 보고 의존 공유 라이브러리(.so) 탐색
- 각 ".so"를 "mmap()"으로 메모리에 로드
- 심볼 릴로케이션(GOT/PLT 패치) 수행 후 실제 entry point로 점프
소스 코드(.c / .java 등)
↓ compiler(gcc등)
오브젝트 파일(.o) <- ELF 형태로 디스크에 저장
↓ linker(ld)
실행 파일 / .so <- ELF 형태로 디스크에 저장
↓ execve / ld.so
메모리(프로세스) <- 여기서 실행
=>
- 컴파일러/링커가 ".o(ELF) 파일"들을 읽어서 -> 최종 ELF 바이너리를 디스크에 씀
- execve/ld.so는 그 디스크의 ELF를 읽어서 -> 메모리에 올림
이후. 프로그램 entry로 점프 후
-----------------------------
커널이 컨텍스트를 바꿔서 이제부터 유저 코드 실행 시작
execve("./hello") 호출
│
▼
Kernel : ELF Header 읽기
↓
"Section Header Table이 어디있지?" -> 찾음
↓
Section Header Table 읽기
↓
".text = 파일 offset 0x0080, 크기 0x0F80"
".data = 파일 offset 0x1500, 크기 0x0500"
↓
VMA 생성(가상 주소 공간 지도 그리기)
VMA에 "이 가상 주소는 이 파일의 이 offset이랑 연결"
↓
실제로 RAM에는 아직 안올림
Page Fault 발생 시:
│
▼
Kernel : "이 가상 주소 -> VMA 확인"
- 이 파일의 offset 0x0080이랑 연결되어 있네
↓
RAM 프레임에 복사
↓
페이지 테이블 업데이트
# 직접 확인
$ readelf -S heelo
Section Headers:
[Nr] Name Type Address Offset Size
[ 1] .text PROGBITS 00400080 000080 000F80 ← 여기!
[ 2] .data PROGBITS 00601500 001500 000500