MONITORが動くようになりAPP実行から終了までできるようになりました。でもそれだけです。まだ入力とかもほとんどできません。プログラム名の入力→実行とか出来るようにしたいな~……。
というわけで、まずはshellを目指すのでした。これがないとHyfax君と会話できないし♪
shellといっても超簡易版です。最初ですんで何かキーを押したら反応が返ってくる、それくらいのやつ。
なにはともあれechoの実装
echoから作ります。shellに組み込む前に動作確認ですね。せっかくAPPが動くようになったんで、APP内で作って動作チェックして、それからshellとして取り込みましょう。
ドライバのソースはこれ。
;********************************
; echo確認用ドライバ
;********************************
exp_echo:
.loop:
mov di, line_buf ; バッファの準備
call echo ; echoの実行
mov si, line_buf ; 戻ってきた値を出力
mov ah, svc_write
int 0x80
mov ah, svc_newline ; 後片付け
int 0x80
mov ax, line_buf ; バッファのクリア
mov bl, 0
mov cx, line_buf_size
call memset
jmp .loop
ret
本体がこちら。
;********************************
; echo
;********************************
; IN : DI = buffer
; OUT: buffer filled, CX = length
; END: buffer is NUL-terminated
; CLOBBER: AX, CX, DI
echo:
.main_loop:
; --- ブロッキングで1キー待ち (AH=0x22) ---
mov ah, svc_getkey ; getkey_wait() -> AX=ASCII(0含む)
int 0x80
cmp al, 0x0d
je ._0d
test al, al
jz .skip_echo
mov al, al
mov ah, svc_putchar
int 0x80
mov [di], al
inc di
inc byte [.cnt]
jmp .main_loop
._0d:
mov byte [di], 0x00
xor cx, cx
mov byte cl, [.cnt]
cmp cl, 0
je .skip_echo
mov ah, svc_newline
int 0x80
.skip_echo:
ret
.cnt db 0
line_buf: times line_buf_size db 0
実行結果

ちゃんと返ってきてます。これで勝ち確ですよね。アプリ名を入力して、前回作ったapploadに渡せば実行できる流れです。とはいえ今はappに居る関係上、このまま実行するとアドレスが被るんで動かないんでしょうけれども。動かすにはもう少し調整が必要です。(今回はやりません。)
履歴機能とか
コマンド(らしきもの)が入力できるようになると、欲しくなってくるのが履歴機能です。command historyですね。以前打ち込んだ内容を↑キーとか↓キーで表示してくれるやつです。プログラミングの練習がてら上下カーソルキーで文字列を変更するプログラムも書いてみましょう。
実行確認用ドライバのソースはこれ。
;********************************
; 履歴(history)確認用ドライバ
;********************************
exp_history:
.loop3:
mov di, line_buf
call history ; history機能
jmp .loop3
ret
本体のソースはこれです。
;********************************
; history
;********************************
history:
.loop4:
mov ah, svc_getkey ; getkey_wait() -> AX=ASCII(0含む)
int 0x80
test al, al
jz .ext_key
cmp al, 0x0d ; return
je ._0d
.ext_key:
.up:
dec byte [hisp]
js .up_worp
jmp .exit
.up_worp:
mov byte [hisp], 3
jmp .exit
.down:
inc byte [hisp]
cmp byte [hisp], 4
jl .exit
mov byte [hisp], 0
jmp .exit
.exit:
cmp byte [.cnt], 0
je .loop4
xor cx, cx
mov cl, 79
call clear_line
xor ax, ax
mov al, [hisp]
mov bx, line_buf_size
mul bx
mov si, hibuf
add si, ax
mov ah, svc_write
int 0x80
mov ah, svc_putchar
mov al, 0x0d
int 0x80
jmp .loop4
._0d:
ret
実行結果はこちら。

上下キーでone、two、three、fourが切り替わります。
そして1行入力へ
echoとhistoryが形になりました。次にこれを悪魔合体させて1行入力を作りましょう。
例によって確認用ドライバのソースはこれ。
;********************************
; line_input確認用ドライバ
;********************************
exp_line_input:
.loop1:
mov di, line_buf ; バッファの準備
call line_input ; historyの実行
xor cx, cx ; 0文字なら何もしない
mov ax, line_buf
call strlen
cmp cx, 0
je .skip
mov si, line_buf ; バッファの表示
mov ah, svc_write
int 0x80
mov ah, svc_newline ; 改行
int 0x80
mov ax, line_buf ; バッファのクリア
mov bl, 0
mov cx, cx
call memset
.skip:
jmp .loop1
ret ; 無限ループなのでここには来ない
本体のソースはこちら。
;********************************
; line_input
;********************************
line_input:
mov ax, line_buf
call strlen
mov [.cnt], cl
.loop2:
; --- ブロッキングで1キー待ち (AH=0x22) ---
mov ah, svc_getkey ; getkey_wait() -> AX=ASCII(0含む)
int 0x80
; for debug
push ax
mov bx, ax
mov ah, svc_puthex
int 0x80
pop ax
test al, al
jz .ext_key
cmp al, 0x0d ; return
je ._0d
cmp al, 0x08 ; BS
je ._bs
cmp al, 0x09 ; tab
je ._tab
jmp .ascii
.ext_key:
cmp ah, 0x48 ; ↑ : History up
je .up
cmp ah, 0x50 ; ↓ : History down
je .down
jmp .loop2
.ascii:
; mov al, al
mov ah, svc_putchar
int 0x80
mov [di], al
inc di
inc byte [.cnt]
jmp .loop2
.up:
dec byte [hisp]
js .up_worp
jmp .exit
.up_worp:
mov byte [hisp], 3
jmp .exit
.down:
inc byte [hisp]
cmp byte [hisp], 4
jl .exit
mov byte [hisp], 0
jmp .exit
.exit:
cmp byte [.cnt], 250 ; バッファサイズ上限チェック (250文字まで)
jae .loop2 ; 上限なら入力を無視してループへ
xor cx, cx
mov cl, [.cnt]
call clear_line
xor ax, ax
mov al, [hisp]
mov bx, line_buf_size
mul bx
mov si, hibuf
add si, ax
mov ah, svc_write
int 0x80
mov ax, line_buf
mov bl, 0
mov cx, line_buf_size
call memset
mov ax, line_buf
mov bx, si
call strcpy
mov [.cnt], cl
mov di, line_buf
add di, cx ; CX = strlen
jmp .loop2
._bs:
cmp byte [.cnt], 0
je .loop2
dec byte [.cnt]
dec di
; 画面上の削除処理 (BS -> 空白 -> BS)
mov al, 0x08
mov ah, svc_putchar
int 0x80 ; カーソル戻す
mov al, ' '
mov ah, svc_putchar
int 0x80 ; 空白で文字を消す
mov al, 0x08
mov ah, svc_putchar
int 0x80 ; カーソルを再度戻す
jmp .loop2
._tab:
; tabで現在の履歴バッファの内容を反映
; 直前のコマンドをリピートしたいときに便利
jmp .exit
._0d:
xor ax, ax
mov al, [hisp]
mov bx, line_buf_size
mul bx
mov si, hibuf
add si, ax
mov ax, si
mov bl, 0
mov cx, line_buf_size
call memset
mov ax, si
mov bx, line_buf
call strcpy
mov ah, svc_newline
int 0x80
mov byte [.cnt], 0
mov di, line_buf
ret
.cnt db 0
hibuf: ; history buffer
db 'one', 0, 0, 0
times 250 db 0
db 'two', 0, 0, 0
times 250 db 0
db 'three',0
times 250 db 0
db 'four', 0, 0
times 250 db 0
hisp: db 0
上下キーで履歴の内容を表示、文字入力可能でエンターキーで確定して戻ります。
戻ったらバッファの内容を表示して文字数、バッファをクリアしています。
実行結果はこちら。

履歴を切り替えてバッファに反映、文字入力をバッファに反映、履歴と文字入力を共にバッファに反映、それぞれできています。
追加機能として,tabキーで現在の履歴を即時反映させます。直前のコマンド表示させるのに上下キーで上って降りてなんてしたくないですから。
(tabが素通しで文字化けしたんで機能を得割り振ったなんてことはないですよ?
本当のところは、どう、なのかですか?
禁則事項です♪)
shellへの遠い道
これで文字列を入力することができるようになります。受け取った文字列をコマンドとして実行したり処理を振り分けたりできるようになるということですね。
現状ではまだ本体に組み込みません。MONIOTRにshell機能を持たせるか、shellとして独立したapp扱いするか決めかねているからです。それによってメモリ配置も考え直す必要があります。今後の宿題ですね。
今回はここまでです。お付き合いありがとうございましたm(_ _)m
この連載の成果物はgithubに公開しています。よろしければどうぞ。
https://github.com/CbWB-Inc/software/tree/main/laboratory/lab02/hyfax-01-long-road-to-shell
《2025/12/28 15:34:41》