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.

c
/* a.c */
#include <stdio.h>

extern int helper(int x);

int main(void) {
    int hasil = helper(10);
    printf("hasil = %d\n", hasil);
    return 0;
}
c
/* b.c */
int helper(int x) {
    return x * x + 7;
}

Kompilasi tiap file menjadi object file dengan flag -c, lalu link keduanya:

bash
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
./app

Output yang Dihasilkan:

text
hasil = 107

helper(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:

bash
gcc a.o -o fail_app

Output yang Dihasilkan:

text
/usr/bin/ld: a.o: in function `main':
a.c:(.text+0xe): undefined reference to `helper'
collect2: error: ld returned 1 exit status

Mari kita telaah pesan error di atas:

  1. Baris /usr/bin/ld: a.o: in function 'main':: pesan berasal dari ld (linker), menyebutkan file a.o dan fungsi main tempat referensi bermasalah ditemukan.
  2. Baris a.c:(.text+0xe): undefined reference to 'helper': lokasi spesifik di section .text file a.o (offset 0xe), dengan pesan bahwa simbol helper dirujuk tetapi tidak ada definisinya. Format (.text+offset) membantu kita melompat langsung ke baris kode C yang bermasalah dengan debugger.
  3. Baris collect2: error: ld returned 1 exit status: collect2 adalah wrapper GCC yang memanggil ld. Pesan ini menandakan ld keluar dengan status 1 (error) sehingga collect2 meneruskan kegagalan ini ke gcc.

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:

bash
gcc -v a.o b.o -o app 2>&1 | tail -3

Output yang Dihasilkan (baris terakhir yang relevan):

text
 /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.o

Mari kita uraikan bagian-bagian penting dari command line collect2 di atas:

  1. collect2: program GCC yang membungkus ld. collect2 menangani konstruktor/destruktor C++ dan tabel exception handling sebelum menyerahkan kontrol ke ld.
  2. -plugin /usr/libexec/gcc/.../liblto_plugin.so: plugin LTO (Link-Time Optimization) untuk membaca object file berformat LLVM/GCC IR.
  3. -m elf_x86_64: format ELF target. Linker menghasilkan binary ELF 64-bit x86-64.
  4. -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.
  5. -pie: Position-Independent Executable. Kernel akan memetakan executable ke alamat acak (ASLR) untuk mitigasi serangan buffer overflow.
  6. Scrt1.o, crti.o, crtbeginS.o: object file C runtime yang disisipkan otomatis sebelum a.o dan b.o. File Scrt1.o berisi entry point _start yang memanggil main, crti.o menginisialisasi section .init_array, dan crtbeginS.o mengatur exception handling table.
  7. a.o b.o: object file yang kita berikan di command line.
  8. -lgcc -lgcc_s -lc: library pendukung compiler (libgcc untuk operasi aritmetika kompleks seperti __divti3, libgcc_s untuk exception handling C++, dan libc standar).
  9. crtendS.o, crtn.o: penutup C runtime — mengakhiri .fini_array dan menyediakan epilog _start.

Memeriksa Dependency Dynamic Library #

Setelah linking selesai, executable bergantung pada shared library tertentu saat runtime. Perintah ldd menampilkan dependency tersebut:

bash
ldd app

Output yang Dihasilkan:

text
	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:

  1. linux-vdso.so.1: virtual dynamic shared object yang dipetakan kernel ke setiap proses — berisi implementasi cepat untuk gettimeofday, clock_gettime, dan getcpu tanpa syscall.
  2. libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6: glibc (GNU C Library) yang berisi printf, malloc, dan ratusan fungsi standar lain. Tanda => menunjukkan bahwa linker runtime sudah menemukan file ini di path yang ditampilkan.
  3. /lib64/ld-linux-x86-64.so.2: runtime dynamic linker yang dimuat kernel saat program dimulai. Linker inilah yang menyelesaikan simbol printf di libc.so.6 ke 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:

bash
nm -u app

Output yang Dihasilkan:

text
                 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.5

Huruf 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.