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_magicMZ, 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를 거치면 이식성이 높아진다