前回からだいぶ間が空いてしまったけど、続き。
前回は擬似命令を使ってただデータを作っただけだったけど、それではアセンブラを使ったとは言い難いので、今回はちゃんとしたアセンブラを書いてみる。
ちゃんとしたアセンブラのコード
と言うことで早速、ちゃんとしたアセンブラのコード。
.file "hello-os.s" // 16-bit code .code16 // partition boot record (1sector) pbr: // jump to boot code jmp entry nop // OEM name (8byte) .ascii "HELLOIPL" // BIOS Parameter Block (FAT12/FAT16) .short 512 // bytes per sector .byte 1 // sectors per cluster .short 1 // reserved sectors .byte 2 // number of file allocation table .short 224 // root directory entries // (32byte/entry * 224entry = 7168byte = 512byte/sector * 14sector) .short 2880 // total sectors .byte 0xf0 // media type (0xf0: floppy disk, 0xf8: hard disk) .short 9 // sectors per file allocation table .short 18 // sectors per track .short 2 // number of heads .int 0 // hidden sectors .int 2880 // large total sectors .byte 0x00 // physical disk number .byte 0x00 // current head .byte 0x29 // extended boot signature (0x29 means DOS 4.0 EBPB) .int 0xffffffff // volume serial number .ascii "HELLO-OS " // volume label (11byte) .ascii "FAT12 " // file sytem type (8byte) .skip 18, 0x00 entry: mov $0, %ax mov %ax, %ss mov $0x7c00, %sp mov %ax, %ds mov %ax, %es mov $message, %si putloop: movb (%si), %al add $1, %si cmp $0, %al je fin mov $0x0e, %ah mov $15, %bx int $0x10 jmp putloop fin: hlt jmp fin message: .string "\n\nhello, world\n" .org 0x0200 - 0x02 .byte 0x55, 0xaa // boot signature // file allocation table (9sectors = 512byte/sector * 9sector = 4608byte) fat: .byte 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 .skip 0x200 * 9 - 8, 0x00 // file allocation table (copy) fat_copy: .byte 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 .skip 0x200 * 9 - 8, 0x00 // root directory entries (14sectors = 512byte/sector * 14sector = 7168byte) root_directories: .skip 0x200 * 14, 0x00 // data area (2847sectors = 2880 - 1 - 9 * 2 - 14) files: .skip 0x200 * 2847, 0x00
本のコードを参考にしてるけど、だいぶ違ってる。
というのも、まず本はNASMに似たnaskという著者作のアセンブラを使っていて、Intel構文だけど、自分はGNU asを使っているので、AT&T構文になっているから。
それに、いろいろ分からなかったことを調べた結果が盛り込まれているというのもある。
FATファイルシステム
まず、FATファイルシステムについてから。
フロッピーディスクはセクタと呼ばれる区画で区切られていて、セクタ単位でデータを読み書きする。
1つのセクタが512バイトで、フロッピーディスクの場合、2880セクタ用意されている。
このセクタがディスク上にどう並んでいるのかはあとで扱うとして、論理的にはセクタが配列のように一列に並んでいると考えておいていい。
そう考えたときに、このセクタの配列は、FATフォーマットではいくつかの領域に分けられる:
- ブートセクタ
PCが起動したときにBIOSによってメモリにロードされて実行されるセクタ - FAT(File Allocation Table)領域
クラスタ(1つ以上のセクタの集まり)の情報を管理するための領域 - ルートディレクトリ領域
ルート直下のディレクトリの情報が置かれる領域 - データ領域
各ファイルの情報が置かれる領域
上のコードだと、以下のように分けられている:
pbr: // ブートセクタ fat: // FAT領域 root_directories: // ルートディレクトリ領域 files: // データ領域
ブートセクタは1セクタ、FAT領域は1つのFile Allocation Tableが普通9セクタで、冗長化でコピーが用意されているので合計18セクタ、ルートディレクトリ領域はFAT12だと普通は14セクタ、そして、残りがデータ領域となるので、データ領域は2847セクタということになる。
Cでイメージを書いておくと、以下のような感じ:
// セクタは512バイトのデータ typedef struct sector_ { unsigned char data[512]; } sector_t; // フロッピーディスク typedef union floppy_ { // 2880セクタからなる sector_t sectors[2880]; // 領域 struct { // ブートセクタ sector_t boot_sector; // FAT領域 struct { // File Allocation Table sector_t fat[9]; // File Allocation Table (copy) sector_t fat_copy[9]; } fat_area; // ルートディレクトリ領域 sector_t root_directories_area[14]; // データ領域 sector_t data_area[2847]; } areas; } floppy_t;
アセンブラのコードを見てみると、今のところちゃんとしたデータが置いてあるのはブートセクタだけで、他の領域は.skip
を使ってほぼ0にしていることが分かると思う。
これは、今書いているのがブート時に読み込まれて実行されるプログラムで、他にファイルをディスク上に作っていないから。
ブートセクタ
さらに、ブートセクタを見てみる。
パーティションが1つの場合、ブートセクタはただ1つなんだけど、ハードディスクとかだと複数のパーティションに分かれている場合があって、その場合、各パーティションの1つめのセクタが論理的にブートセクタになる。
そこで、ディスクの先頭にあるブートセクタを、マスターブートレコード(MBR)、各パーティションの先頭にあるブートセクタをパーティションブートレコード(PBR)と言ったりする。
フロッピーディスクの場合、パーティションは分けられていない(=パーティションは1つだけ存在する)ので、MBRがそのままPBRになっている。
そして、MBRとPBRでは微妙に構造が違っていて、MBRはセクタの最後の方にパーティションの情報を持つパーティションテーブルというのが置かれることになっている。
もちろん、フロッピーディスクの場合、パーティションテーブルは不要なので、構造的にはPBRになっている。
ということで、ラベルはmbr
ではなくpbr
にしている。
// partition boot record (1sector) pbr: ...
PBR(およびMBR)は、以下のような構造になっている:
- ジャンプコード (3byte)
以下のデータを飛び越して実際のブートコードに行くためのジャンプ命令+α - OEM名 (8byte)
ブートセクタの名前とか - BIOSパラメータブロック
ディスクの情報をまとめた領域 - ブートコード
ブート処理を行うコード - パーティションテーブル (64byte) ※MBRのみ
パーティションの情報 - ブートシグニチャ (2byte)
このセクタがブートセクタであることを示す
まずは、ジャンプコード。
// jump to boot code jmp entry nop
見ての通り、実際のブート処理を行うコード(entry以下)へジャンプを行なっている。
なお、ジャンプ命令には2byteのものと3byteのものがあるので、データの位置を揃えるために、2byteのジャンプ命令の後にはnop
命令を入れている。
(本だとニーモニックでnop
とは書かず、直接機械語でDB 0x90
と書いている)
次はOEM名で、特に書くことなし。
// OEM name (8byte) .ascii "HELLOIPL"
その次がBIOSパラメータブロック。
// BIOS Parameter Block (FAT12/FAT16) .short 512 // bytes per sector .byte 1 // sectors per cluster .short 1 // reserved sectors .byte 2 // number of file allocation table .short 224 // root directory entries // (32byte/entry * 224entry = 7168byte = 512byte/sector * 14sector) .short 2880 // total sectors .byte 0xf0 // media type (0xf0: floppy disk, 0xf8: hard disk) .short 9 // sectors per file allocation table .short 18 // sectors per track .short 2 // number of heads .int 0 // hidden sectors .int 2880 // large total sectors .byte 0x00 // physical disk number .byte 0x00 // current head .byte 0x29 // extended boot signature (0x29 means DOS 4.0 EBPB) .int 0xffffffff // volume serial number .ascii "HELLO-OS " // volume label (11byte) .ascii "FAT12 " // file sytem type (8byte)
ここが正直大変だったところで、本だと「その値にしておくものだから」で片付けられている値がけっこうある(^^;
しかも、ネットで調べても仕様がハッキリしない記述が多くて、かなり困った。
以下のwikipedia(en)の記述が結構役に立った感じ:
BIOS parameter block - Wikipedia
ここから18byteほど隙間を空けて、適当にアラインを揃えたところから、実際のブートコードは始まっている。
その詳細はまた後で。
そして、ブートセクタの一番最後にあるのが、ブートシグニチャ。
.org 0x0200 - 0x02 .byte 0x55, 0xaa // boot signature
ブートセクタの最後の方までスキップさせるために、.org
を使っている。
この擬似命令は、ファイルの先頭のアドレスを0として、引数で指定したアドレスまでスキップをしてくれる。
ということで、1セクタが512(= 0x200)バイトなので、そこからブートシグニチャの2バイトを引いたところまで、一気にスキップ。
そして、ブートシグニチャの0x55, 0xaa
を書き込んでいる。
BIOSはこのブートシグニチャが入っていないセクタはブートセクタとして扱わないらしい。
さて、あとは肝心のブートコードの部分だけど、けっこう長くなったので、一旦区切り。
今日はここまで!
- 作者: 川合秀実
- 出版社/メーカー: 毎日コミュニケーションズ
- 発売日: 2006/03/01
- メディア: 単行本
- 購入: 36人 クリック: 735回
- この商品を含むブログ (299件) を見る