#navi_header|Assembler| 1980年代のCP/Mから30年にわたり生き延びてきたデバッガ兼アセンブラが"debug.exe"です。 永らくDOSやWindowsに同梱されてきたdebug.exeですが、さすがにWindows7には含まれていないようです。 もし手元のPCがWindowsXP以前のOSなら、今の内に往時の16bitDOSアセンブラプログラミングを体験してみましょう。 なおdebugコマンドでのアセンブラの記述はIntel方式(destination, source)です。 #more|| 参考資料: - アセンブリ言語入門 -- http://wisdom.sakura.ne.jp/programming/asm/index.html - MS-DOS DEBUG Program -- http://mirror.href.com/thestarman/asm/debug/debug.htm - Ralf Brown's Interrupt List - HTML Version -- http://www.ctyme.com/rbrown.htm - debug (command) - Wikipedia, the free encyclopedia -- http://en.wikipedia.org/wiki/Debug_%28command%29 - COM file - Wikipedia, the free encyclopedia -- http://en.wikipedia.org/wiki/COM_file ---- #outline|| ---- * 実行方法 コマンドプロンプトを開き、"debug" 又は "debug.exe" を実行します。 C:\>debug - "-"がdebug用のコマンド入力プロンプトです。 既存のプログラムをデバッグすることも可能です。debug に続いてデバッグ対象のプログラム名を指定します。 C:\>debug.exe test.com -g =100 プログラムは正常に終了しました. -q * debugコマンド一覧 最初にパラメータの書式についてまとめておきます。 : "Value"形式 : 16進数をそのまま書く。"0x"や"h"などの接頭辞・接尾辞は不要。 : "Address"形式 : #block||> segment:offset形式又は単純なoffset形式。 - "1AFE:00B0" - segment:offset - "DS:A0EE" - segument register:offset - "0FE0" - offset ||< : "List"形式 : #block||> 1つ以上のバイト or 文字データ。 - "10, 20, 30, 40" - " 'A', 'B', 'C', 0" ||< : "Range"形式 : #block||> アドレス範囲を表現する形式。 - "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) : #block||> -A ... 現在のIPからアセンブラ入力を開始 -A "Address" ... "Address"で指定されたアドレスからアセンブラ入力を開始 アセンブラ入力中は、現在入力中のアドレスがsegment:offset形式のプロンプトとして表示されます。 アセンブラ入力を終了してdebugコマンドプロンプトに戻るには、空行を入力します。 ||< : 'G'(Go) : #block||> -G ... 現在のIPから実行開始 -G breakpoint ... 現在のIPから実行開始し、breakpoint("Address"形式)で停止 -G = start breakpoint ... start("Address"形式)から実行を開始、breakpointで停止 -G = start bp1 bp2 ... 上と同じだが、複数のbreakpointを設定可能 なおstartの値がoffset形式で指定された場合、"CS:offset"から開始される。 ||< : 'P'(Proceed) : #block||> 1つ以上の命令を実行後、レジスタ値を表示して停止。LOOPやサブルーチンの中は追わない。 -P ... 現在のIPから1命令実行して停止 -P number ... 現在のIPからnumber命令実行して停止 -P = start ... start("Address"形式)から1命令実行して停止 -P = start number ... start("Address"形式)からnumber命令実行して停止 ||< : 'T'(Trace) : #block||> 1つ以上の命令を実行後、レジスタ値を表示して停止。LOOPやサブルーチンの中まで追うところが'P'と違う点。 -T ... 現在のIPから1命令実行して停止 -T number ... 現在のIPからnumber命令実行して停止 -T = start ... start("Address"形式)から1命令実行して停止 -T = start number ... start("Address"形式)からnumber命令実行して停止 ||< : 'U'(Unassemble) : #block||> メモリ範囲を指定して逆アセンブル表示。 -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の中も追ってくれます。 #pre||> -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"コマンドを試してみましょう。 #pre||> -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) : #block||> アドレスにデータを書き込みます。 -E address ... 指定されたアドレスに1バイトデータを書き込みます。 address("Address"形式)がoffsetの場合はDS:offsetとして処理されます。 -E address list ... 指定されたアドレスから、listで指定されたシーケンシャルデータを 書き込みます。 なお、"list"には"String"形式も指定可能ですが、"\t""\r""\n"等のエスケープシーケンスは展開されません。またC言語のように末尾の0x00は付与されないので、必要なら明示的に指定する必要があります。 ||< : 'D'(Dump) : #block||> メモリ内容を16進ダンプします。 -D ... 最後にダンプしたアドレス or 初回ならDS:0からダンプ -D address ... 指定されたアドレスからダンプ -D range ... 指定されたメモリ範囲をダンプ ||< : 'R'(Register) : #block||> レジスタ内容を表示、あるいは新しい値を設定します。 -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) : #block||> 後述の'W'コマンドで書き出すファイル名を指定します。 -N [Drive:\path\]8_3_name.ext ファイル名はDOSの8.3形式で指定する必要があります。 ||< : 'W'(Write) : #block||> メモリイメージをファイルに出力します。出力するサイズを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"を参照して下さい。 #pre||> 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を使う機会は滅多にないと思いますが、もし万が一、使う場面に遭遇した時にこのページを思い出して頂ければ幸いです。 #navi_footer|Assembler|