Anatomi Overhead Binary C++ Default
Secara default, executable C++ sederhana yang dikompilasi dengan GCC atau Clang di Linux menghasilkan file binary berukuran belasan hingga puluhan kilobyte untuk dynamic linking, dan ratusan kilobyte hingga megabyte jika dikompilasi statis. Overhead ini didorong oleh beberapa komponen infrastruktur runtime:
- libstdc++ dan glibc initialization: Kode startup CRT (C Runtime) seperti
crt1.o,crti.o,crtbegin.o, dan konstruktor global dijalankan sebelum fungsimaindipanggil. - Exception Handling Frame (
.eh_frame): Metadata DWARF yang digunakan runtime untuk unwinding stack saat terjadi exception, tetap dimasukkan meskipun program tidak menggunakan keywordtry-catch. - Run-Time Type Information (
.rodataRTTI): Deskriptor tipe dan vtable metadata untuk operatordynamic_castdantypeid. - Struktur dan Alignment ELF: Standar ABI Linux memisahkan seksi kode dan data ke segmen berbeda yang di-align sesuai ukuran page memori (umumnya 4096 byte atau 2 MB untuk huge pages), menciptakan padding byte kosong di dalam file.
Tahap 1: Eliminasi Overhead via Compiler dan Linker Flags
Langkah pertama mengurangi binary bloat adalah menginstruksikan toolchain untuk menghapus runtime metadata dan fungsi yang tidak terpanggil.
Misalkan terdapat program C++ baseline:
// baseline.cpp
#include <iostream>
int main() {
std::cout << "Hello, World!\n";
return 0;
}Kompilasi standar dynamic binary menghasilkan ukuran sekitar ~17 KB:
g++ baseline.cpp -o baseline
ls -l baseline
# -rwxr-xr-x 1 user user 17304 baselineOptimalkan dengan mematikan exception, RTTI, dan mengaktifkan dead-code elimination di level linker:
g++ -Os -fno-exceptions -fno-rtti -ffunction-sections -fdata-sections baseline.cpp -o optimized -Wl,--gc-sections
strip --strip-all optimized
ls -l optimized
# -rwxr-xr-x 1 user user 14424 optimizedFungsi Masing-Masing Flag
-Os: Mengoptimalkan ukuran instruksi mesin dibanding kecepatan eksekusi (menghindari loop unrolling agresif dan inlining berlebihan).-fno-exceptionsdan-fno-rtti: Menghapus emisi tabel unwinding stack (.gcc_except_table) dan struktur informasi tipe runtime C++.-ffunction-sectionsdan-fdata-sections: Menempatkan setiap fungsi dan variabel ke dalam seksi ELF tersendiri (misal.text.funcName), bukan digabung dalam satu blok.textbesar.-Wl,--gc-sections: Menginstruksikan linker untuk melakukan mark-and-sweep pada tabel simbol dan membuang seksi yang tidak memiliki referensi root.strip --strip-all: Menghapus tabel simbol debug (.symtab) dan string table (.strtab) yang tidak dibutuhkan oleh dynamic loader kernel untuk eksekusi.
Tahap 2: Menghapus Libc Runtime via -nostdlib dan Direct Syscall
Meskipun flag optimasi telah diterapkan, binary masih bergantung pada glibc dan kode inisialisasi C runtime. Untuk memutus ketergantungan ini, gunakan flag -nostdlib. Konsekuensinya, fungsi main standar tidak lagi dipanggil karena tidak ada wrapper libc. Kita harus mendefinisikan symbol entry point langsung: _start.
Sebagai ganti std::cout atau printf, operasi I/O dilakukan melalui pemanggilan kernel langsung (direct system call) menggunakan inline assembly x86_64.
// entry.cpp
extern "C" void _start() {
const char msg[] = "Hello, World!\n";
// sys_write (rax = 1, rdi = fd 1, rsi = buf, rdx = len)
asm volatile (
"mov $1, %%rax\n"
"mov $1, %%rdi\n"
"mov %0, %%rsi\n"
"mov $14, %%rdx\n"
"syscall\n"
:
: "r"(msg)
: "rax", "rdi", "rsi", "rdx"
);
// sys_exit (rax = 60, rdi = error_code 0)
asm volatile (
"mov $60, %%rax\n"
"xor %%rdi, %%rdi\n"
"syscall\n"
);
}Kompilasi tanpa runtime standar:
g++ -Os -fno-exceptions -fno-rtti -nostdlib -ffunction-sections -fdata-sections entry.cpp -o entry -Wl,--gc-sections
strip --strip-all entry
ls -l entry
# -rwxr-xr-x 1 user user 1608 entryUkuran binary berkurang dari ~14 KB menjadi ~1.6 KB. Binary ini berdiri sendiri (statis terhadap kernel ABI), tanpa header dynamic linker (.interp) dan dependensi libc.so.
Tahap 3: Reduksi Struktur ELF Menggunakan Custom Linker Script
File binary berukuran ~1.6 KB masih menyisakan section header default, alignment padding antar program segments, serta metadata GNU (seperti .note.gnu.build-id). Reduksi ke level ratusan byte memerlukan Linker Script kustom.
Buat file link.ld:
ENTRY(_start)
PHDRS {
text_segment PT_LOAD FLAGS(5); /* Read + Execute */
}
SECTIONS {
. = 0x400000;
.text : {
*(.text._start)
*(.text*)
*(.rodata*)
} :text_segment
/DISCARD/ : {
*(.note*)
*(.comment*)
*(.eh_frame*)
}
}Mekanisme Script:
- PHDRS (Program Headers): Mendefinisikan segmen tunggal
PT_LOADdengan permission Read dan Execute (flag 5). Kode mesin dan string literal (.rodata) disatukan dalam satu segmen tanpa batas page alignment. - /DISCARD/: Memerintahkan linker untuk mendrop total seksi metadata GNU comment, note, dan stack unwind.
Kompilasi dengan linker script kustom:
g++ -Os -fno-exceptions -fno-rtti -nostdlib -Wl,-T,link.ld entry.cpp -o mini
strip --strip-all -R .comment -R .note -R .note.gnu.build-id mini
ls -l mini
# -rwxr-xr-x 1 user user 680 miniBinary kini berukuran sekitar 680 byte, hanya memuat header minimum ELF, program header tunggal, dan payload instruksi syscall.
Perbandingan Ukuran Binary Tiap Tahap
| Metode Kompilasi | Ukuran File (Byte) | Reduksi Kumulatif |
|---|---|---|
| Baseline GCC (Dynamic, iostream) | ~17.304 | 0% |
| Compiler Flags (-Os, no-rtti, gc-sections, strip) | ~14.424 | 16.6% |
| -nostdlib + Direct Syscall (_start) | ~1.608 | 90.7% |
| Custom Linker Script + Section Discard | ~680 | 96.1% |
Trade-off Portabilitas dan Keamanan
Mencapai binary sub-kilobyte menuntut trade-off signifikan yang harus dipertimbangkan sebelum diterapkan di lingkungan produksi:
- Portabilitas Syscall: Nomor register dan ABI kernel (
syscall) terikat spesifik ke arsitektur prosesor (x86_64). Kode di atas tidak dapat berjalan di arsitektur ARM64 atau RISC-V tanpa porting manual pada inline assembly register. - Proteksi Memori W^X dan RELRO: Menyatukan
.textdan.rodatake dalam satu segmen dapat memicu risiko keamanan jika tidak cermat. Menghapus CRT default juga mematikan proteksi compiler seperti Stack Canaries (-fstack-protector) dan RELRO (Read-Only Relocations) karena ketiadaan runtime untuk inisialisasi flag proteksi tersebut. - Hilangnya Fitur Standar C++: Tidak ada dukungan untuk
new/deletetanpa allocator custom (implementasimmapataubrksendiri), tidak ada dynamic loading, dan tidak ada standard concurrency primitives (std::thread).
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!