Minggu, 25 Desember 2016

Episode Membuat Sistem Operasi: Mengistall Bootloader Hingga Mencetak Hello World (C dan Assembly + Video)

 

Tulisan ini tidak akan membahas banyak hal, dan tidak pula menjelaskan setiap poinnya dengan panjang lebar. Namun, agar terlihat menarik, saya sengaja menggunakan judul sepanjang itu. hehehe

Tutorial ini dapat diikuti dengan 2 cara; lihat video berikut dahulu, atau baca penjelasan yang diberikan di bawah, sebelum melihat video. #gak_penting


Persiapan
Pertama-tama, kita siapkan virtual disk dan memformatnya dengan filesystem FAT.

#dd if=/dev/zero of=disk.img bs=512 count=20000
#mkdosfs disk.img

Device /dev/zero adalah device yang akan selalu mengembalikan data 0 ketika dibaca. Kita menggunakan argumen input file (if) berupa device ini agar, virtual disk yang baru berisi data yang benar-benar kosong. Lalu output file (of) akan saya namakan sebagai disk.img. Besar virtual disk yang saya inginkan adalah 20000 sektor, yang tiap sektornya terdapat 512 byte. Kedua parameter ini di atur dengan block size (bs) dan count.

Perintah mkdosfs selanjutnya berguna untuk memformat virtual disk dengan filesystem FAT.

Instalasi GRUB 2
Alasan saya memilih GRUB, karena menurut saya GRUB adalah yang terbaik di antara bootloader lain. Bootloader lain umumnya hanya fokus pada dukungan sistem operasi linux, tidak untuk hobyist OS developer seperti kita. Meski begitu, GRUB 2 belum didukung platform Windows, jadi tutorial ini dikhususkan bagi anda yang memahami Linux. Jangan patah semangat, kalau memang tertarik dengan pengembangan OS, coba dual boot Windows + Ubuntu. [RECOMENDED]

losetup /dev/loopX disk.img
(Sekarang, mount  /dev/loopX di suatu tempat)
grub-install --force --root-directory=<lokasi_mount> /dev/nama_device

Pertama, kita harus memasang virtual disk yang baru dibuat ke loop device, yaitu dengan losetup. Gunakan format seperti diatas dengan mengubah X menjadi 0, 1, atau 2. Setelah dipasang, virtual disk akan bekerja sepeti block device nyata yang dapat diakses sebagai /dev/loopX.

Sekarang, mount loop device ke suatu tempat. Biar mudah, kita juga bisa mount lewat file manager langsung.

GRUB menyediakan grub-install sebagai program pembantu proses instalasi. Instalasi dapat dilakukan di block device manapun yang berproperti read write. Hampir semua filesystem didukung oleh GRUB, namun ada kalanya suatu hal membuat GRUB sulit bekerja pada filesystem tersebut. Oleh karena itu, tidak ada salahnya kita sedikit menyulitkan GRUB dengan --force. Hasilnya memang terlihat beberapa warning, tapi jangan kawatir, aman, kok. Tentukan lokasi mount dengan lokasi mount loop device. Lalu, apa nama device? tentu saja /dev/loopX..

Buat konfigurasi GRUB
 Metode loading kernel yang akan dilakukan adalah berdasarkan spesifikasi multiboot 2. Jadi kita perlu meload modul multiboot2, dari konfigurasi berikut.

set timeout=30
set default=0
menuentry 'Hello world' {
insmod multiboot2
multiboot2 /System/hello
}


Simpan pada device yang telah di mount sebagai /boot/grub/grub.cfg.

Buat project
Ada 3 macam file yang harus kita buat.
ldscript.txt
File ini berisi linker script, berguna untuk mengatur ulang section dari object files, sehingga sesuaii dengan keinginan kita.

ENTRY(multiboot_entry)
SECTIONS
{
  .text 0x1000000 :
  {
    text = .;
    /*header multiboot harus berada di awal section*/
    ./Release/multiboot2.s.o(*)
    *(.text)
  }
  .data :
  {
     data = .;
     *(.data)
     *(.rodata*)
  }
  .bss :
  {
    bss = .;
    *(.bss)
  }
  /DISCARD/ :
  {
    *(*)
  }
  end = .;
}  



Setiap mengompile file, dihasilkan sebuah object file. Dalam object file terbagi beberapa macam bagian (section), section umum yang akan selalu dihasilkan adalah .text (berisi instruksi komputer), .data (berisi data program) dan .bss (berisi data program yang memiliki nilai awal 0). Data yang berupa teks yang tidak pernah diubah selma program berjalan biasanya disimpan pada .rodata(read-only data).

int myvar = 0; --> .data

int myvar; sama seperti
int myvar = 0; --> .bss

Untuk melihat section pada objek, gunakan perintah berikut:

objdump -x namafileobjek.o

Program yang dihasilkan dengan linker script ini akan dieksekusi pada alamat memori 0x1000000. Prosedur yang dieksekusi pertama adalah multiboot_entry().

multiboot.s
.intel_syntax noprefix
.extern KMain
.global multiboot_entry

.text
    .align 8
    multiboot_header_start:
        .long 0xE85250D6
        .long 0
        .long multiboot_header_end - multiboot_header_start
        .long -(0xE85250D6+0+(multiboot_header_end - multiboot_header_start))
        .align 8
        address_tag:
            .short    2
            .short    0
            .long    24
            .long    multiboot_header_start
            .long    text
            .long    bss
            .long    end
        .align 8
        entry_tag:
            .short    3
            .short    0
            .long    12
            .long    multiboot_entry
            .align 8

/*    framebuffer_tag:
            .short    5
            .short    0
            .long    20
            .long    640
            .long    480
            .long    32
        .align 8
*/       
        end_tag:
            .short    0
            .short    0
            .long    8
        .align 8
    multiboot_header_end:
   
    multiboot_entry:
        push ebx
        call KMain
        jmp $


Header multiboot harus berada di bagian paling awal dari file yang diload bootloader. Lokasinya juga harus dalam alignment 8 byte. Menurut spesifikasi, bootloader akan mencari header di beberapa byte pertama dari file. Jika ditemukan nilai indikator magic yang tepat, bootloader harus melanjutkan loading file tersebut. Selengkapnya mengenai konfigurasi Multiboot, kunjungi grup Programmer OS Indonesia, atau baca dokumentasi Multiboot v 2.

Saat multiboot_entry() terpanggil, ia akan memnggi fungsi KMain(), di fungsi tersebutlah kita memulai pekerjaan kita. Saat bootloader selesai, ia mengisi EBX dengan alamat multiboot tag. Kita dapat menemukan banyak informasi dari multiboot tag ini. Tapi, sekarang bukan saat yang tepat untuk mempelajarinya.

kmain.c
Untuk menghasilkan output, kita dapat menulis karakter di memori 0xB8000. Setiap 2 byte, dimulai dari alamat tersebut merepresentasikan sebuah karakter yang akan dicetak mulai dari pojok kiri atas. 1 byte pertama adalah karakter dan 1 byte lagi adalah atribut warna karakter. Saya juga sudah menulis penjelasan lebih lengkap di Programmer OS Indonesia.

//Biar tambah keren
static char *vidmem = 0xB8000;
void print(char *str){
    while(*str != 0){
        *vidmem = *str;
        vidmem++;
        *vidmem = 0x0F; //attribute
        vidmem++;
        str++;
    }
}

int KMain(void *arg)
{
    print("Hello world!");
}


Build
Untuk source code C, kompilasi dilakukan dengan perintah:
gcc -c -masm=intel -m32 -nostdinc -fnostack-protector -ffreestanding

-masm=intel digunakan agar kita dapat menggunakan syntax assembly intel saat menggunakan inline assembly di C.
-m32 untuk mnghasilkan kode instuksi 32-bit.
-nostdinc untuk melarang kompiler menginclude standard header otomatis.
-fnostack-protector agar compiler tidak melakukan optimisasi saat akses stack.
-ffreestanding untuk memastikan agar compiler tidak menginput kode tambahan lain, terutama kode yang berkaitan dengan host OS.

Untuk kode assembly:
as --32 -msyntax=intel

Maksudnya, argumen tersebut mengatakan bahwa kita ingin mengompile source yang ditulis dalam syntax intel ke dalam objek yang berformat instruksi 32-bit.

Untuk building file object:
gcc -nostdlib -T ldscript.txt

-nostdlib mencegah linker memadukan program dengan library system operasi host. Jangan lupa, kita haru membuat output agar seuai linker script dengan -T.

Run
Build seluruh source code, taruh hasilnya pada virtual disk sebagai /System/hello.
Unmount virtual disk, lalu jalankan dengan qemu-system-i386 disk,img atau qemu-system-i386 /dev/loop0.

http://www.facebook.com/groups/programmer.os.indonesia

Load disqus comments

0 comments