「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では、ここからさらに
などが実装されている。