Menulis Bootloader BIOS untuk Mode 64-bit
(thasso.xyz)- Menyusun alur bootloader minimal langkah demi langkah, dimulai dari boot sector 512 byte untuk menaikkan CPU x86_64 dari real mode 16-bit ke long mode 64-bit
- Memastikan output terlebih dahulu lewat interrupt BIOS, lalu menghubungkan
nasm,ld,objcopy, dan QEMU untuk memverifikasi apakah boot image benar-benar berjalan - Karena keterbatasan kapasitas boot sector, proses dibagi menjadi stage 1/stage 2, dan sebelum masuk ke protected mode, kode berikutnya dibaca dari disk dengan BIOS
int 0x13 - Mulai dari protected mode 32-bit, routine BIOS tidak bisa dipakai, sehingga diperlukan inisialisasi hardware seperti GDT, flat segmentation, dan output langsung ke buffer VGA
- Untuk masuk ke long mode 64-bit, page table, PAE,
EFER.LME,cr0.PG, dan GDT untuk 64-bit semuanya harus disetel dengan benar; setelah itu kode C freestanding dapat dipanggil seperti kernel
Lingkungan 16-bit yang dimulai dari BIOS
- Setelah reset, CPU x86 berada dalam real mode, dan ukuran operand default adalah 16-bit
- Real mode membentuk ruang alamat 20-bit dengan segmentation, dan dapat menangani memori hingga 1MB
- Kode pertama yang dijalankan BIOS berada di boot sector pada disk
- BIOS mencari disk yang sector pertamanya berakhir dengan magic number
0xaa55 - Sector tersebut dimuat ke alamat memori
0x7c00
- BIOS mencari disk yang sector pertamanya berakhir dengan magic number
- Ruang yang diberikan BIOS hanya 512 byte, sehingga kode ini berfokus pada peran bootstrap untuk memuat sisa bootloader
- Routine BIOS hanya dapat digunakan selama tetap berada di real mode
Perlengkapan dan lingkungan build
- Untuk mengikuti langkahnya, diperlukan Intel 64 and IA-32 Architectures Software Developer’s Manual, assembler, dan QEMU
- Assembler contoh yang digunakan adalah nasm, dengan asumsi pembaca memahami x86 assembly dan sintaks nasm
- Meski tidak memiliki CPU x86_64, program dapat dijalankan dengan emulasi CPU x86 di QEMU
- Untuk materi dasar hingga mode 32-bit, rujuk Writing a Simple Operating System — from Scratch
Membuat boot sector dan memastikan eksekusinya
- Boot sector pertama mencetak
"Hello, world!"dengan routine BIOS, lalu berhenti denganhltdan loop - Untuk mencetak string, digunakan pemanggilan BIOS video services
int 0x10denganah = 0x0e Makefilemembuat object dengan nasm, menautkannya dengan linker script, lalu menghasilkan raw boot image melaluiobjcopy -O binarymake bootmenjalankan image dengan QEMUqemu-system-x86_64 -no-reboot -drive file=$<,format=raw,index=0,media=disk
- Linker script diatur agar boot sector ditempatkan dengan basis
0x7c00- Origin
boot_sectorpadaMEMORYadalah0x7c00, dengan length512 - Section
.bootsignmenambahkan0x55,0xaapada posisi0x7c00 + 510
- Origin
- Offset dan magic number juga bisa ditangani langsung di dalam assembly boot sector, tetapi di sini linker script mengambil peran tersebut
Stage 1 membaca stage 2 dari disk
- Stage 1 adalah kode boot sector yang dimuat BIOS, dan tujuannya adalah memuat stage 2 ke memori
- Stage 2 berisi kode untuk berpindah dari real mode 16-bit ke protected mode 32-bit
- Setelah masuk ke protected mode, routine BIOS tidak bisa digunakan, sehingga pembacaan sector disk harus selesai sebelum transisi
- Akses disk menggunakan BIOS disk services
int 0x13ah = 0x42adalah fungsi BIOS extended readdl = 0x80adalah nomor drive- Disk address packet berisi jumlah sector yang akan dibaca, alamat tujuan, dan sector awal
- Contoh membaca 64 sector dengan
READ_SECTORS_NUM equ 64- Karena boot sector adalah sector 0, stage 2 dibaca mulai dari sector 1
- Alamat tujuannya adalah
BOOT_LOAD_ADDR + SECTOR_SIZE, yaitu0x7c00 + 512
- Di dalam kode masih tersisa penanganan sementara yang menerima kasus ketika sector yang terbaca lebih sedikit dari yang diminta
- Pada awalnya, stage 2 menyalin
print_stringuntuk real mode, mencetak"Hello from stage 2", dan memastikan lompatan dari stage 1 tersambung dengan benar
Beralih ke protected mode 32-bit
- Untuk masuk ke protected mode, pertama-tama harus didefinisikan Global Descriptor Table(GDT)
- Dalam protected mode, segmentation pada dasarnya digunakan untuk proteksi memori
- Pada long mode 64-bit diperlukan paging, tetapi untuk tahap sebelumnya, yaitu masuk ke protected mode, konfigurasi segmentation harus dilakukan lebih dahulu
- Contoh GDT mengikuti flat model dari manual Intel
- Memiliki code segment dan data segment
- Kedua segment dipetakan ke seluruh linear address space
- Karena ini hanya tahap perantara menuju long mode, digunakan model yang paling sederhana
- GDT adalah struktur kontinu yang ditempatkan di memori
- Di awal terdapat null descriptor untuk menangkap invalid translation
- Setelah itu diikuti code segment descriptor dan data segment descriptor
- Transisi dilakukan dengan urutan berikut
- Menonaktifkan interrupt dengan
cli - Memuat alamat dan panjang GDT ke GDTR dengan
lgdt [gdt32_pseudo_descriptor] - Mengaktifkan protected mode dengan menyetel
cr0.PE, yaitu bit 0 padacr0 - Mengosongkan instruction pipeline dan memperbarui
cske code segment baru melalui far jump
- Menonaktifkan interrupt dengan
- Setelah masuk ke protected mode, nilai segment lama tidak lagi bermakna, sehingga
ds,ss,es,fs,gsdisetel ke data segment selector baru - Untuk mengaktifkan kembali interrupt setelah semua konfigurasi, diperlukan pekerjaan tambahan
Mencetak ke layar tanpa BIOS
- Dalam protected mode, routine BIOS tidak lagi bisa dipanggil
- Output string berubah menjadi metode menulis langsung ke VGA buffer
print_string32menuliskan karakter dan byte warna ke alamat0xb8000- Nilai warnanya adalah
0xf - Setiap cell karakter memakai 2 byte
- Nilai warnanya adalah
- Fungsi output ini sangat sederhana, sehingga pesan selalu ditampilkan di kiri atas layar
Page table untuk masuk ke long mode
- IA-32e mode dalam dokumentasi Intel setara dengan long mode pada manual AMD64
- Untuk beralih ke long mode, CPU harus berada dalam protected mode dan paging juga harus diaktifkan
- Konsep paging merujuk pada Introduction to Paging dan OSTEP
- Pada long mode dengan PAE aktif, digunakan page table 4 tingkat
build_page_tablemembuat page table 4 tingkat di alamat yang ditentukan- Ukuran page adalah
0x1000 - Ukuran setiap page table adalah
0x1000 - Jumlah entry adalah
512 - Pertama, empat table diinisialisasi ke 0 sehingga semua entry berada dalam status not present
- Entry pertama PML4 → PDP → PD → page table dihubungkan
- Pada layer page table paling bawah, 512 entry disetel
- Ukuran page adalah
GDT untuk 64-bit dan urutan transisi ke long mode
- Meski paging menangani virtual address space dan manajemen hak akses, GDT tetap diperlukan dalam long mode
- GDT untuk 64-bit juga mengikuti flat model, dan hampir sama dengan GDT untuk protected mode
- Perbedaannya ada pada penyetelan bit yang terkait long mode
- Menyetel flag 64-bit code segment pada code segment
- Jika flag ini disetel, default operation size bit harus bernilai 0
- Transisi ke long mode berlangsung dengan alur berikut
- Membuat page table 4 tingkat di alamat
0x1000 - Memasukkan alamat PML4 table ke
cr3 - Mengaktifkan PAE dengan menyetel bit 5 pada
cr4 - Membaca MSR
0xc0000080dan menyetelEFER.LME, yaitu bit 8 - Mengaktifkan paging dengan menyetel flag
PG, yaitu bit 31 padacr0 - Memuat GDT untuk 64-bit dengan
lgdt - Masuk ke mode 64-bit dengan far jump ke 64-bit code segment
- Membuat page table 4 tingkat di alamat
- Tepat setelah paging diaktifkan, statusnya adalah IA-32e compatibility mode, lalu CPU beralih ke mode 64-bit saat melompat ke GDT dengan flag segment 64-bit yang sudah disetel
- Pesan keberhasilan ditampilkan di kiri atas layar melalui VGA buffer
Memanggil kode C freestanding
- Setelah masuk hingga long mode 64-bit, kode C freestanding dapat dipanggil
kernel.cmenghapus VGA buffer0xb8000dan mencetak"Hello from C"start_long_modedi sisi assembly mencetak string untuk 64-bit, lalu mendeklarasikanextern _start_kerneldan memanggil_start_kernel- Linker script membagi area memori menjadi tiga bagian
boot_sector:0x7c00, length512stage2:0x7e00, length512kernel:0x8000, length0x10000
- Section
.text,.data,.rodata,.bssditempatkan di area kernel Makefilediubah agar membuild assembly dan C sekaligus- C compiler yang digunakan adalah
gcc - CFLAGS utama adalah
-std=c99 -ffreestanding -m64 -mno-red-zone -fno-builtin -nostdinc -Wall -Wextra
- C compiler yang digunakan adalah
- Seluruh kode contoh tersedia melalui tautan unduhan
1 komentar
Komentar Hacker News
Bisa masuk langsung ke long mode dengan kode yang jauh lebih sedikit tanpa melalui protected mode: https://wiki.osdev.org/Entering_Long_Mode_Directly
Pernah ada bootloader untuk kernel 64-bit kecil yang dibuat dengan cara ini, dan bahkan dengan kode untuk membaca kernel dari disk serta mengatur mode VESA, semuanya masih muat longgar di boot sector. Tidak perlu loader tahap kedua.
Untuk memakai hard disk modern sungguhan, lebih baik melihat GPT daripada MBR. GPT bisa menangani disk besar di atas 2TB tanpa terbentur batas tabel partisi. UEFI menghilangkan masalah seperti ini dan memungkinkan tata letak disk yang layak tanpa banyak kesulitan.
Untuk masuk ke mode 64-bit, protected mode tidak diperlukan. Namun sebaiknya jangan pakai BIOS. Itu berantakan dan hanya membuat pekerjaan lebih merepotkan.
Lebih baik memakai UEFI lewat EDK2 atau GnuEFI; keduanya cukup mudah dan nyaman diimplementasikan. Memang perlu sedikit waktu untuk terbiasa dengan konsep awal UEFI, tetapi dengan melihat proyek contoh di GitHub, strukturnya mudah dipahami. EDK agak kurang enak dengan file .dec dan .inf, sedangkan GnuEFI mengharuskan kita membaca file header untuk menemukan fungsi, tetapi tetap jauh lebih baik daripada antarmuka BIOS yang spesifikasinya tidak jelas. Pada hardware nyata, kita bahkan tidak bisa berasumsi bahwa int 0x10, int 0x15, dan semacamnya tersedia dengan benar.
Pada sistem UEFI, kita bisa mengasumsikan fondasi minimum yang stabil, dan fitur hardware maupun platform juga bisa dienumerasi secara sane. Selain itu, UEFI sudah cukup banyak mengonfigurasi platform, sehingga komponen OS loader tidak perlu melakukan banyak inisialisasi; cukup langsung memuat OS, driver, dan komponen yang sesuai dengan desain kernel. Ambil peta memori, akses sistem berkas EFI, lalu baca hal-hal yang diperlukan.
80286 memiliki Machine Status Word (MSW), sebuah register 16-bit, dan 80386 memperluasnya menjadi register 32-bit CR0. Kemudian long mode 64-bit menambahkan EFER MSR dan memperluas CR0 menjadi 64-bit, tetapi sampai hari ini hanya 11 bit yang dipakai di CR0 dan EFER pun hanya punya 8 bit aktif.
Saya penasaran kenapa Intel/AMD sampai dua kali memilih mekanisme baru, alih-alih memakai bit kosong di register yang sudah ada: https://wiki.osdev.org/CPU_Registers_x86-64#CR0
Alasan mengapa CR8 muncul sementara CR1 dan CR5–CR7 masih dicadangkan juga tampaknya mirip.
Hal yang terlihat paling tidak perlu rumit dalam tulisan ini adalah Makefile dan skrip linker. NASM mendukung keluaran biner datar, tetapi sepertinya mereka menganggap memakai itu terlalu “hacky”.
Dulu di OS saya ada file bernama make.sh untuk mengejek hal ini. Sekarang saya memakai hal-hal keren seperti ‘format file’, jadi -fbin dan --oformat=binary tinggal kenangan singkat. Saya lama mencoba memisahkan file C data dan file C kode, men-dump-nya menjadi biner, lalu merakit monster dari sana, tetapi linking dan loading menjadi terlalu sulit. Lebih baik pakai ELF atau PE saja, dan memang sepertinya format-format itu dibuat untuk melakukan hal semacam itu.
Terlihat keren dan seperti latihan yang bagus, tetapi saya tidak tahu apakah ini berguna. Saya penasaran apakah ada UX seperti mainan Fisher-Price yang memungkinkan memeriksa atau mengubah konfigurasi saat berjalan.
Booting adalah proses dari mode mini-me, single-user mode, recovery mode, menuju keadaan lepas landas.
Saya sudah memakai Unix bersama produk Microsoft sejak era Xenix/DOS, kira-kira sudah 40 tahun. Saya bertanya-tanya sudah sejauh apa kemajuannya.
Linux juga saya pakai sejak versi Swedia-nya, yaitu rilis pertama, dan saya juga pernah mencoba GNU 0.1.
Saya minta maaf karena menyebut Xenix sebagai Unix. Xenix adalah produk me-too yang kacau, yang sejak segera setelah rilis hingga masa kemundurannya sudah ingin menjadi barang masa lalu.
Microsoft rasanya bukan perusahaan yang merilis produk, melainkan perusahaan yang mengosongkan kotak pasir kucing ke atas pelanggannya. Contoh terbaru adalah Copilot dan 22H2.
Kalau melihat bagaimana mobil F1, pensil, dan kalkulator saku berkembang, saya bertanya-tanya seberapa dekat kita dengan bentuk ideal yang bisa dipakai.
Saya juga bertanya-tanya kenapa bootloader bukan static kernel mode. Dulu memang begitu, dan belum lama ini ada seseorang yang mengusulkan agar kembali begitu; saya pun setuju.
https://wiki.osdev.org/A20_Line
Terkejut karena semua langkah yang diperlukan untuk mengalihkan CPU ke mode yang benar tampak sama sekali tidak perlu. Sebagian besar terlihat seperti prosedur yang diperlukan karena kompatibilitas mundur
Saya penasaran apakah Intel tidak bisa menyediakan flag atau instruksi untuk memulai langsung dalam mode yang benar sejak awal, atau apakah mereka tidak bisa menghapus kompatibilitas mundur
Seingat saya ARM64 juga punya sebagian masalah serupa. Saya penasaran apakah ada CPU yang sejak awal dirancang sebagai 64-bit sehingga tidak memerlukan kompatibilitas mundur dan secara default masuk ke keadaan yang diinginkan. Mungkin itukah tujuan atau desain Itanium
Itanium, alias Itanic, juga begitu
Kompatibilitas mundur justru alasan utama memilih x86, bukan ARM, MIPS, RISC-V, dan sebagainya. Sayangnya, sebagian orang di Intel dan AMD tampaknya tidak begitu memahami hal ini
Proyek yang bagus. Kalau para pendukung UEFI di sini heran mengapa perlu repot-repot membuat cara bootloader baru, sepertinya mereka melewatkan alasan orang melakukan pekerjaan seperti ini
Seperti yang ditulis penulis di bagian akhir, “kalau Anda mengikuti sampai sini, keren”—itu memang benar-benar keren
Rasanya UEFI sudah ada cukup lama. Sayang sekali BIOS tidak ikut dibuang bersamaan dengan long mode
Direncanakan untuk ditinggalkan bukan berarti sudah dihapus, melainkan tidak lagi diperbarui atau dikembangkan dengan tujuan akhirnya dihapus
Saya penasaran apakah prosedur boot ini juga bekerja di EFI/UEFI. Jika bekerja, saya juga penasaran apakah supervisor UEFI mengemulasikan perpindahan real mode, protected mode, dan long mode, atau melakukannya di perangkat keras sungguhan
Saya menulis proses membuat bootloader UEFI untuk OS hobi di sini: https://0xc0ffee.netlify.app/osdev/05-bootloader-p1.html
Saya penasaran apakah ini lebih sederhana di ARM