Featured image of post Bagaimana Potongan Kode yang Berbeda Menyatu: Mekanisme Kerja Linker dan Loader

Bagaimana Potongan Kode yang Berbeda Menyatu: Mekanisme Kerja Linker dan Loader

Pendahuluan: Arsitek Tak Terlihat di Balik Perangkat Lunak

Setiap hari para pengembang mengetikkan perintah seperti:

1
gcc -O2 main.c utils.c math.c -o myprogram

Proses ini sering kali secara keliru disebut sebagai sekadar “kompilasi”. Padahal kompilasi hanya menyelesaikan separuh perjalanan. Separuh sisanya—menyatukan puluhan file objek dan pustaka yang terpisah, menetapkan alamat memori virtual, dan memuatnya ke dalam RAM untuk dieksekusi CPU—dijalankan oleh Linker dan Loader.

  flowchart TD
    subgraph BuildPipeline ["Dari Kode Sumber Menuju Eksekusi"]
        SourceA["main.c"] -->|Kompilator| ObjA["main.o (Objek Terelokasi)"]
        SourceB["utils.c"] -->|Kompilator| ObjB["utils.o (Objek Terelokasi)"]
        Lib["libc.so / libm.a\n(Pustaka Sistem)"]

        ObjA --> Linker["★ Linker (ld)\n[Resolusi Simbol & Relokasi]"]
        ObjB --> Linker
        Lib --> Linker

        Linker --> Exec["myprogram (Biner ELF yang Dapat Dieksekusi)"]
    end

    Exec -->|execve| Loader["★ Loader (Kernel & ld-linux.so)\n[Pemetaan Memori & Penautan Dinamis]"]
    Loader --> CPU["Eksekusi CPU (_start -> main)"]

    style BuildPipeline fill:#f9f9f9,stroke:#333,stroke-width:2px
    style Linker fill:#ffe6e6,stroke:#d9534f,stroke-width:2px
    style Loader fill:#e6f3ff,stroke:#0066cc,stroke-width:2px

Tanpa linker, pemrograman modular tidak akan pernah ada; kita terpaksa menulis jutaan baris kode dalam satu file raksasa.


1. Anatomi File Objek ELF

File perantara (.o) adalah file objek terelokasi berformat ELF (Executable and Linkable Format):

  • .text: Instruksi kode mesin CPU.
  • .rodata: Data konstanta baca-saja dan teks literal.
  • .data: Variabel global dan statis yang telah diinisialisasi.
  • .bss: Variabel global tanpa inisialisasi. Tidak memakan ruang disk sama sekali! Memori dialokasikan dan dinolkan saat program dijalankan.
  • .symtab: Tabel simbol (daftar nama fungsi dan variabel).
  • .rela.text: Catatan relokasi berisi alamat sementara yang harus diperbaiki linker.

2. Resolusi Simbol (Symbol Resolution)

Linker mencocokkan setiap pemanggilan fungsi eksternal dengan definisi konkretnya.

Simbol Kuat vs Lemah:

  • Kuat: Definisi fungsi dan variabel global terinisialisasi (int x = 1;).
  • Lemah: Variabel global yang belum diinisialisasi (int x;).

Aturan Resolusi:

  1. Dua simbol kuat bernama sama akan menghasilkan error (multiple definition).
  2. Simbol kuat selalu mengalahkan simbol lemah.
  3. Di antara sesama simbol lemah, linker memilih secara acak (berpotensi merusak memori).

Urutan Pustaka Statis: Linker memindai argumen dari kiri ke kanan dalam satu lintasan. Oleh karena itu, opsi -lm wajib diletakkan setelah main.c.


3. Relokasi Alamat (Relocation)

Kompilator mengisi alamat nol sementara untuk fungsi eksternal:

1
callq 0x00000000  # Offset pura-pura
$$\text{Nilai} = S + A - P$$

($S$: alamat simbol, $A$: addend, $P$: posisi instruksi).


4. Penautan Dinamis: PIC, GOT, dan PLT

Pustaka bersama (Shared Libraries .so) diciptakan agar ribuan program tidak menduplikasi kode pustaka di memori. Teknologi ini menggunakan Position-Independent Code (PIC) dengan dua tabel:

  • GOT (Global Offset Table): Menyimpan alamat memori nyata.
  • PLT (Procedure Linkage Table): Tabel lompatan pada bagian kode.

Melalui Lazy Binding, linker dinamis (ld-linux.so) hanya mencari alamat fungsi pada saat fungsi tersebut pertama kali dipanggil.


5. Loader: Dari _start ke main()

Saat menjalankan ./myprogram, panggilan sistem execve memetakan segmen ELF ke memori virtual (mmap), menyiapkan tumpukan stack (argc, argv, envp), dan melompat ke titik masuk _start. Runtime C mengeksekusi konstruktor global dalam .init_array sebelum akhirnya memanggil fungsi main().

comments powered by Disqus