86系のPCをブートさせて遊んでみよう、というお話の3回目になります。テキストは『OSを書く:初歩から一歩ずつ』です。
環境はAMD64+Windows10+VMware Workstation 17 Player+Debian 12.4です。
前回はMakefileを作ったところで気力が尽きちゃったんだけど、今回はどうなるかなぁ(不安)
まぁ、最低でも文字を表示するところは終わらせるつもりですけど…。
スクリーンにプリントする
画面に文字を表示します。定番と言うか、「またお前か!」というか、何と言うか。
Hello World
です。
細かい説明は(不十分かもしれませんが)テキストにありますので、まずはそちらを参考にしてください。ざっくり言うと、表示したい文字列をメモリ上に定義して、その最初のアドレスから文字列が終わるまで1文字ずつ表示しています。
ていうか、これくらいしか言うこと無くない?これ以上何を言えと?
とか思ったり思わなかったり。
ともあれ、ソースを作成します。
; boot.asm mov ax, 0x07c0 mov ds, ax mov ah, 0x0 mov al, 0x3 int 0x10 mov si, msg mov ah, 0x0E print_character_loop: lodsb or al, al jz hang int 0x10 jmp print_character_loop msg: db 'Hello, World!', 13, 10, 0 hang: jmp hang times 510-($-$$) db 0 db 0x55 db 0xAA
PlayWithBoot.2.1
アセンブル & 実行します。makeさん大活躍です。
make qemu
『Hello World!』が表示されました!
めでたしめでたし!
おしまい。
とか言いたいところなんですが、もし私が読み手としてこの記事をみたら
「ふざけんな!ボケぇ」
とか言いたくなるような気がするので、少しだけ追加です。
ahとal
ふと。
ahとalってaxレジスタのハイとロー、上位バイトと下位バイトじゃなかったっけ?
なら
mov ah, 0x0
mov al, 0x3
って
mov ax, 0x0003
とかにしても動くんじゃない?
というわけでソース修正。
こんな感じ。5行目が修正箇所ですね。
; boot.asm mov ax, 0x07c0 mov ds, ax mov ax, 0x0003 int 0x10 mov si, msg mov ah, 0x0E print_character_loop: lodsb or al, al jz hang int 0x10 jmp print_character_loop msg: db 'Hello, World!', 13, 10, 0 hang: jmp hang times 510-($-$$) db 0 db 0x55 db 0xAA
PlayWithBoot.2.2
で、実行。
無事に動きました。
or al, al の謎
まぁ、謎っていうほどのモノじゃないんですが、最初にこれを見た時に非常に悩みました。だってね
or X, Y
ってXとYのorをとって結果をXに設定する、という命令なわけで。
普通のプログラム言語っぽく書くなら
X = X or Y
なわけじゃないですか。
今回はXもYもalなので
al = al or al
になります。で、
同じ値のorをとると、元の値になります。
なので
al = al
になるという。ということはコードとしては
mov al, al
と同等になるという。
意味あるの?
ここでアセンブラを使ってるととても重要な機能と意味を持つフラグレジスタのお話がちょろっと出ます。
アセンブラの場合、全ての命令ではないのですが命令の実行結果によってフラグレジスタが更新されます。雑な言い方で申し訳ないんですが、アセンブラのコーディングでは
命令の実行
フラグの確認
といったスタイルが多くなります。
or al, al
jz hang
がこのパターンですね。
jz命令はゼロフラグが立っていたら(言い換えれば結果がゼロだったら)指定先に飛べという動作です。
表にするとこんな感じ。
lodsbで読 んだalの値 | or la, alの結果 | ゼロフラグ | 文字 | |
---|---|---|---|---|
1回目 | 0x48 | 0x48 | 0 | H |
2回目 | 0x65 | 0x65 | 0 | e |
3回目 | 0x6c | 0x6c | 0 | l |
4回目 | 0x6c | 0x6c | 0 | l |
5回目 | 0x6f | 0x6f | 0 | o |
: : | : : | : : | : : | : : |
13回目 | 0x21 | 0x21 | 0 | ! |
14回目 | 0x0d | 0x0d | 0 | CR |
15回目 | 0x0a | 0x0a | 0 | LF |
16回目 | 0x00 | 0x00 | 1 | NULL |
定義した文字列で一番最後の0x00の時だけ、ゼロフラグが1になります。
なので、最初から文字を1文字ずつ表示して0x00まで来たらhangに飛ぶという処理となり、めでたく『Hello World!』が表示されるというわけです。
蛇足
『alがゼロだったら~』という処理なんだから素直に
cmp al, 0x00
je hang
じゃいけないの?
という向きもあると思います。なので動かしてみましょう。
ソースはこれ。15行目、16行目を修正してます。
; boot.asm mov ax, 0x07c0 mov ds, ax mov ah, 0x0 mov al, 0x3 int 0x10 mov si, msg mov ah, 0x0E print_character_loop: lodsb cmp al, 0x00 je hang int 0x10 jmp print_character_loop msg: db 'Hello, World!', 13, 10, 0 hang: jmp hang times 510-($-$$) db 0 db 0x55 db 0xAA
PlayWithBoot.2.3
ちゃんと動きます。
う~ん、どっちを使うかなんですが、or al, al を使った方がいいことあるんでしょう、きっと。知らないけど。
《2024/4/27 15:25:24》