はじめに
本記事はVB.NETの初歩的な記法だけを使って、簡単な機械語で動く仮想CPUの実装法を解説します(※CPUにもいろいろありますが、この記事ではIntel社が製造しているCPUを対象とします)。その過程を通じて、初心者でもバイナリプログラミングが楽しめることと、バイナリプログラミングの魅力を伝えたいと思っています。
今回はMULの実装を通じて、ソースオペランドにレジスタを指定する機械語の実装法を解説します。
これまでの連載
- VB.NETで仮想CPUを作ろう
- VB.NETで仮想CPUを作ろう (2) - レジスタの実装
- VB.NETで仮想CPUを作ろう (3) - 仮想CPUのGUI化
- VB.NETで仮想CPUを作ろう (4) - テストドライバの改良
- VB.NETで仮想CPUを作ろう (5) - CPUの基礎動作の実装
- VB.NETで仮想CPUを作ろう (6) - MOV命令実装
- VB.NETで仮想CPUを作ろう (7) - ADD命令実装
- VB.NETで仮想CPUを作ろう (8) - SUB命令実装
- VB.NETで仮想CPUを作ろう (9) - INC命令&DEC命令の実装と命令長
下準備
今回は前回の実装を拡張していきますので、あらかじめ第9回までの部分の実装を済ませておいてください。
MUL命令の概要
MUL命令は今まで実装した命令とは性質が異なりますので、まずはMUL命令の概要から解説します。今まで実装してきた算術命令は次の形式でした。
命令 ディスティネーションオペランド ソースオペランド
今まで、ディスティネーションオペランドとソースオペランドを説明していなかったので、この機会に説明しておきます。ディスティネーション(destination)とは、目的地、届け先、行き先などの意を持つ英単語で、算術命令では結果を保存する場所を示しています。一方、ソース(source)とは、源、源泉、転送元などの意を持つ英単語で、算術命令では計算に使用する値(ADDの場合は加算値、SUBの場合は減算値)を示しています。
あと、オペランドについてですが、厳密な定義は複雑なので、機械語を扱う場合には、レジスタ、メモリ、即値などを表しているとイメージすれば分かりやすいと思います。文章だけだと理解しにくいので例を提示します。
MOV EAX, 0 ←ディスティネーションオペランドであるEAXレジスタの値に、ソースオペランド(即値:0)を設定する。 EAX=0 ADD EAX, 10 ←ディスティネーションオペランドであるEAXレジスタの値に、ソースオペランド(即値:10)を加算する。 EAX=10 SUB EAX, 5 ←ディスティネーションオペランドであるEAXレジスタの値から、ソースオペランド(即値:5)を減算する。 EAX=5
ところが、今回実装するMUL命令は次の形式の命令です。
MUL ソースオペランド
このようにディスティネーションオペランドがないので、今までの算術とは違い、結果を保存する場所が指定できません。ではどこへ結果が保存されるのかというと、MUL命令ではAL(1バイトの場合)かEAX(ダブルワードの場合)に固定されています。
さらに、ソースオペランドには今までのように即値を指定することができず、レジスタかメモリを指定せねばなりません。ソースオペランドにレジスタを指定するには、命令フォーマットの概念を理解する必要がありますので、次項でそれを説明します。
命令フォーマット
機械語の命令は0と1の羅列ですので、CPUがそれを解釈するためにフォーマットが定められています。それを命令フォーマットと呼びます。今までの命令は次の命令フォーマットでバイナリ指定してしました。
オペコード(1または2バイト) 即値(1バイトまたは4バイト)
しかし、MULの場合は次の命令フォーマットにしなくてはなりません。
オペコード(1または2バイト) ModR/M(1バイト)
ModR/Mでは、レジスタ、メモリのアドレス指定方式などの情報を指定します。なお、オペコードの次の項目はオペコードによって決まっています。例えばADD命令が複数あるのは次の要素を指定するためなのです。今回はメモリについては扱いませんので、次項でModR/Mでレジスタ指定方法を実装します。