Linker adalah program yang menggabungkan satu atau beberapa object file (.o) bersama library menjadi executable atau shared library. Tugas utama linker adalah symbol resolution — mencocokkan setiap referensi simbol yang belum didefinisikan (berlabel U di nm) dengan definisi simbol di file lain — dan relocation — menghitung alamat absolut setiap simbol dan menulisnya ke instruksi mesin yang merujuk simbol tersebut. Pada chapter ini kita akan melihat linker ld beraksi melalui driver gcc, mengamati error undefined reference, dan memeriksa apa yang dimasukkan linker ke dalam executable akhir.
Memecah Build Menjadi Beberapa Object File #
Proyek C nyata umumnya terdiri dari beberapa file .c. Tiap file dikompilasi terpisah menjadi object file, lalu dilink menjadi satu executable. Strategi ini mempercepat build ulang karena kita hanya perlu mengompilasi ulang file yang berubah.
/* a.c */
#include <stdio.h>
extern int helper(int x);
int main(void) {
int hasil = helper(10);
printf("hasil = %d\n", hasil);
return 0;
}/* b.c */
int helper(int x) {
return x * x + 7;
}Kompilasi tiap file menjadi object file dengan flag -c, lalu link keduanya:
gcc -std=c11 -Wall -Wextra -c a.c -o a.o
gcc -std=c11 -Wall -Wextra -c b.c -o b.o
gcc a.o b.o -o app
./appOutput yang Dihasilkan:
hasil = 107helper(10) mengembalikan 10 * 10 + 7 = 107. Linker berhasil menemukan definisi helper di b.o dan menyambungkannya dengan referensi di a.o. Mari kita lihat apa yang terjadi bila salah satu object file tidak disertakan.
Error undefined reference #
Bila kita hanya men-link a.o tanpa b.o, linker tidak menemukan definisi helper dan mengeluarkan error:
gcc a.o -o fail_appOutput yang Dihasilkan:
/usr/bin/ld: a.o: in function `main':
a.c:(.text+0xe): undefined reference to `helper'
collect2: error: ld returned 1 exit statusMari kita telaah pesan error di atas:
- Baris
/usr/bin/ld: a.o: in function 'main':: pesan berasal darild(linker), menyebutkan filea.odan fungsimaintempat referensi bermasalah ditemukan. - Baris
a.c:(.text+0xe): undefined reference to 'helper': lokasi spesifik di section.textfilea.o(offset0xe), dengan pesan bahwa simbolhelperdirujuk tetapi tidak ada definisinya. Format(.text+offset)membantu kita melompat langsung ke baris kode C yang bermasalah dengan debugger. - Baris
collect2: error: ld returned 1 exit status:collect2adalah wrapper GCC yang memanggilld. Pesan ini menandakanldkeluar dengan status1(error) sehinggacollect2meneruskan kegagalan ini kegcc.
Memeriksa Invocation Linker dengan -v #
Driver gcc sebenarnya memanggil beberapa toolchain di balik layar. Flag -v (verbose) menyebabkan gcc menampilkan command line lengkap yang dipakai untuk memanggil setiap tahap, termasuk linker:
gcc -v a.o b.o -o app 2>&1 | tail -3Output yang Dihasilkan (baris terakhir yang relevan):
/usr/libexec/gcc/x86_64-linux-gnu/14/collect2 -plugin /usr/libexec/gcc/x86_64-linux-gnu/14/liblto_plugin.so -plugin-opt=/usr/libexec/gcc/x86_64-linux-gnu/14/lto-wrapper -plugin-opt=-fresolution=/tmp/ccQXZJ6I.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -o app /usr/lib/gcc/x86_64-linux-gnu/14/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/14/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/14/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/14 -L/usr/lib/gcc/x86_64-linux-gnu/14/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/14/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/14/../../.. a.o b.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/x86_64-linux-gnu/14/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/14/../../../x86_64-linux-gnu/crtn.oMari kita uraikan bagian-bagian penting dari command line collect2 di atas:
collect2: program GCC yang membungkusld.collect2menangani konstruktor/destruktor C++ dan tabel exception handling sebelum menyerahkan kontrol keld.-plugin /usr/libexec/gcc/.../liblto_plugin.so: plugin LTO (Link-Time Optimization) untuk membaca object file berformat LLVM/GCC IR.-m elf_x86_64: format ELF target. Linker menghasilkan binary ELF 64-bit x86-64.-dynamic-linker /lib64/ld-linux-x86-64.so.2: menentukan runtime dynamic linker yang akan dipanggil kernel saat executable dimuat. Runtime linker inilah yang menyelesaikan simbol di shared library saat program dimulai.-pie: Position-Independent Executable. Kernel akan memetakan executable ke alamat acak (ASLR) untuk mitigasi serangan buffer overflow.Scrt1.o,crti.o,crtbeginS.o: object file C runtime yang disisipkan otomatis sebeluma.odanb.o. FileScrt1.oberisi entry point_startyang memanggilmain,crti.omenginisialisasi section.init_array, dancrtbeginS.omengatur exception handling table.a.o b.o: object file yang kita berikan di command line.-lgcc -lgcc_s -lc: library pendukung compiler (libgccuntuk operasi aritmetika kompleks seperti__divti3,libgcc_suntuk exception handling C++, danlibcstandar).crtendS.o,crtn.o: penutup C runtime — mengakhiri.fini_arraydan menyediakan epilog_start.
Memeriksa Dependency Dynamic Library #
Setelah linking selesai, executable bergantung pada shared library tertentu saat runtime. Perintah ldd menampilkan dependency tersebut:
ldd appOutput yang Dihasilkan:
linux-vdso.so.1 (0x00007ffd53ad4000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc6fe32d000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc6fe537000)Mari kita telaah baris-baris di atas:
linux-vdso.so.1: virtual dynamic shared object yang dipetakan kernel ke setiap proses — berisi implementasi cepat untukgettimeofday,clock_gettime, dangetcputanpa syscall.libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6: glibc (GNU C Library) yang berisiprintf,malloc, dan ratusan fungsi standar lain. Tanda=>menunjukkan bahwa linker runtime sudah menemukan file ini di path yang ditampilkan./lib64/ld-linux-x86-64.so.2: runtime dynamic linker yang dimuat kernel saat program dimulai. Linker inilah yang menyelesaikan simbolprintfdilibc.so.6ke alamat absolut yang bisa dipanggil.
Perintah nm -u app menampilkan simbol-simbol yang undefined di executable — yaitu simbol yang akan dicari oleh runtime dynamic linker saat program dimuat:
nm -u appOutput yang Dihasilkan:
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
w __cxa_finalize@GLIBC_2.2.5
w __gmon_start__
U __libc_start_main@GLIBC_2.34
U printf@GLIBC_2.2.5Huruf U menandai simbol undefined (harus diresolve saat runtime), huruf w menandai weak symbol (opsional — boleh tidak ada). Perhatikan suffix @GLIBC_2.34 pada __libc_start_main — ini adalah symbol versioning glibc, yang memungkinkan satu library libc.so.6 menyediakan beberapa versi fungsi yang sama untuk kompatibilitas.