Image: アセンブラで作る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って感じでアセンブルできます。


※コメント欄が表示されない場合はdisqusについてJavascriptが有効であることを確認して下さい。コメントはスパム防止フィルターによる承認制のため、投稿してもすぐに反映されない場合があります。

管理人 : Akamaki (akm)

は、PCとVTuberに夢中になっている電気技術者です。

私はレトロコンピューティングの愛好家ですが、そのようなリグはもう収集していません。

私の活動はトップページで見ることができます。読んでくれてありがとう!