インターネット&PC120%活用:PC活用編

DUMPプログラム(HEXDUMP)の作成 コード編

正直言って、汚いコードなです。その点ご容赦下さい。

ただDUMPするだけの単純なプログラムですが、アセンブラで組むとこんなに長たらしいコード
を書くはめになります。
それでは、コードについて簡単に説明を入れていきます。(かなり、コメントを入れてあるので
まあ、見ていただければ何をやっているかはわかると思います。)

ほぼ、同様の機能を持ったDUMPプログラムをBASICで組んだものが、姉妹サイトの
パソコン活用研究5番街/DUMPプログラム作成」にありますので、興味あれば、比較してみて
下さい。

(1) プログラム起動

まずは、プログラムの起動部分です。コマンドラインで指定されたファイルをオープンし、オープン
できなかったら、エラーメッセージを表示します。

ここで使っている主なラベル(変数)について説明します。
(A) CMDLN  コマンドライン文字列格納用

コマンドライン文字列の取得方法について簡単に説明します。コマンドラインは、MS-DOSにより
プログラムの80H番地からセットされます。
例えば、
A>hexdump hexdupm
とすると、プログラムの80Hから以下のように格納されます。
まず80Hにコマンドラインで与えられた文字列の文字数が格納されます。
81H以降に、コマンドライン文字列が格納されます。

番地 80H 81H 82H 83H 84H 85H 86H 87H 88H 89H
ラベルでの表記 cdml cdml+1 cdml+2 cdml+3 cdml+4 cdml+5 cdml+6 cdml+7 cdml+8 cdml+9
データ 8 ' ' 'h' 'e' 'x' 'd' 'u' 'm' 'p' 0DH
備考 文字数                 改行コード


;-------------------
; Hexdump ver0.0.1
;-------------------

CODE SEGMENT
ASSUME CS:CODE, DS:CODE,ES:CODE,SS:CODE
ORG 80H
CMDLN DB 128 DUP(0)

ORG 100H
        
START:
MOV AH,9 ;INT21Hシステムコール AH=9 :文字列の表示
MOV DX, OFFSET STARTMES  ;DX=文字列先頭アドレス
INT 21H ;スタートメッセージ表示
MOV BL, CMDLN ;BL=コマンドライン文字数
MOV BH,0
MOV CMDLN[BX+1],0 ;コマンドライン(=ファイル名)のASCIZ化 (文字列の末尾に0を追加)
MOV AH,3DH     ;INT21Hシステムコール AH=3DH:ファイルオープン
MOV AL,0      ;AL=0 :読出しモード
MOV DX,OFFSET CMDLN+2 ;ファイル名をDXにセット
INT 21H ;ファイルのオープン 正常にファイルオープンできた場合はAXにファイルハンドル
JC OERROR1     ;キャリーフラグがセットされた場合はエラー(AXにエラーコード)−>OERROR1へ
MOV FHANDLE, AX   ;FHANDLE=AX(ファイルハンドル)
MOV AH,3EH     ;INT21Hシステムコール AH=3EH :ファイルクローズ
MOV BX,FHANDLE ;BX:ファイルハンドル 
INT 21H ;ファイルハンドルのクローズ

      


(2) コマンド入力
起動後、コマンド入力待ちの状態になります。コマンドが入力されるまで、ループします。
コマンドの詳細は「DUMPプログラムの作成 企画編」の2)コマンド を参照して下さい。

;--------------------------------------------------------
;コマンド入力ルーチン
;--------------------------------------------------------
CONL:
CALL PRINTCRLF   ;改行ルーチンのコール
MOV AH,2
MOV DL,'-'
INT 21H      ;コマンド入力待ち '-'の表示
MOV AH,0AH       ;INT21Hシステムコール AH=0AH:キーボード入力       
MOV DX,OFFSET CONBUF   ;DX:入力バッファアドレス   
INT 21H                ;キーボードからのコマンド入力
CMP CONBUF[2],'D'   ;コマンドの判定
JZ DUMPL            ;'D'ならダンプ表示へ
CMP CONBUF[2],'Q'
JZ FIN1             ;'Q'なら終了へ
CMP CONBUF[2],'C'
JZ CHECK            ;'C'ならバイナリデータ検索へ
JMP CONL            ;コマンド入力へ戻る

CHECK:
CALL CHECKL     ;バイナリデータ検索ルーチンコール
JMP CONL

FIN1:
INT 20H             ;終了

(3) ダンプ表示
ダンプ表示をします。表示中も常にキーボードからの入力をチェックしていて、’1’が押されると
一時中断。’2’で再開。’3’で終了します。

ここの部分で使用されている主なラベルについて説明します。
(A) CONDF  ダンプ表示の状態  0 ダンプ表示中  1 一時中断状態
(B) KANF  前の行の行末が漢字だったかどうかを表すフラグ  
         0 漢字ではない
         1 漢字ではない(1バイト目は漢字の可能性のあるコード番号)
         2 漢字だった
   フラグの状態によって、行の先頭の処理が変わります。
   処理の詳細は、「DUMPプログラムの作成 企画編」の(3)キャラクタ表示の処理に書いてあります
   ので参照して下さい。
(C) LINBUF[ ] キャラクタ表示用です。表示できないデータには、 「.」を表示します。
(D) CHECKBUF 検索データ
(E) CHECKF   ダンプ表示の色  1 赤(検索データと一致したデータ)  2 白


コードの説明。
@ 表示の色の変更
表示する色を変更する方法にはいくつかありますが、ここではエスケープシーケンスによる安直な
方法を使っています。
MOV AH,9
MOV DX,OFFSET REDC  ;REDCは表示を赤にするエスケープシーケンス
INT 21H ;表示を赤に変更

ラベル REDCは、表示を赤にするエスケープシーケンスです。
REDC DB 1BH,'[31m$'
エスケープシーケンスについて、詳細はエスケープシーケンスを参照して下さい。

;-----------------------------------------------------
;ダンプ表示
;-----------------------------------------------------
DUMPL:
MOV AH, 3DH       ;INT21Hシステムコール AH=3DH:ファイルオープン
MOV AL,0                 ;AL=0 読出しモード
MOV DX, OFFSET CMDLN+2  ;DX:パス名の先頭アドレス
INT 21H         ;ファイルオープン
MOV ADDRESS, WORD PTR 0
JMP LOOPL

OERROR1:
JMP OERROR

LOOPL:
MOV AH,6
MOV DL,0FFH
INT 21H           ;キーボードから1文字入力 ALに入力データがリターンされる
CMP AL,'1'        ;押されたキーの判定
JZ LSTOP          ;1なら一時中断
CMP AL, '2'
JZ LRESTART       ;2なら再開
CMP AL, '3'
JZ FINISH         ;3なら終了
JMP LOOP1

FINISH:
INT 20H

LSTOP:
MOV CONDF,1       ;CONDFフラグを1にする。(一時中断状態)
JMP LOOP1

LRESTART:
MOV CONDF,0       ;CONDFフラグを0にする。(ダンプ実行)

LOOP1:
CMP CONDF,1
JZ LOOPL          ;CONDFフラグが1ならLOOPLに戻り、ループ(待ち状態)継続

LOOPA:
CALL PRINTCRLF
CALL PRINTADR     ;アドレス表示プロシージャのコール
CALL LINEBUFINIT  ;キャラクタ表示用LINBUF[]の初期化
MOV BX,0
CMP KANF,0    ;前の行の行末は漢字ではない(KANF=0)−−>LOOPBへ
JZ LOOPB

;--------------------------------------------------------------------------
;前の行の行末は漢字ではない(KANF=1)の場合 KANBUF−>AL−>LINEBUF[0]
;前の行末が漢字だった(KANF=2)場合 LINEBUF[0]には「 」(空白)を入れSKIPへ
;---------------------------------------------------------------------------
MOV AL,KANBUF  
CMP KANF,1
JZ SKIP1
MOV LINEBUF[0],' '
MOV KANF,0
JMP SKIP

SKIP1:
MOV KANF,0
MOV LINEBUF[0],AL
JMP SKIP

LOOPB:
CALL GETC   ;1バイト読み込み。ALに入力されたデータを保持
JNZ EOF    ;GETCプロシージャコールの結果、Zフラグ=0(読み込まれたデータがない)ならEOFへ
MOV LINEBUF[BX],AL  ;LINEBUF[BX]=AL 

SKIP:
CMP AL,CHECKBUF  ;AL(入力データ)とCHECKBUF(検索データ)の比較
JNZ NCOLOR     ;一致していなければNCOLORへ、一致していれば、表示色を赤に
MOV AH,9
MOV DX,OFFSET REDC  ;REDCは表示を赤にするエスケープシーケンス ...@
INT 21H           ;表示を赤に変更
MOV CHECKF,1   ;CHECKF 表示の色フラグ 1 赤  0 白
MOV AL,CHECKBUF

NCOLOR:
CALL PUTHEX    ;16進数での表示
CMP CHECKF,1   ;CHECKF=1(表示が赤)なら−>白に戻しておく 
JNZ WHIT     ;CHECKF=0(表示白)なら以下飛ばして、WHITへ
MOV AH,9
MOV DX,OFFSET WHITEC ;WHITEC 表示を白にするエスケープシーケンス
INT 21H      ;表示を白に変更
MOV CHECKF,0   ;CHECKFも0に戻しておく

WHITE:
INC BX      ;BX←BX+1
CMP BX, 10H    ;BXが16未満(1行=16文字分未処理なら)LOOPBに戻って次のデータの処理継続 
JB LOOPB 

CALL PUTC     ;キャラクタ表示のコール 
ADD ADDRESS,16  ;ADDRESSを16加算 
JMP LOOPL

        

(4) エラー処理、EOF処理、CLOSE処理
OERRORはファイルが正常にオープンできなかったときの処理です。このHEXDUMPでは、エラーメッセージ
を表示してプログラム終了します。
RERRORは、ファイルの読み込みエラーの時の処理です。エラーメッセージを表示し、CLOSE処理に
飛びます。
EOFは、EOF(ファイルの読み込み終了)の時の、最終行のキャラクタ表示の行末処理です。
最終データ(LINEBUF[BX])の後に、0と$を付加してからPUTC(キャラクタ表示ルーチン)をコールしています。
CLOSEはファイルのクローズ処理です。

OERROR:                 ;エラーメッセージを表示して終了
MOV DX, OFFSET OERRMES
MOV AH,9
INT 21H
INT 20H

RERROR:
MOV DX, OFFSET RERRMES
MOV AH,9
INT 21H
JMP CLOSE

EOF:         ;EOF時の行末処理(0、$の付加)->キャラクタ表示
MOV LINEBUF[BX+1],0
MOV LINEBUF[BX+2],'$'
CALL PUTC

CLOSE:       ;ファイルクローズ処理
MOV AH, 3EH
MOV BX,FHANDLE
INT 21H
JMP CONL

(5) PRINTADR PROC アドレスの表示
アドレスの表示をします。
PUTHEX(バイナリデータ->16進数のキャラクタ表示 変換ルーチン)では、レジスターALのデータを
16進数としてキャラクタ表示します。従って、アドレス(0000H〜FFFFH)を上位桁、下位桁に分けて、
ALに入れて、PUTHEXをコールしています。

PRINTADR PROC    ;アドレス部の表示
MOV AX,ADDRESS
MOV AL,AH            ;AL<-アドレスの上位桁(AH)
CALL PUTHEX     ;まず、上位桁を16進表示
MOV AX,ADDRESS
CALL PUTHEX     ;アドレスの下位桁の16進表示
MOV DL,':'
MOV AH,2
INT 21H       ;':'の表示
RET
PRINTADR ENDP 

(6) CHECKL PROC 検索データ(Cコマンド)処理
Cコマンドの処理をします。

コードの説明
(A) 入力された16進数(キャラクタコード)->バイナリー値変換
Cコマンドは以下のように与えられるます(16進数で7Bを検索したい場合)
-C7B

すると、CONBUF[3]には'7'のキャラクタコード37Hが入ります。CONBUF[4]には'B'のキャラクタコード
42Hが入ります。
この2つのキャラクタコードを処理して、最終的にCHECKBUFというラベルにバイナリー値として7BHが
入るように処理します。

簡単に処理の手順を追って見ます。
@  MOV AL, CONBUF[3]
   CALL HENKAN
HENKANルーチンをコールします。これでレジスターAL(=CONBUF[3])に入っている37H(キャラクター'7')
は、バイナリー値07Hとして戻されます。
A SAL AL,1     ;4回左シフトして、上位4ビットに値をシフト
4回左シフトすることにより、07Hは70Hになります。
00000111 =07H
01110000 =70H <---4回左シフトした。
B MOV AH,AL
上位桁の情報はとりあえず、AHに移しておきます。
C MOV AL,CONBUF[4]  ;CONBUF[4]:検索したいデータの下位バイト(キャラクタコード)
   CALL HENKAN
下位桁('B')も同様に処理し、ALにバイナリー値で0BHが入ります
D  ADD AH,AL
上位桁 AH(=70H)+AL(=0BH)でキャラクターコード −>バイナリー値への変換完了です。


(B) HENKANルーチン
ここが、キャラクタコード−>バイナリー値変換を実際に1バイト単位で行っている部分です。
アセンブラでプログラムを組む場合、必ず必要になる変換です。何をやっているのかをよく理解して
下さい。
逆のパターンで、バイナリー値をキャラクタ表示(16進数)するコードの詳細な説明が
DEBUGで機械語プログラムその2にあげてあります。そちらを参照していただければ、ここのコードの
意味が理解できると思います。
コード自体は CMP(比較) JMP(分岐) SUB(減算)だけですから簡単ですね。  

CHECKL PROC    ;検索データ(Cコマンド)処理
MOV AL,CONBUF[3]  ;CONBUF[3]:検索したいデータの上位バイト(キャラクタコード)
CALL HENKAN    ;キャラクタコード->バイナリー値
SAL AL,1     ;4回左シフトして、上位4ビットに値をシフト
SAL AL,1
SAL AL,1
SAL AL,1
MOV AH,AL     ;AH<-AL AHに検索したいデータの上位バイトがバイナリー値(上位4ビット)としてセットされた
MOV AL,CONBUF[4]  ;CONBUF[4]:検索したいデータの下位バイト(キャラクタコード)
CALL HENKAN    ;ALに検索したいデータの下位バイトがバイナリー値(下位4ビット)としてセットされた
ADD AH,AL     ;AH←AH(上位バイト)+AL(下位バイト)
MOV CHECKBUF,AH  ;CHECKBUF:検索データ
RET

;キャラクタコード−>バイナリデータ変換
HENKAN:
CMP AL,30H
JB CHECKEND    ;キャラクタコード30H未満はCHECKENDへ
CMP AL,39H
JBE ZERO     ;30H〜39H(数字の0〜9)はZEROへ
CMP AL,41H
JB CHECKEND
CMP AL,46H    ;41H〜46H(アルファベット小文字a〜f)はHEXへ
JBE HEX
JMP CHECKEND

ZERO:    ;30H(=48)を引いて、数値(0〜9)に変換
SUB AL,30H
RET

HEX:     ;37Hを引いて、数値(aH = 10 〜 fH =16)に変換
SUB AL,37H
RET

CHECKEND:
MOV AL,0
RET
CHECKL ENDP


(7) PUTHEX PROC バイナリーデータ−>キャラクタコード変換

PUTHEXは、バイナリー値を実際にダンプ表示する機能の部分であり、
@バイナリーデータ−>キャラクタコード変換を行い ((6)とは逆の作業です)
A表示する
という作業をしています。

簡単にコードを追ってみましょう。例えば、'7B'というバイナリー値を表示する場合を考えます。
@ AND AL,0F0H  ;下位桁をマスク
まず、上位桁'7'を取り出すために、下位桁をマスクします。
AL(7BH)     01111011
AND AL, 0F0H 11110000
         -----------
          01110000
これで、レジスタALに'70'がとりこまれます。

A SHR AL,1 ;4回右シフトして
4回右シフトして、'70' −> '07' とします。

B HEXDISPコール
HEXDISPでは、0〜9は30H足して、aH=10 〜fH=16 は37H足してキャラクタコードに変換して
DISP(実際の表示のシステムコール)へジャンプします。
今回は、7なので30Hを足して、37H('7'のキャラクタコード)とします。これで、モニター上に7が表示
されます。

C以下、下位桁の処理を行います。


この、バイナリーデータ->キャラクタコード変換 はよく使うコードなので、理解しておく必要があります。
詳細な説明が、DEBUGで機械語プログラムその2にありますので、参照して下さい。

;----------------------------------------------------------------
;16進数での表示 バイナリデータ−>キャラクタコード
;----------------------------------------------------------------
PUTHEX PROC
MOV CHARA,AL ;CHARA=AL(入力データ)
AND AL,0F0H  ;下位桁をマスク
SHR AL,1       ;4回右シフトして
SHR AL,1
SHR AL,1
SHR AL,1
CALL HEXDISP  ;上位桁表示
MOV AL,CHARA
AND AL,0FH   ;上位桁マスク
CALL HEXDISP  ;下位桁表示
MOV DL,' '
MOV AH,2
INT 21H    ;空白を1個表示
RET

;--------------------------------------------------------------------------
;16進数として表示。
;0〜9は30H足して、aH=10 〜fH=16 は37H足してキャラクタコードに変換してDISPへ
;--------------------------------------------------------------------------
HEXDISP:  
CMP AL,9
JA TOHEX
MOV DL,30H
ADD DL,AL
JMP DISP

TOHEX:
MOV DL,37H
ADD DL,AL

DISP:
MOV AH,2  ;INT21Hシステムコール AH=2 文字の出力
INT 21H   ;文字の出力 DL=キャラクタコード
RET
PUTHEX ENDP

(8) PRINTCRLF PROC 改行
改行します。 CR(0DH)+LF(0AH)の出力です。

PRINTCRLF PROC  ;改行
MOV DL,0DH
MOV AH,2
INT 21H
MOV DL,0AH
MOV AH,2
INT 21H
RET
PRINTCRLF ENDP


(9) GETC PROC 1バイトデータ取得
GETC PROCは(3)ダンプ表示の部分から呼び出され、1バイトのデータを取得します。
読み出した1バイトのデータは、ALに入れてリターンします。

RERROR1: ニアジャンプでは届かないので、ここでジャンプを中継しています。(GETC PROCとはまったく
関係なし)

;------------------------------------------------------------
;1バイトデータ取得
;------------------------------------------------------------
GETC PROC
PUSH BX
MOV BX,FHANDLE  ;BX=ファイルハンドル
MOV AH,3FH      ;INT21Hシステムコール AH=3FH:ファイル読出し 
MOV DX, OFFSET BUF ;DX=入力バッファ先頭アドレス(OFFSET BUF) 
MOV CX,1  ;CX 読み込むバイト数
INT 21H   ;ファイルの読出し  
JC RERROR1
CMP AX,CX  ;AX:読み込まれたバイト数 CX:読み込むバイト数の比較−>メインルーチンで判定
MOV AL,BUF  ;読み込んだキャラクタ(=BUF)はALに入れる
POP BX
RET
GETC ENDP

RERROR1:
JMP RERROR


(10) LINBUFINIT PROC
LINEBUF[ ]を初期化するルーチンです。
ここでは、LOOPの使い方を覚えて下さい。レジスタCXに繰り返し回数を設定します。書式は以下の通りです。
LOOP ラベル名
CXが0になまでラベル名に戻りループします。この時CXは1づつ減算されます。

LINEBUFINIT PROC   ;キャラクタ表示用LINEBUF[]の初期化
MOV CX,16
INITS:   ;LINEBUF[]を0に初期化
MOV SI,CX
MOV LINEBUF[SI],0
LOOP INITS ;CX <--CX-1 CX=0になるまでループ(16回) 
RET
LINEBUFINIT ENDP

(11) PUTC PROC
キャラクタ表示のルーチンです。
ここの処理は、若干面倒です。処理のフローについては、DUMPプログラム作成 企画編に詳しく書きました
ので、参照して下さい。また、HEXDUMPフローチャートも参照して下さい。

;------------------------------------------------
;キャラクタ表示
;------------------------------------------------
PUTC PROC
MOV BX,0

PUTCS:
MOV AL,LINEBUF[BX]
CMP AL,20H
JB NOASCI  ;キャラクタコードが20H未満ならNOASCI(文字でない)の処理へ
CMP AL,24H
JZ NOASCI
CMP AL,80H
JB ASCI      ;20Hから7FHはASCI(1バイト文字処理)へ
CMP AL,9FH 
JBE KANJI  ;80Hから9FHはKANJI(漢字処理)へ
CMP AL,0E0H
JB ASCI   ;A0HからDFHはASCI(1バイト文字処理)へ
CMP AL,0FCH
JBE KANJI  ;E0HからFCHはKANJI(漢字処理)へ

NOASCI:
MOV LINEBUF[BX],'.'
JMP ASCI

KANJI:
CMP BX,15
JZ KANJI15  ;行末(BX=15)ならKANJI15(行末漢字処理)へ
MOV AH,LINEBUF[BX+1] ;2バイト目(LINEBUF[BX+1])をAHに読み込み
CALL ISKANJI2    ;2バイト目が漢字コードかどうかの判定へ
CMP KAN2,1   ;KAN2=1 漢字コードなら
JZ KANTRUE   ;KANTRUE(漢字表示)へ
MOV LINEBUF[BX],'.'   ;漢字でないなら'.'の表示
JMP ASCI

KANTRUE:
INC BX
JMP ASCI

KANJI15:  ;行末の場合の漢字(2バイト文字)の処理
CALL GETC  ;次の行の先頭の1バイトを読み込み
MOV AH,AL
CALL ISKANJI2 ;2バイト目が漢字コードかどうかの判定
CMP KAN2,1
JZ KANTRUE15  ;KAN2=1 漢字ならKANTRUE15(行末の漢字表示)へ
MOV KANF,1   ;漢字でなければKANF=1に設定 
MOV KANBUF,AH  ;先読みした次の行の先頭バイト(AH)をKANBUFに保存。次の行の処理で使う
MOV LINEBUF[15],'.'
MOV LINEBUF[16],' '
JMP ASCI

KANTRUE15:
MOV LINEBUF[16],AH
MOV KANF,2     ;KANF=2 行末は漢字だった。
MOV KANBUF,AH

ASCI:
INC BX
CMP BX,16
JAE PUTCEND ;1行分(16データ)処理したらPUTCENDへ
JMP PUTCS  ;16データ未満ならPUTCSに戻って処理を継続
PUTCEND:
MOV AH,9
MOV DX, OFFSET LINEBUF[0]
INT 21H   ;1行分のキャラクタ表示
RET

;----------------------------------------------------------
;2バイト目が漢字コードかどうかの判定
;40H〜7EH  80H〜FCHなら漢字 −>KAN2フラグ=1   
;----------------------------------------------------------
ISKANJI2:   
CMP AH,40H
JB FALSE
CMP AH, 7EH
JBE TRUE
CMP AH,80H
JB FALSE
CMP AH,0FCH
JBE TRUE

FALSE:
MOV KAN2,0
RET

TRUE:
MOV KAN2,1
RET

PUTC ENDP

(12) データ定義、コード終了

STARTMES DB 'HEXDUMP ver0.1  <制作  河西  毅>',0Dh,0AH
         DB 'コマンド',0DH,0AH
         DB 'D  バイナリーダンプ  以下のサブコマンドがあります',0DH,0AH
         DB '1  中断     2  再開     3  終了',0DH,0AH
         DB  'Q  HEXDUMP終了',0DH,0AH
         DB  'C  バイナリデータ検索  指定したデータが赤色で表示。 指定例)C38, C7E',0DH,0AH
         DB  '全て大文字で指定して下さい',0DH,0AH,'$'
OERRMES  DB 'ファイルが開けません。コマンドラインに正しいファイル名を指定して下さい',0DH,0AH,'$'
RERRMES  DB 'データ読み込みエラー',0DH,0AH,'$'
REDC DB 1BH,'[31m$'
WHITEC DB 1BH,'[m$'
FHANDLE DW 0
ADDRESS DW 0
CHARA DB 0
BUF DB 0
KANF DB 0
KANBUF DB 0
KAN2 DB 0
CONDF DB 0
CONBUF DB 4,5 DUP(0)
CHECKBUF DB 0
CHECKF DB 0
LINEBUF DB 17 DUP(0),'$'

CODE ENDS
END START


(13) 終わりに
機能としては、ダンプするだけのシンプルなプログラムですが、アセンブラで組むと、結構たいへんな
作業になります。HEXDUMPはエラートラップなどはかなりオオザッパなので、そこらへん強化していくと
もっと面倒なコードになります。


TopPage

 

inserted by FC2 system