6

背景情報:最終的には、オリジナルの任天堂やゲームボーイなどの実機のエミュレーターを書きたいと思います。しかし、私はどこか、もっと簡単なところから始める必要があると決めました。私のコンピュータサイエンスのアドバイザー/教授は、彼が最初にエミュレートするために作成した非常に単純な架空のプロセッサの仕様を私に提供してくれました。1つのレジスタ(アキュムレータ)と16のオペコードがあります。各命令は16ビットで構成され、最初の4ビットにはオペコードが含まれ、残りはオペランドです。命令は、バイナリ形式の文字列として提供されます(例: "0101 0101 0000 1111")。

私の質問: C ++では、処理のために命令を解析するための最良の方法は何ですか?私の究極の目標を念頭に置いてください。これが私が考慮したいくつかのポイントです:

  1. コードは自己変更型であるため、命令を読みながら処理して実行することはできません。命令は後の命令を変更する可能性があります。これを回避するために私が見ることができる唯一の方法は、すべての変更を保存し、各命令に対して変更を適用する必要があるかどうかを確認することです。これは、各命令の実行との大量の比較につながる可能性がありますが、これは良くありません。そのため、命令を別の形式で再コンパイルする必要があると思います。

  2. オペコードを文字列として解析して処理することはできますが、命令全体を数値として受け取らなければならない場合があります。たとえば、増分オペコードは、命令のオペコードセクションでさえも変更する可能性があります。

  3. 命令を整数に変換する場合、intのオペコードまたはオペランドセクションだけを解析する方法がわかりません。各命令をintとして、オペコードをintとして、オペランドをintとして3つの部分に再コンパイルしたとしても、命令全体をインクリメントする必要があるため、問題は解決しません。その後、影響を受けるオペコードまたはオペランドを解析します。さらに、この変換を実行する関数を作成する必要がありますか、それとも「バイナリ形式」の文字列を整数に変換する関数(JavaのInteger.parseInt(str1、2)など)を持つC ++用のライブラリがありますか?

  4. また、ビットシフトなどの操作もできるようにしたいと思います。それをどのように達成できるかはわかりませんが、それがこの再コンパイルの実装方法に影響を与える可能性があります。

あなたが提供できる助けやアドバイスをありがとう!

4

4 に答える 4

5

元のコードを整数の配列に解析します。この配列はコンピュータのメモリです。

ビット単位の演算を使用して、さまざまなフィールドを抽出します。たとえば、これは次のとおりです。

unsigned int x = 0xfeed;
unsigned int opcode = (x >> 12) & 0xf;

0xfに格納されている16ビット値から最上位4ビット(ここでは)を抽出しunsigned intます。次に、egswitch()を使用してオペコードを検査し、適切なアクションを実行できます。

enum { ADD = 0 };

unsigned int execute(int *memory, unsigned int pc)
{
  const unsigned int opcode = (memory[pc++] >> 12) & 0xf;

  switch(opcode)
  {
  case OP_ADD:
    /* Do whatever the ADD instruction's definition mandates. */
    return pc;
  default:
    fprintf(stderr, "** Non-implemented opcode %x found in location %x\n", opcode, pc - 1);
  }
  return pc;
}

メモリの変更は、整数の配列に書き込む場合にすぎません。必要に応じて、ビット単位の計算を使用することもできます。

于 2010-03-02T14:24:28.307 に答える
1

命令を読み、符号なし整数に変換し、メモリに格納してから、メモリから実行するのが最善の方法だと思います。

  1. 命令を解析してメモリに保存すると、各命令の変更のリストを保存するよりも、自己変更がはるかに簡単になります。その場所のメモリを変更するだけです(古い命令が何であったかを知る必要がないと仮定します)。

  2. 命令を整数に変換しているので、この問題は議論の余地があります。

  3. オペコードとオペランドのセクションを解析するには、ビットシフトとマスキングを使用する必要があります。たとえば、オペコードを取得するには、上位4ビットをマスクして、12ビット下にシフトします(instruction >> 12)。マスクを使用してオペランドを取得することもできます。

  4. あなたのマシンにはビットをシフトする命令があるということですか?これは、オペランドの格納方法には影響しません。これらの命令の1つを実行する場合は、C++ビットシフト演算子<<とを使用できます>>

于 2010-03-02T14:30:32.860 に答える
0

念のため、これが私がC++で書いた最後のCPUエミュレーターです。実際、これは私がC++で作成した唯一のエミュレーターです。

仕様の言語は少し特異ですが、それは完全に立派で単純なVMの説明であり、おそらく教授のVMと非常によく似ています。

http://www.boundvariable.org/um-spec.txt

これが私の(やや過剰に設計された)コードで、いくつかのアイデアが得られるはずです。たとえば、um.cppのGiant Switchステートメントで、数学演算子を実装する方法を示しています。

http://www.eschatonic.org/misc/um.zip

多くの人がコンテストに参加したので、Web検索と比較するための他の実装を見つけることができるかもしれません(私はそれらの1人ではありませんでした:私はずっと後でそれをしました)。C ++ではそれほど多くはありませんが、私は推測します。

もし私があなたなら、仮想マシンの仕様がそれらの操作を定義する方法であるなら、私は最初に文字列として命令を保存するだけです。次に、実行するたびに、必要に応じてそれらを整数に変換します。遅くなりますが、それで何ですか?あなたのVMは、タイムクリティカルなプログラムを実行するために使用する実際のVMではありません。また、ドッグスローインタープリターは、この段階で知っておく必要のある重要なポイントを示しています。

ただし、VMは実際にはすべてを整数で定義し、文字列はプログラムがマシンにロードされたときにプログラムを説明するためだけに存在する可能性があります。その場合は、最初にプログラムを整数に変換してください。VMがプログラムとデータを一緒に保存し、同じ操作が両方に作用する場合は、これが最適な方法です。

それらから選択する方法は、プログラムを変更するために使用されるオペコードを調べることです。新しい命令は整数として提供されますか、それとも文字列として提供されますか?いずれにせよ、最初に最も簡単なことは、おそらくプログラムをその形式で保存することです。動作したら、後でいつでも変更できます。

上記のUMの場合、マシンは32ビット用のスペースを持つ「プラッター」で定義されます。明らかに、これらはC ++で32ビット整数として表すことができるので、それが私の実装で行われています。

于 2010-03-02T15:44:18.393 に答える
0

カスタム暗号化プロセッサ用のエミュレータを作成しました。基本クラスのツリーを作成することにより、C++のポリモーフィズムを活用しました。

struct Instruction  // Contains common methods & data to all instructions.
{
    virtual void execute(void) = 0;
    virtual size_t get_instruction_size(void) const = 0;
    virtual unsigned int get_opcode(void) const = 0;
    virtual const std::string& get_instruction_name(void) = 0;
};

class Math_Instruction
:  public Instruction
{
  // Operations common to all math instructions;
};

class Branch_Instruction
:  public Instruction
{
  // Operations common to all branch instructions;
};

class Add_Instruction
:  public Math_Instruction
{
};

私もいくつかの工場を持っていました。少なくとも2つは役に立ちます:

  1. テキストから命令を作成するファクトリ。
  2. オペコードから命令を作成するファクトリ

命令クラスには、入力ソース(例std::istream)またはテキスト(std::string)からデータをロードするメソッドが必要です。当然の出力方法もサポートする必要があります(命令名やオペコードなど)。

アプリケーションに入力ファイルからオブジェクトを作成させ、それらをのベクトルに配置しましたInstructionexecutorメソッドは、配列内の各命令の'execute()`メソッドを実行します。このアクションは、詳細な実行を実行する命令リーフオブジェクトに細流化されました。

エミュレーションが必要なグローバルオブジェクトは他にもあります。私の場合、データバス、レジスタ、ALU、メモリの場所が含まれていました。

コーディングする前に、プロジェクトの設計と検討にもっと時間をかけてください。single-step特に、有能なデバッガーとGUIを実装することは、非常に難しいと感じました。

幸運を!

于 2010-03-02T18:07:35.863 に答える