「ブートで遊ぼっ!」カテゴリーアーカイブ

ブートで遊ぼっ!(7)

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
; 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まで、一つにまとめてみました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
; 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の部分)と指示します。
読み込めたら後は今まで通り、表示するだけ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
; 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も変わることないでしょ?
そういう予測のもとにソースを書きます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
; 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を足してみましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
; 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までの数を合計して解を画面にプリントする。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
; 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を例にしてソースを書いてみました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
; 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回処理をすればいいわけです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
; 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になったら終了すればいいわけです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
; 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番です。

画面に他のものをプリントする。

これ、ものすごく面白い問題だなぁと。といいますか「他のもの」ってのが曖昧過ぎるなぁ。いろんな解釈が出来そう…。
思いつくままに作ってみましょう。

別の文字をプリントする。

これは簡単ですね。文字列の定義を変えるだけです。なのでこんな感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
; 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つの文字列を表示してみます。これも難しくないですね。
ドカンとコピペして該当箇所をちょこっと変えます。
こんな感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
; 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に表示したい文字列のアドレスを指定して、サブルーチンを呼び出します。
こんな感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
; 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を表示したいとか。
なにも考えずに書いたソースがこれ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
; 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になります。
なのでソースはこうなります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
; 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》

ブートで遊ぼっ!(3)

86系のPCをブートさせて遊んでみよう、というお話の3回目になります。テキストは『OSを書く:初歩から一歩ずつ』です。
環境はAMD64+Windows10+VMware Workstation 17 Player+Debian 12.4です。
前回はMakefileを作ったところで気力が尽きちゃったんだけど、今回はどうなるかなぁ(不安)
まぁ、最低でも文字を表示するところは終わらせるつもりですけど…。

スクリーンにプリントする

画面に文字を表示します。定番と言うか、「またお前か!」というか、何と言うか。
Hello World
です。
細かい説明は(不十分かもしれませんが)テキストにありますので、まずはそちらを参考にしてください。ざっくり言うと、表示したい文字列をメモリ上に定義して、その最初のアドレスから文字列が終わるまで1文字ずつ表示しています。
ていうか、これくらいしか言うこと無くない?これ以上何を言えと?
とか思ったり思わなかったり。
ともあれ、ソースを作成します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
; 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さん大活躍です。

1
make qemu

『Hello World!』が表示されました!

めでたしめでたし!
おしまい。

とか言いたいところなんですが、もし私が読み手としてこの記事をみたら
「ふざけんな!ボケぇ」
とか言いたくなるような気がするので、少しだけ追加です。

ahとal

ふと。
ahとalってaxレジスタのハイとロー、上位バイトと下位バイトじゃなかったっけ?
なら

mov ah, 0x0
mov al, 0x3

って

mov ax, 0x0003

とかにしても動くんじゃない?
というわけでソース修正。
こんな感じ。5行目が修正箇所ですね。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
; 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回目0x480x480H
 2回目0x650x650e
 3回目0x6c0x6c0l
 4回目0x6c0x6c0l
 5回目0x6f0x6f0o
: :: :: :: :: :
 13回目0x210x210!
 14回目0x0d0x0d0CR
 15回目0x0a0x0a0LF
 16回目0x000x001NULL
alの値とゼロフラグ

定義した文字列で一番最後の0x00の時だけ、ゼロフラグが1になります。
なので、最初から文字を1文字ずつ表示して0x00まで来たらhangに飛ぶという処理となり、めでたく『Hello World!』が表示されるというわけです。

蛇足

『alがゼロだったら~』という処理なんだから素直に

cmp al, 0x00
je hang

じゃいけないの?
という向きもあると思います。なので動かしてみましょう。
ソースはこれ。15行目、16行目を修正してます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
; 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》

ブートで遊ぼっ!(2)

86系のPCをブートさせて遊んでみよう、というお話の2回目です。テキストは『OSを書く:初歩から一歩ずつ』です。
環境はAMD64+Windows10+VMware Workstation 17 Player+Debian 12.4です。
今回はMakefileを作るのと、余裕があれば文字を表示するところまでかな。届くかな?

ビルドプロセスを短縮する

Makefileを作ってビルド…というかアセンブルから実行までを簡単にしましょう、というお話。
『今時make?』という気もしなくはないんですが、アセンブラでboot周りのことをやるのなら『やっぱmakeよね!』とも思います。
やっていることというか、やりたいことは凄く単純で

と、2回コマンドを打っていたのを

で済まそうということ。
いちいちだらだらタイプしたくないし、ヒストリから拾うのも面倒だし、という怠け者の発想ですね。怠けるためにメンドクサイ仕込みをするとか、世の中よくあることです。
makeの使い方とかMakefileの書式とかを知りたい方は、ネットで調べてもらえば沢山記事があると思いますので、そちらに任せます。

テキスト通りMakefileというファイルを作成します。


実行してみます。



ん?
なにこれ?

って、どういうこと?

ちょっと調べてみたら、Makefileではコマンドの前にタブがないといけないみたいです。
何て言うか…『年寄りはこれだから困る』とか言いたくなってみたり?
気を取り直してMakefileを修正します。

2行目、5行目、8行目の先頭にタブを入れます。
で、改めて実行します。

ちゃんと動きました。めでたしめでたし。

ここで気力が尽きました。文字列の表示は次回だなぁ…。

《2024/4/19 11:14:24》

ブートで遊ぼっ!

x86系のPCをブートさせて遊んでみようと思います。
OSを作ろうとか、大それたことは言いません。技術力も知識も能力も全く足りないですから。
そもそもOSを作るのって、とっても

テキストは『OSを書く:初歩から一歩ずつ』です。ざっと目を通してみたんですが、それほどボリュームないし、解説もそれなりにあるし、これなら1日あれば確認できるかなと。チュートリアルだしねw

前提条件

テキストがDebianを前提にしているので、それに乗っかります。
今回のわたしの環境は

Intel Core i7
Windows10
VMwera Workstation 17 Plyer
Debian 12.4

です。Debianはこのために新規で入れました。ほとんど、まっさらです。

テキストで『nasm』『build-essential』『qemu』が必要だというのでインストールします。
Debianを起動してターミナルエミュレーターを実行し、以下を入力します。

む!

とか返ってきた。
どうしよう、どうする?

テキストは2016年とか2017年に書かれたものだし、今は2024年だし、8年もあれば生まれた子供も小学校3年生?になるし、パッケージ構成が変わっててもおかしくはないなぁ。
ともあれ、これでお終いにするってのは、あまりに情けなさすぎる。どうしたものか。

少し調べてみたら、色々な情報がありました。余計なものをインストールしても邪魔にならなければいいんだけれど、できるなら最小限の構成にしたいなぁ。

なので、これでどうだっ!

インストールは成功。
ではソースを書きましょう。
ファイル名は『boot.asm』です。

1
2
3
4
5
6
7
8
9
10
; boot.asm
hang:
jmp hang
 
times 510-($-$$) db 0
 
; This is a comment
 
db 0x55
db 0xAA

PlayWithBoot.1

コードの解説はテキストに任せて、書かれたとおりにオペレーションします。

問題ないですね。

動きました。こんな感じです。


これで前提条件が終了です。

なんか、思ったより先が長そう…

《2024/4/17 0:04:09》