home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

Assembler/ForFun(x86_32)/01, 16bit DOS with debug.exe

Assembler/ForFun(x86_32)/01, 16bit DOS with debug.exe

Assembler / ForFun(x86_32) / 01, 16bit DOS with debug.exe
id: 782 所有者: msakamoto-sf    作成日: 2010-09-14 19:58:24
カテゴリ: Assembler 

1980年代のCP/Mから30年にわたり生き延びてきたデバッガ兼アセンブラが"debug.exe"です。
永らくDOSやWindowsに同梱されてきたdebug.exeですが、さすがにWindows7には含まれていないようです。
もし手元のPCがWindowsXP以前のOSなら、今の内に往時の16bitDOSアセンブラプログラミングを体験してみましょう。

なおdebugコマンドでのアセンブラの記述はIntel方式(destination, source)です。

参考資料:



実行方法

コマンドプロンプトを開き、"debug" 又は "debug.exe" を実行します。

C:\>debug
-

"-"がdebug用のコマンド入力プロンプトです。

既存のプログラムをデバッグすることも可能です。debug に続いてデバッグ対象のプログラム名を指定します。

C:\>debug.exe test.com
-g =100
プログラムは正常に終了しました.
-q

debugコマンド一覧

最初にパラメータの書式についてまとめておきます。

"Value"形式
16進数をそのまま書く。"0x"や"h"などの接頭辞・接尾辞は不要。
"Address"形式

segment:offset形式又は単純なoffset形式。

  • "1AFE:00B0" - segment:offset
  • "DS:A0EE" - segument register:offset
  • "0FE0" - offset
"List"形式

1つ以上のバイト or 文字データ。

  • "10, 20, 30, 40"
  • " 'A', 'B', 'C', 0"
"Range"形式

アドレス範囲を表現する形式。

  • "Address_From [[,] Address_To]" - "100, 200", "CS:200,300" "10 20" (Address_To省略時は適当な標準値が使用される)
  • "Address L Length" - "100 L 20" (100から20hバイトの範囲)
"String"形式
ダブル or シングルクォートで囲まれた文字列。

以下、本記事で使用する主要なdebugコマンドを紹介していきます。その他のdebugコマンドについては冒頭で紹介した解説記事や"?"コマンドを参照して下さい。

コマンドやパラメータは、大文字小文字どちらで入力してもOKです。

アセンブラの入力と実行:A,G,P,T,Uコマンド

'A'(Assemble)
-A            ... 現在のIPからアセンブラ入力を開始
-A "Address"  ... "Address"で指定されたアドレスからアセンブラ入力を開始

アセンブラ入力中は、現在入力中のアドレスがsegment:offset形式のプロンプトとして表示されます。
アセンブラ入力を終了してdebugコマンドプロンプトに戻るには、空行を入力します。

'G'(Go)
-G ... 現在のIPから実行開始
-G breakpoint ... 現在のIPから実行開始し、breakpoint("Address"形式)で停止
-G = start breakpoint ... start("Address"形式)から実行を開始、breakpointで停止
-G = start bp1 bp2 ... 上と同じだが、複数のbreakpointを設定可能

なおstartの値がoffset形式で指定された場合、"CS:offset"から開始される。

'P'(Proceed)

1つ以上の命令を実行後、レジスタ値を表示して停止。LOOPやサブルーチンの中は追わない。

-P ... 現在のIPから1命令実行して停止
-P number ... 現在のIPからnumber命令実行して停止
-P = start ... start("Address"形式)から1命令実行して停止
-P = start number ... start("Address"形式)からnumber命令実行して停止
'T'(Trace)

1つ以上の命令を実行後、レジスタ値を表示して停止。LOOPやサブルーチンの中まで追うところが'P'と違う点。

-T ... 現在のIPから1命令実行して停止
-T number ... 現在のIPからnumber命令実行して停止
-T = start ... start("Address"形式)から1命令実行して停止
-T = start number ... start("Address"形式)からnumber命令実行して停止
'U'(Unassemble)

メモリ範囲を指定して逆アセンブル表示。

-U ... 最後に'U'が実行されたアドレスから32byte逆アセンブル
-U address ... 指定アドレスから32byte逆アセンブル
-U range ... 指定範囲を逆アセンブル
練習

以下のアセンブラをCS:0100から打ち込み、G/P/Tで実行してみましょう。

MOV     CX,0005
MOV     AX,0000
MOV     BX,0000
ADD     AX,CX
INC     BX
LOOP    0109
> debug.exe
-A 100
2CED:0100 mov cx,5
2CED:0103 mov ax,0
2CED:0106 mov bx,0
2CED:0109 add ax,cx
2CED:010B inc bx
2CED:010C loop 109
2CED:010E
※"2CED:xxxx"は勝手に表示されます。それ以降のアセンブラコードを
実際にキーボードから入力します。最後に空行でアセンブラ入力終了です。

ではIP=100から開始して、ADDコマンドの109hで止めてみます。

-G =100 109
AX=0000  BX=0000  CX=0005  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=2CED  ES=2CED  SS=2CED  CS=2CED  IP=0109   NV UP EI PL NZ NA PO NC
2CED:0109 01C8          ADD     AX,CX

AX, BX, CXが"MOV"命令の通りになり、IPは109hを指しているのが分かります。

続いて"P"コマンドで1命令だけ実行してみます。

-P
AX=0005  BX=0000  CX=0005  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=2CED  ES=2CED  SS=2CED  CS=2CED  IP=010B   NV UP EI PL NZ NA PE NC
2CED:010B 43            INC     BX

AXにCXの値が加算され、IPが次の命令を指します。

今度は"T"コマンドで、複数の命令を実行させてみます。"T"なのでLOOPの中も追ってくれます。

-T 5

AX=0005  BX=0001  CX=0005  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=2CED  ES=2CED  SS=2CED  CS=2CED  IP=010C   NV UP EI PL NZ NA PO NC
2CED:010C E2FB          LOOP    0109

AX=0005  BX=0001  CX=0004  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=2CED  ES=2CED  SS=2CED  CS=2CED  IP=0109   NV UP EI PL NZ NA PO NC
2CED:0109 01C8          ADD     AX,CX

AX=0009  BX=0001  CX=0004  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=2CED  ES=2CED  SS=2CED  CS=2CED  IP=010B   NV UP EI PL NZ NA PE NC
2CED:010B 43            INC     BX

AX=0009  BX=0002  CX=0004  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=2CED  ES=2CED  SS=2CED  CS=2CED  IP=010C   NV UP EI PL NZ NA PO NC
2CED:010C E2FB          LOOP    0109

AX=0009  BX=0002  CX=0003  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=2CED  ES=2CED  SS=2CED  CS=2CED  IP=0109   NV UP EI PL NZ NA PO NC
2CED:0109 01C8          ADD     AX,CX

LOOP → 109へ戻ってまたLOOP という動きを、1命令毎に細かく追うことが出来ます。

最後に"U"コマンドを試してみましょう。

-U 100
2CED:0100 B90500        MOV     CX,0005
2CED:0103 B80000        MOV     AX,0000
2CED:0106 BB0000        MOV     BX,0000
2CED:0109 01C8          ADD     AX,CX
2CED:010B 43            INC     BX
2CED:010C E2FB          LOOP    0109
2CED:010E 0000          ADD     [BX+SI],AL
2CED:0110 0000          ADD     [BX+SI],AL
2CED:0112 0000          ADD     [BX+SI],AL
2CED:0114 0000          ADD     [BX+SI],AL
2CED:0116 0000          ADD     [BX+SI],AL
2CED:0118 0000          ADD     [BX+SI],AL
2CED:011A 0000          ADD     [BX+SI],AL
2CED:011C 3400          XOR     AL,00
2CED:011E DC2C                  FSUBR   QWORD PTR [SI]

開始アドレスしか指定していない為、32byte分逆アセンブルされました。
実際に入力したのは10Dまでなので、範囲を絞ってみましょう。

-U 100 10D
2CED:0100 B90500        MOV     CX,0005
2CED:0103 B80000        MOV     AX,0000
2CED:0106 BB0000        MOV     BX,0000
2CED:0109 01C8          ADD     AX,CX
2CED:010B 43            INC     BX
2CED:010C E2FB          LOOP    0109

範囲指定なので"L"を使うこともできます。

-U 100 L D
2CED:0100 B90500        MOV     CX,0005
2CED:0103 B80000        MOV     AX,0000
2CED:0106 BB0000        MOV     BX,0000
2CED:0109 01C8          ADD     AX,CX
2CED:010B 43            INC     BX
2CED:010C E2FB          LOOP    0109

メモリ・レジスタの表示と書き込み:E,D,Rコマンド

'E'(Enter)

アドレスにデータを書き込みます。

-E address ... 指定されたアドレスに1バイトデータを書き込みます。
               address("Address"形式)がoffsetの場合はDS:offsetとして処理されます。
-E address list ... 指定されたアドレスから、listで指定されたシーケンシャルデータを
                    書き込みます。

なお、"list"には"String"形式も指定可能ですが、"\t""\r""\n"等のエスケープシーケンスは展開されません。またC言語のように末尾の0x00は付与されないので、必要なら明示的に指定する必要があります。

'D'(Dump)

メモリ内容を16進ダンプします。

-D  ... 最後にダンプしたアドレス or 初回ならDS:0からダンプ
-D address ... 指定されたアドレスからダンプ
-D range ... 指定されたメモリ範囲をダンプ
'R'(Register)

レジスタ内容を表示、あるいは新しい値を設定します。

-R  ... 現在のレジスタ内容を表示
-R register ... 指定されたレジスタ内容を表示し、新しい値を入力

"register"には、'R'で表示されるレジスタ名(AX, BX, ...)の他にフラグレジスタの"F"も指定出来ます。

練習

debugコマンド起動時点でのレジスタ値を確認してみます。

> debug.exe
-R
AX=0000  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=2CED  ES=2CED  SS=2CED  CS=2CED  IP=0100   NV UP EI PL NZ NA PO NC
2CED:0100 B90500        MOV     CX,0005

DS, ES, SS, CS が全て同じセグメントになっています。IPが0100hに設定済です。CS:0 - CS:FFhはdebug.exe用の予約領域です。
SPはFFEEになっています。DS,CS,SS全て同じセグメントなので、あまりスタックにpushしすぎるとプログラムやデータ領域を壊してしまうかもしれませんので注意が必要です。

AXの値を変更してみます。

-r ax
AX 0000
:

":"で新しい値の入力待ちです。"1234"(hex)を入力してみます。

-r ax
AX 0000
:1234(リターン)
-r
AX=1234  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
...

AXが更新されているのが分かります。

DS:0200hから"Hello World!"を書き込んでみましょう。まずは1byte単位で書き込んでみます。

-E 200
2CED:0200  00.

ここで入力待ちなので、そのまま16進で"48"(='H')を入力します。

-E 200
2CED:0200  00.48(リターン)
-

引き続き、DS:0201に"e"(0x65)を書き込んでみます。

-E 201
2CED:0201  00.65(リターン)

DS:0202以降は"llo, World!"と文字列でまとめて書き込んでみます。

-E 202 "llo, World!" 00

では"D"コマンドでダンプしてみます。

-D 200 L F
2CED:0200  48 65 6C 6C 6F 2C 20 57-6F 72 6C 64 21 00 00      Hello, World!..
-

ちゃんと"Hello, World!"をメモリアドレス上に書き込めたことを確認出来ました。

ちなみに"\r\n\00"を末尾に指定するのであれば、

-E 200 "Hello, World!" 0D 0A 00

とする必要があります。

ファイルへの書き込みと終了:N,W,Qコマンド

'N'(Name)

後述の'W'コマンドで書き出すファイル名を指定します。

-N [Drive:\path\]8_3_name.ext

ファイル名はDOSの8.3形式で指定する必要があります。

'W'(Write)

メモリイメージをファイルに出力します。出力するサイズをBXとCXレジスタで指定します。"BX:CX"と記述する場合もありますが、segment:offsetの計算ではなく、単純に高位16bitをBX、低位16bitをCXで指定します。したがって理論的には32bitサイズのメモリイメージをファイルに出力可能ですが、そもそも64KiBセグメントに囲まれた16bitDOSプログラミングの世界なので、BXは0で問題ないでしょう。

-W  ... CS:0100h からBX:CXサイズ分出力
-W CS:0200  ... CS:0200h からBX:CXサイズ分出力

CS:0100hからのメモリイメージをファイルに書き込むと、結果としてCOMファイルが出力されます。DOS MZ実行形式ではありません。
ファイルサイズは最大 65280 Bytes で、これがそのままCS:0100h以降にロードされます。65280は丁度64KiB - FFhです。
CS:0000h - CS:00FFh はシステム(or debug.exe)の予約領域です。

'Q'(Quit)
debugコマンドを終了します。

N, W コマンドについては後述の"Hello, World!"プログラムで練習してみます。

"Hello, World!"

ではいよいよdebugコマンドを使って "Hello, World!" を作ってみます。
使用するDOSファンクションは次の2つで充分でしょう。

AH=09h DOS 1+ - WRITE STRING TO STANDARD OUTPUT
AH=4Ch DOS 2+ - EXIT - TERMINATE WITH RETURN CODE

AH=09hで表示する文字列は、"$"終端になっている必要があります。
DOSファンクションコールの詳細は、"Ralf Brown's Interrupt List"を参照して下さい。

C:\>debug
-A 100
2CED:0100 mov ah,9
2CED:0102 mov dx,200
2CED:0105 int 21
2CED:0107 mov ah,4c
2CED:0109 mov al,1
2CED:010B int 21
2CED:010D
-E 200 "Hello, World!$"
-N HELLOW.COM
-R BX
BX 0000
:0
-R CX
CX 0000
:300
-W
00300 バイト書き込み中.
-Q

実行してみます。

C:\>HELLOW.COM
Hello, World!
C:\>echo %ERRORLEVEL%
1

"Hello, World!"が表示され、戻り値(=1)もERRORLEVELを通じて確認出来ました。

まとめ

debug.exeの主要コマンドと、実際に"Hello, World!"を組み立てる手順を見ていきました。
32bitから64bitへの移行が進んでいる2010年現在、16bitDOS環境のdebug.exeを使う機会は滅多にないと思いますが、もし万が一、使う場面に遭遇した時にこのページを思い出して頂ければ幸いです。



プレーンテキスト形式でダウンロード
現在のバージョン : 1
更新者: msakamoto-sf
更新日: 2010-09-19 21:00:24
md5:89db47e830b6c303a30fca64d9cccec9
sha1:52dd4a6470bb422e67bf4cb764eccaef46d9b95b
コメント
コメントを投稿するにはログインして下さい。