SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

VB.NETで学ぶ機械語の基礎

VB.NETで仮想CPUを作ろう(13)
- 機械語駆動式 関数電卓を作ろう! 解答編(前半)

VB.NETで学ぶ機械語の基礎 第13回

ダウンロード VirtualCPU13.zip (448.7 KB)

仕様5・6・7の実装例

仕様5 「四則演算(加算、減算、乗算、除算)機能をつける」
仕様6 「インクリメント、デクリメント、Moveが行えるようにする」
仕様7 「指定した機械語が確認できるようにする」

 仕様5と6は、これまでの回でやってきたことで、仮想CPUへ機械語を命令しその結果を受け取ることにより実現できます。

 この仕様については、IntelCpuオブジェクトの動きを管理するオブジェクトを作る方法などがありますが、今回筆者は生産効率を考えて、発行した命令を表すCommandオブジェクトを実装する方法を選びました。その理由は、他の仕様も視野に入れて検討した結果、集中管理するのはコストがかかるので、命令そのものをオブジェクト捉えて自身を管理させたらいいと考えたからです。

 筆者が考えたCommandオブジェクトは、命令を表すオブジェクトであり、自身だけを把握しています。文章だけでは分かり辛いと思いますのでコードを提示した後詳しい解説を行います。

命令オブジェクト
Imports System.Text
Imports VirtualCPU

'機械語命令を表すオブジェクト
Public Class Command
    Private m_cpu As IntelCpu
    Private builder As StringBuilder 'このオブジェクトを文字列として表す時に使用します

    '命令の名前
    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_destination As RegisterName
    Public Property Destination() As RegisterName
        Get
            Return Me.m_destination
        End Get
        Set(ByVal value As RegisterName)
            Me.m_destination = value
        End Set
    End Property

    'ソースオペランド
    Private m_source As RegisterName
    Public Property Source() As RegisterName
        Get
            Return Me.m_source
        End Get
        Set(ByVal value As RegisterName)
            Me.m_source = value
        End Set
    End Property

    '指定する値
    Private m_value As UInteger
    Public Property Value() As UInteger
        Get
            Select Case Me.m_source
                Case RegisterName.IMM
                    Return Me.m_value
            End Select
        End Get

        Set(ByVal value As UInteger)
            Select Case Me.m_source
                Case RegisterName.IMM
                    Me.m_value = value
            End Select
        End Set
    End Property

    'コンストラクタ共通の処理
    Private Sub New(ByRef cpu As IntelCpu, ByVal name As CommandName, ByVal destination As RegisterName)
        Me.builder = New StringBuilder()
        Me.m_cpu = cpu
        Me.m_cmd = Name
        Me.m_destination = Destination
    End Sub

    'ソースにレジスタを指定してインスタンスを生成する
    Sub New(ByRef cpu As IntelCpu, ByVal name As CommandName, ByVal destination As RegisterName, ByVal source As RegisterName)
        Me.New(cpu, name, destination)
        Me.m_source = source
    End Sub

    'ソースに即値を指定してインスタンスを生成する
    Sub New(ByRef cpu As IntelCpu, ByVal name As CommandName, ByVal destination As RegisterName, ByVal source As UInteger)
        Me.New(cpu, Name, destination)
        Me.m_value = source
        Me.m_source = RegisterName.IMM
    End Sub

    '指定された命令を表す文字列を返します
    Public Overrides Function ToString() As String

        '初期化
        builder.Remove(0, builder.Length)

        'オペコード ディスティネーション ソース の形式で文字列を返します。
        'ただし、即値が指定された場合はソースは値、それ以外はレジスタの名前とします。
        'また、INC命令のようにソースがない場合は表示しません。
        If Me.m_cmd = CommandName.Subtract Then
            'SUB命令の場合は直接指定する
            builder.Append("SUB")
        Else
            builder.Append(Me.m_cmd.ToString().ToUpper())
        End If
        Dim length As Integer = IntelCpu.GetCommandLength(Me.m_cmd)
        builder.Append(" ")
        builder.Append(Me.m_destination.ToString())
        If length > 1 Then
            builder.Append(" ")
            If Me.m_source = RegisterName.IMM Then
                builder.Append(Me.m_value.ToString())
            Else
                builder.Append(Me.m_source.ToString())
            End If
        End If

        '作成した文字列を返す
        Return Me.builder.ToString()

    End Function

    'バイナリ表現でこのオブジェクトのインスタンスを返します
    Public Function ToBinary() As Byte()

        '準備
        Dim binary(4) As Byte

        'コマンドとディスティネーションをバイナリ化
        binary(0) = IntelCpu.GetBinary(Me.m_cmd, Me.m_destination)

        'ソース部分をバイナリ化
        Dim length = IntelCpu.GetCommandLength(Me.m_cmd)
        If length > 1 Then
            If Me.m_source = RegisterName.IMM Then

                '即値をバイナリ化する
                Dim values As Byte() = IntelCpu.DivisionValue(Value)
                values = IntelCpu.GetLittleEndianValue(values)
                For i As Integer = 0 To 3
                    binary(i) = values(i)
                Next
            Else 'レジスタが指定されているのでModR/Mのバイナリを取得する

                ReDim Preserve binary(1)
                binary(1) = IntelCpu.GetModRMBinary(Me.m_cmd, Me.m_source)
            End If
        Else
            ReDim Preserve binary(0)
        End If

        '最終結果を返す
        Return binary
    End Function

End Class

 少し長いコードですが、実装そのものはさほど難しくありません。初心者の方も安心して読み進めてください。

 まずプロパティについてですが、機械語命令が持つ情報である、命令の名前、ディスティネーション、ソース(レジスタの場合使用)、ソースの即値です。各情報については、これまでの連載で説明したので特に違和感を感じないと思います。

 ToStringメソッドとToBinaryメソッドについては説明が必要だと思いますので詳細に説明します。

 ToStringメソッドはCommandListのItemsに追加したときに分かりやすく表示するために作りました。すなわち、このメソッドの内容が、命令の一覧が表示される場所に表示されるのです。例えば、「MOV EAX, 0」のようになります。 このメソッドで行っていることは、先ほどの例のような文字列を構築しているだけです。もし分からない場合は、MSDNなどでStringBuilderクラスについて調べてみてください。このメソッドで行っていることがよく分かります。

 ToBinaryメソッドはIntelCpuクラスを使用して、指定された機械語命令をバイナリ形式に変換しています。今までの連載を通じて作成してきたTestDriverプロジェクトの「機械語実行に関連するメソッド」と同じ処理を行っています。実はこのメソッドは後で処理を追加していきますので、その時改めて説明します。

 これで準備ができたので、MOVなどのボタンが押された時の処理を実装します。これらの命令系ボタンは個別に実装しても良いのですが、それでは効率が悪いので同じメソッドを共有することにします。コードは次のようになります。

各命令ボタンのクリックイベントに関連するコード
'機械語駆動式関数電卓を正常に動作させるための下準備を行います
Private Sub Calculator_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

    '各種変数の初期化を行う
    cpu = New IntelCpu()
    Me.InitRegisters()

    'コントロールの準備
    RegisterCombo.SelectedIndex = 0
    ValueCombo.SelectedIndex = 0
    NumberButtons = New List(Of Button)()
    NumberButtons.AddRange(New Button() {NumberButton0, NumberButton1, _
        NumberButton2, NumberButton3, NumberButton4, NumberButton5, _
        NumberButton6, NumberButton7, NumberButton8, _
        NumberButton9, NumberButtonA, NumberButtonB, _
        NumberButtonC, NumberButtonD, NumberButtonE, NumberButtonF})

    '10進数を標準にする
    DecimalOption_CheckedChanged(Nothing, EventArgs.Empty)

    '各命令系ボタンにコマンド名を設定する
    Me.AddButton.Tag = CommandName.Add
    Me.IncButton.Tag = CommandName.Inc
    Me.SubButton.Tag = CommandName.Subtract
    Me.DecButton.Tag = CommandName.Dec
    Me.MulButton.Tag = CommandName.Mul
    Me.DivButton.Tag = CommandName.Div

    'イベントの設定
    AddHandler cpu.RegisterValueChanged, AddressOf RegisterValueChanged

End Sub

'各機械語命令に関するボタンが押された時のイベントです
Private Sub CommandButtons_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles IncButton.Click, SubButton.Click, MulButton.Click, MovButton.Click, DivButton.Click, DecButton.Click, AddButton.Click
    '押されたボタンに対応する機械語命令を導出
    Dim name As CommandName = CType(CType(sender, Control).Tag, CommandName)

    'エラーチェック
    If Me.ValueCombo.SelectedIndex = 0 And IntelCpu.IsImm(name) = False Then
        MessageBox.Show(name.ToString().ToUpper() & "命令は即値を指定できません。", "操作エラー", _
                MessageBoxButtons.OK, MessageBoxIcon.Error)
        Exit Sub
    End If

    'ディティネーション・レジスタを決める
    Dim reg As RegisterName = CType(Me.RegisterCombo.SelectedIndex, RegisterName)

    '命令を生成する
    Dim cmd As Command
    If ValueCombo.SelectedIndex = 0 Then 'ソースとして即値が指定された場合
        cmd = New Command(Me.cpu, name, reg, Me.inputValue)
    Else 'ソースとしてレジスタが指定された場合
        '※レジスタは0は即値なので1を引いている
        cmd = New Command(Me.cpu, name, reg, CType(Me.ValueCombo.SelectedIndex - 1, RegisterName))
    End If

    '命令を実行する
    If Me.StepExeMenu.Checked = True Then
        '最終命令を即実行
        Me.cpu.ReadBinary(cmd.ToBinary())
        Me.cpu.AnalyzeBinary()
        Try
            Me.cpu.ExecuteCommand()
        Catch zero As DivideByZeroException
            MessageBox.Show("0で除算してしまいました", "演算エラー", _
                MessageBoxButtons.OK, MessageBoxIcon.Error)
            Exit Sub
        End Try
    End If

    '命令を記録/表示する(仕様7はこれでOK)
    Me.CommandList.Items.Add(cmd)
End Sub

'IntelCpuクラスの新メソッド
'即値が指定が可能なのか判定します
Public Shared Function IsImm(ByVal cmd As CommandName) As Boolean
    If cmd = CommandName.Mul OrElse cmd = CommandName.Div Then
        Return False
    End If
    Return True
End Function

 では上記実装の説明を行います。

 まず各命令ボタンで同じメソッドを使用するために、ButtonクラスのTagプロパティを有効活用しています。このアプリケーションが起動したとき(Loadイベント)の処理として、各ボタンのTagプロパティに対応する命令を割り当てておきます。

 次に、クリックイベントの冒頭で、Tagプロパティから対応する命令の情報を取り出すことにより、何のボタンがクリックされたのか分かる仕組みにしています。

 このようなTagプロパティを使った処理は、業務アプリではよくあることなので、ぜひ覚えて置いてください。役に立つはずです。

 これでどの命令が指定されたのか判別できるようになったので、次にエラーチェックを行います。これが必要な理由は、INCとDEC命令では1で固定なので即値が指定できないからです。ですから、操作している人が混乱しないようにエラーメッセージを出すようにしました。

 その次に、ディスティネーションアドレス(処理対象となるレジスタ)を決めてから、実行命令に関する情報を整理しています。この処理については先ほど説明したCommandクラスに処理を任せているだけです。

 ここまでくれば、後は命令を実行してその命令を記録するだけです。この2つの処理は簡単ですが、例外に気をつけてください。DIV命令でソースで0が指定されたときDivideByZeroException例外が発生します。この例外を捕捉しておかないと、アプリケーションが異常な状態になりますので、必ずチェックしてください。

 以上で仕様5、6、7の実装は終わりです。次は仕様8の実装を解説します。

次のページ
実装8

この記事は参考になりましたか?

VB.NETで学ぶ機械語の基礎連載記事一覧

もっと読む

この記事の著者

インドリ(インドリ)

分析・設計・実装なんでもありのフリーエンジニア。ブログ「無差別に技術をついばむ鳥(http://indori.blog32.fc2.com/)」の作者です。アドバイザーをしたり、システム開発したり、情報処理技術を研究したりと色々しています。座右の銘は温故知新で、新旧関係なく必要だと考えたものは全て学...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/3424 2010/04/27 12:04

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー