|
ブートローダその9 画面に文字を表示する
|
前回までの内容
これまでで、
- スタックはデータを積み上げたり、下ろしたりすることができる
- スタックの操作にはPOP命令、PUSH命令命令を使用する(SPレジスタは直接操作しなくてもよい)
- CALL命令で関数呼び出しをし、RET命令で戻ることができる(スタック操作はしなくも勝手にやってくれる)
ということがわかりました。それではブートローダの開発を進めていきましょう!
画面に文字を表示する
画面に文字を表示することができればデバッグがかなり楽になります
アセンブラ言語ではC言語のprintf関数のようなものがありませんので
自分で表示する関数を準備する必要があります
しかし、ブートローダは512バイトとかなり小さなプログラムで関数全部を自分で作ってしまうと
かなり大きなプログラムになり512バイトにおさまらなくなってきます
そこでBIOSの割り込み処理を利用します
文字を表示する
文字を表示するにはBIOSの助けを借ります
BIOSは起動時に割り込み処理を用意してくれますのでそれを利用します
割り込み処理を行うにはINT命令を実行します
INT命令で指定する割り込み番号は0x10番です
これでベクタテーブルの0x10番目に登録されている文字表示処理が呼び出されます
文字表示処理には引数が要ります
引数は汎用レジスタに値を設定することで渡すことができます
どのように設定すればよいのでしょうか
INT 0x10(文字表示処理)の引数
INT 0x10(文字表示処理)の引数は汎用レジスタのAH、AL(AXレジスタの上位8ビットと下位8ビットです)、
BH、BL(BXレジスタの上位8ビットと下位8ビットです)を使います
そしてAH、AL、BH、BLに値を入れてINT 0x10を実行すれば文字が表示される仕組みとなっています
- AH:表示処理の処理内容を指定します。AH=0x0Eを入れます。0x0Eは1文字を表示する処理となります。
ついでですが、文字列を表示する処理はAH=0x13を指定します。挑戦してみたい方は0x13を入れてください。
- AL:表示したい文字をアスキーコードで入れます
- BH:ビデオページ番号をいれます。BH=0x00をいれます。
- BL:文字の表示色を指定します。BL=適当な値を入れてみてください。ここの例ではBL=0x07を入れました
このようなINT命令の詳しい説明は
General Software, Inc.
が発行している
BIOSのマニュアル
に載っていますので調べて見たい方は読んでみてください
では実際に文字を表示させてみましょう
MOV AH, 0x0E
MOV AL, 0x41
MOV BH, 0x00
MOV BL, 0x07
INT 0x10
上記コードを今までのブートローダに組み込んでみてください
今まで真っ黒な画面でしたが、何か1文字表示されましたでしょうか
試しに他の文字や文字の色なんかも変えて色々試してみてはいかがでしょうか
文字を表示する
こんどは文字列を表示させてみましょう
文字列を表示させる関数です。これをブートローダに組み込んでみてください
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
;
; DisplayMessage
; display ASCIIZ string
;
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
DisplayMessage:
PUSH AX
PUSH BX
StartDispMsg:
LODSB
OR AL, AL
JZ .DONE
MOV AH, 0x0E
MOV BH, 0x00
MOV BL, 0x07
INT 0x10
JMP StartDispMsg
.DONE:
POP BX
POP AX
RET
10数行のプログラムですが、さっぱりわかがわかりません。。。
まずは動かしてみましょう
準備としてまず表示する文字列を用意します
ImageName DB "Bood-bye Small World", 0x00
この関数では文字列の最後が0x00であれば、文字列の終わりとして判断しますので
文字列の直ぐ後に0x00を置いておきます
このように続けてデータを宣言することができます
ここで変数のラベル名はImageNameとしました
次に、
MOV SI, ImageName
CALL DisplayMessage
表示させたい文字列の先頭アドレスをSIレジスタに格納します
SIレジスタはレジスタの説明のときにほんの少しだけ出てきました
SIレジスタは”DSレジスタが指すセグメント内のデータに対するポインタ。文字列操作では
読み取り元のアドレスを指します ”と出てきました。このときはなんのことやら
意味がわからなかった説明ですが、セグメントが理解できていれば簡単だと思います
文字列もデータですのでDSデータセグメントとデータセグメントの開始位置からのオフセットを
指定すればアクセスできます。特に文字列の場合はオフセットとしてSIレジスタを使用します
SIレジスタは汎用レジスタですが、文字列関係の命令の場合は文字列のポインタとして
専用の役割が与えられます。今回この関数は文字列を操作する命令を使っていますので
SIレジスタを使用しています。
SIレジスタに先ほどのImageNameのアドレスを格納した後、関数DisplayMessageを
CALLしてみてください
文字列が表示されましたでしょうか?
やっとこれで少しはOSっぽくなってきたでしょうか?
それでは次に関数の内容について見ていきたいと思います
文字を表示するDisplayMessage関数の動作
DisplayMessage関数を見てみてください
まずは前回で出てきましたPUSH命令とPOP命令です
この関数ではAXレジスタとBXレジスタの内容を変更する関数なので関数から抜けるときに
元のAX、BXの値に戻しておきたいので、関数の入れ口PUSH命令、出口にPOP命令を使い
レジスタAX、BXの値を元に戻しています
PUSH命令の次に見慣れない小難しい命令LODSBが鎮座されております
この命令はちょっと難しいです。しかし、DSセグメントとSIレジスタによるオフセットを理解していれば
何のこともありません
LODSBは”Load String”命令です。読んだ字の如く”文字列をロード”します
ではどこから読み込んでいるのでしょうか?もう簡単ですね
DSレジスタが指しているセグメント内の、SIレジスタが指しているオフセットの位置にある
1バイトをALレジスタに格納します。
この命令を実行するだけでAL(AXの下位8ビット)に文字列の1文字分のデータが入ります
更に、1文字読み込んだ後は自動的にSIレジスタの値を+1します
ですのでLODSB命令を実行した後は、SIレジスタは次の文字を指している状態になっています
なんとなくわかってきました。この関数はLDSBでALに1文字入れる→INT 0x10命令で1文字表示させる
を繰り返す関数となります。
繰り返しにはJMP命令を使っています
JMP StartDispMsg
JMP命令で”StartDispMsg”ラベルの場所まで飛んでいって、またLODSB命令から開始します
ではどうやって文字列がの終わりを判断しているのでしょうか?
それが次の2行が判断しています
OR AL, AL
JZ .DONE
1つ目はALの値をALの値でORしています
同じ値どうしでORすると結局ALの値は何も変わりません。元のままです
元のままですが、ALが0x00場合には違った結果となります
OR命令やADD命令など算術的にな計算を行った後結果が0だった場合は
CPUは”計算の結果が0だった”ということを憶えておいてくれます
その”計算の結果が0だった”ということを憶えておくフラグをZF(Zero flag:ゼロフラグ)といいます
命令が実行された後”計算の結果が0だった”場合このZFの値が1になります
逆に計算結果が0でない場合はZFの値は0になります
ちょっとややこしいです。とにかく計算結果が0かどうかを判断するフラグとなります
このOR命令はこの性質を利用しています
ImageName文字列の宣言の際に最後に0x00を置きました
SIレジスタが最後の0x00を指し、LODSB命令でALにx00が格納され、
OR命令で0x00どうしのALを計算した結果のみZFが1になります
ここで2つ目の命令です。JZは”Jamp if Zero”命令で、”もしゼロならジャンプすると”いう命令です
この”もしゼロなら”の部分がまさにZFを見ていて、ZFが1すなわち計算結果が0もっと言えば
文字列の終端0x00だった場合に、.DONEラベルにジャンプします
C言語だとfor文で簡単に書ける繰り返し命令ですが、アセンブラだと結構めんどいですね。。。
さて、ここで説明いたしましたZFというフラグはどこにあるのでしょうか?
ここでまた重要で特殊なレジスタが出てきます。FLAGSレジスタというレジスタにZFの値が格納されています
EFLAGSレジスタ
FLAGSレジスタも重要ですので、何かとCPUのマニュアルでEFLAGSレジスタの説明を見るときが多いです
EFLAGSレジスタというものがでてきましたが、EFLAGSレジスタは32ビットのレジスタで、
16ビットのFLAGSレジスタを32ビットに拡張したレジスタです
ここでいきなり32ビットのレジスタですが、ブートローダを作った後は直ぐに32ビットの環境になるので、
ちょっと先取りして32ビットのレジスタとして説明します
(FLAGSレジスタは下記図の0ビット目から15ビット目までのことを言います)
とりあえずここではこんなレジスタがあるんだ、程度でいいと思います
それではEFLGSレジスタの中身を見ていきましょう
EFLAGSレジスタはCPUのステータスを32ビットの各ビットが0か1かで、その状態になっているかどうかを表します
ステータスを表す各ビットの割り当てが下記図となります
それでは各ビットについて。ビットの割り当て図の白い部分は予約領域で、該当の数字が入っています
EFLAGSレジスタ
|
ビット
|
ステータス
フラグ
|
説明
|
0
|
CF
|
Carry Flag キャリーフラグ
1:足し算なので最上位ビットが繰り上げた場合。(例:0xFFFF+1や0x0000-1をした場合)
0:繰り上げが発生しなかった場合。(例:x0FFFE+1をした場合)
|
2
|
PF
|
Parity Flag パリティフラグ
1:演算した結果の最下位バイトに値1のビットが偶数個ある場合(例:0xFF03など)
0:逆に奇数個の場合(例:0x0F10など)
|
4
|
AF
|
Adjust Flag 調整フラグ
1:演算した結果のビット3(最下位バイトの最上位ビット)に繰り上げが発生した場合(例:0x0080+0x08など)
0:逆に繰り上げが発生しなかった場合
|
6
|
ZF
|
Zero Flag ゼロフラグ
1:演算した結果が0の場合
0:逆に0位外の場合
|
7
|
SF
|
Sign Flag 符号フラグ
1:演算した結果が負の場合
0:逆に正の場合
|
8
|
TF
|
Trap Flag トラップフラグ
1:デバッグのシングルステップモードが有効
0:逆にシングルステップモードが無効
|
9
|
IF
|
Interrupt Enable Flag 割り込み可能フラグ
1:割り込みが有効。STI命令を実行するとこのフラグが1になります
0:割り込みが無効。CLI命令を実行するとこのフラグが0になります
割り込みを有効にするか無効にするかするフラグです。
|
10
|
DF
|
Direction Flag 方向フラグ
1:文字列関係の命令(LODSBなど)で実行した後例えばSIレジスタの値がインクリメントされる
0:逆にデクリメントされる
|
11
|
OF
|
Overflow Flag オバーフローフラグ
1:演算結果が格納するレジスタより大きい数字になった場合(例:0xFF+0x01をALレジスタに格納)
0:逆に格納するレジスタより小さい場合
|
12-13
|
IOPL
|
I/O privilege level field I/O特権レベルフィールド
このフィールドには特権レベルの値を入れます(通常0から3)
0以外:ユーザモードとして動作します
0 :特権モードとして動作します
このフィールドの値は特にCPL(Current Privilege Level)と言います。後々出てきます。
|
14
|
NT
|
Nested Task Flag ネストタスクフラグ
1:現在のタスクが直前に実行されたタスクにリンクされている場合
0:逆にリンクされていない場合
|
16
|
RF
|
Resume Flag 再開フラグ
デバッグ例外に対するプロセッサの応答を制御する
|
17
|
VM
|
Virtual-8086 mode Flag 仮想8086モードフラグ
1:仮想8086モードが有効
0:仮想8086モードが無効(プロテクティッドモード)
|
18
|
AC
|
Alignment check Flag アライメントチェックフラグ
1:アライメントチェックが有効(CR0レジスタのAMビットもセットしておく必要があります)
0:アライメントチェックが無効
|
19
|
VIF
|
Virtual interrupt Flag 仮想割り込みフラグ
1:仮想割り込みが有効(CR4レジスタのVMEビットもセットしておく必要があります)
0:仮想割り込みが無効
|
20
|
VIP
|
Virtual interrupt pending Flag 仮想割り込み保留フラグ
1:仮想割り込みが保留状態(CR4レジスタのVMEビットもセットしておく必要があります)
0:仮想割り込みに保留無し
|
21
|
ID
|
Identification Flag 識別フラグ
プログラムから値0か1を書き込むことができると、CPUID命令が使えることを意味します
|
当面はZFとIFとIOPLを使用していきます
次回はフロッピーディスクからのファイル読込について説明いたします