このテーマを進めるにあたって、どうしようかいろいろ考えたのですが、まずはディスクイメージを作るところから始めることにしました。
QEMUでの実行ではプログラムを指定というよりディスクイメージを指定しするので、これがないと何もできません。今回はFAT16の32MByteのハードディスクイメージを作成します。
作り方はこれ。
@echo “=== Create 32MB raw image ===”
dd if=/dev/zero of=disk.img bs=1M count=32 status=none
parted -s disk.img mklabel msdos
parted -s disk.img mkpart primary fat16 1MiB 100%
parted -s disk.img set 1 boot on
mkfs.fat -F 16 -n MYDISK –offset 2048 disk.img
ddでファイルのガワを作って、パーティションテーブル作って、ブート可能にして、フォーマットしてます。
まんまですねw
プログラムの方なんですが、オレオレOSというかオレオレ環境なので、どんな構成でも構わないっちゃ構わないんですが、FAT16を選択してしまった以上、決まったお作法があります。
Fat構造だとまず最初にMBR(Master Boot Record)から512Byte 読み込んで実行します。そこでパーティションテーブルを確認して、最初にブート可能なパーティションのVBR(Volume Boot Record)の位置を取り出して、そこを読み込みます。
VBRでは実行するプログラムをファイルシステムから探して読み込みます。
というような、ちょっと面倒くさいお作法になります。
都合上、MBRに置くプログラムをboot0.bin、VBRに置くプログラムをboot1.bin、ファイルシステムから読み込むプログラムをmonitor.binとしています。
アイキャッチの図の通りです。
今回はboot0.binを作りたいと思います。
これは結構楽なんですよね。なにせMBRに置いておけばあとは勝手に読み込まれて実行されるので。
まずは「とにかく動けばいいや」というコードを書いてみます。実はVBRの位置は0x800セクタ、VBRの大きさはMAX4セクタとわかっているので、パラメータを揃えて実行するだけです。
こんな感じ?
; ============================================================
; boot0.asm -- Minimal MBR loader (for HDD, loads boot1 at LBA=2048)
; 制約: FAT16フォーマットのみ対応
; ============================================================
BITS 16
MBR_BASE equ 0x7C00
ORG MBR_BASE
%define BOOT1_SEG 0x0800
%define BOOT1_OFF 0x0000
%define PART_TBL_OFF 446
start:
xor ax, ax
mov ss, ax
mov sp, MBR_BASE
; BOOT DRIVEの保存
mov [BOOT_DRIVE], dl
; --- 表示 ---
mov ax, msg_boot0
call ps
.load:
; --- Disk address packet (for INT 13h AH=42h) ---
mov si, dap
mov word [si + DAP.LBA_Low ], 0x800 ; VBRのLBAを設定
mov word [si + DAP.LBA_High ], 0
mov word [si + DAP.NumBlocks ], 4
mov word [si + DAP.BufferOff ], BOOT1_OFF ; バッファのオフセット
mov word [si + DAP.BufferSeg ], BOOT1_SEG ; バッファのセグメント
mov dl, [BOOT_DRIVE] ; HDD (0x00=FDD)
mov ah, 0x42 ; Extended Read
int 0x13
jc .fail
; 実行確認用デバッグライト
mov ax, BOOT1_SEG
mov es, ax
mov si, BOOT1_OFF
mov cx, 16
.loop
mov al, es:si
call phd1
inc si
loop .loop
; -----------------------------------
; boot1へ遷移
; -----------------------------------
jmp BOOT1_SEG:BOOT1_OFF ; jump to boot1 (VBR)
.fail:
mov al, ah
call phd1
mov ax, msg_fail
call ps
.hlt:
hlt
jmp .hlt
ps:
mov si, ax
.print:
lodsb
or al, al
jz .exit
mov ah, 0x0E
int 0x10
jmp .print
.exit:
ret
phd1:
push ax
mov ah, al
shr al, 4
call .n
mov al, ah
and al, 0x0F
call .n
pop ax
ret
.n:
cmp al, 9
jbe .d
add al, 'A' - 10
jmp .o
.d:
add al, '0'
.o:
push ax
mov ah, 0x0e
int 0x10
; out 0xE9, al
pop ax
ret
BOOT_DRIVE db 0x0
; --- メッセージ ---
msg_boot0 db "B0: loading Boot1...", 0x0D,0x0A,0
msg_fail db "B0: Disk error!", 0x0D,0x0A,0
; ============================
; パーティションエントリ構造体 (16バイト)
; ============================
struc PART_ENTRY
.boot_flag resb 1
.chs_start resb 3
.type resb 1
.chs_end resb 3
.lba_start resd 1
.sectors resd 1
endstruc
;==========================================
; Disk Address Packet (DAP)
; for INT 13h AH=42h/43h
;==========================================
struc DAP
.Size resb 1 ; 構造体のサイズ (必ず16)
.Reserved resb 1 ; 予約 (0)
.NumBlocks resw 1 ; 読み/書きするセクタ数
.BufferOff resw 1 ; 転送先バッファのオフセット
.BufferSeg resw 1 ; 転送先バッファのセグメント
.LBA_Low resd 1 ; 開始LBA下位16bit
.LBA_High resd 1 ; 開始LBA上位32bit
endstruc
; --- DAP構造体エリア (16バイト) ---
align 16
dap:
db 0x10 ; size of packet (16 bytes)
db 0x00 ; reserved
dw 0 ; sectors to read (1)
dw 0 ; offset to load
dw 0 ; segment to load
dq 0 ; LBA of VBR
times 446-($-$$) db 0 ; boot code: 446 bytes total
; --- パーティションテーブル + シグネチャ ---
; partition entry 1 (bootable, FAT16, start=2048, length=18432)
; PARTITION_ENTRY:
; boot_indicator db 0x80
; start_head db 0x01
; start_sector db 0x01
; start_cylinder db 0x00
; partition_type db 0x06
; end_head db 0xFE
; end_sector db 0x3F
; end_cylinder db 0x0F
; start_lba_l dw 0x0800
; start_lba_h dw 0x0000
; total_sectors_l dw 0x4800
; total_sectors_h dw 0x0000
; times 64-16 db 0 ; remaining partition entries
; dw 0xAA55
確認用に読み込んだ内容の先頭16バイトを表示しています。実行結果はこんな感じ。

ディスクイメージをダンプするとこんな感じ。
od -tx1z -Ax build/disk.img
000000 31 c0 8e d0 bc 00 7c 88 16 9a 7c b8 9b 7c e8 4d >1.....|...|..|.M<
000010 00 be d0 7c c7 44 08 00 08 c7 44 0c 00 00 c7 44 >...|.D....D....D<
000020 02 04 00 c7 44 04 00 00 c7 44 06 00 08 8a 16 9a >....D....D......<
000030 7c b4 42 cd 13 72 19 b8 00 08 8e c0 be 00 00 b9 >|.B..r..........<
000040 10 00 26 8a 04 e8 24 00 46 e2 f7 ea 00 00 00 08 >..&...$.F.......<
000050 88 e0 e8 17 00 b8 b2 7c e8 03 00 f4 eb fd 89 c6 >.......|........<
000060 ac 08 c0 74 06 b4 0e cd 10 eb f5 c3 50 88 c4 c0 >...t........P...<
000070 e8 04 e8 09 00 88 e0 24 0f e8 02 00 58 c3 3c 09 >.......$....X.<.<
000080 76 04 04 37 eb 02 04 30 50 e6 e9 58 c3 50 88 e0 >v..7...0P..X.P..<
000090 e8 d9 ff 58 50 e8 d4 ff 58 c3 00 42 30 3a 20 6c >...XP...X..B0: l<
0000a0 6f 61 64 69 6e 67 20 42 6f 6f 74 31 2e 2e 2e 0d >oading Boot1....<
0000b0 0a 00 42 30 3a 20 44 69 73 6b 20 65 72 72 6f 72 >..B0: Disk error<
0000c0 21 0d 0a 00 90 90 90 90 90 90 90 90 90 90 90 90 >!...............<
0000d0 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
0000e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
*
0001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 00 >................<
0001c0 01 10 0e 03 60 ff 00 08 00 00 00 f8 00 00 00 00 >....`...........<
0001d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
*
0001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa >..............U.<
000200 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
*
100000 eb 3c 90 6d 6b 66 73 2e 66 61 74 00 02 04 04 00 >.<.mkfs.fat.....<
100010 02 00 02 00 f8 f8 40 00 20 00 04 00 00 00 00 00 >......@. .......<
100020 00 00 00 00 80 00 29 50 0d de 7c 4d 59 44 49 53 >......)P..|MYDIS<
100030 4b 20 20 20 20 20 46 41 54 31 36 20 20 20 0e 1f >K FAT16 ..<
100040 be 5b 7c ac 22 c0 74 0b 56 b4 0e bb 07 00 cd 10 >.[|.".t.V.......<
100050 5e eb f0 32 e4 cd 16 cd 19 eb fe 54 68 69 73 20 >^..2.......This <
100060 69 73 20 6e 6f 74 20 61 20 62 6f 6f 74 61 62 6c >is not a bootabl<
セクタ0x800、バイトで0x100000の先頭はeb 3c 90。正しく実行されてますね。
さて、骨格ができたところで肉付けを。というかパーテションテーブルをきちんと見るように修正します。パーティションテーブル、4エントリをなめて当たりを探す処理を追加です。
一気に行っちゃいます。
; ============================================================
; boot0.asm -- Minimal MBR loader (for HDD, loads boot1 at LBA=2048)
; 制約: FAT16フォーマットのみ対応
; ============================================================
BITS 16
MBR_BASE equ 0x7C00
ORG MBR_BASE
%define BOOT1_SEG 0x0800
%define BOOT1_OFF 0x0000
%define PART_TBL_OFF 446
%define PART_ENT_SZ 16
start:
xor ax, ax
mov ss, ax
mov sp, MBR_BASE
; BOOT DRIVEの保存
mov [BOOT_DRIVE], dl
; --- 表示 ---
mov ax, msg_boot0
call ps
; -----------------------------------
; boot_indicatorを4件分検査
; -----------------------------------
mov si, MBR_BASE ; MBR先頭
add si, PART_TBL_OFF ; パーティションテーブル開始位置
mov cx, 4 ; 4エントリ
.show_loop: ; ブート可能な最初のエントリを探す
mov al, [si] ; boot_indicator
cmp al, 0x80 ; ブート可能か
je .boot_ok
add si, PART_ENT_SZ ; 次のエントリ
loop .show_loop
mov ax, msg_fail2 ; ブート可能パーティションなし
call ps
jmp .hlt
.boot_ok:
mov ax, [si + PART_ENTRY.lba_start]
.load:
; --- Disk address packet (for INT 13h AH=42h) ---
mov si, dap
mov word [si + DAP.LBA_Low ], ax ; VBRのLBAを設定
mov word [si + DAP.LBA_High ], 0
mov word [si + DAP.NumBlocks ], 4
mov word [si + DAP.BufferOff ], BOOT1_OFF ; バッファのオフセット
mov word [si + DAP.BufferSeg ], BOOT1_SEG ; バッファのセグメント
mov dl, [BOOT_DRIVE] ; HDD (0x00=FDD)
mov ah, 0x42 ; Extended Read
int 0x13
jc .fail
; 実行確認用デバッグライト
mov ax, BOOT1_SEG
mov es, ax
mov si, BOOT1_OFF
mov cx, 16
.loop:
mov al, es:si
call phd1
inc si
loop .loop
; -----------------------------------
; boot1へ遷移
; -----------------------------------
jmp BOOT1_SEG:BOOT1_OFF ; jump to boot1 (VBR)
.fail:
mov al, ah
call phd1
mov ax, msg_fail
call ps
.hlt:
hlt
jmp .hlt
ps:
mov si, ax
.print:
lodsb
or al, al
jz .exit
mov ah, 0x0E
int 0x10
jmp .print
.exit:
ret
phd1:
push ax
mov ah, al
shr al, 4
call .n
mov al, ah
and al, 0x0F
call .n
pop ax
ret
.n:
cmp al, 9
jbe .d
add al, 'A' - 10
jmp .o
.d:
add al, '0'
.o:
push ax
mov ah, 0x0e
int 0x10
; out 0xE9, al
pop ax
ret
BOOT_DRIVE db 0x0
; --- メッセージ ---
msg_boot0 db "B0: loading Boot1...", 0x0D,0x0A,0
msg_fail db "B0: Disk error!", 0x0D,0x0A,0
msg_fail2 db "B0: Not Bootable", 0x0D,0x0A,0
; ============================
; パーティションエントリ構造体 (16バイト)
; ============================
struc PART_ENTRY
.boot_flag resb 1
.chs_start resb 3
.type resb 1
.chs_end resb 3
.lba_start resd 1
.sectors resd 1
endstruc
;==========================================
; Disk Address Packet (DAP)
; for INT 13h AH=42h/43h
;==========================================
struc DAP
.Size resb 1 ; 構造体のサイズ (必ず16)
.Reserved resb 1 ; 予約 (0)
.NumBlocks resw 1 ; 読み/書きするセクタ数
.BufferOff resw 1 ; 転送先バッファのオフセット
.BufferSeg resw 1 ; 転送先バッファのセグメント
.LBA_Low resd 1 ; 開始LBA下位16bit
.LBA_High resd 1 ; 開始LBA上位32bit
endstruc
; --- DAP構造体エリア (16バイト) ---
align 16
dap:
db 0x10 ; size of packet (16 bytes)
db 0x00 ; reserved
dw 0 ; sectors to read (1)
dw 0 ; offset to load
dw 0 ; segment to load
dq 0 ; LBA of VBR
times 446-($-$$) db 0 ; boot code: 446 bytes total
; --- パーティションテーブル + シグネチャ ---
; partition entry 1 (bootable, FAT16, start=2048, length=18432)
; PARTITION_ENTRY:
; boot_indicator db 0x80
; start_head db 0x01
; start_sector db 0x01
; start_cylinder db 0x00
; partition_type db 0x06
; end_head db 0xFE
; end_sector db 0x3F
; end_cylinder db 0x0F
; start_lba_l dw 0x0800
; start_lba_h dw 0x0000
; total_sectors_l dw 0x4800
; total_sectors_h dw 0x0000
; times 64-16 db 0 ; remaining partition entries
; dw 0xAA55
事項結果は前と変わらないので省略。
最後にboot0.binをディスクイメージに書き込む例を挙げて終わりにします。
パーティションテーブルより前の446バイトを書き込んでいます。
@echo "=== Initialize MBR manually ==="
# MBR領域の最初446Bを boot0 で上書き
dd if=boot0.bin of=disk.img bs=446 count=1 conv=notrunc status=none
お疲れさまでした。(主にわたしが疲れてるという……)
《2025/12/3 223:41:32》