アセンブラで作るDOS用TSRプログラム [NASM]
今後自分で使うことは一生ないか分からんけど、DOSのTSRプログラムを作るのに滅茶苦茶手こずったので、NASM用に書いたのを残しておく。
特に、メモリに入っているセグメント:オフセット(4バイト)アドレスへfar jumpするときの書き方がMASMのサンプルしかなくて、NASMだとどうすればいいのか分からなかった。CS(コードセグメント)指定のつけ忘れとか、farやDWORDを入れるか入れないかとかで迷った。正解はごくシンプルなんだけど。
IBM PC互換機のビデオBIOSであるINT 10hをフックして、表示する文字が全て大文字になるプログラム。DOS/Vでも動くと思うが、2バイト文字は文字化けする。IBM PC互換機以外で実行してパソコンが火を噴いても責任は負えません🤭
;Copyright (c) 2023 akm
;This content is under the MIT License.
;THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND.
;NASM用プロプロセッサ
[BITS 16];16ビットを基本単位とする
;IDは他と重複しなさそうな数値を適当に決める
;ID_TSRの上位8ビットは00hからBFhまでDOS予約
ID_TSR equ 0xFAC9
ID_INSTALLED equ 0x1111
;-----------------------------------------------
section .text
global start
start:
;COMプログラム用のオフセットアドレスを設定
org 0x100
;エントリーポイントへジャンプ
jmp init_entry
;-----------------------------------------------
;常駐時に使うワークエリア
org_int10: dw 0,0
org_int2F: dw 0,0
;-----------------------------------------------
;new_int10 - 今回フックするターゲットの新しい処理
new_int10_entry:
;テレタイプ式書き込み(INT 10H, AH=0Eh)がコールされたときだけフックする
cmp ah, 0xE
jne new_int10_next
;ここに前処理を入れる
cmp al, 0x61
jb new_int10_call
cmp al, 0x7A
ja new_int10_call
and al, 0xDF
new_int10_call:
;リアルモードにおけるINT命令相当の処理 = pushf + call far
pushf
call far [cs:org_int10]
;ここに後処理を入れる
;後処理
iret
new_int10_next:
;元々のInt10hベクターへジャンプ
jmp far [cs:org_int10]
;-----------------------------------------------
;new_int2F - 多重起動や起動後の動作変更などプロセス間通信用
new_int2F_entry:
;ID確認
cmp ax, ID_TSR
jne new_int2F_next
;自分のIDと一致するならID_INSTALLEDをAXに代入
mov ax, ID_INSTALLED
iret
new_int2F_next:
;元々のInt2Fhベクターへジャンプ
jmp far [cs:org_int2F]
;-----------------------------------------------
end_resident_code:;メモリ節約のためこれ以降のコードは常駐時に切り捨て
;-----------------------------------------------
;print - メッセージ出力 : DX = $で終わる文字列のアドレス
print:
;INT 21H, AH=09H - 文字列表示
mov ax, cs
mov ds, ax
mov ah, 9
int 0x21
ret
;-----------------------------------------------
;checkTSR - 常駐チェック
checkTSR:
;常駐済みならAL=1、そうでないならAL=0を返す
mov ax, ID_TSR
push ds
int 0x2F
pop ds
cmp ax, ID_INSTALLED
je checkTSR_Installed
jmp checkTSR_InstallTSR
checkTSR_Installed:
mov al, 1
jmp checkTSR_exit
checkTSR_InstallTSR:
mov al, 0
checkTSR_exit:
ret
;-----------------------------------------------
;init_entry - 初回起動時エントリーポイント
init_entry:
mov dx, Mes_Version
call print
;INT 21H, AH=30H - DOSバージョン番号の取得
mov ax, 0x3000
int 0x21
mov dx, Mes_DOSVer
cmp al, 2
jb mes_exit
;check TSR already exists (AL = 1 if installed)
call checkTSR;
mov dx, Mes_AlreadyInstalled
cmp al, 1
je skip_install
;Case 1: 常駐してプロセス終了
; INT 10Hのベクターを取得・上書き
;INT 21H, AH=25H - 割り込みベクトルの取得
mov ax, 0x3510
int 0x21
mov word [org_int10], bx
mov word [org_int10 + 2], es
;INT 21H, AH=35H - 割り込みベクトルの設定
mov dx, new_int10_entry
mov ax, 0x2510
int 0x21
; INT 2FHのベクターを取得・上書き
mov ax, 0x352F
int 0x21
mov word [org_int2F], bx
mov word [org_int2F + 2], es
mov dx, new_int2F_entry
mov ax, 0x252F
int 0x21
mov dx, Mes_Installed
call print
;INT 21H, AH=31H - 常駐のままプロセス終了
mov ax, 0x3100
;常駐として残すサイズを計算してDXに代入
lea dx, end_resident_code
mov cl, 4
shr dx, cl
add dx, 0x11;0x10(100h) for PSP, and one(10h) for remainders
int 0x21
;Case 1 END
skip_install:
mes_exit:
;Case 2: エラーまたは常駐済みのため、常駐せず終了
; DX = エラーメッセージの文字列のアドレス
call print
exit:
;INT 21H, AH=4CH - プロセスの終了(EXIT)
mov ax, 0x4C00
int 0x21
;*** exit and return to DOS ***
;-----------------------------------------------
section .data
Mes_Installed: db "INT 10hをフックして常駐しました." ,0Dh,0Ah,"$"
Mes_NotInstalled: db "常駐せずに終了します." ,0Dh,0Ah,"$"
Mes_AlreadyInstalled: db "既に常駐しています." ,0Dh,0Ah,"$"
Mes_DOSVer: db "Error: DOS 2.0 or later required." ,0Dh,0Ah,"$"
Mes_Version: db "TSR(常駐)プログラムサンプル Version X.XX" ,0Dh,0Ah,"$"
NASMをパスが通るところにおいて、コマンドnasm google.asm -o google.com
って感じでアセンブルできます。