131바이트 C 실행 파일의 비밀

by DD
3일 전
조회수 0

GCC 컴파일러 옵션을 단계적으로 적용하여 실행 파일 크기를 최소화하는 과정을 탐구함

`-s`, `-nostartfiles`, `-nostdlib`, `-static`, `-no-pie` 등 다양한 플래그를 통해 불필요한 정보 제거

ELF 헤더, 프로그램 헤더, 섹션 헤더 등 ELF 구조 이해를 바탕으로 크기 최적화

최종적으로 131바이트의 초소형 실행 파일 생성 성공

GCC 컴파일러 옵션을 통한 바이너리 크기 최적화

초기 15,816바이트의 기본 C 프로그램에서 시작하여, `-s` 플래그로 디버깅 정보를 제거하며 14,352바이트까지 줄였습니다. `-nostartfiles` 옵션은 `main` 함수 이전에 실행되는 런타임 초기화 코드를 생략하게 하여 13,632바이트로 감소시켰습니다. 이 과정에서 동적 링크(Dynamic Linking)에 필요한 라이브러리 로더(`ld-linux-x86-64.so.2`)와 관련 메타데이터가 상당한 용량을 차지함을 확인했습니다.

정적 링크 및 런타임 제거 전략

동적 링크를 피하기 위해 `-static` 옵션을 사용하고, 표준 라이브러리 링크를 막는 `-nostdlib`와 고정 주소 실행 파일 생성을 위한 `-no-pie` 옵션을 적용했습니다. 이를 통해 시스템 콜(System Call) 직접 호출 방식으로 전환하여 8,704바이트까지 크기를 줄였습니다. `_start` 함수에서 `SYS_exit` 시스템 콜을 직접 호출하는 방식은 런타임 오버헤드를 최소화하는 핵심 전략입니다.

불필요한 ELF 섹션 제거 기법

`.comment` 섹션(컴파일러 정보 포함)은 `-fno-ident`로, `.eh_frame` 섹션(스택 언와인딩 정보)은 `-fno-exceptions -fno-asynchronous-unwind-tables`로 제거하여 4,400바이트까지 줄였습니다. 또한 `.note.gnu.property` 섹션은 `-Wa,-mx86-used-note=no` 옵션으로 제거하여 4,320바이트를 달성했습니다. 이 섹션들은 실행에 필수적이지 않지만 기본적으로 포함되는 정보들입니다.

링커 스크립트를 활용한 최종 최적화

ELF 로더의 섹션 정렬(Section Alignment) 요구사항을 완화하기 위해 `-Wl,--nmagic` 옵션을 사용하여 400바이트까지 줄였습니다. 이후 `-Wl,-z,nosectionheader` 옵션으로 섹션 헤더를 제거하고, 사용자 정의 링커 스크립트(`tiny.ld`)를 통해 `PT_GNU_STACK` 프로그램 헤더까지 제거함으로써 최종적으로 131바이트 바이너리를 완성했습니다. 이 과정은 ELF 파일 구조에 대한 깊은 이해를 요구합니다.

The smallest C binary