Program yang dikompilasi berhasil dan berjalan mulus di mesin kita bukan jaminan bahwa program yang sama akan berjalan di mesin pengguna. Sejak Chapter Proses Kompilasi dan Linking kita tahu bahwa secara default g++ menghasilkan executable yang dinamis: biner hanya berisi kode yang kita tulis sendiri, sementara kode yang sudah tersedia — operasi string, I/O, aritmetika floating point — dipinjam dari library sistem pada saat program berjalan. Konsekuensinya, mesin tujuan wajib menyediakan kumpulan runtime dependencies yang tepat, dan inilah sumber keluhan klasik “program jalan di komputer saya, tapi error di tempat klien”. Chapter pertama section ini membekali kita dengan tiga alat pemeriksa: file untuk mengenali jenis biner, ldd untuk mendaftar library yang dibutuhkan, dan readelf untuk melihat interpreter ELF. Ketiganya kita jalankan nyata terhadap satu program contoh di lingkungan praktikum.
Program Contoh yang Kita Periksa #
Sebagai bahan uji, kita pakai aplikasi kecil aplikasi-cuaca.cpp yang mencetak prakiraan cuaca statis — cukup kompleks untuk menarik dependensi library standar, cukup sederhana untuk tidak mengaburkan tujuan kita:
#include <iomanip>
#include <iostream>
#include <string_view>
// Aplikasi contoh untuk latihan deployment: mencetak prakiraan cuaca yang
// tersimpan tetap di dalam program (tanpa jaringan, tanpa berkas data).
void cetakBaris(std::string_view kota, int suhu, int kelembapan) {
std::cout << std::left << std::setw(9) << kota
<< std::right << std::setw(4) << suhu << " C"
<< std::setw(7) << kelembapan << "%\n";
}
int main() {
std::cout << "Prakiraan cuaca hari ini\n";
std::cout << "Kota Suhu Kelembapan\n";
cetakBaris("Jakarta", 31, 78);
cetakBaris("Bandung", 24, 82);
cetakBaris("Surabaya", 33, 70);
return 0;
}Output yang Dihasilkan:
Prakiraan cuaca hari ini
Kota Suhu Kelembapan
Jakarta 31 C 78%
Bandung 24 C 82%
Surabaya 33 C 70%Beberapa titik yang layak dicatat: Baris 7 (cetakBaris) menerima std::string_view — pemirsa tanpa kepemilikan memori — sehingga pemanggil bisa mengirim string literal tanpa salinan. Baris 8-10 (std::setw, std::left, std::right) merapikan kolom tabel; manipulator ini menempel pada stream dan tetap berlaku hingga diganti. Baris 12 (main) adalah titik masuk program, tetapi — dan ini kunci chapter ini — main bukanlah hal pertama yang dieksekusi; sebelum sampai ke sana ada proses pemuatan yang melibatkan pihak luar. Kompilasi kita pakai pola biasa:
g++ -std=c++23 -Wall -Wextra -O2 aplikasi-cuaca.cpp -o aplikasi-cuaca
./aplikasi-cuacaOutput yang Dihasilkan:
Prakiraan cuaca hari ini
Kota Suhu Kelembapan
Jakarta 31 C 78%
Bandung 24 C 82%
Surabaya 33 C 70%Mengenali Jenis Biner dengan file #
Perintah file membaca header biner dan melaporkan apa adanya jenis berkas tersebut — sering kali langkah pertama yang kita lakukan saat menerima biner dari orang lain:
file aplikasi-cuacaOutput yang Dihasilkan:
aplikasi-cuaca: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=dd9608e1bc73ab5288a7f0853cb89fef994958f3, for GNU/Linux 3.2.0, not strippedSatu baris ini padat informasi yang semuanya relevan bagi deployment: ELF 64-bit menunjukkan format dan arsitektur biner (Executable and Linkable Format, format standar Linux); pie executable berarti Position-Independent Executable yang alamat memorinya direlokasi oleh kernel sebagai pelindung ASLR; dynamically linked adalah konfirmasi bahwa program membutuhkan library eksternal saat berjalan; interpreter /lib64/ld-linux-x86-64.so.2 menunjuk dynamic linker yang akan kita bedah sebentar lagi; dan not stripped berarti tabel simbol masih utuh — hal yang akan kita ubah di Chapter Release Build. Nilai BuildID[sha1] adalah sidik jari isi biner: angkanya akan berbeda di mesin Anda karena versi compiler yang berbeda, dan itu wajar.
ldd: Daftar Library yang Dibutuhkan Saat Runtime #
Perintah ldd menjawab pertanyaan inti: library apa saja yang akan diminta program ini saat berjalan? Kita saring alamat memorinya dengan sed agar barisnya bersih — alamat itu berbeda di setiap eksekusi:
ldd aplikasi-cuaca | sed 's/ (0x[0-9a-f]*)//'Output yang Dihasilkan:
linux-vdso.so.1
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6
/lib64/ld-linux-x86-64.so.2
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1Setiap baris adalah salah satu pihak yang harus hadir di mesin pengguna:
| Entri | Peran |
|---|---|
libstdc++.so.6 | Implementasi C++ standard library: std::cout, std::string, exception, dan kawan-kawannya |
libgcc_s.so.1 | Runtime helper GCC: operasi yang belum dimiliki CPU secara langsung dan unwinding exception |
libm.so.6 | Fungsi matematika C: std::cos, std::sqrt, dan sejenisnya |
libc.so.6 | glibc, pustaka dasar C yang hampir semua program Linux menyentuhnya |
ld-linux-x86-64.so.2 | Dynamic linker yang memimpin seluruh proses pemuatan ini |
linux-vdso.so.1 | Library virtual dari kernel — tidak ada berkasnya di disk, disediakan langsung di memori |
Panah => menunjukkan di jalur mana library itu ditemukan; bila suatu library tidak ditemukan, ldd mencetak not found di posisinya — dan program yang bersangkutan menolak jalan dengan pesan error while loading shared libraries. Urutan dan jalur persisnya bisa berbeda antar sistem, jadi perlakukan output di atas sebagai pola, bukan konstanta. Yang tidak berubah adalah prinsipnya: biner dinamis membawa daftar permintaan (entri NEEDED di ELF), dan sistem tujuan harus bisa memenuhinya.
Interpreter ELF: Siapa yang Menjalankan Program Kita #
Mengapa main bukan hal pertama yang dieksekusi? Jawabannya ada di header program. Kernel Linux memuat biner, lalu melihat entri program header bernama PT_INTERP yang menunjuk sebuah berkas lain:
readelf -l aplikasi-cuaca | grep -i interpreterOutput yang Dihasilkan:
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]Bukan biner kita yang langsung dieksekusi oleh kernel, melainkan dynamic linker /lib64/ld-linux-x86-64.so.2. Linker inilah yang membaca daftar NEEDED dari biner kita, mencari setiap library pada jalur pencarian yang akan kita pelajari di chapter berikutnya, memetakan semuanya ke memori, merapikan relokasi alamat, dan barulah melompat ke titik masuk program sehingga main akhirnya berjalan. Dengan kata lain, setiap executable dinamis punya satu dependensi paling dasar yang sering terlupa: keberadaan dynamic linker yang cocok. Pada distribusi Linux modern jalur ini praktis selalu ada, tetapi saat menautkan secara statis di chapter Static Linking vs Dynamic Linking nanti, kita akan melihat apa yang berubah ketika pihak ketiga ini tidak lagi diperlukan.
Implikasi untuk Distribusi #
Kini resep “program jalan di mana saja” bisa kita rumuskan secara eksplisit. Mesin tujuan harus: (1) memiliki arsitektur CPU yang cocok dengan biner (x86-64 di output file), (2) memiliki dynamic linker di jalur yang sama, dan (3) menyediakan semua library NEEDED — atau versi yang kompatibel. Untuk glibc berlaku aturan praktis yang penting: biner yang dibangun di glibc lama berjalan di sistem dengan glibc lebih baru, tetapi tidak sebaliknya; itulah sebabnya strategi umum industri adalah membangun pada distribusi Linux paling tua yang masih ingin didukung. Untuk libstdc++ mekanisme penjaganya adalah simbol versi seperti GLIBCXX_3.4.30 — bila program menuntut simbol yang belum ada di sistem tujuan, pemuatan gagal dengan pesan version GLIBCXX_... not found.
Cara paling sederhana memvalidasi semua syarat ini adalah menjalankan ldd di mesin tujuan dan memastikan tidak ada not found. Tetapi perhatikan: daftar di atas baru mencakup library sistem. Begitu kita membuat library sendiri — libkalkulator.so untuk logika bisnis yang dipakai bersama beberapa aplikasi — pertanyaannya berubah: di jalur mana pencari mencari library kita, dan bagaimana memastikan library itu ikut terbawa saat aplikasi dipasang di mesin lain? Itulah topik chapter berikutnya.
Referensi: Link Options — GCC Online Docs .