OS별 실행파일 구조와 실행 권한 이해하기 (PE, ELF, Mach-O)
1. 플랫폼별 실행파일 포맷
Windows의 실행파일은 PE (Portable Executable) 구조를 따른다.
하지만 Linux와 macOS도 각자의 실행파일 포맷이 존재한다.
| OS | 포맷 | 확장자 |
|---|---|---|
| Windows | PE (Portable Executable) | .exe, .dll |
| Linux | ELF (Executable and Linkable Format) | 확장자 없음, .so |
| macOS | Mach-O (Mach Object) | 확장자 없음, .dylib |
2. PE 구조 (Windows)

- DOS Header: 레거시 호환용.
e_magic이MZ,e_lfanew가 PE 헤더 오프셋을 가리킴 - Optional Header: 이름과 달리 필수. x86은
IMAGE_OPTIONAL_HEADER32, x64는IMAGE_OPTIONAL_HEADER64 - RVA (Relative Virtual Address): ImageBase 기준 상대 주소. 실제 메모리 주소 = ImageBase + RVA
- Data Directories: Import/Export Table, TLS, Debug 등의 위치를 가리킴
3. ELF 구조 (Linux)
┌─────────────────────┐
│ ELF Header │ ← 매직넘버 0x7F 'ELF', 아키텍처, 진입점
├─────────────────────┤
│ Program Headers │ ← 실행 시 메모리 로딩 정보 (세그먼트)
├─────────────────────┤
│ .text │
│ .data │
│ .bss │
│ .dynamic │ ← 동적 링킹 정보
├─────────────────────┤
│ Section Headers │ ← 링킹/디버깅용 섹션 정보
└─────────────────────┘
4. Mach-O 구조 (macOS)
┌─────────────────────┐
│ Mach-O Header │ ← 매직넘버, CPU 타입, 명령 수
├─────────────────────┤
│ Load Commands │ ← 세그먼트, 라이브러리, 진입점 등 메타정보
├─────────────────────┤
│ __TEXT segment │ ← 코드, 읽기전용 데이터
│ __DATA segment │ ← 읽기/쓰기 데이터
│ __LINKEDIT │ ← 심볼, 서명 등
└─────────────────────┘
Fat Binary (Universal Binary)
Apple Silicon 전환 때 주목받은 개념으로, 하나의 파일에 여러 아키텍처를 동시에 포함할 수 있다.
┌──────────────────────────┐
│ Fat Header │
├──────────────────────────┤
│ Mach-O (x86_64) │ ← Intel Mac용
├──────────────────────────┤
│ Mach-O (arm64) │ ← Apple Silicon용
└──────────────────────────┘
file myapp
# → Mach-O universal binary with 2 architectures
5. Linux/macOS의 실행 권한
Windows는 확장자 기반으로 실행 여부를 판단하지만,
Linux/macOS는 실행파일 포맷 + 실행 권한(permission) 두 가지가 모두 필요하다.
| Windows | Linux / macOS | |
|---|---|---|
| 실행 판단 기준 | 확장자 (.exe, .bat) |
파일 권한 (x bit) |
| 권한 개념 | 거의 없음 | 필수 |
권한 구조
-rwxr-xr-x 1 user group 12345 file
│rwx → 소유자 (read, write, execute)
│ rwx → 그룹
│ rwx → 기타
x (execute) 비트가 없으면 ELF든 Mach-O든 실행 자체가 안 된다.
# 권한 없을 때
./myapp
# → Permission denied
# 권한 부여 후
chmod +x myapp
./myapp # ✅ 실행됨
6. 텍스트 파일에 실행 권한을 주면?
권한은 "실행 시도를 허락"할 뿐, 실제 실행은 커널이 파일 포맷을 해석할 수 있어야 한다.
./파일 실행
│
▼
권한(x bit) 있음? ──No──→ Permission denied
│
Yes
│
▼
첫 2바이트 확인
├── 0x7F 'ELF' → 커널이 직접 ELF 로드
├── 0xCF 'Mach-O' → 커널이 직접 Mach-O 로드
├── '#!' → Shebang 파싱 → 인터프리터 실행
└── 그 외 → /bin/sh 로 넘김 (쉘 스크립트 시도)
| 케이스 | 결과 |
|---|---|
| Shebang 있는 텍스트 | ✅ 정상 실행 |
| Shebang 없는 텍스트 | ⚠️ 쉘이 명령어로 해석 시도 → 대부분 에러 |
| 포맷이 틀린 바이너리 | ❌ Exec format error |
7. Shebang (#!)이란?
"Sharp(#) + Bang(!)" 의 합성어.
파일 첫 줄에 작성하며, "이 파일을 어떤 인터프리터로 실행해라" 고 OS에게 알려주는 역할이다.
#!/bin/bash
#!/usr/bin/env python3
#!/usr/bin/env node
동작 원리
./script.sh 실행
│
▼
커널: "첫 줄이 #! 이네?"
│
▼
/bin/bash /home/user/script.sh ← 인터프리터에 파일을 인자로 넘겨 실행
8. #!/usr/bin/env를 쓰는 이유
Python3가 설치된 경로는 시스템마다 다르다.
Mac → /usr/local/bin/python3
Ubuntu → /usr/bin/python3
pyenv 환경 → /home/user/.pyenv/bin/python3
#!/usr/bin/python3처럼 경로를 하드코딩하면 다른 환경에서 실패할 수 있다.
#!/usr/bin/env python3
# env가 PATH를 순서대로 뒤져서 python3를 찾아줌 ✅
비유하자면
#!/usr/bin/python3= "무조건 2번 출구로 나와" (없으면 못 만남)
#!/usr/bin/env python3= "네가 있는 가장 가까운 출구로 나와" (어디서든 만남)
정리
- 모든 OS는 자신만의 실행파일 포맷이 있다 (PE / ELF / Mach-O)
- Linux/macOS는 포맷 + 실행 권한(
chmod +x) 두 가지가 모두 필요 - Shebang(
#!)으로 인터프리터를 지정할 수 있으며,env를 거치면 이식성이 높아진다