x86リアルモードで動くモニタプログラムのHyfaxを作るお話の8回目です。boot0→boot1→monitorとやってきて、monitorに機能を実装しました。今回はmonitorからappをロードし実行する部分です。
アプリのロードと起動
実はアプリのロードと起動って、実を言うともう完成してるって言ってもいいんですよね。そもそも既にあるboot1の機能は
①『MONITOR BIN』をルートディレクトリから探す
②見つかった『MONITOR BIN』をメモリに読み込む
③読み込んだメモリに制御を移す
という処理をやっているわけです。んじゃmonitorは何をするのかというと
①『APP BIN』をルートディレクトリから探す
②見つかった『APP BIN』をメモリに読み込む
③読み込んだメモリに制御を移す
何のことはない、ファイル名が違うだけなんです。(厳密に言うと、読み込むメモリの位置も違うんですが。)そこだけこちょこちょっと変えれば完成してしまうのですね。
ただ、boot1をまるっとコピーして必要な部分だけ修正するのは楽なんですが、例によって「ほとんど同じコードがあちこちに存在する」というのがね。背筋がぞわぞわしてくるので関数化してしまいます。
該当部分をloadapp.asmにくくりだして修正を加えます。
axにファイル名、es:bxに読み込むエリアのセグメントとオフセットを指定します。
[BITS 16]
loadapp:
mov [TARGET_APP_NAME], ax
mov ax, es
mov [TARGET_APP_SEG], ax
mov [TARGET_APP_OFF], bx
xor ax, ax
mov es, ax
; -----------------------------------
; Bootドライブ
; -----------------------------------
mov al, es:[BOOT_DRV_OFF]
mov [BOOT_DRIVE], al
; -----------------------------------
; VBR_LBA = start_lba
; -----------------------------------
mov ax, es:[VBR_START_LBA]
mov word [VBR_LBA], ax
; -----------------------------------
; SEC_PER_CLUS
; -----------------------------------
mov al, es:[SEC_PER_CLUS_OFF]
mov byte [SEC_PER_CLUS], al
; -----------------------------------
; ROOT_ENT_CNT
; -----------------------------------
mov ax, es:[ROOT_ENT_CNT_OFF]
mov [ROOT_ENT_CNT], ax
; -----------------------------------
; FAT_LBA
; -----------------------------------
mov ax, es:[FAT_START_LBA]
mov [FAT_LBA], ax
; -----------------------------------
; DATA_LBA
; -----------------------------------
mov ax, es:[DATA_START_LBA]
mov word [DATA_LBA], ax
; -----------------------------------
; DIR_LBA
; -----------------------------------
mov ax, es:[DIR_START_LBA]
mov [DIR_LBA], ax
; -----------------------------------
; CLUS_SIZ
; -----------------------------------
mov ax, es:[CLUS_SIZ_OFF]
mov [CLUS_SIZ], ax
; -----------------------------------
; BYTE_PER_SEC
; -----------------------------------
mov ax, es:[BYTE_PER_SEC_OFF]
mov [BYTE_PER_SEC], ax
mov [BYTE_PER_SEC_SAVE], ax
; --- 転送先を設定 ---
mov bx, BUF_SEG
mov es, bx
mov bx, BUF_OFF
; -----------------------------------
; Dir EntからTarget Fileのエントリを探す
; -----------------------------------
mov ax, [ROOT_ENT_CNT]
mov bx, 32
mul bx
mov bx, [BYTE_PER_SEC]
div bx
mov word [DIR_ENT_SEC_CNT], ax
mov ax, [DIR_LBA]
mov si, [TARGET_APP_NAME]
dir_ent_sec_loop:
; -----------------------------------
; Dir Entを読み込む
; -----------------------------------
; Dir Entを読み込むための設定
mov si, dap
mov word [si + DAP.NumBlocks], ENT_SECTORS ; sectors
mov word [si + DAP.BufferOff], BUF_OFF ; buffer offset
mov word [si + DAP.BufferSeg], BUF_SEG ; buffer segment
mov ax, [DIR_LBA]
mov word [si + DAP.LBA_Low], ax ; LBA low Low
mov dword [si + DAP.LBA_High], 0 ; LBA high
mov dl, [BOOT_DRIVE]
mov ah, 0x42
int 0x13
jc disk_error
mov di, BUF_OFF
mov cx, 16
find_entry:
mov al, es:[di]
cmp al, 0x00
je not_found
cmp al, 0xE5
je next_entry
mov al, es:[di+11]
test al, 0x18 ; Volume or Dir
jnz next_entry
; --- compare 11 bytes "MONITOR BIN" ---
push di
mov si, [TARGET_APP_NAME]
mov bx, 11
cmp_loop:
mov al, es:[di]
mov ah, [si]
; PUTC '['
; PUTC ah
; PUTC ':'
; PUTC al
; PUTC ']'
cmp al, ah
jne cmp_ng
inc di
inc si
dec bx
jnz cmp_loop
jmp find_dir_ent
cmp_ng:
pop di
next_entry:
add di, 32
loop find_entry
next_sec:
mov ax, [DIR_LBA]
inc ax
mov [DIR_LBA], ax
mov ax, [DIR_ENT_SEC_CNT]
dec ax
mov [DIR_ENT_SEC_CNT], ax
cmp ax, 0
jnz dir_ent_sec_loop
not_found:
mov al, 'N'
out 0xE9, al
jmp done
disk_error:
mov al, 'X'
out 0xE9, al
mov al, ah
call phd2
jmp hlt
find_dir_ent:
; --- 一致 ---
; クラスタ・LBA変換
pop di
mov bx, es:[di+26] ; FirstCluster low
mov ax, es:[di+20] ; FirstCluster high
mov [TARGET_CLUS], bx ; ファイルのあるクラスタ
sub bx, 2
mov al, [SEC_PER_CLUS]
mov ah, 0
mul bx
mov bx, ax
mov ax, [DATA_LBA] ; データ開始LBA
add ax, bx
mov [FILE_LBA], ax ; ファイルのLBA
; ファイルサイズ・セクタ変換
mov ax, es:[di+28] ; File size low word
add ax, 511
shr ax, 9
mov [FILE_SECTOR], ax ; ファイルのセクタ数
mov cx, 0
file_read_loop:
PUTC '.'
mov si, dap
mov ax, [SEC_PER_CLUS]
mov word [si + DAP.NumBlocks], ax ; sectors
mov ax, [SEC_PER_CLUS]
mov bx, [BYTE_PER_SEC_SAVE]
mov [BYTE_PER_SEC], bx
mul bx
mul cx
add ax, [TARGET_APP_OFF]
mov word [si + DAP.BufferOff], ax ; buffer offset
add ax, [TARGET_APP_SEG]
mov word [si + DAP.BufferSeg], ax ; buffer segment
mov ax, [FILE_LBA]
mov word [si + DAP.LBA_Low], ax ; LBA low Low
mov dword [si + DAP.LBA_High], 0 ; LBA high
mov dl, [BOOT_DRIVE]
mov ah, 0x42
int 0x13
jc disk_error
mov ax, [FILE_SECTOR]
sub ax, [SEC_PER_CLUS]
mov [FILE_SECTOR], ax
cmp ax, 0
jle exit_loop
inc cx
; ------------------------------------------------
; FAT16チェーンを辿って次のクラスタを取得する
; current cluster = [TARGET_CLUS]
; ------------------------------------------------
; ★ FAT16: エントリサイズ 2byte
; offset_bytes = cluster * 2
; FAT_sector = FAT_LBA + offset_bytes / BytesPerSec
; byte_offset = offset_bytes % BytesPerSec
; ------------------------------------------------
mov ax, [TARGET_CLUS] ; 現在のクラスタ番号
mov bx, 2
mul bx ; DX:AX = cluster * 2 (FAT内オフセット[byte])
mov bx, [BYTE_PER_SEC] ; 通常 512
div bx ; AX = FAT内セクタオフセット, DX = セクタ内byteオフセット
mov [FAT_TARGET_CLUS], ax ; FAT内セクタオフセットとして再利用
mov [FAT_TARGET_ENT], dx ; セクタ内byteオフセット
; FATセクタのLBA = FAT_LBA + FAT内セクタオフセット
mov ax, [FAT_LBA]
add ax, [FAT_TARGET_CLUS]
mov [TARGET_FAT_LBA], ax
; ------------------------------------------------
; FATセクタを1セクタだけBUFへ読み込み
; ------------------------------------------------
mov si, dap
mov word [si + DAP.NumBlocks], 1 ; FATは1セクタで十分
mov word [si + DAP.BufferOff], BUF_OFF
mov word [si + DAP.BufferSeg], BUF_SEG
mov ax, [TARGET_FAT_LBA]
mov word [si + DAP.LBA_Low], ax
mov dword [si + DAP.LBA_High], 0
mov dl, [BOOT_DRIVE]
mov ah, 0x42
int 0x13
jc disk_error
; ------------------------------------------------
; 読み込んだFATセクタから次クラスタ取得
; ------------------------------------------------
mov ax, BUF_SEG
mov es, ax
mov si, BUF_OFF
mov bx, [FAT_TARGET_ENT] ; セクタ内byteオフセット
add si, bx
mov ax, [es:si] ; FAT16なので2バイト読み
; ------------------------------------------------
; FAT16: 終端判定 (0xFFF8以上をEOFとみなす)
; ------------------------------------------------
cmp ax, 0xFFF8
jae exit_loop ; EOFなので読み込み終了
cmp ax, 0x0002
jb exit_loop ; 無効クラスタはとりあえず終了扱い
; ------------------------------------------------
; 次クラスタ → FILE_LBAへ変換
; clusterN → LBA = DATA_LBA + (clusterN - 2) * SEC_PER_CLUS
; ------------------------------------------------
mov [TARGET_CLUS], ax ; 現在クラスタを更新
mov bx, ax ; bx = clusterN
sub bx, 2
mov al, [SEC_PER_CLUS]
mov ah, 0
mul bx ; AX = (clusterN - 2) * SEC_PER_CLUS
mov bx, ax
mov ax, [DATA_LBA]
add ax, bx
mov [FILE_LBA], ax ; 次クラスタの先頭LBA
jmp file_read_loop
exit_loop:
done:
; 読み込んだ内容の冒頭をダンプ(目視確認用)
; mov ax, [TARGET_APP_SEG]
; mov es, ax
; mov si, [TARGET_APP_OFF]
; mov cx, 0x20
; call dump_mem
; mov ax, BOOT1_SEG
; mov ds, ax
; mov ax, _s_msg_jmp_monitor
; call ps
ret
; _s_filename db 'APPF BIN', 0x00
_s_msg_start db 'B1:Start', 0x0d, 0x0a, 0x00
_s_msg_jmp_monitor db 'B1:jmp', 0x0d, 0x0a, 0x00
_s_msg_read_monitor db 'B1:read', 0x0a, 0x0d, 0x00
BOOT_DRIVE db 0
SEC_PER_CLUS db 0
ROOT_ENT_CNT dw 0
VBR_LBA dw 0
DATA_LBA dw 0
DIR_LBA dw 0
FILE_SECTOR dw 0
FILE_LBA dw 0
DIR_ENT_LBA dw 0
DIR_ENT_SEC_CNT dw 0
FIRST_CLUS dw 0
TARGET_FAT_LBA dw 0
FAT_TARGET_ENT dw 0
FAT_TARGET_CLUS dw 0
FAT_LBA dw 0
FAT_ENT_CNT dw 0
FILE_TARGET_CLUS dw 0
BYTE_PER_CLUS dw 0
TARGET_CLUS dw 0
CLUS_SIZ dw 0
BYTE_PER_SEC dw 0
BYTE_PER_SEC_SAVE dw 0
TARGET_APP_SEG dw 0
TARGET_APP_OFF dw 0
TARGET_APP_NAME dw 0
align 16
dap: ;
db 0x10, 0x00 ; size / reserved
dw 0 ; sectors
dw 0x0000 ; buffer offset
dw 0x0 ; buffer segment
dq 0x0 ; LBA (low=2048, high=0)
こうするとboot1.asmがどうなるかというと、こうなります。すっかすかです。
[BITS 16]
org 0x8000
%macro PUTC 1
push ax
mov al, %1
out 0xE9, al
pop ax
%endmacro
%include 'hifax.asm'
jmp start
nop
OEMLabel db 'MSDOS5.0' ; 8 bytes
BytesPerSec dw 0x0200
SecPerClus db 4 ; 10MB パーティションでは4が妥当
RsvdSecCnt dw 0x0004 ; mformatのデフォルト
NumFATs db 2
RootEntCnt dw 0x0200 ; HDDでは512が一般的
TotSec16 dw 0xf800 ; パーティションサイズ (9MB)
Media db 0xf8 ; HDD
FATSz16 dw 0x0040 ; FAT16サイズ(要計算)114?
SecPerTrk dw 0x0020
NumHeads dw 4
HiddSec dd 0 ; パーティション開始LBA
TotSec32 dd 0
; ======================================================
start:
PUTC 'B'
PUTC '1'
PUTC ':'
cli
mov ax, 0x7000
mov ss, ax
mov sp, 0xfff0
sti
mov ax, _s_msg_start
call ps
mov ax, MON_SEG
mov es, ax
mov bx, MON_OFF
mov ax, _s_target_name
call loadapp
jmp MON_SEG:MON_OFF
hlt:
hlt
jmp hlt
_s_target_name db 'MONITOR BIN', 0x00
%include 'loadapp.asm'
%include 'common.asm'
monitor.asmにもloadappを呼び出す部分を追加します。
; ============================================================================
; monitor.asm
; バッファ = 09e0:0000, DLはブートからの値をそのまま使用
; 制約: FAT16フォーマットのみ対応(FATsz16 != 0 必須)
; ============================================================================
[BITS 16]
[SECTION .text]
jmp start
%include 'hifax.asm'
; ---------------- デバッグ出力 ----------------
%macro PUTC 1
push ax
mov al,%1
out 0xE9,al
pop ax
%endmacro
global start
; ---------------- 本体 ----------------
start:
PUTC 'M'
PUTC ':'
cli
mov ax, STACK_SEG
mov ss, ax
mov sp, 0xE000
sti
mov ax, MON_SEG
mov ds, ax
mov es, ax
call install_int80
mov si, _s_msg_monitor
mov ah, 0x20 ; write(DS:SI, CX)
int 0x80
mov ax, APP_SEG
mov es, ax
mov bx, APP_OFF
mov ax, _s_target_name
call loadapp
jmp APP_SEG:APP_OFF
jmp hlt
hlt:
hlt
jmp hlt
; =====================================================================
; syscall.asm (INT 80h 拡張版)
; =====================================================================
[BITS 16]
[SECTION .text]
; ------------------------------------------------------------
; install_int80: INT80h のハンドラを IVT に登録
; ------------------------------------------------------------
install_int80:
cli
push ax
push bx
push es
xor ax, ax
mov es, ax ; IVTセグメント
mov bx, 0x80*4 ; INT80hエントリ番地
mov word [es:bx], int80_handler
mov word [es:bx+2], MON_SEG ; モニタのCS値
pop es
pop bx
pop ax
sti
ret
; ------------------------------------------------------------
; INT80h ハンドラ本体
; ------------------------------------------------------------
int80_handler:
; push ax
push bx
push cx
push dx
push si
push di
push bp
push ds
push es
cmp ah, 0x20
je .svc_write
cmp ah, 0x21
je .svc_puthex
cmp ah, 0x22
je .svc_getkey
cmp ah, 0x23
je .svc_putchar
cmp ah, 0x24
je .svc_newline
cmp ah, 0x25
je .svc_cls
cmp ah, 0x26
je .svc_reboot
jmp .svc_exit
; ------------------------------------------------------------
.svc_write: ; AH=20h : write string DS:SI (0終端)
mov bx, 0x0007
mov ah, 0x0E
.w_loop:
lodsb
test al, al
jz .svc_exit
int 0x10
jmp .w_loop
; ------------------------------------------------------------
.svc_puthex: ; AH=21h : AXを16進で出力
push ax
mov al, ah
call phd2
pop ax
call phd2
jmp .svc_exit
; ------------------------------------------------------------
.svc_getkey: ; AH=22h : キー入力待ち (ALに返す)
xor ax, ax
int 0x16
jmp .svc_iret
; ------------------------------------------------------------
.svc_putchar: ; AH=23h : ALを1文字出力
mov ah, 0x0E
mov bx, 0x0007
int 0x10
jmp .svc_exit
; ------------------------------------------------------------
.svc_newline: ; AH=24h : 改行(CR+LF)
mov ah, 0x0E
mov al, 0x0D
int 0x10
mov al, 0x0A
int 0x10
jmp .svc_exit
; ------------------------------------------------------------
.svc_cls: ; AH=25h : 画面クリア
mov ax, 0x0003
int 0x10
jmp .svc_exit
; ------------------------------------------------------------
.svc_reboot: ; AH=26h : 再起動
; jmp 0FFFFh:0000h
cli
.wait_kbc:
in al, 0x64
test al, 0x02
jnz .wait_kbc
mov al, 0xFE
out 0x64, al
hlt
; ------------------------------------------------------------
.svc_exit:
.svc_iret:
pop es
pop ds
pop bp
pop di
pop si
pop dx
pop cx
pop bx
; pop ax
iret
%include 'loadapp.asm'
%include 'common.asm'
_s_msg_monitor db 'Hello from Monitor', 0x0d, 0x0a, 0x00
_s_msg_wait: db 'Press any key to continue...', 0x0d, 0x0d, 0
_s_target_name db 'APPF BIN', 0x00
times 1280-($-$$) db 0
実行結果はこちら。

monitorからのHello、appからのHello、Monitor内・App内で実行したSyscallの結果が確認できます。
終わりに
8回にわたり続けてきました『Hyfaxのこと』もこれで一区切りです。この後は文字入力を可能にして、簡易シェルを作成し、複数アプリを実行してもよし、マルチタスクに挑戦してもよし。ロングモードに移行してもよしです。望むままの世界が開けています。
さて、名残は尽きないものの、皆様の開発ライフに幸が多からんことを祈りつつ、この辺で終わりにしたいと思います。
お付き合いありがとうございました。
この連載の成果物はgithubに公開しています。よろしければどうぞ。
https://github.com/CbWB-Inc/software/tree/main/laboratory/lab02/hyfax_base
《2025/12/21 1:08:21》