やっつけIPL

512byteより大きなプログラムを実行してみよう。
前回は小さなプログラムを書いて,画面に文字を表示することが出来た。AT互換機はディスクの最初のセクタ,つまり512byteを読み込んで実行してくれる分けだから,ただディスクの先頭セクタにそのプログラムを書き込めばよかった。さて512byte以上のプログラムを実行させるにはどうすればいいだろうか?
まず大きなプログラムを先頭セクタ以外の場所に書き込んでおいて,先頭セクタに,ディスクからそのプログラムを読み込むプログラムを用意してやればいい。
このプログラムはInitial Program Loader (IPL)と呼ばれる。


アセンブラを使っているとわけもわからず泣けてくるので,
私たちは可能な限り高級言語を使用してプログラムを書いていきたい。
しかし,512byteに理想のプログラムを詰め込むには,高級言語が吐き出すバイナリでは大きすぎるようなので,ここではアセンブラで記述する。

コードは次の通り:

ipl.S

#define IPLBASE  0x7c00
#define IPLSTACK 0xffff
#define ROOTDIR  0x7e00
#define KERBASE  0x4000
#define PUTS(x)  mov si, offset x; call putstr
#define WORD word ptr 
#define BYTE byte ptr
	
.text
.code16gcc			// 16bit-realmode
.intel_syntax noprefix		// I hate AT&T-style
.global _start
_start:		jmp	begin
		nop
OEM_name:	.ascii  "OS      "	// OEM name
sect_size:	.word   512		// Bytes per sector
culst_size:	.byte   1		// Sector per cluster
rsvd_sects:	.word   1
fat_cont:	.byte   2		// number of FATs
root_ents:	.word   224		// max root enstrys
total_sect:	.word   2880
media_type:	.byte   0xf0
fat_size:	.word   9
trk_size:	.word   18
head_cont:	.word   2
hidd_sect:	.long   0
huge_sect:	.long   0
boot_dev:       .byte   0
reserved:       .byte   0
signature:	.byte   0x29
vol_ID:		.long   0xffffffff
vol_label:	.ascii  "DISK       "
fat_name:	.ascii  "FAT12   "
		.space	18

begin:
// code located at 0000:7C00, CS=0 and IP=0x7c00.
	cli			// Disable interrupts
	xor	ax, ax		// Set up needed data segment reg
	mov	ds, ax		// CS = DS = 0
	mov	es, ax		// ES = 0
	mov	ss, ax		// Set up stack
	mov	esp, IPLSTACK	// stack 0x0000:0xffff
	sti

	mov	[boot_dev], dl	// save boot dev
	
	PUTS(msg_start)		// start msg

	// reset disk
	mov	dl, [boot_dev]
	xor	ax, ax
	int	0x13

	PUTS(msg_load_dir)
	
	// CX = size of root directory
	xor	dx, dx
	mov	ax, 32			// 32 byte directory entry
	mul	WORD [root_ents]	// total size of directory
	div	WORD [sect_size]	// size in sectors
	mov	cx, ax
	mov	[root_size], cx		// save root dir size
	// AX = location of root directory
	movzx	ax, BYTE [fat_cont]	// number of FATs
	mul	WORD [fat_size]		// size in sectors
	add	ax, WORD [rsvd_sects]	// adjust for bootsector
	mov	[root_base], ax		// save root dir base
	// ES:BX = into memory of root directry
	mov	bx, ROOTDIR
	// load root directry
	call	readsectors

	// search file
	mov	cx, WORD [root_ents]	// load loop counter
	mov	di, ROOTDIR		// locate first root entry
search_loop:
	push	cx
	mov     cx, 11			// check 11byte
	mov     si, offset file_name	// file name
	push	di
rep	cmpsb				// check file name
	pop	di
	je	load_file		// found!
	pop	cx
	// step
	dec	cx
	add	di, 32
	loop	search_loop		// next entry
	PUTS(msg_not_found)		// not found
	jmp	loop
	
load_file:
	PUTS(msg_load_file)
	// CX = set file size
	mov	ax, [di + 0x1C]		// file size in byte
	xor	dx, dx
	div	WORD [sect_size]
	inc	ax			// size in sectors
	mov	cx, ax			// CX =  sectors count
	// AX = set file head
	mov	ax, [di + 0x1A]		// file head cluster
	sub	ax, 0x02
	movzx	bx, BYTE [culst_size]
	mul	bx			// sector number
	add	ax, WORD [root_size]	// add data base
	add	ax, WORD [root_base]	// AX = sector head
	// ES:BX = location
	mov	bx, KERBASE

	// load file
	call	readsectors

real2prot:	
	cli				// disable intrreput
	lgdt	gdtr			// enable GDT
	// Enable A20
	mov	ax, 0x2401
	int	0x15
	// Enable protected mode
	mov	eax, cr0
	or	eax, 1
	mov	cr0, eax
	// Reload CS, flush pileline
	jmp	0x08, offset code32
code32:
	.code32
	// set segments
	mov	eax, 0x10
	mov	ds, eax
	mov	es, eax
	mov	fs, eax
	mov	gs, eax
	mov	ss, eax
	//mov	esp, 0x90000

	// jmp c code
	jmp	0x08, KERBASE
	
	.code16gcc
	
loop:
	jmp	loop

// read sectors
//  AX = sorce sector head. cx = sector count.
//  ES:BX = into memory location.
readsectors:
nextsect:	
	push	ax
	push	bx
	push	cx
	call	readsect
	PUTS(msg_prog)
	pop	cx
	pop	bx
	pop	ax
	// step
	inc	ax
	add	bx, WORD [sect_size]
	loop	nextsect
	// read ok
	PUTS(msg_ok)
	ret
	
// read sector via BIOS
//  params: AX = sorce sector head. ES:BX = into memory loation.
readsect:
	xor	dx, dx		// prepare dx:ax for operation
	div	WORD [trk_size]	// AL = tracks.
	mov	cl, dl
	inc	cl		// CL = sector
	xor	dx, dx		// prepare dx:ax for operation
	div	WORD [head_cont]
	mov	ch, al		// CH = sylinder
	mov	dh, dl		// DH = head
	mov	dl, [boot_dev]	// DL = disk
	mov	ax, 0x0201	// read per 1 sect
	int	0x13
	ret

// purint string via BIOS
//  params: SI = address of string.
putstr:
        mov     ah, 0x0E   
        mov     bx, 0x0007
put_loop:
	lodsb              
        or      al, al     
        jz      put_end
        int     0x10       
        jmp     put_loop
put_end:
	ret

// data
msg_start:	.string "IPL:\r\n"
msg_load_dir:	.string "load root dir"
msg_load_file:	.string "load file"
msg_not_found:	.string "not found"
msg_prog:	.string "."
msg_ok:		.string "ok!\r\n"
file_name:	.ascii "KERNEL  BIN"
root_size:	.word 0
root_base:	.word 0

.align	8
gdt:	// GDT
	.word	0,0,0,0			// 0x00:null
	.word	0xFFFF,0,0x9800,0x00fd	// 0x08:flat code
	.word	0xFFFF,0,0x9200,0x00fd	// 0x10:flat data
gdtr:	.word	. - gdt - 1
	.long	gdt

// boot signetur
	.org	510
	.word	0xaa55

解説

このプログラムは,FAT16ファイルシステムでフォーマットされたディスクから,目的のファイル名がつけられたプログラムを探し出し,メモリへ読み込み実行する。ついでにプロテクトモードの移行もしている。
私たちはOSのカーネルの開発に興味があるわけだから,IPLの作成に専念するのは面白く無い。しかし,OSの起動プロセスを理解するためにいい機会だと思うので,非常に単純なIPLを作ることにした。単純だが一つのファイルで出来たOSを起動するには十分で,しばらくはこのIPLを使う。
OSの作成の進行の必要に応じて,高機能なGBUBといったIPLに乗り換えることにする。


コメントを十分に付けたので,コメントを見ればだいたいの動作が解ると思うので,ここでは詳しくコードについては述べない。下記に示すURLも非常に参考になる。
プロテクトモードなど重要な点については後で解説することにする。