x86レガシーBIOSで遊んでみようというネタの4回目になります。
環境は以前と変わってWindows11+VSCode+WSLになります。
さて、拡張ディスクリードというBIOSコールがあるそうです。
なんでもシリンダ、ヘッダ、セクタの構成を気にしないで、セクタ番号だけでディスクを読み込めるとか。それは何気に嬉しいなぁ、というわけで試してみました。
拡張ディスクリード
拡張ディスクリードがどんなものかというと、DAP構造体とかいうものを使うそうです。DAP構造体に必要なパラメータを設定して実行すると、ディスクから読み込まれるらしいです。DAP構造体ってのは1バイト目が0x10固定、2バイト目が0固定……
説明するよりソース見た方が早いですかね。なのでソースです。
;>===========================
;> BIOSと戯れてみる
;>===========================
BITS 16
ORG 0x7C00
start:
cli
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
sti
.load:
; --- Disk address packet (for INT 13h AH=42h) ---
mov si, dap
mov dl, 0x80 ; HDD (0x00=FDD)
mov ah, 0x42 ; Extended Read
int 0x13
jc .fail
mov si, 0x8000
mov cx, 0x10
call dump_mem
jmp .hlt
.fail:
mov al, ah
call ph1
mov al, ':'
call pc
mov si, msg_fail
call ps
.showfail:
lodsb
or al, al
jz .hlt
mov ah, 0x0E
int 0x10
jmp .showfail
.hlt:
hlt
jmp .hlt
; --- メッセージ ---
msg_fail db "Disk read error!", 0x0D,0x0A,0
; --- DAP構造体 (16バイト) ---
align 16
dap:
db 0x10 ; size of packet (16 bytes)
db 0x00 ; reserved
dw 1 ; sectors to read (1)
dw 0x8000 ; offset to load
dw 0x0000 ; segment to load
dq 0 ; LBA
;>****************************
;> pc : 1文字出力
;>****************************
pc:
push ax
mov ah, 0x0e
int 0x10
pop ax
ret
;>****************************
;> ps : 文字列出力
;>****************************
ps:
push ax
push si
mov si, ax
.loop:
lodsb
test al, al
jz .exit
call pc
jmp .loop
.exit:
pop si
pop ax
ret
;>****************************
;> ph1 : 1Byteを16進で出力
;>****************************
ph1:
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:
call pc
ret
;>****************************
;> dump_mem : メモリダンプ
;>****************************
dump_mem:
push si
.next:
test cx, cx
jz .done
mov al, [es:si]
call ph1
mov al, ' '
call pc
inc si
dec cx
jmp .next
.done:
pop si
ret
; --- パーティションテーブル + シグネチャ ---
times 446-($-$$) db 0 ; boot code: 446 bytes total
; partition entry 1 (bootable, FAT16, start=2048, length=18432)
db 0x80, 0x01, 0x01, 0x00, 0x06, 0xFE, 0x3F, 0x0F, 0x00, 0x08, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00
times 64-16 db 0 ; remaining partition entries
dw 0xAA55
Sector1 db 0x01, 'SECTOR 1', 0x0a, 0x0d, 0x00
times 1024-($-$$) db 0
Sector2 db 0x02, 'SECTOR 2', 0x0a, 0x0d, 0x00
times 2048-($-$$) db 0
実行するとBIOS.BINの最初のセクタの冒頭がダンプされます。
で、DAP構造体です。
DAP構造体
; --- DAP構造体 (16バイト) ---
align 16
dap:
db 0x10 ; size of packet (16 bytes)
db 0x00 ; reserved
dw 1 ; sectors to read (1)
dw 0x8000 ; offset to load
dw 0x0000 ; segment to load
dq 0 ; LBA
内容というか意味はコメントである程度分かると思います。
size of packet : 16固定です。
reserved :0固定なのかな?
sectors to read :読み込むセクタ数
offset of load :データを読み込むバッファのアドレス
segment to load :データを読み込むバッファのセグメント
LBA :読み込み開始セクタ(0オリジン)
下4つを指定して読み込むわけですね。
今回は
sectors to read :1
offset of load :0x8000
segment to load :0x0000
LBA :0
ですから、ディスクの0セクタ目から1セクタ分を0x0x0000:8000に読み込むことになります。ディスクをダンプしてみて0x100000バイト目を読み込みたいときは1セクタ分の512で割って0x800セクタを読めばいいわけです。
いやぁ、楽です。ヘッダ・シリンダ・セクタを計算しないですむってホント楽です。
固定データだと、読み込む場所が変わるたびに構造体を用意しなければいけないんで面倒だし不合理です。せっかくなのでこの辺を可変にしてみましょう。
パラメータ指定
パラメータ指定する場合のサンプルコードはこちら。
;>===========================
;> BIOSと戯れてみる
;>===========================
BITS 16
ORG 0x7C00
start:
cli
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
sti
.load:
; setup dap structure
mov si, dap
mov word [si+0x02], 1
mov word [si+0x04], 0x8000
mov word [si+0x06], 0x0000
mov word [si+0x08], 2
; --- Disk address packet (for INT 13h AH=42h) ---
mov si, dap
mov dl, 0x80 ; HDD (0x00=FDD)
mov ah, 0x42 ; Extended Read
int 0x13
jc .fail
mov si, 0x8000
mov cx, 0x10
call dump_mem
jmp .hlt
.fail:
mov al, ah
call ph1
mov al, ':'
call pc
mov si, msg_fail
call ps
.showfail:
lodsb
or al, al
jz .hlt
mov ah, 0x0E
int 0x10
jmp .showfail
.hlt:
hlt
jmp .hlt
; --- メッセージ ---
msg_fail db "Disk read error!", 0x0D,0x0A,0
; --- 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
;>****************************
;> pc : 1文字出力
;>****************************
pc:
push ax
mov ah, 0x0e
int 0x10
pop ax
ret
;>****************************
;> ps : 文字列出力
;>****************************
ps:
push ax
push si
mov si, ax
.loop:
lodsb
test al, al
jz .exit
call pc
jmp .loop
.exit:
pop si
pop ax
ret
;>****************************
;> ph1 : 1Byteを16進で出力
;>****************************
ph1:
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:
call pc
ret
;>****************************
;> dump_mem : メモリダンプ
;>****************************
dump_mem:
push si
.next:
test cx, cx
jz .done
mov al, [es:si]
call ph1
mov al, ' '
call pc
inc si
dec cx
jmp .next
.done:
pop si
ret
; --- パーティションテーブル + シグネチャ ---
times 446-($-$$) db 0 ; boot code: 446 bytes total
; partition entry 1 (bootable, FAT16, start=2048, length=18432)
db 0x80, 0x01, 0x01, 0x00, 0x06, 0xFE, 0x3F, 0x0F, 0x00, 0x08, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00
times 64-16 db 0 ; remaining partition entries
dw 0xAA55
Sector1 db 0x01, 'SECTOR 1', 0x0a, 0x0d, 0x00
times 1024-($-$$) db 0
Sector2 db 0x02, 'SECTOR 2', 0x0a, 0x0d, 0x00
times 2048-($-$$) db 0
キモというかポイントはこの部分。
; setup dap structure
mov si, dap
mov word [si+0x02], 1
mov word [si+0x04], 0x8000
mov word [si+0x06], 0x0000
mov word [si+0x08], 2
ここで、DAPの位置から2バイト目にセクタ数、4バイト目にメモリオフセット、6バイト目にセグメント、8バイト目に開始セクタを指定しています。
実行すると第2セクタが読み込まれます。
これを少し修正すれば汎用のディスク読み込みルーチンができそうですね。
さて、今日はここまでです。お疲れさまでした。
《2025/11/02 07:56:23》