またまた間が空いてしまったけど、前回の続き。
今日は実際のブートコードの部分を見ていく。
Intel構文とAT&T構文
前回も少し触れた通り、本はNASMに似た筆者作のアセンブラを使っていて、その構文はIntel構文になっている。
一方、自分の使っているGNU asは、AT&T構文で、いろいろ違ってくる。
詳細は以下のページなどを参照:
Linux のアセンブラー: GAS と NASM を比較する
基本的なところを押さえておくと、以下のとおり:
- オペランドのディスティネーションとソースが逆
- NASMはディスティネーション、ソースの順
- GNU asは、ソース、ディスティネーションの順
- 即値の指定の仕方が違う
- NASMは数字やラベルをそのまま書く
- GNU asは数字やラベルの前に
$
をつける
- レジスタの指定の仕方が違う
- 数字やラベルによるメモリの参照の仕方が違う
- NASMは数字やラベルを
[]
で囲う
バイト幅の指定が必要な場合、[]
の前にbyte
(1-byte)、word
(2-byte)、dword
(4-byte)をつける
なお、NASMは数字やラベルの前にbyte ptr
(1-byte)、word ptr
(2-byte)、dword ptr
(4-byte)をつけるのでもOKっぽい(※) - GNU asは数字やラベルをそのまま書き、バイト幅を示すためにオペコードに
b
(1-byte)、w
(2-byte)、l
(4-byte)をつける
- NASMは数字やラベルを
- レジスタによるメモリの参照の仕方が違う
※上記のIBMのページだとこう書かれているけど、なんか怪しい・・・
なお、NASMに関してと、レジスタによるメモリの参照は、ネット上の資料を漁っただけなので、正しくないかも・・・
(しっかりした資料が見つかってないので、自信がない・・・ちなみに、これを調べてたせいで更新が遅くなった)
16ビットモード
まず、x86のCPUには、16ビットモード(リアルモード)と32ビットモード(プロテクトモード)がある。
起動した直後は16ビットモードになっているので、アセンブラで出力されるコードも16ビットモード用のものになっていないと困る。
そこで、次のように、擬似命令を使ってこのコードが16ビットモード用のものであることを示してやる:
// 16-bit code .code16
ブートコード
そして、前回見た通り、ジャンプコード、BIOSパラメータブロックが続いて、そのあとにブートコードが来ることになる。
ジャンプコードでは、このブートコードの先頭に飛んでくるようにジャンプを行なっている。
最初にやっているのは、レジスタの初期化。
entry: mov $0, %ax mov %ax, %ss mov $0x7c00, %sp mov %ax, %ds mov %ax, %es // 続く
まずアキュムレータレジスタ(ax)を0で初期化し、さらにスタックのセグメントレジスタ(ss)、データのセグメントレジスタ(ds)、追加のセグメントレジスタ(es)をaxを使って0に初期化。
そして、スタックポインタ(sp)を0x7c00で初期化している。
ここからは文字列の出力。
まず、文字列が置かれているmessageというラベルのアドレスをソースインデックスレジスタ(si)に代入。
// 続き mov $message, %si // 続く
アドレスの内容ではなく、アドレスの値をそのまま入れるので、即値になるように$
をつける必要があるのに注意。
(最初、つけるのを忘れて動かなかった)
そしたら、siの指す内容をaxの下位8ビット(al)に読み込む。
// 続き putloop: movb (%si), %al // 続く
最初はsiの値はmessageのアドレスになっているので、改行文字¥n
が読み込まれることになる。
そして、次々と読み込んで出力したいので、siの値を1つずつ増やしていくことになる。
// 続き add $1, %si // 続く
ここで、もし読み込んだ文字がヌル文字¥0
だった場合、文字列の終端になっているので、ループを抜け出すことになる。
// 続き cmp $0, %al je fin // 続く
あとは、実際に文字を出力する処理。
これにはBIOSのAPIを利用する。
// 続き mov $0x0e, %ah mov $15, %bx int $0x10 // 続く
決められたレジスタに値をセットして、ソフトウェア例外を起こしている(int命令)。
これで制御がBIOSに移って、文字を出力したらまた戻ってきてくれる。
最後に、次の文字を出力するために、ループの先頭に戻る。
// 続き jmp putloop // 続く
一つ一つ読み解くと、こんな感じ。
Cで書いてみると、次のようなイメージ:
char* si = message; while (1) { char al = *si; si++; if (al == '¥0') { break; } putchar(al); }
文字列の出力が終わったら、あとは無限ループ。
// 続き fin: hlt jmp fin
hlt
命令はCPUを休止状態にする命令で、例外が起きるまでCPUを休ませておいてくれる。
さて、これでブートコードも見たわけだけど、実はこれではまだ正しく動かなかったり。
というのも、ブートセクタはBIOSによってメモリの0x7c00に読み込まれるのだけど、このコードだと先頭が0x0000だと思ってリンクが解決されてしまうから。
なので、アドレスが正しくなっていない。
このアドレスの問題を解決するには、もう一工夫必要になってくる。
それについては、また次で。
今日はここまで!
- 作者: 川合秀実
- 出版社/メーカー: 毎日コミュニケーションズ
- 発売日: 2006/03/01
- メディア: 単行本
- 購入: 36人 クリック: 735回
- この商品を含むブログ (299件) を見る