月別アーカイブ: 2024年5月

ブートで遊ぼっ!(7)

86系のPCをブートさせて遊んでみよう、というお話の5回目になります。テキストは『OSを書く:初歩から一歩ずつ』です。
環境はAMD64+Windows10+VMware Workstation 17 Player+Debian 12.4です。
テキストの練習問題もこれで最後です。
頑張りましゅっ(噛んだ)

6. BIOSからキー押下情報を取り込む。

キーボードからの入力を得ることができれば、いろんな事が出来そうです。
期待が膨らみます。
使うBIOSコールは0x16。設定値は無し。キーが押されるとahとalに値が入って戻ってくるみたいです。ソースはこんな感じ?

; boot.asm
;
mov ax, 0x07c0
mov ds, ax

mov ah, 0x0
mov al, 0x3
int 0x10


mov ah, 0x00
int 0x16

mov cx, ax
mov ch, 0x00
mov bx, 10

loop:

mov dx, 0
mov ax, cx
div bx
mov cx, ax
mov ah, 0x0e
mov al, dl
add al, 0x30
int 0x10

cmp cx, 0
jne loop


jmp hang

[text]
; end of proccess
;
hang:
jmp hang

times 510-($-$$) db 0

db 0x55
db 0xAA

PlayWithBoot.6.1

実行すると、こんな感じ。

これはエンターを押した場合のalの値ですね。
なんだか簡単に終わってしまった?
一応、練習問題はこれで終わりですけど、これだけだと寂しいので、少し遊んでみましょう。

おまけ

せっかくなので練習問題の1から6まで、一つにまとめてみました。

; boot.asm
;
mov ax, 0x07c0
mov ds, ax

mov ah, 0x0
mov al, 0x3
int 0x10


; practice 1 (print other)

mov ax, p1_title
call print_str

mov al, 1

mov ah, 0x0e
add al, 0x30
int 0x10

mov ax, crlf
call print_str
call print_str

; practice 2 (add 2 num)

mov ax, p2_title
call print_str

mov ax, p2_msg
call print_str

mov bh, 1
mov bl, 3
add bl, bh

mov ah, 0x0e
mov al, bl
add al, 0x30
int 0x10

mov ax, crlf
call print_str
call print_str

; practice 3 (add 1 to 100 and print it)

mov ax, p3_title
call print_str

mov bx, 0
mov ax, 0

add_loop:

add ax, bx
add bx, 1

cmp bx, 100
jle add_loop

mov bx, ax

print_loop:

mov dx, 0
mov ax, bx
mov bx, 10
div bx
mov bx, ax
mov ah, 0x0e
mov al, dl
add al, 0x30
int 0x10

cmp bx, 0

jne print_loop

mov ax, crlf
call print_str
call print_str


; practice 4 (print valu in address)

mov ax, p4_title
call print_str

mov ax, 0x0000
mov bx, _test
mov byte al, [bx]
mov bx, ax

p4_loop:

mov dx, 0
mov ax, bx
mov bx, 10
div bx
;mov cx, dx
mov bx, ax
mov ah, 0x0e
mov al, dl
add al, 0x30
int 0x10

cmp bx, 0

jne p4_loop

mov ax, crlf
call print_str
call print_str

; practice 5 (read disk)

mov ax, p5_title
call print_str

mov ax, 0x07c0
mov es, ax
mov bx, 512

mov ah, 0x02 ; Read Sectors From Drive
mov dl, 0x80 ; Drive
mov al, 0x01 ; Sectors To Read Count ;
mov ch, 0x00 ; Cylinder
mov cl, 0x02 ; Sector(starts from 1, not 0) ; set 2. becouse not need MBR
mov dh, 0x00 ; Head

int 0x13     ; Execute disk read

mov ax, 512
call print_str

mov ax, crlf
call print_str
call print_str

; practice 6 (key read)

mov ax, p6_title
call print_str

mov ax, p6_msg
call print_str

mov ah, 0x00
int 0x16

mov bx, ax
mov bh, 0

p6_loop:

mov dx, 0
mov ax, bx
mov bx, 10
div bx
mov bx, ax
mov ah, 0x0e
mov al, dl
add al, 0x30
int 0x10

cmp bx, 0

jne p6_loop

jmp hang


; end of proccess
;
hang:
jmp hang

p1_title:
	db 'practice 1 (print other)', 0x0a, 0x0d, 0x0a, 0x0d, 0x00

p2_title:
	db 'practice 2 (add 2 num)', 0x0a, 0x0d, 0x0a, 0x0d, 0x00

p2_msg:
	db '1 + 3 : ', 0x00

p3_title:
	db 'practice 3 (add 1 to 100 and print it)', 0x0a, 0x0d, 0x0a, 0x0d, 0x00

p4_title:
	db 'practice 4 (print valu in address)', 0x0a, 0x0d, 0x0a, 0x0d, 0x00

p5_title:
	db 'practice 5 (read disk)', 0x0a, 0x0d, 0x0a, 0x0d, 0x00

p6_title:
	db 'practice 6 (key read)', 0x0a, 0x0d, 0x0a, 0x0d, 0x00

p6_msg:
	db 'ascii code : ', 0x00

crlf:
	db '', 0x0a, 0x0d, 0x00

_test:
	db 0x15, 0x00

print_str:

        push ax
        push si

        mov si, ax
        mov ah, 0x0E

loop:
        lodsb

        or al, al
        jz loop_end

        int 0x10

        jmp loop

loop_end:

        pop si
        pop ax

        ret


times 510-($-$$) db 0

db 0x55
db 0xAA

top_of_2nd_sector:
	db 'Hello Sector No.1', 0x0d, 0x0a, 0x00

times 1024-($-$$) db 0

PlayWithBoot.6.2

実行すると、こんな感じです。

これで一通りやり切しました。
他にもやりようはあるし、数値の表示を左からきちんと表示するとか、手を加えられるところも沢山あると思います。
そのあたり、気になった部分はそれそれ直してみていただければと。

一連のソースですが、githubに上げてみました。素の設定なので公開されている筈です。よろしければ覗いてやってください。

https://github.com/cbwb-inc/software/PlayWithBoot

かなり時間がかかってしまいましたが、『ブートで遊ぼっ!』これにて終了です。
物凄く楽しかった♪

《2024/5/9 12:30:24》

ブートで遊ぼっ!(6)

86系のPCをブートさせて遊んでみよう、というお話の6回目になります。テキストは『OSを書く:初歩から一歩ずつ』です。
環境はAMD64+Windows10+VMware Workstation 17 Player+Debian 12.4です。
今回はテキストの練習問題の4からです。

4. あるメモリアドレスの内容をプリントする

とかすると、axには_testのアドレスが入るわけで、あとはその中身を取り出して…取り出して?
どうやって?
というのがこの課題なんでしょう。
今までやってきたように、axとかに入っている値を扱いたい場合は上の書き方をします。で、それをアドレスとしてその中身を扱いたい場合[]で囲います。例えば今回の「_test」のアドレスの中身を取り出したい場合

とかして、_testのアドレスを取得した上で

とするわけです。1
欲しいのは0x15という値(41行目の_testのあとにdb命令?で定義しています)
alは1Byte。
mov命令は気が利かないので、alが1Byteなんだからbxのアドレスから1Byteだけ読むなんてことをしてくれません。なのでbxのアドレスから([bx])1Byteをalに移送してくれ(move byte alの部分)と指示します。
読み込めたら後は今まで通り、表示するだけ。

; boot.asm

mov ax, 0x07c0
mov ds, ax

mov ah, 0x0
mov al, 0x3
int 0x10

mov ax, 0x0000
mov bx, _test
mov byte al, [bx]
mov bx, ax

loop:

mov dx, 0
mov ax, bx
mov bx, 10
div bx
mov cx, dx
mov bx, ax
mov ah, 0x0e
mov al, dl
add al, 0x30
int 0x10

cmp bx, 0
jne loop


jmp hang

; end of proccess
;
hang:
jmp hang

_test: db 0x15, 0x00

times 510-($-$$) db 0

db 0x55
db 0xAA

PlayWithBoot.5.1

実行すると、こんな感じです。

5. BIOSを使ってディスクから何かを読み込んでみる

わたしはここで挫折しかかりました。ディスク読み込みのBIOSコールは調べられたんですが、パラメータが良くわかりませんでした。
ていうか、端的に言って、シリンダー、ヘッダー、セクターが理解できません。
なんか情報が錯綜しているというか、混じりあってる?
その上、ブートでセクタにはパーティションデーブルがあって、パーティションを特定したらFATがあって、みたいな?
お詫びと開き直りをするまで、結構かかりましたとも。ええ。
で、思ったわけです。
あのね?
わたしはディスクから読み込めればいいの!
FATなんて関係ないの!
と。
そもそもブートセクタは読めているわけで(だから動いてるし)、それはheaderが0、cylinderが0、sectorが1(sectorは1オリジンだそうです)以外にあり得ない。
ならば、それはheaderが0、cylinderが0、sectorを2にすれば読めるんじゃない?
さすがに2sectorの1KByteでheaderもcylinderも変わることないでしょ?
そういう予測のもとにソースを書きます。

; boot.asm

mov ax, 0x07c0
mov ds, ax
mov es, ax

mov ah, 0x0
mov al, 0x3
int 0x10


mov bx, 0x200   ; Destination address to read

mov ah, 0x02    ; Read Sectors From Drive
mov dl, 0x80    ; Drive
mov al, 0x01    ; Sectors To Read Count ;
mov ch, 0x00    ; Cylinder
mov cl, 0x02    ; Sector(starts from 1, not 0) ; set 2. becouse not need MBR
mov dh, 0x00    ; Head

int 0x13        ; Execute disk read

mov si, 0x0200
mov ah, 0x0E

loop:

lodsb

or al, al
jz loop_end

int 0x10

jmp loop

loop_end:


jmp hang


; end of proccess
;
hang:
jmp hang


times 510-($-$$) db 0

db 0x55
db 0xAA

test:
	db 'Hello 2nd sector!', 0x0a, 0x0d, 0x00

_padding:
        times 0x0400-($-$$) db 0

PlayWithBoot.5.2

さて、動かしてみましょう。
こういうのってワクワクしますよねw

はい。無事に動きました。

今回はここまでです。
なんだか、何かをやり遂げた気がします^^

《2024/5/9 1:18:24》

  1. mov al, [_test]とか書けた気がする…。 ↩︎

ブートで遊ぼっ!(5)

86系のPCをブートさせて遊んでみよう、というお話の5回目になります。テキストは『OSを書く:初歩から一歩ずつ』です。
環境はAMD64+Windows10+VMware Workstation 17 Player+Debian 12.4です。
前回、練習問題の1をやったので続きを。

2. 2つの数値を加算する。

すでに終わっている気もしないでもないですが、改めて。
1と3を足してみましょう。

; boot.asm
;
mov ax, 0x07c0
mov ds, ax

mov ah, 0x0
mov al, 0x3
int 0x10

mov ah, 0x0E

mov al, 1
mov bl, 3
add al, bl

add al, 0x30

int 0x10

; end of proccess
;
hang:
jmp hang

times 510-($-$$) db 0

db 0x55
db 0xAA

PlayWithBoot.4.1

説明の必要もないくらいですね。alとblにそれぞれ1と3を入れて、alとblを加算。
それだけです。
ただ、結果を確認できないのも何なので前回やった数値を表示するやりかたで、数値の1を文字の「1」に変換して表示しています。alに0x30を足すところですね。
これを実行すると、こうなります。


3. 1から100までの数を合計して解を画面にプリントする。

ネタばれしてるような気もしますが、こんな感じでソースを書いてみました。

; boot.asm
;
mov ax, 0x07c0
mov ds, ax

mov ah, 0x0
mov al, 0x3
int 0x10

mov ax, 0
mov bx, 0

loop:

add bx, 1
add ax, bx

cmp bx, 100
jl loop

add al, 0x30

int 0x10

; end of proccess
;
hang:
jmp hang

times 510-($-$$) db 0

db 0x55
db 0xAA

PlayWithBoot.4.2

実行すると、こうなります。

えっと、実は数値に0x30を足して文字に変換するって、1文字にしか対応していないんです。0~9は変換できるんですが、10から先には対応していません。
ではどうするか?
『1文字にしか対応できないなら、1文字ずつ表示すればいいじゃない』
はい、その通りです。例えば12を表示するとして、1と2を1文字ずつ表示すればいいんです。ではどうやって?
う~ん、中学受験の点取らせ問題ですね。
10で割った余りが1の位の数値になります。
答えを同じく10で割って余りが次の位の数値になります。
答えを…とまぁ数値の桁数だけ繰り返すわけです。
具体的にはこんな感じ。

12を例にしてソースを書いてみました。

; boot.asm
;
mov ax, 0x07c0
mov ds, ax

mov ah, 0x0
mov al, 0x3
int 0x10

mov cx, 12

mov dx, 0
mov ax, cx
mov bx, 10
div bx
mov cx, ax
mov ah, 0x0e
mov al, dl
add al, 0x30
int 0x10

mov dx, 0
mov ax, cx
mov bx, 10
div bx
mov cx, ax
mov ah, 0x0e
mov al, dl
add al, 0x30
int 0x10

; end of proccess
;
hang:
jmp hang

times 510-($-$$) db 0

db 0x55
db 0xAA

PlayWithBoot.4.3

割り算を行うdiv命令ってなんだか癖があって、axに入れた数値をbxで割って、答えがaxに、余りがdxに設定されます。
今回は10で割るので余りは確実に一桁。答えの下半分であるdlだけ見れば良いということになります。
1の位から上に向かって処理するので、12は21と表示されます。
こんな感じですね。

ここで大いなるお詫びと、胸を張った開き直りをしなければなりません。
12が対象なんだから、そのまま「12」と表示したいところです。
出来るんですけどね。ただものすごく少しだけ面倒くさいんです。
これがお仕事とかなら12と表示させるんですが、今回は処理の結果を確認したいだけなんです。なら21と表示されても、右から読めばいいじゃないですか。
というわけで、この場に限って「わたしはアラビア人~」と言い聞かせながら、右から読むことにします。

さて、元の問題に戻ります。ここでちょっとズルをします。1~100までを足すと答えは5050になります。つまり4桁です。4回処理をすればいいわけです。

; boot.asm
;
mov ax, 0x07c0
mov ds, ax

mov ah, 0x0
mov al, 0x3
int 0x10

mov cx, 12

mov dx, 0
mov ax, cx
mov bx, 10
div bx
mov cx, ax
mov ah, 0x0e
mov al, dl
add al, 0x30
int 0x10

mov dx, 0
mov ax, cx
mov bx, 10
div bx
mov cx, ax
mov ah, 0x0e
mov al, dl
add al, 0x30
int 0x10

; end of proccess
;
hang:
jmp hang

times 510-($-$$) db 0

db 0x55
db 0xAA

PlayWithBoot.4.4

ハイライトは1回分にしか入れてません。同じ処理の繰り返しだし。
で、実行するとこうなります。

無事に0505が表示されました。右から見て5050です。

さて、このソースもソフトウェアを作成する立場としては、ちょっと看過できないです。ほぼ同じコードがドカンドカンと4回もあります。繰り返しなのだからループにしちゃいましょう。
12は2桁なので2回、5050は4桁なので4回実行しています。どんな数値にも対応するためには繰り返しを終わらせる条件がわからなければいけません。
これも中学入試の点取らせ問題レベルかなぁ。
答えが0になったらどんな数で割っても余りは出ません。というわけで答えが0になったら終了すればいいわけです。

; boot.asm
;
mov ax, 0x07c0
mov ds, ax

mov ah, 0x0
mov al, 0x3
int 0x10

mov ax, 0
mov bx, 0

._loop

add bx, 1
add ax, bx

cmp bx, 100
jl ._loop

mov cx, ax

mov dx, 0
mov ax, cx
mov bx, 10
div bx
mov cx, ax
mov ah, 0x0e
mov al, dl
add al, 0x30
int 0x10

mov dx, 0
mov ax, cx
mov bx, 10
div bx
mov cx, ax
mov ah, 0x0e
mov al, dl
add al, 0x30
int 0x10

mov dx, 0
mov ax, cx
mov bx, 10
div bx
mov cx, ax
mov ah, 0x0e
mov al, dl
add al, 0x30
int 0x10

mov dx, 0
mov ax, cx
mov bx, 10
div bx
mov cx, ax
mov ah, 0x0e
mov al, dl
add al, 0x30
int 0x10

; end of proccess
;
hang:
jmp hang

times 510-($-$$) db 0

db 0x55
db 0xAA

PlayWithBoot.4.5

ハイライトは100まで足す部分と、1桁表示するところです。表示は繰り返しなので1回だけで。
実行するとこんな感じです。

今回はここまでです。
お疲れさまでした。

《2024/5/8 6:47:24》

ブートで遊ぼっ!(4)

86系のPCをブートさせて遊んでみよう、というお話の4回目になります。テキストは『OSを書く:初歩から一歩ずつ』です。
環境はAMD64+Windows10+VMware Workstation 17 Player+Debian 12.4です。
一応前回まででテキストの本文(?)については触れたので、今回からは練習問題について書いてみようかと思います。
まずは練習問題の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, x86!', 13, 10, 0

hang:
jmp hang

times 510-($-$$) db 0

db 0x55
db 0xAA

PlayWithBoot.3.1

実行すると、こう。

文字列を追加してみる

さすがに前節で「練習問題クリア!」とかいうのは何なので、文字列を追加して2つの文字列を表示してみます。これも難しくないですね。
ドカンとコピペして該当箇所をちょこっと変えます。
こんな感じ。

; 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 print_2nd_character

int 0x10

jmp print_character_loop

print_2nd_character:
mov si, msg2
mov ah, 0x0E

print_character_loop2:
lodsb

or al, al
jz hang

int 0x10

jmp print_character_loop2

msg:
db 'Hello, World!', 13, 10, 0

msg2:
db 'Hello, x86!', 13, 10, 0

hang:
jmp hang

times 510-($-$$) db 0

db 0x55
db 0xAA

PlayWithBoot.3.2

22行目から34行までがコピーした部分です。設定する文字列のアドレスだけ変更してます。39行目と40行目は追加した文字列の定義ですね。
実行するとこう。

サブルーチンにしてみる

一応、練習問題としてはクリアなのかもしれませんが、ソフトウェアを作る立場としては、ほとんど同じで少しだけ違うコードって気持ちが悪いわけです。
こういう場合、どんな言語でも処理を共通化して、呼び出す形にします。
今回もそうしてみましょう。axに表示したい文字列のアドレスを指定して、サブルーチンを呼び出します。
こんな感じ。

; boot.asm
;
mov ax, 0x07c0
mov ds, ax

mov ah, 0x0
mov al, 0x3
int 0x10

mov si, msg
call print_character

mov si, msg2
call print_character

jmp hang

; sub routine
;
print_character:

mov ah, 0x0E

print_character_loop:
lodsb

or al, al
jz print_character_end

int 0x10

jmp print_character_loop

print_character_end:

ret

; define messages
;
msg:
db 'Hello, World!', 13, 10, 0

msg2:
db 'Hello, x86!', 13, 10, 0

; end of proccess
;
hang:
jmp hang

times 510-($-$$) db 0

db 0x55
db 0xAA

うん。すっきりしました。
実行するとこう。前と変わらないですね^^;
(というか変わったら困ります。それはバグです。)

数値を表示してみる

さて、今までは文字を表示していたんですが、『別のもの』を『文字列でないもの』と解釈することもできるわけです。実際『レジスタの中身を見たい』とか、数値を表示したくなるわけで。例えば数値の1を表示したいとか。
なにも考えずに書いたソースがこれ。

; boot.asm
;
mov ax, 0x07c0
mov ds, ax

mov ah, 0x0
mov al, 0x3
int 0x10

mov ah, 0x0E
mov al, 0x01
int 0x10

; end of proccess
;
hang:
jmp hang

times 510-($-$$) db 0

db 0x55
db 0xAA

PlayWithBoot.3.3

実行すると、こうなります。

あらまぁ、望んだ結果にはなりませんでした。
表示したいのは1で、表示できるのは”1″なんですよね。
文字の”1″を指定してあげないと1は表示されません。
結論から言うと文字の”1″は文字コード(これは数値です)の0x31になります。
なのでソースはこうなります。

; boot.asm
;
mov ax, 0x07c0
mov ds, ax

mov ah, 0x0
mov al, 0x3
int 0x10

mov ah, 0x0E
mov al, 0x01

add al, 0x30

int 0x10

; end of proccess
;
hang:
jmp hang

times 510-($-$$) db 0

db 0x55
db 0xAA

PlayWithBoot.3.4

実行してみます。

望み通り1が表示されました。

ここまでやれば、練習問題1はクリアしたと言っていいんじゃないかな。
なので、今回はここまでです。

《2024/5/3 17:07:24》