この実習ではコンピュータがソフトウェアをどのように実行するかを確認し、プログラミングの理解を深めることにある。
今回の実習ではコマンドプロンプト上でdebugコマンドを使って実習します。ソースコードをテキストエディタで入力しません。マシン語のコードをファイルに保存します。
入力を誤るとプログラムが暴走することもあります。
まずは「コマンドプロンプト」または「MS-DOSプロンプト」を起動します。起動するには「スタート」→「プログラム」→「アクセサリ」の順にメニューを開くと、メニュー項目として表示されるのでそれを開きます。次のようなウィンドウが表示されれば起動成功です。
このウィンドウの中ではキーボードからコンピュータを操作するコマンドを入力します。
次にdebugを実行してみます。キーボードからコマンド名debugと入力し、Enterキーを押します。このコマンドを実行すると次のような表示に変わります。
この状態はdebugがコマンドの入力を待っている状態です(プロンプトが点滅しているはずです)。
debugを終了するにはコマンドqを使います。debugのプロンプトにコマンドqを入力し、Enterキーを押します。コマンドプロンプトに戻るはずです。
debugコマンドで利用できるコマンドの一覧はこの状態で?を入力し、Enterキーを押すと表示されます。
この表示は左から動作、コマンド名、コマンドの引数やオプションです。[]で囲まれている引数は省略可能という意味です。
今回の実習では32ビットCPUを搭載したPCを利用していますがdebugコマンドではリニアなアドレスを利用できず、16ビットCPU8086のセグメントを利用したアドレスしか扱えません。そのため、アドレスの表記が「セグメント:オフセットアドレス」という形式になります。セグメントはメモリを16バイト単位で区切った領域のアドレスです。セグメントとオフセットアドレスはそれぞれ16ビットで表現します。
8ビットCPUとの互換性を持たせるためにセグメントという考え方が利用されました。8086では最大1Mバイト(アドレスバスが20本ある)までのメモリを扱えます。
メモリには実行されるプログラムやそのプログラムが利用するデータが一時的に記憶されます。メモリの内容を人間が読める形式(16進数とASCII)で表示することを「ダンプする」といいます。debugのコマンドdでダンプされます。
dコマンドで範囲を指定しない場合は128バイト単位でメモリの内容が表示されます。
表示は左側からメモリアドレス、16バイトごとのメモリの内容、メモリの内容の文字列表示となります。
これを見て分かるとおり、メモリの内容はすべて2進コードです。
CPU内部には処理するデータやメモリアドレスを一時的に記憶するメモリであるレジスタがあります。レジスタの種類、個数、ビット幅はCPUの種類によって異なります。
debugでレジスタの内容を確認するにはrコマンドを使います。引数を指定しなければすべてのレジスタの値が表示されます。
rコマンドの引数にレジスタ名を指定するとレジスタに値を設定できます。
16ビット幅のレジスタだが上位8ビット、下位8ビットに分けて利用することもある。たとえば、AXは上位8ビットをAH、下位8ビットをALという名前で利用できる。他のレジスタも同様である。
レジスタ | 読み | 役割 |
---|---|---|
AX | アキュムレータ | 演算 |
BX | ベースレジスタ | 特定のメモリへのポインタ |
CX | カウントレジスタ | カウンタ |
DX | データレジスタ | データの一時的な記憶 |
IP以外は汎用レジスタと同じ機能を持つが通常メモリのアドレスを指定するポインターとして利用する。
レジスタ | 読み | 役割 |
---|---|---|
SI | ソースインデックス | 転送命令の転送元を指定 |
DI | ディスティネーションインデックス | 転送命令の転送先を指定 |
レジスタ | 読み | 役割 |
---|---|---|
BP | ベースポインタ | |
SP | スタックポインタ | スタックトップアドレスを保持 |
IP | インストラクションポインタ | 次に実行されるコードのアドレスを保持 |
セグメントアドレスを保持するレジスタ。プログラムやデータのセグメントを指定する。
レジスタ | 読み | 役割 |
---|---|---|
CS | コードセグメント | コードの保存されているセグメントを保持 |
DS | データセグメント | データの保存されているセグメントを保持 |
ES | エクストラセグメント | DS以外のデータを保存しているセグメントを保持 |
SS | スタックセグメント | スタック用セグメントを保持 |
加減算、比較演算などの演算結果などを保持するレジスタ。条件によって処理を分岐する場合に参照する。
フラグはレジスタ全体ではなく、ビット単位で設定されている。
フラグの種類を表に示す。
フラグ | 読み | 役割 | 1のとき | 0のとき |
---|---|---|---|---|
OF | オーバーフローフラグ | 演算結果がオーバーフローすると1になる | OV | NV |
DF | ディレクションフラグ | ポインタの増減方向 | DN | UP |
IF | インタラプトイネーブル | 割り込みを受け付けるときは1に | EI | DI |
TF | トラップフラグ | シングルステップモードで利用 | ||
SF | サインフラグ | 演算結果の符号。負のとき1になる | NG | PL |
ZF | ゼロフラグ | 演算結果が0のとき1になる | ZR | NZ |
AF | 補助キャリーフラグ | BCD演算で使用 | AC | NA |
PF | パリティフラグ | 演算結果が偶数パリティのとき1、奇数パリティのとき0 | PE | PO |
CF | キャリーフラグ | 演算結果でけた上がりが有ると1に | CY | NC |
debugを使ってアセンブラのプログラムを入力するにはaコマンドを使います。引数にオフセットアドレスを指定するとそのアドレスからの入力になります。
コマンドを入力するとアドレスが表示されるのでそれに続けてアセンブリ言語のプログラムを入力します。Enterキーを押すと次のアドレスが表示されます。もし、入力を終了するのであればアドレスが表示された状態でEtnerキーを押します。
入力に誤りがあるとその場で指摘されるので再入力します。入力する命令は大文字小文字のどちらでもかまいません。
メモリ内のでマシンコードをアセンブリ言語で表示することを逆アセンブルといいます。debugではuコマンドを使います。
debugはプログラムを一命令ずつ実行できます。そのときに使うコマンドがtです。命令を実行するとレジスタの内容が表示されます。
さまざまな命令があるがその中で最も多く利用されている命令。CPUのレジスタ間、レジスタとメモリ間でのデータのやりとりを行う命令。次のような書式になる。
MOV データの受け側,データの送り側
MOVはmoveを略した命令後だが、命令を実行後送り側のデータはそのまま残るので実際にはデータのコピーといえる。
まず、1バイトのデータFFHをレジスタALにロードするコードを示します。
MOV AL,0FF
データは16進数で、アルファベットで始まるので0を付けて、数値であることを明示しています。
次にレジスタALの値をオフセットアドレス0200Hのメモリにストアするコードを示します。
MOV [0200],AL
メモリアドレスを示す場合にはアドレスを[]で囲みます。
では、この二つのコードを入力してトレースしてみましょう。次の手順で確認してみます。
では、逆にメモリ中のデータをレジスタにロードするにはどうしたらいいでしょうか。オフセットアドレス0201のデータをレジスタALにロードするコードを考えてみてください。
MOV (1),(2)
次にメモリ間のデータ転送のコードを示す。次の例はメモリアドレス201にあるデータをメモリアドレス401に転送するコードである。
MOV AL,[0201] MOV [0401],AL
このコードが示すようにメモリ間でデータを転送する場合はレジスタを経由する。MOV [0401],[0201]というコードは利用できない。
メモリアドレスはこれまでの例のように直接指定する方法以外にレジスタを使うこともできる。これらを組み合わせることで複数の組み合わせが可能になる。