9

私の質問は次のとおりです。

  1. 一般に、Portable Executable 形式 (Windows/Unix 上) は x86/x64 命令セットとどのように関連していますか?
  2. PE 形式は、プロセッサでサポートされているオペコードの正確なセットを保存しますか?それとも、OS が CPU に一致するように変換するより一般的な形式ですか?
  3. EXE ファイルは、必要な命令セットの拡張機能 (3DNOW! や SSE/MMX など) をどのように示していますか?
  4. オペコードは、Windows、Mac、UNIX などのすべてのプラットフォームで共通ですか?
  5. Intel や AMD のような Intel i386 互換の CPU チップは、共通の命令セットを使用します。しかし、ARM 搭載の CPU は異なるオペコードを使用していると確信しています。これらは非常に異なるものですか、それとも概念は似ていますか? レジスタ、int/float/double、SIMD など?

.NET、Java、Flash などの新しいプラットフォームでは、命令セットは、JIT が実行時にネイティブ形式に変換するスタックベースのオペコードです。このような形式に慣れているので、「古い」ネイティブ EXE 形式がどのように実行され、フォーマットされるかを知りたいと思います。たとえば、JIT は必要に応じてスタック コマンドを 16/32 の利用可能な CPU レジスタに変換するため、新しいプラットフォーム オペコードでは通常、「レジスタ」は使用できません。ただし、ネイティブ形式では、レジスタをインデックスで参照し、再利用できるレジスタと頻度を調べる必要があります。

4

2 に答える 2

10

ARM オペコードは x86 オペコードと大きく異なりますか?

はい、そうです。異なるプロセッサフ​​ァミリのすべての命令セットは完全に異なり、互換性がないと想定する必要があります。命令セットは最初に、次の 1 つ以上を指定するエンコーディングを定義します。

  • 命令オペコード;
  • アドレッシング モード。
  • オペランドのサイズ;
  • アドレスサイズ;
  • オペランド自体。

エンコーディングはさらに、アドレス指定できるレジスタの数、下位互換性が必要かどうか、迅速にデコードできる必要があるかどうか、および命令がどれほど複雑になるかによって異なります。

複雑さについて: ARM 命令セットでは、特殊なロード/ストア命令を使用してすべてのオペランドをメモリからレジスタにロードし、レジスタからメモリに格納する必要がありますが、x86 命令は単一のメモリ アドレスをオペランドの 1 つとしてエンコードできるため、個別のロード/ストア命令。

次に、命令セット自体です。異なるプロセッサには、特定の状況に対処するための特殊な命令があります。2 つのプロセッサ ファミリが同じもの (add命令など) に対して同じ命令を持っている場合でも、それらは非常に異なる方法でエンコードされ、セマンティクスがわずかに異なる場合があります。

ご覧のように、どの CPU 設計者もこれらすべての要素を決定できるため、異なるプロセッサ ファミリの命令セット アーキテクチャは完全に異なり、互換性がありません。

レジスタ、int/float/double、および SIMD は、異なるアーキテクチャでは非常に異なる概念ですか?

いいえ、とても似ています。最新のすべてのアーキテクチャにはレジスタがあり、整数を処理できます。ほとんどのアーキテクチャは、ある程度のサイズの IEEE 754 互換の浮動小数点命令を処理できます。たとえば、x86 アーキテクチャには、既知の 32 ビットまたは 64 ビットの浮動小数点値に合わせて切り捨てられた 80 ビットの浮動小数点値があります。SIMD 命令の背後にある考え方も、それをサポートするすべてのアーキテクチャで同じですが、多くのアーキテクチャではサポートされておらず、異なる要件や制限があります。

オペコードは、Windows、Mac、Unix などのすべてのプラットフォームで共通ですか?

3 つの Intel x86 システムがあり、1 つは Windows を実行し、1 つは Mac OS X を実行し、もう 1 つは Unix/Linux実行すると、同じプロセッサで実行されるため、オペコードはまったく同じです。ただし、オペレーティング システムはそれぞれ異なります。メモリ割り当て、グラフィックス、デバイス ドライバー インターフェイス、スレッド化などの多くの側面で、オペレーティング システム固有のコードが必要です。そのため、通常、Windows 用にコンパイルされた実行可能ファイルを Linux で実行することはできません。

PE 形式は、プロセッサでサポートされているオペコードの正確なセットを保存しますか?それとも、OS が CPU に一致するように変換するより一般的な形式ですか?

いいえ、PE 形式はオペコードのセットを保存しません。前に説明したように、異なるプロセッサ ファミリの命令セット アーキテクチャは、あまりにも異なっているため、これを実現することはできません。PE ファイルは通常、1 つの特定のプロセッサ ファミリおよびオペレーティング システム ファミリのマシン コードを格納し、そのようなプロセッサおよびオペレーティング システムでのみ実行されます。

ただし、例外が 1 つあります。.NET アセンブリも PE ファイルですが、プロセッサやオペレーティング システムに固有ではない一般的な命令が含まれています。このような PE ファイルは、他のシステムで「実行」できますが、直接実行することはできません。たとえば、Linuxのmonoは、そのような .NET アセンブリを実行できます。

EXE ファイルは、必要な命令セットの拡張機能 (3DNOW! や SSE/MMX など) をどのように示していますか?

実行可能ファイルはそれが構築された命令セットを示すことができますが ( Chris Dodd の回答を参照)、実行可能ファイルが必要な拡張機能を示すことができるとは思いません。ただし、実行可能コードを実行すると、そのような拡張子を検出できます。たとえば、x86 命令セットには、CPUIDその特定の CPU でサポートされているすべての拡張機能と機能を返す命令があります。実行可能ファイルはそれをテストし、プロセッサが要件を満たさない場合に中止します。

.NET とネイティブ コード

.NET アセンブリと、CIL (Common Intermediate Language) と呼ばれるその命令セットについて、ある程度はご存知のようです。各 CIL 命令は特定のエンコーディングに従い、そのオペランドに評価スタックを使用します。CIL 命令セットは、非常に一般的でハイレベルに保たれています。メソッドが(Windows では によってmscoree.dll、Linux では によってmono) 実行され、メソッドが呼び出されると、Just-In-Time (JIT) コンパイラはメソッドの CIL 命令を受け取り、それらをマシン コードにコンパイルします。オペレーティング システムとプロセッサ ファミリに応じて、コンパイラは使用するマシン命令とそのエンコード方法を決定する必要があります。コンパイルされた結果はメモリのどこかに保存されます。次にメソッドが呼び出されると、コードはコンパイルされたマシン コードに直接ジャンプし、ネイティブ実行可能ファイルと同じくらい効率的に実行できます。

ARM 命令はどのようにエンコードされますか?

私は ARM を使用したことはありませんが、ドキュメントをざっと見てみると、次のことがわかります。ARM 命令の長さは常に 32 ビットです。多くの例外的なエンコーディングがあります (分岐命令やコプロセッサ命令など) が、ARM 命令の一般的な形式は次のようになります。

31 28 27 26 25 21 20 16
+---+---+---+---+---+---+---+---+---+---+---+---+- --+---+---+---+--
| | 状態 | 0 | 0 |R/I| オペコード | さ | オペランド 1 | ...
+---+---+---+---+---+---+---+---+---+---+---+---+- --+---+---+---+--

                   12 0
  --+---+---+---+---+---+---+---+---+---+---+---+--- +---+---+---+---+
... | 目的地 | オペランド 2 |
  --+---+---+---+---+---+---+---+---+---+---+---+--- +---+---+---+---+

フィールドの意味は次のとおりです。

  • 条件: 真の場合に命令が実行される条件。これは、Zero、Carry、Negative、および Overflow フラグを調べます。1110 に設定すると、命令は常に実行されます。
  • R/I : 0 の場合、オペランド 2はレジスタです。1 の場合、オペランド 2は定数値です。
  • Opcode : 命令のオペコード。
  • S : 1 の場合、ゼロ、キャリー、ネガティブ、およびオーバーフロー フラグは、命令の結果に従って設定されます。
  • Operand1 : 最初のオペランドとして使用されるレジスタのインデックス。
  • Destination : デスティネーション オペランドとして使用されるレジスタのインデックス。
  • オペランド 2 : 第 2 オペランド。R/Iが 0 の場合、レジスタのインデックス。R/Iが 1 の場合、符号なし 8 ビットの定数値。これらのいずれかに加えて、オペランド 2 のいくつかのビットは、値がシフト/回転されているかどうかを示します。

詳細については、知りたい特定の ARM バージョンのドキュメントを参照してください。この例では、このARM7TDMI-S データ シートの第 4 章を使用しました。

各 ARM 命令は、どんなに単純であっても、エンコードに 4 バイトかかることに注意してください。オーバーヘッドが発生する可能性があるため、最近の ARM プロセッサでは、 Thumbと呼ばれる代替の 16 ビット命令セットを使用できます。32 ビット命令セットが表現できるすべてのことを表現することはできませんが、サイズも半分です。

一方、x86-64 命令には可変長エンコーディングがあり、あらゆる種類の修飾子を使用して個々の命令の動作を調整します。ARM 命令を x86 および x86-64 命令のエンコード方法と比較したい場合は、私が OSDev.org に書いたx86-64 命令エンコードの記事を読む必要があります。


あなたの元の質問は非常に広いです。もっと知りたい場合は、調査を行い、知りたい特定の事柄で新しい質問を作成する必要があります。

于 2013-03-12T12:17:04.123 に答える
5

PE ファイル形式 (および非 Windows マシンの ELF/COFF ファイル形式) は、ファイルの先頭に表示されるヘッダーを定義し、このヘッダーには「マシン」コードがあります。PE ファイルでは、「マシン」コードは 2 バイトであり、仕様ではさまざまなマシンの一連の定数が定義されています。

0x1d3   Matsushita AM33
0x8664  AMD x64
0x1c0   ARM little endian   
0x1c4   ARMv7 (or higher) Thumb mode only
0xebc   EFI byte code   
0x14c   Intel 386 or later processors and compatible processors 
0x200   Intel Itanium processor family  
0x9041  Mitsubishi M32R little endian   
0x266   MIPS16  
0x366   MIPS with FPU
0x466   MIPS16 with FPU 
0x1f0   Power PC little endian  
0x1f1   Power PC with floating point support    
0x166   MIPS little endian  
0x1a2   Hitachi SH3 
0x1a3   Hitachi SH3 DSP 
0x1a6   Hitachi SH4 
0x1a8   Hitachi SH5     
0x1c2   ARM or Thumb (“interworking”)   
0x169   MIPS little endian WCE v2   

次に、PE (または ELF) ファイル内に、(バイナリ) マシン コードを含む 1 つ以上の「コード」セクションがあります。そのコードはメモリにロードされ、CPU によって直接実行されます。OS または動的リンカー/ローダー (実際の読み込みを行う) は、実行中のマシンを認識しているため、コードの読み込みと実行を試みる前に、ヘッダーの「マシン」コードをチェックして一致することを確認します。一致しない場合、実行可能ファイルは実行できないため、拒否されます。

于 2013-01-31T17:15:55.130 に答える