Oleh : Reza Ervani bin Asmanu
Dalam dunia pengembangan perangkat lunak modern, kita terbiasa bekerja di atas lapisan abstraksi yang tebal. Ketika kita menulis print("Hello World") di Python atau C, sistem operasi, driver grafis, dan ribuan baris kode di belakang layar bekerja keras untuk menampilkan teks tersebut ke layar.
Namun, bagaimana jika kita menghilangkan sistem operasi tersebut? Bagaimana jika kita ingin berbicara langsung dengan perangkat keras tanpa perantara? Inilah yang disebut dengan pemrograman Bare Metal.
Artikel ini akan memandu Anda membuat kode bootloader paling sederhana untuk arsitektur Intel x86. Kita tidak akan menggunakan fungsi bantuan BIOS, melainkan memanipulasi memori video (VRAM) secara langsung untuk menampilkan karakter di layar.
Persiapan Lingkungan Pengembangan
Sebelum memulai, kita memerlukan dua alat utama yang menjadi standar dalam pengembangan sistem operasi:
- NASM (The Netwide Assembler): Compiler untuk menerjemahkan kode bahasa rakitan (Assembly) menjadi kode mesin biner.
- QEMU: Emulator PC untuk menjalankan kode kita tanpa perlu melakukan restart komputer fisik berulang kali.
- Make: Utilitas untuk otomatisasi proses kompilasi.
Konsep Dasar: Sektor Boot (The Boot Sector)
Ketika komputer berbasis x86 (Intel/AMD) dinyalakan, prosesor memulainya dalam mode 16-bit yang disebut Real Mode. BIOS (Basic Input/Output System) akan memeriksa media penyimpanan (Harddisk/Flashdisk) dan mencari “Tanda Kehidupan” pada 512 byte pertama, yang dikenal sebagai Sector 0.
Agar kode kita dianggap sebagai program yang sah oleh BIOS, kode tersebut harus memenuhi syarat berikut:
- Ukurannya tidak boleh lebih dari 512 byte.
- Dua byte terakhir harus bernilai
0xAA55(dikenal sebagai Magic Number atau Boot Signature). - Kode akan dimuat oleh BIOS ke alamat memori
0x7C00.
Implementasi Kode (boot.asm)
Berikut adalah kode Assembly lengkap yang akan melakukan tugas sederhana: menulis huruf “Hi” berwarna-warni langsung ke memori video VGA.
Simpan kode ini dengan nama file boot.asm.
Cuplikan kode
; file: boot.asm
; Penulis: [Nama Anda]
; Deskripsi: Bootloader minimal x86 yang menulis langsung ke Video Memory
[bits 16] ; Memberitahu assembler bahwa kita bekerja di Real Mode (16-bit)
[org 0x7c00] ; Direktif memori: BIOS akan memuat kode ini di alamat 0x7C00
start:
; --- 1. Inisialisasi Segment Register ---
; Di x86 Real Mode, kita tidak bisa menulis langsung ke alamat fisik tinggi (0xB8000).
; Kita harus menggunakan Segment Register (ES).
; Rumus Alamat Fisik = (Segment * 16) + Offset
mov ax, 0xb800 ; Alamat dasar memori VGA Text Mode (dibagi 16)
mov es, ax ; Pindahkan nilai ke register ES (Extra Segment)
; --- 2. Manipulasi Memori Video ---
; Struktur Video Memory Text Mode:
; Byte Genap (0, 2, 4...) = Kode Karakter (ASCII)
; Byte Ganjil (1, 3, 5...) = Atribut Warna (Background | Foreground)
; Menulis Huruf 'H' (Posisi 0)
mov byte [es:0], 'H' ; Karakter 'H'
mov byte [es:1], 0x1F ; Warna: Latar Biru (1), Teks Putih (F)
; Menulis Huruf 'i' (Posisi 1)
mov byte [es:2], 'i' ; Karakter 'i'
mov byte [es:3], 0xC4 ; Warna: Latar Merah (C), Teks Merah (4)
; --- 3. Infinite Loop (Halt) ---
; Setelah tugas selesai, CPU harus "ditahan" agar tidak mengeksekusi
; memori sampah di baris berikutnya.
jmp $ ; Lompat ke baris ini sendiri selamanya (Hang)
; --- 4. Boot Signature ---
; Mengisi sisa ruang dari baris kode di atas sampai byte ke-510 dengan nol.
times 510-($-$$) db 0
; Menambahkan Magic Number wajib di dua byte terakhir.
dw 0xaa55
Penjelasan Teknis
Poin paling krusial dalam kode di atas adalah penggunaan alamat 0xB8000. Ini adalah alamat fisik standar untuk VGA Text Mode Buffer. Setiap karakter yang muncul di layar monitor pada mode teks tersimpan di alamat ini. Dengan menulis byte ke alamat tersebut, kita memintas sistem operasi dan langsung “menggambar” di monitor.
PENJELASAN KODE SECARA LENGKAP
1. [bits 16] — Mode “Zaman Dulu”
Ini adalah perintah untuk Assembler (penerjemah kode).
- Apa artinya? Prosesor komputer modern (seperti i7 atau Ryzen) itu canggih (64-bit). Tapi, demi alasan sejarah (agar komputer baru tetap bisa menjalankan program tahun 80-an), saat pertama kali dinyalakan, prosesor berpura-pura menjadi chip tua bernama Intel 8086.
- Kenapa penting? Dalam mode 16-bit ini, cara prosesor menghitung alamat memori berbeda dengan mode modern. Kalau kita tidak menulis baris ini, penerjemah akan salah mengartikan kode kita menjadi instruksi 32-bit yang “ngaco” dan komputer akan crash seketika.
2. [org 0x7c00] — Titik Kumpul Wajib
Ini juga perintah untuk Assembler, singkatan dari Origin (Asal).
- Masalahnya: Kode kita tidak tahu dia ada di mana dalam memori RAM yang luas.
- Kenyataannya: BIOS (program bawaan pabrik motherboard) punya aturan baku: “Cari bootloader di disk, lalu salin ke RAM tepat di alamat 0x7c00, lalu jalankan.”
- Penjelasan: Perintah ini memberitahu kode kita: “Hei, nanti saat kamu dijalankan, anggaplah posisi awalmu ada di alamat 0x7c00.”
- Efeknya: Jika dihapus, instruksi lompat (seperti memanggil fungsi atau loop) akan nyasar ke alamat yang salah karena salah hitung titik awal.
3. mov ax, 0xb800 & mov es, ax — Mengarahkan Mata ke Layar
Kita mulai masuk ke instruksi mesin.
- Tujuannya: Kita ingin menulis sesuatu ke Layar Monitor. Alamat memori untuk layar (Video Memory) ada di 0xb800.
- Masalah Hardware: Prosesor Intel memiliki aturan keras: “Kamu TIDAK BOLEH memasukkan angka langsung ke saku Segmen (ES, DS, SS).”
- Solusinya:
mov ax, 0xb800: Masukkan alamat layar ke saku umum (AX) dulu.mov es, ax: Baru pindahkan dari saku umum ke saku Extra Segment (ES).
- Hasilnya: Sekarang register
ESsudah menunjuk ke layar monitor.
4. xor di, di & mov cx, 2000 — Persiapan Pembersihan
Sebelum menulis, kita siapkan alat pel.
xor di, di(Reset Kursor):- Caranya: XOR adalah operasi logika. Jika angka di-XOR dengan dirinya sendiri, hasilnya pasti 0.
- Artinya: Kita membuat
DI(Destination Index/Jari Telunjuk) menjadi 0. Kita menunjuk Pojok Kiri Atas layar.
mov cx, 2000(Siapkan Hitungan):- Faktanya: Layar teks standar memiliki lebar 80 kotak dan tinggi 25 kotak. Total: $80 \times 25 = 2000$ kotak.
- Tujuannya: Kita isi register
CX(Counter) dengan 2000 agar nanti kita bisa mengulang perintah “hapus” sebanyak 2000 kali.
5. loop_clear: — Ritual Pembersihan Layar
Ini adalah blok pengulangan (loop) untuk membersihkan layar.
- Struktur Memori VGA: Satu kotak di layar butuh 2 byte.
- Byte 1: Hurufnya (ASCII).
- Byte 2: Warnanya (Attribute).
mov byte [es:di], ' ': Taruh karakter SPASI (kosong) di lokasi yang ditunjukES:DI.mov byte [es:di+1], 0x07: Taruh warna di sebelah karakter tadi (di+1). Kode0x07artinya Background Hitam (0), Teks Abu-abu (7).add di, 2: Geser jari telunjuk (DI) sebanyak 2 langkah. Kenapa 2? Karena kita baru saja mengisi 1 huruf + 1 warna. Kita harus melompati keduanya untuk ke kotak berikutnya.loop loop_clear: Perintah ajaib Intel.- Otomatis mengurangi
CXsebanyak 1. - Mengecek: “Apakah CX sudah 0?”
- Jika belum, dia lompat kembali ke atas (
loop_clear).
- Otomatis mengurangi
6. mov si, msg... & call print_string — Menyiapkan Naskah & Panggil Aktor
Disini kita mencetak 3 baris teks.
mov si, msg_judul:SI(Source Index) adalah saku untuk memegang alamat sumber data. Kita isi dengan alamat teks judul.mov di, ...:DI(Destination Index) adalah saku untuk menunjuk posisi di layar.- Baris 1:
mov di, 0(Awal memori). - Baris 2:
mov di, 160.- Kenapa 160? Karena 1 baris = 80 karakter. 1 karakter = 2 byte. Jadi 1 baris memakan $80 \times 2 = 160$ byte memori.
- Baris 3:
mov di, 320($160 + 160$).
- Baris 1:
mov ah, ...: Kita simpan kode warna diAHsebagai “oleh-oleh” yang akan dibawa saat memanggil fungsi cetak.call print_string: Perintah untuk melompat ke fungsiprint_stringdi bawah, jalankan kodenya, lalu kembali lagi ke sini (ret) setelah selesai.
7. jmp $ — Rem Tangan Darurat
- Apa artinya? Simbol
$artinya “alamat baris ini sendiri”. Jadijmp $artinya “Lompatlah ke tempat kamu berdiri sekarang”. - Kenapa penting? Prosesor itu bodoh. Kalau tidak dihentikan, dia akan terus lari ke bawah dan mencoba mengeksekusi data teks (
msg_judul) sebagai kode program. Itu akan membuat komputer hang atau mengeluarkan suara aneh. Ini menahan prosesor agar diam di tempat (Infinite Loop).
8. print_string: — Mesin Pencetak (Fungsi)
Ini adalah sub-program yang bekerja mencetak huruf satu per satu.
mov al, [si]: Ambil 1 huruf dari gudang data (SI), taruh diAL.or al, al/jz .done: Detektif Kode.- Cek apakah huruf yang diambil adalah angka 0? (Null Terminator).
- Jika Ya (
jz– Jump if Zero), lompat ke.done(Selesai).
mov [es:di], al: Salin huruf tadi ke layar (ES:DI).mov [es:di+1], ah: Salin warna (yang kita simpan diAHtadi) ke sebelah huruf.add di, 2: Geser posisi layar 2 langkah (huruf + warna).inc si: Geser posisi data string 1 langkah (karena data string cuma huruf berurutan, tidak ada selipan warna).jmp .next_char: Ulangi lagi untuk huruf berikutnya.ret: Return. Kembali ke baris pemanggil (call) di atas tadi.
9. db & times — Gudang Data & Tanda Tangan
Bagian paling bawah file, tempat penyimpanan data mentah.
db '...', 0: Define Byte. Menyimpan teks ASCII. Angka 0 di akhir adalah “Tanda Stop” agar fungsiprint_stringtahu kapan harus berhenti mencetak.times 510-($-$$) db 0: Padding (Pengganjal).- Aturan BIOS: Sebuah bootsector WAJIB berukuran persis 512 byte. Tidak boleh kurang, tidak boleh lebih.
- Logika: Hitung sisa ruang yang kosong, lalu isi semuanya dengan nol sampai ukurannya pas 510 byte.
dw 0xaa55: Tanda Tangan Ajaib (Magic Number).- Mengisi 2 byte terakhir (byte ke-511 dan 512).
- Fungsinya: BIOS akan mengecek 2 byte terakhir ini. Kalau isinya BUKAN
0xaa55, BIOS akan menganggap disk ini rusak/bukan bootable dan menolak menjalankannya.
Otomatisasi dengan Makefile
Untuk mempermudah proses kompilasi dan pengujian, kita menggunakan Makefile. Ini menghindarkan kita dari mengetik perintah panjang berulang kali.
Simpan kode ini dengan nama file Makefile (tanpa ekstensi):
Makefile
# Variabel Konfigurasi
ASM = nasm
QEMU = qemu-system-x86_64
SRC = boot.asm
BIN = boot.bin
# Target Utama
all: $(BIN)
# Kompilasi Assembly menjadi Binary Mentah (Raw Binary)
$(BIN): $(SRC)
$(ASM) -f bin $(SRC) -o $(BIN)
# Target untuk menjalankan emulator
run: $(BIN)
$(QEMU) $(BIN)
# Target untuk membersihkan file hasil kompilasi
clean:
rm -f $(BIN)
Cara Menjalankan
- Buka terminal pada direktori proyek Anda.
- Jalankan perintah berikut:Bash
make run - Jendela emulator QEMU akan muncul.
Analisis Hasil
Saat QEMU berjalan, Anda akan melihat layar hitam dengan tulisan “Hi” (warna-warni) di pojok kiri atas. Anda mungkin juga akan melihat sisa tulisan dari BIOS seperti “Booting from Hard Disk…”.
Mengapa demikian?
- “Hi” Muncul: Kode kita berhasil menulis ke alamat memori video
0xB8000. Huruf ‘H’ dan ‘i’ menimpa karakter apapun yang sebelumnya ada di posisi pertama dan kedua layar. - Layar Tidak Bersih: Karena kode kita sangat minimalis (hanya 512 byte), kita tidak menyertakan rutinitas untuk “membersihkan layar” (Clear Screen). Sisa teks adalah artefak dari BIOS yang berjalan sebelum kode kita dimuat.
- Sistem Berhenti (Hang): Komputer tidak me-restart atau mati, tetapi diam selamanya. Ini adalah efek dari instruksi
jmp $yang kita buat untuk mencegah CPU mengeksekusi instruksi tak dikenal di luar kode kita.
Kesimpulan
Selamat! Anda baru saja membuat sebuah program yang berjalan langsung di atas perangkat keras (Bare Metal) tanpa bantuan sistem operasi apapun.
Kode sederhana ini adalah fondasi dari semua sistem operasi x86, mulai dari MS-DOS kuno hingga Windows dan Linux modern. Langkah selanjutnya dalam perjalanan ini adalah mempelajari bagaimana beralih dari Real Mode (16-bit) yang terbatas menuju Protected Mode (32-bit) untuk mengakses memori yang lebih besar dan fitur prosesor modern.
