x86 bootloaderから簡単なOSカーネルを動かしてみる
「x86 bootloaderでHello Worldを書いてみる」では、リアルモード(16ビット)で動作する簡単なbootloaderを書いてみた。 ここでは、CPUの動作モードをプロテクテッドモード(32ビット)に切り替え、C言語コードからコンパイルした簡単なOSカーネルを動作させるbootloaderを書いてみる。
環境
Ubuntu 14.04.2 LTS 64bit版、VirtualBox 4.3.28
$ uname -a Linux vm-ubuntu64 3.13.0-48-generic #80-Ubuntu SMP Thu Mar 12 11:16:15 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 14.04.2 LTS Release: 14.04 Codename: trusty $ gcc --version gcc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4
bootloaderを書いてみる
CPUの動作モードをプロテクテッドモードに切り替え、カーネルイメージを実行するbootloaderを書いてみると次のようになる。
/* bootloader.s */ .intel_syntax noprefix .globl _start .code16 _start: mov ax, 0x00 /* initialize stack */ mov ss, ax mov sp, 0x6000 lea si, msg_booting call print call load_kimage call setup_gdt call enable_a20_gate call set_video_mode call enter_pmode msg_booting: .asciz "Booting...\r\n" print: mov ah, 0x0e /* VIDEO - TELETYPE OUTPUT */ mov bl, 0x07 /* Light Gray */ mov bh, 0x00 print_loop: lodsb test al, al jz print_end int 0x10 jmp print_loop print_end: ret load_kimage: mov ah, 0x02 /* DISK - READ SECTOR(S) INTO MEMORY */ mov al, 0x10 /* number of sectors to read (must be nonzero) */ mov ch, 0x00 /* low eight bits of cylinder number */ mov cl, 0x02 /* sector number 1-63 (bits 0-5) */ mov dh, 0x00 /* head number */ mov dl, 0x00 /* drive number (bit 7 set for hard disk) */ mov bx, 0x0000 /* ES:BX -> data buffer */ mov es, bx mov bx, 0x8000 int 0x13 jc load_failure mov bx, 0x6000 mov [bx], al /* number of sectors transferred */ ret load_failure: cli hlt setup_gdt: cli pusha lgdt [gdt_toc] sti popa ret gdt_toc: .word 8*3 .word gdt, 0x0000 gdt: .word 0x0000, 0x0000, 0x0000, 0x0000 /* null descriptor */ .word 0xffff, 0x0000, 0x9a00, 0x00cf /* code descriptor */ .word 0xffff, 0x0000, 0x9200, 0x00cf /* data descriptor */ enable_a20_gate: mov ax, 0x2401 /* SYSTEM - later PS/2s - ENABLE A20 GATE */ int 0x15 ret set_video_mode: mov ah, 0x00 /* VIDEO - SET VIDEO MODE */ mov al, 0x13 /* VGA mode 13 (320x200x256) */ int 0x10 ret enter_pmode: mov eax, cr0 or eax, 1 mov cr0, eax jmp 0x08:start_pmode .code32 start_pmode: mov ax, 0x10 /* initialize segment selector and stack */ mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax mov esp, 0xfffffff0 reloc_kernel: cld mov ebx, 0x6000 xor ecx, ecx mov cl, [ebx] shl ecx, 9 /* sector -> bytes */ mov esi, 0x8000 mov edi, 0x100000 rep movsb boot_kernel: cli mov ebp, 0x100000 add ebp, [ebp+0x18] /* Elf32_Ehdr e_entry */ call ebp hlt
上のコードの内容を説明すると、次のようになる。
- boot messageを表示する
- カーネルイメージを読み込む
- ここでは、ディスクの2セクタ目から10セクタ分(512バイト×10)をアドレス0x8000にロードしている
- GDTを設定する
- ここでは、コードセグメント、データセグメントそれぞれがメモリ全体(0x00000000から0xFFFFF000)を指すようにしている
- A20 gateを有効にする
- アドレスバスの制限を解除し、0x100000以上のアドレスを使えるようにする
- キーボードコントローラを使う方法、Fast A20 Gateを使う方法などもあるが、ここではBIOS命令を使っている
- ビデオモードを変更する
- ここでは、320x200の256色モード(VGA mode 13)に変更している
- CPUの動作モードをプロテクテッドモードに切り替える
- カーネルイメージを再配置する
- 0x8000からの10セクタ分を0x100000に再配置する
- 再配置しなくても動作するが、ここではLinuxのメモリマップを意識して合わせている
- カーネルイメージを起動する
- ELFヘッダのエントリポイントの値を読み、そのアドレスをcallする
このコードでは、カーネルイメージがMBRに続く2セクタ目以降に置かれていることを想定している。
カーネルイメージを書いてみる
次に、簡単な画面描画を行うカーネルイメージ(VGAドライバ)を書いてみる。 VGA mode 13では、VRAMと呼ばれるメモリが0xa0000にあり、ここから320×200バイトが各ピクセルを表す。 各ピクセルの値は0から255となり、これがパレットで設定された256色それぞれに対応する。
VRAMに256色の縦縞を描くプログラムコードを書くと次のようになる。
/* kimage.c */ void _start() { int width = 320; int height = 200; unsigned char *vram = (void *)0xa0000; int x, y; for (y=0; y<height; y++) { for (x=0; x<width; x++) { *(vram+y*width+x) = x & 0xff; } } /* infinite loop */ while (1) {} }
なお、一旦320×200のchar型配列を確保し、これに値をセットした後一度にVRAMにmemcpyする方法も可能である。 この場合のchar型配列はフレームバッファと呼ばれる。
ディスクイメージを作ってみる
bootloaderとカーネルイメージを組み合わせて、ディスクイメージを作ってみる。
まず、bootloaderを0x7c00をコードの起点としてコンパイルし、C形式の文字列に変換する。
さらに、カーネルコードをPIEでコンパイルする。
ここでは、カーネルイメージを少しでも小さくするために最適化オプション(-O1
)もつけている。
$ gcc -nostdlib -Ttext=0x7c00 bootloader.s -o bootloader $ objdump -s -j.text bootloader | grep "^ " | cut -d" " -f3-6 | perl -pe 's/(\w{2})\s*/\\x\1/g' \xb8\x00\x00\x8e\xd0\xbc\x00\x60\x8d\x36\x1e\x7c\xe8\x1c\x00\xe8\x29\x00\xe8\x46\x00\xe8\x6b\x00\xe8\x6e\x00\xe8\x72\x00\x42\x6f\x6f\x74\x69\x6e\x67\x2e\x2e\x2e\x0d\x0a\x00\xb4\x0e\xb3\x07\xb7\x00\xac\x84\xc0\x74\x04\xcd\x10\xeb\xf7\xc3\xb4\x02\xb0\x10\xb5\x00\xb1\x02\xb6\x00\xb2\x00\xbb\x00\x00\x8e\xc3\xbb\x00\x80\xcd\x13\x72\x06\xbb\x00\x60\x88\x07\xc3\xfa\xf4\xfa\x60\x0f\x01\x16\x65\x7c\xfb\x61\xc3\x18\x00\x6b\x7c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x9a\xcf\x00\xff\xff\x00\x00\x00\x92\xcf\x00\xb8\x01\x24\xcd\x15\xc3\xb4\x00\xb0\x13\xcd\x10\xc3\x0f\x20\xc0\x66\x83\xc8\x01\x0f\x22\xc0\xea\x9f\x7c\x08\x00\x66\xb8\x10\x00\x8e\xd8\x8e\xc0\x8e\xe0\x8e\xe8\x8e\xd0\xbc\xf0\xff\xff\xff\xfc\xbb\x00\x60\x00\x00\x31\xc9\x8a\x0b\xc1\xe1\x09\xbe\x00\x80\x00\x00\xbf\x00\x00\x10\x00\xf3\xa4\xfa\xbd\x00\x00\x10\x00\x03\x6d\x18\xff\xd5\xf4 $ gcc -m32 -nostdlib -fPIE -pie -O1 kimage.c -o kimage
1セクタ目(MBR)にbootloader、2セクタ目以降にカーネルイメージが配置されるように、ディスクイメージを作成するPythonスクリプトを書くと次のようになる。
# create_img.py fname = 'boot.img' kname = 'kimage' with open(kname) as f: kimage = f.read() buf = '\xb8\x00\x00\x8e\xd0\xbc\x00\x60\x8d\x36\x1e\x7c\xe8\x1c\x00\xe8\x29\x00\xe8\x46\x00\xe8\x6b\x00\xe8\x6e\x00\xe8\x72\x00\x42\x6f\x6f\x74\x69\x6e\x67\x2e\x2e\x2e\x0d\x0a\x00\xb4\x0e\xb3\x07\xb7\x00\xac\x84\xc0\x74\x04\xcd\x10\xeb\xf7\xc3\xb4\x02\xb0\x10\xb5\x00\xb1\x02\xb6\x00\xb2\x00\xbb\x00\x00\x8e\xc3\xbb\x00\x80\xcd\x13\x72\x06\xbb\x00\x60\x88\x07\xc3\xfa\xf4\xfa\x60\x0f\x01\x16\x65\x7c\xfb\x61\xc3\x18\x00\x6b\x7c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x9a\xcf\x00\xff\xff\x00\x00\x00\x92\xcf\x00\xb8\x01\x24\xcd\x15\xc3\xb4\x00\xb0\x13\xcd\x10\xc3\x0f\x20\xc0\x66\x83\xc8\x01\x0f\x22\xc0\xea\x9f\x7c\x08\x00\x66\xb8\x10\x00\x8e\xd8\x8e\xc0\x8e\xe0\x8e\xe8\x8e\xd0\xbc\xf0\xff\xff\xff\xfc\xbb\x00\x60\x00\x00\x31\xc9\x8a\x0b\xc1\xe1\x09\xbe\x00\x80\x00\x00\xbf\x00\x00\x10\x00\xf3\xa4\xfa\xbd\x00\x00\x10\x00\x03\x6d\x18\xff\xd5\xf4' buf += '\x00' * (510-len(buf)) buf += '\x55\xaa' buf += kimage buf += '\x00' * (512-(len(buf)%512)) with open(fname, 'wb') as f: f.write(buf)
上のコードは、kimageというファイル名のカーネルイメージを読み込み、boot.imgというファイル名のディスクイメージを作成する。
スクリプトを実行し、ディスクイメージを作成する。
$ python create_img.py $ hd boot.img 00000000 b8 00 00 8e d0 bc 00 60 8d 36 1e 7c e8 1c 00 e8 |.......`.6.|....| 00000010 29 00 e8 46 00 e8 6b 00 e8 6e 00 e8 72 00 42 6f |)..F..k..n..r.Bo| 00000020 6f 74 69 6e 67 2e 2e 2e 0d 0a 00 b4 0e b3 07 78 |oting..........x| 00000030 62 37 00 ac 84 c0 74 04 cd 10 eb f7 c3 b4 02 b0 |b7....t.........| 00000040 10 b5 00 b1 02 b6 00 b2 00 bb 00 00 8e c3 bb 00 |................| 00000050 80 cd 13 72 06 bb 00 60 88 07 c3 fa f4 fa 60 0f |...r...`......`.| 00000060 01 16 78 36 35 7c fb 61 c3 18 00 6b 7c 00 00 00 |..x65|.a...k|...| 00000070 00 00 00 00 00 00 00 ff ff 00 00 00 9a cf 00 ff |................| 00000080 ff 00 00 00 92 cf 00 b8 01 24 cd 15 c3 b4 00 b0 |.........$......| 00000090 13 cd 10 c3 0f 78 32 30 c0 66 83 c8 01 0f 22 c0 |.....x20.f....".| 000000a0 ea 9f 7c 08 00 66 b8 10 00 8e d8 8e c0 8e e0 8e |..|..f..........| 000000b0 e8 8e d0 bc f0 ff ff ff fc bb 00 60 00 00 31 c9 |...........`..1.| 000000c0 8a 0b c1 e1 09 be 00 80 78 30 30 00 bf 00 00 10 |........x00.....| 000000d0 00 f3 a4 fa bd 00 00 10 00 03 6d 18 ff d5 f4 00 |..........m.....| 000000e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.| 00000200 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 00000210 03 00 03 00 01 00 00 00 11 02 00 00 34 00 00 00 |............4...| (snip)
VirtualBoxで起動してみる
「x86 bootloaderでHello Worldを書いてみる」と同様の手順で仮想フロッピーコントローラにディスクイメージを指定し起動すると、次のスクリーンショットのようになる。
bootloaderからカーネルイメージが実行され、256色の縦縞が表示されていることが確認できる。
一般的なOSでは、ここからさらに
などが実装されている。