Library sistem seperti libstdc++.so.6 di chapter sebelumnya punya satu keistimewaan: semua distribusi Linux sudah menyiapkannya di jalur yang dikenal dynamic linker. Begitu kita membuat library sendiri — misalnya libkalkulator.so yang berisi logika bisnis dan dipakai beberapa aplikasi sekaligus — kemewahan itu hilang, dan pertanyaan deployment yang sesungguhnya baru dimulai: pada jalur mana pencari library mencari berkas kita, dan bagaimana memastikan berkas itu benar-benar ada di mesin tujuan? Chapter ini menguji seluruh siklusnya secara nyata: kita membangun sebuah shared library, melihat program gagal karena librarynya tak ditemukan, lalu memperbaikinya tiga cara — LD_LIBRARY_PATH, rpath dengan $ORIGIN, dan penempatan library dalam bundle aplikasi.

Program: Menemukan Diri Sendiri Saat Runtime #

Semua mekanisme bab ini bertumpu pada satu fakta: sistem tahu di direktori mana sebuah executable berada. rpath $ORIGIN memanfaatkan informasi yang sama. Program berikut membuktikan bahwa aplikasi bisa membaca lokasinya sendiri — lewat symlink /proc/self/exe yang selalu menunjuk berkas executable yang sedang berjalan — lalu memeriksa apakah ada folder lib/ di sebelahnya:

cpp
#include <filesystem>
#include <iostream>

// Demo mekanisme di balik $ORIGIN: aplikasi menemukan dirinya sendiri,
// lalu memeriksa apakah ada folder lib/ di sebelahnya — layout deployment
// klasik di mana library bersama mengikuti aplikasi.
namespace fs = std::filesystem;

int main() {
    fs::path exe = fs::read_symlink("/proc/self/exe");
    std::cout << "Nama executable : " << exe.filename().string() << "\n";

    fs::path dirLib = exe.parent_path() / "lib";
    if (fs::exists(dirLib)) {
        std::cout << "Folder lib/ ditemukan di sebelah executable\n";
    } else {
        std::cout << "Folder lib/ tidak ada di sebelah executable\n";
    }
    return 0;
}

Output yang Dihasilkan:

text
Nama executable : prog.out
Folder lib/ tidak ada di sebelah executable

Bedah singkat: Baris 10 (fs::read_symlink("/proc/self/exe")) mengubah symlink virtual kernel menjadi jalur absolut executable — di sandbox praktikum hasil kompilasi harness bernama prog.out, di mesin Anda namanya mengikuti flag -o yang Anda pakai. Baris 13 (exe.parent_path() / "lib") menyusun jalur kandidat dengan operator / milik std::filesystem, cara portabel menyambung komponen jalur tanpa tahu apakah pemisahnya / atau \. Baris 14 (fs::exists) memeriksa keberadaan folder itu. Hasilnya memang “tidak ada” karena program diuji di folder polos tanpa library — nanti di chapter ini kita menjalankannya ulang di dalam bundle yang lengkap dan melihat jawabannya berubah. Perlu dicatat, /proc/self/exe adalah fasilitas Linux; di Windows padanannya GetModuleFileName dan di macOS _NSGetExecutablePath.

Membuat dan Menautkan Library Bersama Sendiri #

Kita mulai dari library dua fungsi aritmetika sederhana:

cpp
// kalkulator-lib.cpp — dikompilasi menjadi shared library
double tambah(double a, double b) {
    return a + b;
}

double kali(double a, double b) {
    return a * b;
}

Dan aplikasi pemakainya yang hanya mendeklarasikan fungsi tersebut — badan kodenya baru ada di dalam libkalkulator.so:

cpp
// aplikasi.cpp — dikompilasi menautkan libkalkulator.so
#include <iostream>

double tambah(double a, double b);   // deklarasi dari library
double kali(double a, double b);

int main() {
    std::cout << "12 + 30 = " << tambah(12, 30) << "\n";
    std::cout << "12 * 30 = " << kali(12, 30) << "\n";
    return 0;
}

Dua flag baru muncul saat kompilasi library: -fPIC (Position-Independent Code) membuat kode yang bisa dipetakan ke alamat memori mana pun — syarat agar satu salinan library dipakai bersama oleh banyak proses — dan -shared memerintahkan hasil akhir berupa .so, bukan executable. Aplikasi kemudian ditautkan dengan -L. (cari library di direktori saat ini) dan -lkalkulator (tautkan libkalkulator.so; konvensi lib<NAMA>.so menjadikan kalkulator namanya):

bash
g++ -std=c++23 -O2 -fPIC -shared kalkulator-lib.cpp -o libkalkulator.so
g++ -std=c++23 -Wall -Wextra aplikasi.cpp -L. -lkalkulator -o aplikasi
ldd aplikasi | grep kalkulator

Output yang Dihasilkan:

text
	libkalkulator.so => not found

Perhatikan: kompilasi dan linking sukses, tetapi ldd langsung menemukan masalah. Entri NEEDED untuk libkalkulator.so sudah tertanam di aplikasi; yang gagal adalah pencarian berkasnya — tahap yang baru berlangsung saat program dijalankan, bukan saat dikompilasi.

Ketika Library Tidak Ditemukan #

Apa yang dirasakan pengguna saat menjalankan aplikasi itu di mesin tanpa library-nya? Kita buktikan:

bash
./aplikasi
echo $?

Output yang Dihasilkan:

text
./aplikasi: error while loading shared libraries: libkalkulator.so: cannot open shared object file: No such file or directory
127

Dynamic linker menelusuri jalur pencariannya, tidak menemukan libkalkulator.so, dan menolak memulai program — exit code 127. Pesan inilah wajah klasik dari deployment yang kurang lengkap, dan tujuan sisa chapter ini adalah memastikan pesan itu tidak pernah sampai ke pengguna kita.

LD_LIBRARY_PATH: Solusi Cepat Saat Pengembangan #

Cara termudah memberi tahu dynamic linker di mana mencari adalah variabel lingkungan LD_LIBRARY_PATH:

bash
LD_LIBRARY_PATH=. ./aplikasi

Output yang Dihasilkan:

text
12 + 30 = 42
12 * 30 = 360

Program jalan karena jalur direktori saat ini ikut disisipkan ke urutan pencarian library. Nyaman untuk iterasi cepat di mesin pengembang, tetapi buruk untuk distribusi: nilainya harus diatur di setiap shell pengguna, mudah lupa, dan karena berlaku untuk semua proses yang mewarisinya, variabel ini juga jalur masuk serangan — pihak lain bisa menyelipkan library tiruan di depan daftar pencarian.

rpath dan $ORIGIN: Library yang Mengikuti Aplikasi #

rpath (runtime search path) adalah daftar direktori yang ditanamkan di dalam biner saat linking, melalui flag -Wl,-rpath,... yang meneruskan opsi ke linker. Bagian yang cerdas adalah nilai khusus $ORIGIN: sebuah variabel yang dynamic linker perluas menjadi direktori tempat executable berada, apa pun direktori itu di mesin pengguna. Kita susun ulang aplikasinya:

bash
g++ -std=c++23 -Wall -Wextra aplikasi.cpp -L. -lkalkulator \
    -Wl,-rpath,'$ORIGIN' -o aplikasi
./aplikasi
ldd aplikasi | grep kalkulator
readelf -d aplikasi | grep -iE 'runpath|rpath'

Output yang Dihasilkan:

text
12 + 30 = 42
12 * 30 = 360
	libkalkulator.so => /tmp/demo-rpath/./libkalkulator.so
 0x000000000000001d (RUNPATH)            Library runpath: [$ORIGIN]

Kini tanpa variabel lingkungan apa pun, ldd menunjukkan library terbaca dari direktori aplikasi itu sendiri. Entri RUNPATH di header dinamis memastikan informasi itu menempel permanen pada biner (GCC modern menuliskan RUNPATH; RPATH adalah varian lama yang perilakunya sedikit berbeda — diwariskan ke library yang dimuat). Bila library ditaruh di subfolder lib/, nilai yang umum dipakai adalah $ORIGIN/lib; bila aplikasi dan library bersebelahan persis seperti demo ini, $ORIGIN saja sudah cukup. Jangan lupa tanda kutip tunggal: tanpa itu, shell Anda sendiri yang mengekspansi $ORIGIN — biasanya menjadi string kosong — dan linker menerima rpath yang rusak.

Menempatkan Library Bersama Aplikasi dalam Bundle #

Strategi $ORIGIN membuka pola distribusi paling waras untuk aplikasi Linux mandiri: satu folder berisi aplikasi dan seluruh library miliknya. Kita rakit bundle-nya dan uji:

bash
mkdir -p /tmp/bundle/lib
cp info-eksekusi aplikasi /tmp/bundle/
g++ -std=c++23 -Wall -Wextra aplikasi.cpp -L. -lkalkulator \
    -Wl,-rpath,'$ORIGIN/lib' -o /tmp/bundle/aplikasi
cp libkalkulator.so /tmp/bundle/lib/
cd /tmp/bundle
./aplikasi
./info-eksekusi

Output yang Dihasilkan:

text
12 + 30 = 42
12 * 30 = 360
Nama executable : info-eksekusi
Folder lib/ ditemukan di sebelah executable

Bundle ini siap dipindah: salin folder itu ke mesin Linux lain dengan arsitektur yang sama dan semuanya tetap berjalan, karena RUNPATH relatif terhadap $ORIGIN, bukan terhadap jalur /tmp. Bandingkan output info-eksekusi di sini dengan versi terverifikasi di awal chapter: programnya byte-per-byte sama, tetapi laporannya berubah dari “tidak ada” menjadi “ditemukan” — bukti bahwa satu-satunya yang berbeda adalah lingkungan tempat biner berada, dan program membaca lingkungan itu secara jujur.

ldconfig: Cache Library di Jalur Sistem #

Jalur ketiga adalah memasang library ke direktori sistem sehingga semua program menemukannya tanpa pengaturan apa pun. Sistem menjaga indeksnya dalam cache /etc/ld.so.cache yang dibangun ulang oleh perintah ldconfig:

bash Contoh — dipasang di mesin tujuan, bukan di praktikum
sudo cp libkalkulator.so /usr/local/lib/
sudo ldconfig
ldconfig -p | grep kalkulator
text
	libkalkulator.so (libc6,x86-64) => /usr/local/lib/libkalkulator.so

Setelah ldconfig berjalan, library terdaftar di cache (lihat dengan ldconfig -p) dan aplikasi kita menemukannya tanpa LD_LIBRARY_PATH maupun rpath. Cara ini pantas dipakai bila library memang layak dibagikan di seluruh sistem — kira-kira sejajar dengan memasangnya lewat paket .deb/.rpm yang akan kita praktikkan di Chapter Deployment di Linux. Untuk aplikasi yang berdiri sendiri, rpath $ORIGIN lebih disukai karena tidak menyentuh sistem dan tidak menimpa versi library milik aplikasi lain.

Dengan ini seluruh jalur pencarian library sudah kita kuasai: RUNPATH dari biner, LD_LIBRARY_PATH dari lingkungan, lalu cache ld.so.cache dan direktori bawaan. Semua itu berlaku karena program kita dinamis — binernya ramping, tetapi selalu menuntut pihak luar. Chapter berikutnya membuka alternatif radikalnya: menautkan semuanya ke dalam satu biner yang berdiri sendiri, lengkap dengan harga yang harus dibayar.

Referensi: Link Options — GCC Online Docs .