Bedah Linker Script (linker.ld): Arsitek Tata Letak Memori Sistem Operasi

Oleh: Reza Ervani bin Asmanu

Dalam proses kompilasi sebuah program, kita sering mendengar istilah Compiler. Namun, ada pahlawan tak terlihat yang bekerja di tahap akhir bernama Linker. Jika Compiler bertugas menerjemahkan bahasa manusia (C/Assembly) menjadi bahasa mesin per satuan berkas, maka Linker bertugas menyatukan potongan-potongan kode tersebut menjadi satu kesatuan program yang utuh.

Pada pengembangan aplikasi biasa (misalnya aplikasi desktop di atas Windows atau Linux), Linker bekerja secara otomatis dengan aturan standar yang disediakan oleh OS. Namun, dalam pengembangan bare metal OS, aturan standar tersebut tidak berlaku. Kita tidak memiliki OS yang mengatur memori; kitalah OS-nya.

Di sinilah peran fail linker.ld (Linker Script). Ia adalah “cetak biru” atau “denah lantai” yang memberi tahu Linker bagaimana menyusun setiap byte kode kita di dalam memori fisik Raspberry Pi.

1. Mengapa Kita Membutuhkannya?

Saat kita menjalankan perintah make, proses yang terjadi adalah:

  1. boot.S dikompilasi menjadi boot.o
  2. main.c dikompilasi menjadi main.o

Berkas .o (Object files) ini berisi kode mesin, tetapi alamat memorinya belum “matang” (relocatable). Mereka belum tahu di mana mereka akan tinggal di RAM.

Tanpa skrip linker khusus, Linker bawaan (ld) mungkin akan meletakkan kode di alamat 0x0000 atau alamat acak lainnya. Ini fatal bagi Raspberry Pi, karena:

  1. Alamat 0x00000x0100 direservasi untuk Exception Vector Table (penanganan error CPU).
  2. Bootloader Raspberry Pi (GPU) secara keras (hardcoded) akan memuat kernel kita dan melempar eksekusi ke alamat 0x8000.

Oleh karena itu, kita wajib menulis linker.ld untuk memaksa kode kita duduk manis dimulai dari alamat 0x8000.

2. Bedah Sintaks linker.ld

Mari kita analisis skrip yang kita gunakan baris demi baris.

A. Pintu Masuk (ENTRY)

Cuplikan kode

ENTRY(_start)

Perintah ini memberitahu Linker: “Jika ada yang bertanya di mana program ini dimulai, jawablah di simbol _start.”

Ini merujuk pada label _start yang telah kita buat “global” di dalam fail boot.S. Informasi ini penting agar header fail ELF (format output sebelum jadi .img) memiliki metadata yang benar.

B. Definisi Bagian (SECTIONS)

Ini adalah blok utama. Di sinilah kita menggambar peta memori.

Cuplikan kode

SECTIONS
{
    /* 1. Inisialisasi Location Counter */
    . = 0x8000;

Tanda titik (.) di sini disebut Location Counter. Bayangkan ini sebagai kursor atau penunjuk.

  • Perintah . = 0x8000; berarti: “Setel kursor penulisan memori sekarang di alamat 0x8000.”
  • Apapun yang kita tulis setelah baris ini akan diletakkan mulai dari alamat 8000, 8001, 8002, dan seterusnya.

C. Bagian Kode (.text)

Cuplikan kode

    .text : {
        KEEP(*(.text.boot))
        *(.text)
    }

Blok ini mengatur instruksi program (kode yang dieksekusi).

  1. KEEP(*(.text.boot)): Ini adalah instruksi paling krusial.
    • Kita mengambil semua section bernama .text.boot (yang ada di boot.S).
    • Kita menaruhnya paling depan.
    • Perintah KEEP memastikan Linker tidak membuang kode ini meskipun Linker merasa kode ini tidak pernah dipanggil oleh siapapun (karena memang dipanggil oleh GPU, bukan oleh kode lain).
    • Hasilnya: Instruksi pertama di boot.S dijamin berada tepat di alamat 0x8000.
  2. *(.text): Setelah .text.boot terpasang, barulah masukkan seluruh section .text dari fail-fail lain (seperti kode dari main.c). Tanda bintang * adalah wildcard yang berarti “dari semua file object (.o)”.

D. Bagian Data (.data dan .bss)

Cuplikan kode

    .data : {
        *(.data)
    }

Section .data menyimpan variabel global yang memiliki nilai awal. Contoh: int skor = 100;. Linker akan menempatkan nilai 100 ini tepat setelah kode program selesai.

Cuplikan kode

    .bss : {
        __bss_start = .;
        *(.bss)
        __bss_end = .;
    }

Section .bss (Block Started by Symbol) menyimpan variabel global yang belum diinisialisasi. Contoh: int buffer[1024];.

  • Variabel ini tidak memakan tempat di fail kernel.img (untuk menghemat ukuran file), tetapi mereka memakan tempat di RAM saat program jalan.
  • Simbol __bss_start dan __bss_end kita buat di sini agar nanti kita bisa menulis kode C/Assembly untuk membersihkan (meng-nol-kan) area memori ini saat booting (walaupun di tutorial awal LED Blink, kita belum menggunakannya).

E. Pembersihan (/DISCARD/)

Cuplikan kode

    /DISCARD/ : {
        *(.comment)
        *(.gnu*)
        *(.note*)
        *(.eh_frame*)
    }

Compiler GCC seringkali menyertakan metadata tambahan (“sampah”) yang berguna untuk debugging di OS Linux, tetapi tidak berguna untuk bare metal. Bagian ini memerintahkan Linker untuk membuang semua itu agar fail kernel.img kita bersih dan berukuran kecil.

Kesimpulan

Fail linker.ld adalah jembatan yang menghubungkan kode abstrak kita dengan realitas fisik perangkat keras.

  1. Ia menetapkan Titik Awal (0x8000) sesuai kemauan Bootloader GPU.
  2. Ia menyusun Urutan agar kode inisialisasi (boot.S) dijalankan sebelum logika utama (main.c).
  3. Ia mengelola Data agar variabel kita menempati alamat RAM yang valid.

Tanpa skrip ini, prosesor Raspberry Pi akan tersesat karena mencoba mengeksekusi data yang salah di alamat yang salah.