x86リアルモードでモニターを作ろうシリーズです。Windows11+wsl2で構築しています。
キーボードをタイプしてプログラムを起動できるようになりました。そこでコマンドを作ってみようと思います。定番ではあるものの作るコマンドはechoです。与えられた引数を表示するだけのプログラムですね。実用性はともかく、引数を受け取ってそれを操作する基本を押さえるのに重宝するプログラムです。
処理の概要
今回作るまで知らなかったのですが例えば『echo aaa bbb』とか打ち込むと途中の複数の空白を一つにまとめて『aaa bbb』と表示されるのですね。何のことはない、コマンド自体をsplitでパースして、コマンド自体を除いた引数部分を順に表示するという仕様なわけです。
ということであれば、もうsplitはできているわけで、コマンド部分を読み取り、それを実行し、コマンド内では引数部分を列挙表示すればよいということです。これならすぐできそうです。
実際の実行イメージはこんな感じでしょうか
mov bx, [app_arg_data]
; APPを実行
jmp APP_SEG:APP_OFF
レジスタはどれでもよかったのですが、axは成功失敗を受け取るために使用していたので、引数はbxで渡しています。bxにsplitした引数を設定して、アプリを実行です。
実際の実行部にあわせて、必要な部分で引数を設定していきます。まず最初はtss.asmですね。抜粋するとこんな感じ。
; 入力文字列をパース
; DS:SI = 入力文字列
; ES:DI = THS 構造体先頭
; al = 区切り文字
mov si, line_buf
mov di, line_buf_ths
mov al, ' '
call split
mov bx, di ; di = line_buf_ths
mov si, [line_buf_ths]
mov ah, svc_exec
int 0x80
コマンドの実行はシステムコールにしています。
で、monitor.asmのsvc_execがこんな感じ。
; ★★★ BXを一旦AXに退避 ★★★
mov ax, bx
; ★★★ ここで引数を monitor のグローバル変数に保存 ★★★
pop ax ; 退避していたAXの値
mov [app_arg_data], ax
; --- 83形式に変換 (DSはMON_SEGのままでOK) ---
call to_83
; --- 実行 ---
mov ax, file_name
call runapp
mov [.exit_code], ax
都合、loadapp.asm、monitor.asm、tts.asmの3つを変更しています。
echo本体
echo本体は引数を受け取って順次表示するだけです。こんな感じ。
; echo.asm - simple and working version
BITS 16
%include 'hyfax.asm'
%macro PUTC 1
push ax
mov al, %1
out 0xE9, al
pop ax
%endmacro
start:
PUTC 'E'
PUTC ':'
; ★★★ BXを先にDIに退避 ★★★
mov di, bx
; --- 通常の初期化 ---
push cs
pop ds
push cs
pop es
push cs
pop ss
mov sp, 0xfffe
; ★★★ ths_offsetに保存 ★★★
mov [ths_offset], di
; ; メッセージ表示
; mov si, msg_hello
; mov ah, svc_write
; int 0x80
; mov ah, svc_newline
; int 0x80
; TTS_SEGから引数の数を取得
mov ax, TTS_SEG
mov es, ax
mov di, [ths_offset]
mov cx, es:[di + THS.cnt]
; デバッグ: 引数の数を表示
; mov si, msg_cnt
; mov ah, svc_write
; int 0x80
; mov bx, cx
; mov ah, svc_puthex
; int 0x80
; mov ah, svc_newline
; int 0x80
; 引数が2つ以下(コマンド名のみ)なら何も表示しない
cmp cx, 2
jl .no_args
; 全ての引数を表示(1番目から)
mov word [current_index], 1
.loop:
; 現在のインデックスを取得
mov si, [current_index]
; 終了判定
mov ax, TTS_SEG
mov es, ax
mov di, [ths_offset]
cmp si, es:[di + THS.cnt]
jge .done
; デバッグ: インデックスを表示
; mov si, msg_idx
; mov ah, svc_write
; int 0x80
; mov bx, [current_index]
; mov ah, svc_puthex
; int 0x80
; mov al, ':'
; mov ah, svc_putchar
; int 0x80
; 引数のオフセットを取得
mov si, [current_index]
mov bx, si
shl bx, 1
add bx, THS.off
mov bx, es:[di + bx] ; BX = 文字列のオフセット(TTS_SEG内)
; デバッグ: オフセットを表示
; push ds
; push cs
; pop ds
; mov si, msg_off
; mov ah, svc_write
; int 0x80
; pop ds
; push bx
; mov ah, svc_puthex
; int 0x80
; mov al, '='
; mov ah, svc_putchar
; int 0x80
; pop bx
; 文字列を表示(TTS_SEGから)
mov ax, TTS_SEG
mov ds, ax
mov si, bx
mov ah, svc_write
int 0x80
mov ah, svc_putchar
mov al, ' '
int 0x80
; DSを戻す
push cs
pop ds
; 改行を表示
; mov ah, svc_newline
; int 0x80
; 次の引数へ
inc word [current_index]
jmp .loop
.no_args:
mov si, msg_no_args
mov ah, svc_write
int 0x80
.done:
mov ah, svc_newline
int 0x80
mov ah, svc_newline
int 0x80
; mov si, msg_wait
; mov ah, svc_write
; int 0x80
; mov ah, svc_getkey
; int 0x80
exit:
push word TTS_SEG
push word 0x0000
retf
%include 'common.asm'
; ---- data ----
msg_hello: db 'Hello from ECHO', 13, 10, 0
msg_cnt: db 'Arg count: ', 0
msg_idx: db 'Index ', 0
msg_off: db ' offset=', 0
msg_no_args: db '(no arguments)', 0
msg_wait: db 13, 10, 'Press any key to return shell...', 13, 10, 0
ths_offset: dw 0
current_index: dw 0
times 3072-($-$$) db 0
デバッグ用の表示がそのまま残っていますが、許してください。いつ必要になるかわからない開発中の状況ですんでそのままにしてあります。
実際の実行結果はこうなります。

終わりに
今回はここまでになります。
それにしても、コマンドとか作ってできることが多くなってくるとわくわくしますよね♪
このソースはgithubで公開しています。よろしければどうぞ。
https://github.com/CbWB-Inc/software/tree/main/laboratory/lab02/hyfax-05-echo
《2026/01/11 15:38:35》