OS自作入門 1, 2日目 機械語のブートセクタを解読する

# きっかけ
　[「30日でできる!OS自作入門」](https://book.mynavi.jp/ec/products/detail/id=22078)のゼミがインターン先で始まることになって、その予習を始めました。本では、1日目にブートセクタをバイナリエディタで入力して実行したあと、2, 3日目でアセンブリ言語、C言語を導入していて、機械語とは1日でおさらばっぽいです。個人的には機械語⇔アセンブリの対応にも興味があるので、ブートセクタの部分だけでも機械語・アセンブリ対訳で読んでみたいのです。

# 目標
- 1日目で入力したブートセクタ（helloos.imgの冒頭512バイト）を1バイトずつ追って、2日目に出てくるアセンブリ（helloos.nas）との対応を確認する。
- そのために：Intel 8086の機械語をデコードする方法を調べる。起動直後、CPUがリアルモードで動いているときは8086の上位互換なので、8086について知っていればしばらくは読めそう。

　2つ目もやると長くなるので、この記事では1つ目だけ書きます。2つ目は別の機会にまとめるかもしれないです。2つ目に該当する参考文献だけを次項に書くことにします。

# 仮定と参考資料

　機械語をアセンブリに翻訳して、やっていることを理解することが目標なので、レジスタなどのCPUのことについて改めてまとめたりはしません。そのほか、本の1, 2日目に書いてあることも仮定します。

　参考にするドキュメントは[こちら](https://edge.edx.org/c4x/BITSPilani/EEE231/asset/8086_family_Users_Manual_1_.pdf)です。160ページ以降（Machine Instruction Encodeing and Decoding）に出てくるTable 4-8, 4-10, 4-13をめちゃくちゃ使います。45ページからの2.7 Instruction Setに各命令の詳細があるので、必要に応じて参照するようにします。

# おことわり
　機械語を調べ始めたのが昨日なので、ドキュメント全部読めていませんし、憶測で読み進めている箇所が多々あります。したがって、その規則はどこに書いてあるのか？と聞かれても確かなことは答えられないかもしれません。誤字・脱字・表記・間違いの指摘や、参考になる資料を教えてくださるのは大歓迎です。
　説明中の＝（イコール）は必ずしも数学的な等号ではありません。左右が意味的に同じようなものだと考えたときには、説明の短縮のために＝を使っています。

# ソースコード（機械語）
　読むプログラムは、以下にコピペしたブートセクタです。512バイトです。これがBIOSによってメモリの0x7C00から0x7DFFに読み込まれ、先頭から実行されるんだそうです。なのでEB 4E ...から読み始めればいいわけです。

```
00000000   EB 4E 90 48  45 4C 4C 4F  49 50 4C 00  02 01 01 00  .N.HELLOIPL.....
00000010   02 E0 00 40  0B F0 09 00  12 00 02 00  00 00 00 00  ...@............
00000020   40 0B 00 00  00 00 29 FF  FF FF FF 48  45 4C 4C 4F  @.....)....HELLO
00000030   2D 4F 53 20  20 20 46 41  54 31 32 20  20 20 00 00  -OS   FAT12   ..
00000040   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000050   B8 00 00 8E  D0 BC 00 7C  8E D8 8E C0  BE 74 7C 8A  .......|.....t|.
00000060   04 83 C6 01  3C 00 74 09  B4 0E BB 0F  00 CD 10 EB  ....<.t.........
00000070   EE F4 EB FD  0A 0A 68 65  6C 6C 6F 2C  20 77 6F 72  ......hello, wor
00000080   6C 64 0A 00  00 00 00 00  00 00 00 00  00 00 00 00  ld..............
00000090   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000000A0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000000B0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000000C0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000000D0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000000E0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000000F0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000100   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000110   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000120   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000130   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000140   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000150   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000160   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000170   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000180   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
00000190   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000001A0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000001B0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000001C0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000001D0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000001E0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  ................
000001F0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 55 AA  ..............U.
```

# 読んでいく

## 0x 0000 0000

```
00000000   EB 4E 90 48  45 4C 4C 4F  49 50 4C 00  02 01 01 00  .N.HELLOIPL.....
```

### EB 4E
　まずどの命令なのかを決定したいです。Table 4-13を見ます。どうやら8086の命令は、最初の1バイトでだいたい何をする命令なのかわかるようです。左側、1ST BYTEのカラムで**EB**を探します。2ND BYTEカラムに**IP-INC8**、ASM-86 INSTRUCTION FORMATのカラムに**JMP SHORT-LABEL**とありました。
　命令を解釈します。**JMP**なので無条件ジャンプです。**SHORT-LABEL**は「後続の8bitを符号付き2進数と思え」という意味っぽいです（2.7 Instruction Setを読みました）。したがって、**EB 4E**でひとかたまりの命令だということになります。
　ジャンプ先を計算します。**0x4E**を2進数に直すと**0100 1110**ですが、最高位が0なので符号付き2進数としてもそのまま正の数**0x4E**ということになります。ジャンプ先は、直後の命令の番地（この場合は**EB 4E**の次ですから**0x0002**）に先ほど計算した**0x4E**を加算することで**0x0050**とわかりました。なお、このジャンプは相対的なジャンプなので、今の計算ではブートセクタが0x7C00以降にコピーされる事実を無視しています。絶対番地が必要なら、**0x0050 + 0x7C00 = 0x7C50**とすればよいでしょう。
　helloos.nasには、**JMP entry**という行がありました。おそらく、この**entry**がアセンブル時に**0x0050**に変換され、さらに番地を逆算して**EB 4E**になったんじゃないでしょうか。アセンブラのソースコード読んでここらへんの仕組みも知りたいです。
　これは後でわかることですが、**0x0050**に飛んだあと、プログラムの実行は0x004F以前に戻ってくることはありません。なので0x0002から0x004Fまでは無視して0x0050以降を読んでいくことにします。本によればここにはフロッピーディスクとかFAT12ファイルシステムとかに必要なデータが書いてあるらしいです。

## 0x 0000 0050

```
00000050   B8 00 00 8E  D0 BC 00 7C  8E D8 8E C0  BE 74 7C 8A  .......|.....t|.
```
### B8 00 00
　どんどん読んでいきます
　Table 4-13いわく、**B8**は**MOV AX IMMED16**だそうです。**AX**レジスタに2バイトの**直値**を代入するということです。**B8**に加えて、2バイトの直値**00 00**でひとセットです。**MOV AX, 0**がhelloos.nasでの該当箇所だと思います。
### 8E D0
　**8E**は**MOV SEGREG, REG16/MEM16**です。これは面倒くさいやつです。まず、直後のバイトを2進数と見ます（**D0 -> 1101 0000**）。そしてこれを2bit, 3bit, 3bitに分割し（**1101 0000 -> 11 010 000**）、それぞれを**MOD, REG, R/M**と呼びます（**MOD = 11, REG = 010, R/M = 000**）。
　これらの使い方を説明します。**MOV SEGREG, REG16/MEM16**の**SEGREG**を決定するためには、**REG（= 010）**を使用します。本にはセグメントレジスタとして、

```
000 ES
001 CS
010 SS
011 DS
100 FS
101 GS
```
が挙げられていました。左の番号は自分でつけたものです。このセグメントレジスタの中で、番号が**REG（= 010）**のもの、すなわち**SS**が**SEGREG**となります。
　次に、**REG16/MEM16**の決定方法ですが、これは2段階にわけられます。1段階目は、レジスターから読み出すのかメモリーから読み出すのかの決定です。これには**MOD（= 11）**を使用します。Table 4-8を見ると、**MOD = 11**は**Register Mode**だということがわかります。2段階目は、どのレジスターから読み出すのか、ということになりますが、これも本に出てきた16ビットのレジスターと**R/M（= 000）**を見比べればわかります。

```
000 AX
001 CX
010 DX
011 BX
100 SP
101 BP
110 SI
111 DI
```
の中で番号が**R/M（= 000）**のものは**AX**です。これで**8E D0**が**MOV SS, AX**に翻訳できました。

### BC 00 7C
　**BC**は**MOV SP, IMMED16**ですね。**00 7C**が直値ということになります。ここで注意しないといけないのは、**SP**に代入されるのは**0x007C**ではなくて、**0x7C00**だということです。2バイト以上の直値が出てきたら、バイト単位に区切って逆順に並び替えればいいんだと自分は理解しています。このようなエンコード方式（？）をリトルエンディアンと言うらしいです。helloos.nasにもちゃんと**MOV SP, 0x7c00**という行があります。

### 8E D8
　**8E = MOV SEGREG, REG16/MEM16**,
　**D8 = 1101 1000 = 11 011 000**
したがって、
　**SEGREG = DS**,
　**REG16/MEM16 = AX**
ゆえに
　**8E D8 = MOV DS, AX**

### 8E C0
　**C0 = 1100 0000 = 11 000 000**
したがって
　**SEGREG = ES**,
　**REG16/MEM16 = AX**
ゆえに
　**8E C0 = MOV ES, AX**

### BE 74 7C
**BE = MOV SI, IMMED16**
　すなわち、**SI**レジスタに直値**0x7C74**が代入されます。helloos.nasに**MOV SI, msg**なる行がありますが、msgラベルの場所がブートセクタの中で**0x0074**と計算され、実際のメモリ上では**0x7C74**の場所にあると計算されているんだと思われます。
　8Aというバイトが残っていますが、putloopの前で区切りがいいので節を変えます。

## 0x 0000 0060

```
00000050   B8 00 00 8E  D0 BC 00 7C  8E D8 8E C0  BE 74 7C 8A  .......|.....t|.
00000060   04 83 C6 01  3C 00 74 09  B4 0E BB 0F  00 CD 10 EB  ....<.t.........
00000070   EE F4 EB FD  0A 0A 68 65  6C 6C 6F 2C  20 77 6F 72  ......hello, wor
```
### 8A 04
　**8A = MOV REG8, REG8/MEM8**,
　**04 = 0000 0100 = 00 000 100**
8ビットレジスタの**REG（= 000）**番目ですから、**REG8 = AL**です。

```
000 AL
001 CL
010 DL
011 BL
100 AH
101 CH
110 DH
111 BH
```
　Table 4-8を見ると、**MOD = 00**だから**Memory Mode**だということがわかります。また、**no displacement follows**なので、後続のバイトはもうありません。じゃあメモリーのどこを読みに行くんだというのは、Table 4-10を見ればわかります。**R/M = 100, MOD = 00**なので、**"(SI)"**を読み込めということになります。**"(SI)"**はhelloos.nasでは**[SI]**と書かれていますが、レジスタ**SI**が指し示すメモリー上の番地です。初回のループではmsg（= 0x7c74）番地の1バイトが**AL**に代入されます。

### 83 C6 01
　**C6 = 1100 0110 = 11 000 110**ですから、1バイト目が**83**, 2バイト目が**MOD 000 R/M**の行が該当する命令です。**ADD REG16/MEM16, IMMED8**ですね。**MOD = 11, R/M = 110**なので、**R/M = 110**番目の16ビットレジスター**SI**がdestです。srcは**IMMED8**, すなわち1バイトの直値であり、それは**0x01**です。

### 3C 00
　**3C = CMP AL, IMMED8**
**AL**と1バイトの直値**0x00**を比較してフラグを立てます。

### 74 09
　**74 = JE/JZ SHORT-LABEL**
前項の比較結果が**E**qual/**Z**eroのときにジャンプします。**09 = 0000 1001**を符号付き2進数とみなしても**09**です。直後の命令の番地が**0x0068**なので、**0x0068 + 0x0009 = 0x0071**が行き先です（面倒くさいのでブートセクタの中での相対値です。）。ラベルで言うとfin, 命令で言うと**F4**ですね。

### B4 0E
　**B4 = MOV AH, IMMED8**
**0E**は直値です。

### BB 0F 00
　**BB = MOV BX, IMMED16**
**0F 00 = 0x000F**は直値です。リトルエンディアンに注意。

### CD 10
　**CD = INT IMMED8**
**10**が直値です。

### EB EE
　きりが良いので0x 0000 0070に食い込みます。**EB**は**JMP SHORT-LABEL**です。今まで符号付き2進数といいながら全部正の数だったのですが、初めて負の数が登場します。計算方法を説明しましょう。**EE**を2進数に直します（**= 1110 1110**）。最高位とそれ以外に分けます（**= 1 1101110**）。最高位だけマイナスをつけることに注意して10進数に直すと、**- 128 + 64 + 32 + 8 + 4 + 2 = - 18**となります。直後の命令の番地**0x0071**から**18 = 0x0012**を引いた番地に飛びます。**0x0071 - 0x0012 = 0x005F**で、ラベルで言うとputloopです。

## 0x 0000 0070

```
00000070   EE F4 EB FD  0A 0A 68 65  6C 6C 6F 2C  20 77 6F 72  ......hello, wor
```
### F4
　**F4 = HLT**です。

### EB FD
　**EB**は**JMP SHORT-LABEL**でした。ジャンプ先を計算しましょう。**FD = 1111 1101 = 1 1111101 = - 128 + 64 + 32 + 16 + 8 + 4 + 1 = - 3**です。直後の命令（というかデータ）の番地は**0x0074**ですから**0x0074 - 0x0003 = 0x0071**にジャンプすることになります。命令で言うと**F4**, ラベルで言うとfinですね。

## データ部分

```
00000070   EE F4 EB FD  0A 0A 68 65  6C 6C 6F 2C  20 77 6F 72  ......hello, wor
00000080   6C 64 0A 00  00 00 00 00  00 00 00 00  00 00 00 00  ld..............
```
**0A 0A**から始まる16バイトは文字のASCIIコードです。そのあとは**0x01FD**番地までは**00**が続きます。このブートセクタに限って言えば、この**00**だらけの部分のコードが実行されることはないし、データとして読み込まれることもありません。

## 一応言及しておく

```
000001F0   00 00 00 00  00 00 00 00  00 00 00 00  00 00 55 AA  ..............U.
```
　**0x01FE, 0x01FF**の**55 AA（0xAA55）**はブートセクタの末尾に必ずつけなければならないシグネチャだそうです。
　このあとには、2セクタ目と11セクタ目の冒頭に、FAT12ファイルシステム的に必要らしい**F0 FF FF（0xFFFFF0？）**がありますが、**0x01FF**までのブートセクタが続きを読み込まずに無限ループに入るので、今回は使われません。以上で、1日目に入力したブートセクタの挙動を最初から最後まで追うことができました。