はじめに
本記事はVB.NETの初歩的な記法だけを使って、簡単な機械語で動く仮想CPUの実装法を解説します(※CPUにもいろいろありますが、この記事ではIntel社が製造しているCPUを対象とします)。その過程を通じて、初心者でもバイナリプログラミングが楽しめることと、バイナリプログラミングの魅力を伝えたいと思っています。
今回はDIVの実装を通じて、仮想環境におけるイベントの実装法を解説します。
これまでの連載
- 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命令の実装と命令長
- VB.NETで仮想CPUを作ろう (10) - MLU命令の実装とModR/Mについて
下準備
今回は前回の実装を拡張していきますので、あらかじめ第10回までの部分の実装を済ませておいてください。
DIV命令の説明
DIV命令はMUL命令と同じく少し複雑な命令ですので、先ずはDIV命令の概要を説明します。DIV命令はMUL命令と多くの類似点があります。第一に命令形式が同じです。
DIV ソースオペランド
ですから、MUL命令と同じくディスティネーションオペランドがないのでADD命令などとは異なり、結果を保存する場所が指定できません。ではどこへ結果が保存されるのかというと、これもMULと同じで、AL(1バイトの場合)かEAX(ダブルワードの場合)に固定されています。また、DIV命令もソースオペランドには今までのように即値を指定することができず、レジスタかメモリを指定せねばなりません。
第二に、MUL命令とほぼ同じバイナリを使用します。オペコードはMUL命令と同じで、ModR/Mの指定法も同じです。唯一の違いは、ModR/Mの「レジスタ/オペコードフィールド」が違うだけです。
最後に使用するレジスタもMUL命令と同じです。すなわち、1バイトの値を指定する場合はALとAHレジスタを使い、ダブルワード(4バイト)の値を指定する場合は、EAXとEDXレジスタを使用します。
ここまでの説明で、MUL命令と似ているのなら実装は簡単だと思われた方もいるでしょう。しかし、この「似ている」が曲者なのです。似ていると言うことは、「どこで区別するか」が重要となってきます。しかし、プログラムにとって何かを区別するということは、大変重要かつ難しいことです。人間ならば一目瞭然のことでも機械には理解できないことなので、非常に細かい単位まで物事を深く考えて、間違えないように非常に慎重に作業をせねばなりません。しかし人間は機械とは違い、誰でも非常に似ているものは間違えやすいので、「似ている時こそ要注意」せねばなりません。でも心配ご無用です。筆者が一つ一つ丁寧に説明しますので、初心者の方も安心して読み進めて下さい。
SearchOpeCodeMapメソッドの修正
DIV命令を判別するにはSearchOpeCodeMap
メソッドを修正せねばなりません。しかし、先ほど説明したようにMUL命令とオペコードが同じバイナリ値なので、このメソッドだけでは判別する方法がありません。そこで筆者はCommandName
構造体にUnKnown
を追加してから、次のように実装しました。
'オペコードマップを検索し、実行する命令と対象となるレジスタを探索する。 Private Shared Function SearchOpeCodeMap(ByVal binary As Byte) As OpeCode Dim result As OpeCode = New OpeCode() '指定されている命令を特定するために行と列を導出 Dim row As Byte = CByte((binary And &HF0) >> 4) Dim col As Byte = CByte(binary And &HF) '行と列から命令を割り出す result.NextInfo = InfoType.Value result.State = OpeCodeState.Fixed Select Case row Case 15 Select Case col Case 6 result.Name = CommandName.UnKnown result.BitCount = 8 result.NextInfo = InfoType.Register result.State = OpeCodeState.ModRM Case 7 result.Name = CommandName.UnKnown result.BitCount = 32 result.NextInfo = InfoType.Register result.State = OpeCodeState.ModRM Case Else Throw New ArgumentException("行" & row & "列" & col & "には対応しておりません。") End Select Case Else Throw New ArgumentException("行" & row & "列" & col & "には対応しておりません。") End Select '命令長設定 result.Length = GetCommandLength(result.Name) '結果を返す Return result End Function Public Structure OpeCode 'オペコードの状態 Private m_state As OpeCodeState Public Property State() As OpeCodeState Get Return Me.m_state End Get Set(ByVal value As OpeCodeState) Me.m_state = value End Set End Property '命令の名前 Private m_cmd As CommandName Public Property Name() As CommandName Get Return Me.m_cmd End Get Set(ByVal value As CommandName) Me.m_cmd = value End Set End Property '処理対象とするレジスタ Private m_reg As RegisterName Public Property Destination() As RegisterName Get Return Me.m_reg End Get Set(ByVal value As RegisterName) Me.m_reg = value End Set End Property '現在どのビット数の値を扱っているのかわかった方がいいです Private m_bit As Byte Public Property BitCount() As Byte Get Return m_bit End Get Set(ByVal value As Byte) m_bit = value End Set End Property '8ビットの場合、上位アドレスなのかの情報がいる。 Private m_hi As Boolean Public Property IsHi() Get Return Me.m_hi End Get Set(ByVal value) Me.m_hi = True End Set End Property 'レジスタの値 Private m_val As Register Public Property Value() As Register Get Return Me.m_val End Get Set(ByVal value As Register) Me.m_val = value End Set End Property '命令長 Private m_length As Byte Public Property Length() As Byte Get Return Me.m_length End Get Set(ByVal value As Byte) Me.m_length = value End Set End Property 'オペコードの次の情報 Private m_next As InfoType Public Property NextInfo() As InfoType Get Return Me.m_next End Get Set(ByVal value As InfoType) Me.m_next = value End Set End Property End Structure
以上のようにこのメソッドでは不明としておきます。それに加えて、この状況のように命令が不完全な場合、命令を確定するための情報をOpeCode
構造体に追加しました。こうしておけば、複数のバイナリを必要とする機械語命令を実装できるようになります。
この状態ではまだDIV命令を判別できませんので、次はGetModRMBinary
メソッドを修正します。