MS-DOSのMBRの処理を追う

## 「手探りでCUI OS作成に挑む」連載

この記事は「手探りでCUI OS作成に挑む」連載の一部です。
全体の目次は[「手探りでCUI OS作成に挑む」連載目次](https://qiita.com/earthen94/items/b5d77f98f0cf85a13346)を御覧下さい。

## 目的
現在CUIのOSを開発しています。
動きをできるだけMS-DOSに準拠させるためにMBRをDUMP及び逆アセンブルして処理を追います。

前回QEMUを用いて仮想HDDにMS-DOSをインストールしました。
この仮想HDDのMBR部分(先頭512バイト)の中身を解析していきます。
https://qiita.com/earthen94/items/7491ac22a98e4ae823c9

（因みに私は横文字が嫌いなのですが、この分野はどう頑張っても避けて通れずいろんな横文字を多用してしまいました。）

## MBRとは

## DUMP及び逆アセンブルの生データ

逆アセンブル結果は全てアセンブリ形式で出力されていますが、実際には文字列データやパーティションの情報等も全て無理やりアセンブリとして変換されるので全てがコードという訳ではありません。
```
test@test-ThinkPad-X280:~$ sudo xxd -g1 -l512 msdos_hdd.img
00000000: fa 33 c0 8e d0 bc 00 7c 8b f4 50 07 50 1f fb fc  .3.....|..P.P...
00000010: bf 00 06 b9 00 01 f2 a5 ea 1d 06 00 00 be be 07  ................
00000020: b3 04 80 3c 80 74 0e 80 3c 00 75 1c 83 c6 10 fe  ...<.t..<.u.....
00000030: cb 75 ef cd 18 8b 14 8b 4c 02 8b ee 83 c6 10 fe  .u......L.......
00000040: cb 74 1a 80 3c 00 74 f4 be 8b 06 ac 3c 00 74 0b  .t..<.t.....<.t.
00000050: 56 bb 07 00 b4 0e cd 10 5e eb f0 eb fe bf 05 00  V.......^.......
00000060: bb 00 7c b8 01 02 57 cd 13 5f 73 0c 33 c0 cd 13  ..|...W.._s.3...
00000070: 4f 75 ed be a3 06 eb d3 be c2 06 bf fe 7d 81 3d  Ou...........}.=
00000080: 55 aa 75 c7 8b f5 ea 00 7c 00 00 49 6e 76 61 6c  U.u.....|..Inval
00000090: 69 64 20 70 61 72 74 69 74 69 6f 6e 20 74 61 62  id partition tab
000000a0: 6c 65 00 45 72 72 6f 72 20 6c 6f 61 64 69 6e 67  le.Error loading
000000b0: 20 6f 70 65 72 61 74 69 6e 67 20 73 79 73 74 65   operating syste
000000c0: 6d 00 4d 69 73 73 69 6e 67 20 6f 70 65 72 61 74  m.Missing operat
000000d0: 69 6e 67 20 73 79 73 74 65 6d 00 00 00 00 00 00  ing system......
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 80 01  ................
000001c0: 01 00 06 0f 3f dd 3f 00 00 00 e1 69 03 00 00 00  ....?.?....i....
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.
```
```
test@test-ThinkPad-X280:~$ sudo dd if=msdos_hdd.img of=mbr_dump.bin bs=512 count=1
记录了1+0 的读入
记录了1+0 的写出
512字节已复制，0.000234651 s，2.2 MB/s
test@test-ThinkPad-X280:~$ objdump -b binary -m i8086 -M intel -D mbr_dump.bin

mbr_dump.bin：     文件格式 binary


Disassembly of section .data:

00000000 <.data>:
   0:	fa                   	cli    
   1:	33 c0                	xor    ax,ax
   3:	8e d0                	mov    ss,ax
   5:	bc 00 7c             	mov    sp,0x7c00
   8:	8b f4                	mov    si,sp
   a:	50                   	push   ax
   b:	07                   	pop    es
   c:	50                   	push   ax
   d:	1f                   	pop    ds
   e:	fb                   	sti    
   f:	fc                   	cld    
  10:	bf 00 06             	mov    di,0x600
  13:	b9 00 01             	mov    cx,0x100
  16:	f2 a5                	repnz movs WORD PTR es:[di],WORD PTR ds:[si]
  18:	ea 1d 06 00 00       	jmp    0x0:0x61d
  1d:	be be 07             	mov    si,0x7be
  20:	b3 04                	mov    bl,0x4
  22:	80 3c 80             	cmp    BYTE PTR [si],0x80
  25:	74 0e                	je     0x35
  27:	80 3c 00             	cmp    BYTE PTR [si],0x0
  2a:	75 1c                	jne    0x48
  2c:	83 c6 10             	add    si,0x10
  2f:	fe cb                	dec    bl
  31:	75 ef                	jne    0x22
  33:	cd 18                	int    0x18
  35:	8b 14                	mov    dx,WORD PTR [si]
  37:	8b 4c 02             	mov    cx,WORD PTR [si+0x2]
  3a:	8b ee                	mov    bp,si
  3c:	83 c6 10             	add    si,0x10
  3f:	fe cb                	dec    bl
  41:	74 1a                	je     0x5d
  43:	80 3c 00             	cmp    BYTE PTR [si],0x0
  46:	74 f4                	je     0x3c
  48:	be 8b 06             	mov    si,0x68b
  4b:	ac                   	lods   al,BYTE PTR ds:[si]
  4c:	3c 00                	cmp    al,0x0
  4e:	74 0b                	je     0x5b
  50:	56                   	push   si
  51:	bb 07 00             	mov    bx,0x7
  54:	b4 0e                	mov    ah,0xe
  56:	cd 10                	int    0x10
  58:	5e                   	pop    si
  59:	eb f0                	jmp    0x4b
  5b:	eb fe                	jmp    0x5b
  5d:	bf 05 00             	mov    di,0x5
  60:	bb 00 7c             	mov    bx,0x7c00
  63:	b8 01 02             	mov    ax,0x201
  66:	57                   	push   di
  67:	cd 13                	int    0x13
  69:	5f                   	pop    di
  6a:	73 0c                	jae    0x78
  6c:	33 c0                	xor    ax,ax
  6e:	cd 13                	int    0x13
  70:	4f                   	dec    di
  71:	75 ed                	jne    0x60
  73:	be a3 06             	mov    si,0x6a3
  76:	eb d3                	jmp    0x4b
  78:	be c2 06             	mov    si,0x6c2
  7b:	bf fe 7d             	mov    di,0x7dfe
  7e:	81 3d 55 aa          	cmp    WORD PTR [di],0xaa55
  82:	75 c7                	jne    0x4b
  84:	8b f5                	mov    si,bp
  86:	ea 00 7c 00 00       	jmp    0x0:0x7c00
  8b:	49                   	dec    cx
  8c:	6e                   	outs   dx,BYTE PTR ds:[si]
  8d:	76 61                	jbe    0xf0
  8f:	6c                   	ins    BYTE PTR es:[di],dx
  90:	69 64 20 70 61       	imul   sp,WORD PTR [si+0x20],0x6170
  95:	72 74                	jb     0x10b
  97:	69 74 69 6f 6e       	imul   si,WORD PTR [si+0x69],0x6e6f
  9c:	20 74 61             	and    BYTE PTR [si+0x61],dh
  9f:	62 6c 65             	bound  bp,DWORD PTR [si+0x65]
  a2:	00 45 72             	add    BYTE PTR [di+0x72],al
  a5:	72 6f                	jb     0x116
  a7:	72 20                	jb     0xc9
  a9:	6c                   	ins    BYTE PTR es:[di],dx
  aa:	6f                   	outs   dx,WORD PTR ds:[si]
  ab:	61                   	popa   
  ac:	64 69 6e 67 20 6f    	imul   bp,WORD PTR fs:[bp+0x67],0x6f20
  b2:	70 65                	jo     0x119
  b4:	72 61                	jb     0x117
  b6:	74 69                	je     0x121
  b8:	6e                   	outs   dx,BYTE PTR ds:[si]
  b9:	67 20 73 79          	and    BYTE PTR [ebx+0x79],dh
  bd:	73 74                	jae    0x133
  bf:	65 6d                	gs ins WORD PTR es:[di],dx
  c1:	00 4d 69             	add    BYTE PTR [di+0x69],cl
  c4:	73 73                	jae    0x139
  c6:	69 6e 67 20 6f       	imul   bp,WORD PTR [bp+0x67],0x6f20
  cb:	70 65                	jo     0x132
  cd:	72 61                	jb     0x130
  cf:	74 69                	je     0x13a
  d1:	6e                   	outs   dx,BYTE PTR ds:[si]
  d2:	67 20 73 79          	and    BYTE PTR [ebx+0x79],dh
  d6:	73 74                	jae    0x14c
  d8:	65 6d                	gs ins WORD PTR es:[di],dx
	...
 1be:	80 01 01             	add    BYTE PTR [bx+di],0x1
 1c1:	00 06 0f 3f          	add    BYTE PTR ds:0x3f0f,al
 1c5:	dd 3f                	fnstsw WORD PTR [bx]
 1c7:	00 00                	add    BYTE PTR [bx+si],al
 1c9:	00 e1                	add    cl,ah
 1cb:	69 03 00 00          	imul   ax,WORD PTR [bp+di],0x0
	...
 1fb:	00 00                	add    BYTE PTR [bx+si],al
 1fd:	00 55 aa             	add    BYTE PTR [di-0x56],dl
```

## 分析
さすがに自力で分析することは面倒なためChatGPTを使って分析します。
元々精度はなかなか良いですし、ChatGPTが出力した逆アセンブル結果は自分で逆アセンブルしたものと照らし合わせて間違えていないか確認しながら分析するので心配はありません。

### 初期化処理（0x0000～0x000F）
割り込みを禁止し、スタックとセグメントレジスタを初期化したあとに割り込みを許可する典型的な書き方ですね。
```
0x0000: cli               ; 割り込み禁止
0x0001: xor ax, ax        ; AX = 0
0x0003: mov ss, ax        ; SS = 0（スタックセグメントを0に設定）
0x0005: mov sp, 0x7C00    ; SP = 0x7C00（スタックを初期化）
0x0008: mov si, sp        ; SI = SP
0x000A: push ax; pop es   ; ES = 0
0x000C: push ax; pop ds   ; DS = 0
0x000E: sti               ; 割り込み許可
0x000F: cld               ; 方向フラグクリア（文字列操作用処理）
```

### 自己複製（Relocation）（0x0010～0x0018）
MBRコードを 0x600 に複製し、そちらへ移動して続きのコードを実行します。

MBRはBIOSによって0x7C00へ読み込まれ、0x7C00から実行が始まります。
MBRの処理が終わり使命を終えるとVGR(OSを起動するブートローダ)を0x7C00へ読み込むのですが、そうすると自身が上書きされてしまうために退避をしています。(多分)
```
0x0010: mov di, 0x0600    ; 書き込み先アドレス（偏移0x600）
0x0013: mov cx, 0x0100    ; ワード数 = 256（512バイト）
0x0016: rep movsw         ; DS:SI から ES:DI に 512 バイトをコピー
0x0018: jmp 0x0000:0x061D ; コピー先コードへジャンプ
```

### パーティション探索（0x001D～0x0031）
MBRには最大4つのパーティションが登録できます。
(Windows等で一つのHDDをCドライブ、Dドライブ、Eドライブと複数に分割できるあの機能です。)

先頭のデータが**0x80**(OS起動可能)となっているパーティションを探し、あれば**0x0035**の処理へ移ります。
最大4つまで登録できるため最大4回走査します。
```
001D: BE BE 07         ; mov si, 0x07BE  （パーティションテーブルの先頭）
0020: B3 04            ; mov bl, 0x04     （最大4つの項目を走査）

0022: 80 3C 80         ; cmp byte [si], 0x80  （アクティブフラグ確認）
0025: 74 0E            ; je 0x0035 （見つかったらジャンプ）
0027: 80 3C 00         ; cmp byte [si], 0x00  （非アクティブならOK）
002A: 75 1C            ; jne 0x0048（それ以外は不正 → エラー処理）
002C: 83 C6 10         ; add si, 0x10 （次のエントリへ）
002F: FE CB            ; dec bl （カウンタ減）
0031: 75 EF            ; jne 0x0022 （ループ続行）
```

パーティションテーブルの項目は以下の用になっています。(16バイト/項目×4項目)
このようなデータが最大4つ登録できます。この処理では**0x00**しか参照していません。


| オフセット | 大きさ | 説明                     | 値の例（`0x01BE-0x01CD`） | 補足                          |
|------------|--------|--------------------------|---------------------------|-------------------------------|
| 0x00       | 1バイト| **ブートフラグ**         | `0x80`                    | `0x80`=アクティブ, `0x00`=非アクティブ |
| 0x01       | 1バイト| **開始ヘッド番号**       | `0x01`                    | CHS（Cylinder-Head-Sector）のHead |
| 0x02       | 2バイト| **開始シリンダ/セクタ**  | `0x0001`                  | 下位6ビットがセクタ番号、上位10ビットがシリンダ番号 |
| 0x04       | 1バイト| **パーティション種類** | `0x06`                    | `0x06`=FAT16, `0x0B`=FAT32など |
| 0x05       | 1バイト| **終了ヘッド番号**       | `0x0F`                    |                              |
| 0x06       | 2バイト| **終了シリンダ/セクタ**  | `0x3FDD`                  |                              |
| 0x08       | 4バイト| **開始LBAアドレス**      | `0x0000003F`              | 論理セクタ番号（リトルエンディアン） |
| 0x0C       | 4バイト| **セクタ数**             | `0x000369E1`              | パーティションの大きさ（セクタ単位） |


### 0x0048 - エラー文言出力（Invalid partition）
エラーの文言を出力しそのあと停止します。
CPUを停止させたいときによく使うjmp $ですね。
現在実行中のアドレスへjmpし続けること次の命令を実行させず、停止させることができます。
BIOS呼び出し0x10を使用して文字を一文字づつ出力しています。

mov si, 0x068BのところははMBR内の固定オフセット（0x68B = 0x600 + 0x8B）を指す
```
0048: BE 8B 06         ; mov si, 0x068B;
004B: AC               ; lodsb        ; 1文字読み込み
004C: 3C 00            ; cmp al, 0    ; 終端文字(0)かどうか判別
004E: 74 0B            ; je 0x005B
0050: 56               ; push si
0051: BB 07 00         ; mov bx, 0x0007
0054: B4 0E            ; mov ah, 0x0E
0056: CD 10            ; int 0x10     ; BIOS機能 1文字表示
0058: 5E               ; pop si
0059: EB F0            ; jmp 0x004B
005B: EB FE            ; jmp 0x005B（停止）
```
この処理は関数として3ヶ所から呼び出されている。
以下にその呼び出し部分を挙げる。

#### パーティションテーブル不正​​ 
"Invalid partition table"と表示する
mov si, 0x068Bで文字列を指す
0x68B = 0x600 + 0x8B
```
002A: 75 1C            ; jne 0x0048（それ以外は不正 → エラー処理）
```

#### ​​OS読み込みエラー​​（パーティションセクタの読み込みに失敗した場合）
"Error loading operating system"と表示する
0x06A3 = 0x600 + 00A3
※この場合**mov si, 0x068B**は実行されない
```
0073: BE A3 06         ; mov si, 0x06A3
0076: EB D3            ; jmp 0x004B（エラー表示へ）
```

#### Boot sectorの署名が無効
"Missing operating system"と表示
0x06C2 = 0x600 + 0xC2 
※この場合**mov si, 0x068B**は実行されない
```
0078: BE C2 06         ; mov si, 0x06C2（Missing OS）
007B: BF FE 7D         ; mov di, 0x7DFE（署名チェック位置）
007E: 81 3D 55 AA      ; cmp word [di], 0xAA55
0082: 75 C5            ; jne 0x004B（署名不正 → Missing OS）
```


### 0x005D - パーティションセクタの読み込み開始
5回まで読み込み、最終的に失敗したら "Error loading operating system" を表示

```
005D: BF 05 00         ; mov di, 0x0005（再試行回数）
0060: BB 00 7C         ; mov bx, 0x7C00（読み込み先アドレス）
0063: B8 01 02         ; mov ax, 0x0201（セクタ1個読み込み）
0066: 57               ; push di
0067: CD 13            ; int 0x13（BIOS機能）
0069: 5F               ; pop di
006A: 73 0C            ; jae 0x0078（成功）
006C: 31 C0            ; xor ax, ax
006E: CD 13            ; int 0x13（ディスクリセット）
0070: 4F               ; dec di
0071: 75 ED            ; jne 0x0060（再試行）
0073: BE A3 06         ; mov si, 0x06A3
0076: EB D3            ; jmp 0x004B（エラー表示へ）
```

### 0x0078 - Boot sectorの署名確認と実行
読み込んだブートセクタの末尾が0xAA55であるかを確認し、
違う場合は有効なブートセクタではないと判断してMissing operating systemと表示する。
```
0078: BE C2 06         ; mov si, 0x06C2（Missing OS）
007B: BF FE 7D         ; mov di, 0x7DFE（署名チェック位置）
007E: 81 3D 55 AA      ; cmp word [di], 0xAA55
0082: 75 C5            ; jne 0x004B（署名不正 → Missing OS）
0084: 89 EE            ; mov si, bp（保存したパーティション情報）
0086: EA 00 7C 00 00   ; jmp 0x0000:0x7C00（Boot Sectorへジャンプ）
```


### 0x008B〜：エラー文言（データ）

#### 1つめのパーティション項目（0x01B0〜0x01BF）
```
008B: "Invalid partition table" + 00  
00A3: "Error loading operating system" + 00  
00C2: "Missing operating system" + 00
```

### パーティションテーブル（0x01B0〜0x01FF）
| オフセット      | サイズ   | 説明               | 値（16進）     | 解析値                     |
|----------------|----------|--------------------|----------------|----------------------------|
| 0x01BE         | 1バイト  | ブートフラグ       | 80             | アクティブ（起動可能）     |
| 0x01BF〜0x01C1 | 3バイト  | 開始CHSアドレス     | 01 01 00       | ヘッド:1、セクタ:1、シリンダ:0（詳細計算要） |
| 0x01C2         | 1バイト  | パーティションタイプ | 06             | FAT16                      |
| 0x01C3〜0x01C5 | 3バイト  | 終了CHSアドレス     | 0F 3F DD       | ヘッド:15、セクタ:63、シリンダ:221（詳細計算要） |
| 0x01C6〜0x01C9 | 4バイト  | 開始LBA            | 3F 00 00 00    | 63（0x3F）                 |
| 0x01CA〜0x01CD | 4バイト  | セクタ数           | E1 69 03 00    | 224,737セクタ（約109.7MB） |

## 今後
次回は、LBA 63 に存在すると推定されるボリューム・ブート・レコード（VBR）の解析を行います。
最終的には、MS-DOS と同様の手順で自作 OS を起動することを目標としています。

一般的な OS 自作書籍では、MBR（マスターブートレコード）と VBR（ボリュームブートレコード）の役割を明確に分けず、一体化した単純な構成で解説されている例が多く見られます。MBR から直接カーネルを読み込んで起動する方法も確かに有効ですが、FAT16 などのファイルシステムを取り入れる場合、MS-DOS 方式の「MBR → VBR → IO.SYS → MSDOS.SYS」的な2段階構成の方が実装しやすく、将来的な拡張にも対応しやすいと考えています。

私のこれまでの記事でも、MBR（先頭の 512 バイト）から直接カーネルコードを呼び出す例を扱いましたが、ここからは実際の OS に近い構成を意識し、MBR と VBR を分けて扱っていきます。