Pengembangan Kernel Bare Metal Raspberry Pi: Implementasi dan Debugging dengan QEMU pada Fedora Linux

Dalam pengembangan sistem operasi (OS Development), langkah pertama yang paling krusial bukanlah membuat antarmuka grafis, melainkan memastikan kita memiliki kendali penuh atas perangkat keras. Artikel ini mendokumentasikan proses pembuatan kernel bare metal sederhana untuk Raspberry Pi (BCM2835) yang berfungsi mengedipkan LED, serta bagaimana melakukan verifikasi eksekusi kode tersebut tanpa perangkat keras fisik menggunakan emulator QEMU dan GDB pada Fedora Linux.

1. Persiapan Lingkungan Pengembangan (Environment)

Pada sistem operasi Fedora Linux, paket toolchain untuk arsitektur ARM dan emulator QEMU dapat diinstal dengan mudah. Berbeda dengan distro lain, Fedora menyediakan GDB yang mendukung multi-architecture secara bawaan, sehingga kita tidak memerlukan paket khusus gdb-multiarch.

Perintah instalasi:

sudo dnf install arm-none-eabi-gcc arm-none-eabi-binutils arm-none-eabi-newlib qemu-system-arm gdb

2. Struktur Kode Sumber

Proyek ini terdiri dari empat berkas utama yang bekerja secara sinergis untuk menghasilkan image kernel.

A. main.c: Kernel Utama

Kode ini ditulis murni dalam C tanpa ketergantungan pada pustaka standar (<stdint.h> atau libc). Kita mengakses register GPIO secara langsung melalui Memory Mapped I/O untuk mengontrol Pin 16 (LED Status pada model Raspberry Pi lama).

/*
 * main.c - Kode Kernel Bare Metal Sederhana
 * Tujuan: Mengedipkan LED pada GPIO 16 (Active Low)
 */

void main() {
    /* Definisi Register GPIO (BCM2835 Base: 0x20000000 + Offset GPIO: 0x200000) */
    volatile unsigned int* GPFSEL1 = (volatile unsigned int*)0x20200004;
    volatile unsigned int* GPSET0  = (volatile unsigned int*)0x2020001C;
    volatile unsigned int* GPCLR0  = (volatile unsigned int*)0x20200028;

    /* Konfigurasi GPIO 16 sebagai OUTPUT
     * Membersihkan bit 18-20, lalu set bit 18 ke 1 (Biner 001)
     */
    *GPFSEL1 &= ~(7 << 18);
    *GPFSEL1 |= (1 << 18);

    /* Loop Utama */
    while (1) {
        /* Nyalakan LED (Active Low -> Tulis ke Register Clear) */
        *GPCLR0 = (1 << 16);

        /* Delay Manual */
        for (volatile int i = 0; i < 500000; i++);

        /* Matikan LED (Tulis ke Register Set) */
        *GPSET0 = (1 << 16);

        /* Delay Manual */
        for (volatile int i = 0; i < 500000; i++);
    }
}

B. boot.S: Assembly Startup

Kode assembly ini bertugas menyiapan Stack Pointer (SP) di alamat 0x8000 sebelum memanggil fungsi main di C.

Cuplikan kode

.section ".text.boot"
.global _start

_start:
    mov sp, #0x8000  /* Set Stack Pointer */
    bl main          /* Lompat ke kode C */

halt:
    b halt           /* Loop selamanya jika main kembali */

C. linker.ld: Skrip Linker

Skrip ini memastikan kode dimuat di alamat memori 0x8000, sesuai dengan spesifikasi bootloader Raspberry Pi.

Cuplikan kode

SECTIONS
{
    . = 0x8000;
    .text : { *(.text.boot) *(.text) }
    .data : { *(.data) }
    .bss  : { *(.bss) }
}

D. Makefile: Otomatisasi Kompilasi

Berkas ini mengatur proses assemble, compile, dan link. Perhatikan penggunaan flag -g untuk menyertakan simbol debug, yang sangat penting untuk analisis di GDB.

Makefile

# --- Toolchain ---
CC = arm-none-eabi-gcc
OBJCOPY = arm-none-eabi-objcopy

# --- Definisi Folder ---
SRC_DIR = source
BUILD_DIR = build

# --- Flags Kompilasi (Compile Time) ---
CFLAGS = -g -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles

# --- Flags Linker (Link Time) ---
# PERBAIKAN DISINI: Kita tambahkan -nostdlib dan -nostartfiles
LDFLAGS = -T linker.ld -nostdlib -nostartfiles

# --- Target Utama ---
all: kernel.img

# 1. Membuat Binary Image
kernel.img: $(BUILD_DIR)/kernel.elf
	$(OBJCOPY) $(BUILD_DIR)/kernel.elf -O binary kernel.img

# 2. Linker
$(BUILD_DIR)/kernel.elf: $(BUILD_DIR)/boot.o $(BUILD_DIR)/main.o
	$(CC) $(LDFLAGS) $(BUILD_DIR)/boot.o $(BUILD_DIR)/main.o -o $(BUILD_DIR)/kernel.elf

# 3. Compile Assembly
$(BUILD_DIR)/boot.o: $(SRC_DIR)/boot.S
	@mkdir -p $(BUILD_DIR)
	$(CC) $(CFLAGS) -c $(SRC_DIR)/boot.S -o $(BUILD_DIR)/boot.o

# 4. Compile C
$(BUILD_DIR)/main.o: $(SRC_DIR)/main.c
	@mkdir -p $(BUILD_DIR)
	$(CC) $(CFLAGS) -c $(SRC_DIR)/main.c -o $(BUILD_DIR)/main.o

# --- Bersih-bersih ---
clean:
	rm -rf $(BUILD_DIR) kernel.img

3. Proses Kompilasi dan Emulasi

Setelah semua berkas siap, jalankan perintah make untuk menghasilkan berkas kernel.elf dan kernel.img. Selanjutnya, kita jalankan kernel tersebut menggunakan QEMU dengan mode pause (-S) dan membuka port gdb server (-s).

qemu-system-arm -M raspi0 -kernel kernel.elf -nographic -S -s

Catatan: Parameter -M raspi0 digunakan karena memiliki arsitektur yang sama dengan Raspberry Pi 1 (BCM2835).

4. Analisis dan Verifikasi dengan GDB

Tahap ini membuktikan bahwa kode benar-benar berjalan di level register CPU. Kita menggunakan GDB untuk terhubung ke QEMU.

A. Koneksi dan Eksekusi Awal

Pada terminal terpisah, jalankan GDB:

gdb kernel.elf

Di dalam prompt GDB, hubungkan ke target dan pasang breakpoint:

Cuplikan kode

(gdb) target remote localhost:1234
(gdb) break main
(gdb) continue

Hasilnya nampak seperti ini

Reading symbols from kernel.elf...
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
_start () at source/boot.S:5
5	    mov sp, #0x8000  /* Set Stack Pointer */
(gdb) break main
Breakpoint 1 at 0x800c: file source/main.c, line 15.
(gdb) continue
Continuing.

Breakpoint 1, main () at source/main.c:15
15	    *GPFSEL1 &= ~(7 << 18);
(gdb) 

B. Inspeksi Instruksi Assembly

Gunakan mode visual untuk melihat instruksi mesin yang sedang dijalankan:

Cuplikan kode

(gdb) layout asm

Gunakan perintah ni (Next Instruction) untuk menelusuri kode baris demi baris hingga melewati instruksi str (Store Register) yang menulis ke alamat GPIO.

C. Verifikasi Memori (Bukti Keberhasilan)

Untuk membuktikan bahwa LED telah dinyalakan atau dimatikan, kita memeriksa alamat register GPIO Level 0 (0x20200034). Register ini menampung status tegangan pin saat ini.

Setelah instruksi “Matikan LED” dijalankan, kita memeriksa memori:

Cuplikan kode

(gdb) x /4x 0x20200034

Hasil yang diperoleh:

0x20200034: 0x00010000 0x00000000 0x00000000 0x00000000

Nilai 0x00010000 menunjukkan bahwa bit ke-16 bernilai 1 (HIGH). Ini mengonfirmasi bahwa CPU telah berhasil mengeksekusi logika program untuk mengontrol periferal perangkat keras secara akurat.


Kesimpulan

Melalui eksperimen ini, kita telah berhasil membangun fondasi sistem operasi dasar. Kita tidak hanya menulis kode, tetapi juga memvalidasi perilaku hardware virtual secara presisi menggunakan GDB. Langkah selanjutnya dalam pengembangan ini adalah implementasi driver UART untuk memungkinkan komunikasi serial (cetak teks) dari kernel ke terminal.