如何存储在EXE格式的代码?(How is code stored in the EXE forma

2019-07-19 01:44发布

我的问题如下:

  1. 如何移植可执行文件格式(在Windows / UNIX)涉及到一般设置在x86 / x64指令?
  2. 请问PE格式储存操作码的处理器所支持的具体设置,或者是它的操作系统转换为CPU搭配一个更通用的格式?
  3. 如何EXE文件显示所需的扩展指令集(如3DNOW!或者SSE / MMX?)
  4. 是操作码跨如Windows,Mac和Unix平台上的所有常见?
  5. 像英特尔和AMD的人英特尔的i386兼容的CPU芯片使用共同的指令集。 但我敢肯定,ARM供电的CPU使用不同的操作码。 这些是非常非常不同的或者概念相似? 寄存器,INT /浮动/双,SIMD等?

像.NET,Java或闪存新平台,指令集是基于堆栈的一个JIT转换为原生格式在运行时操作码。 而习惯了这样,我想知道如何“老”原生EXE格式执行和格式化的格式。 例如,“寄存器”,通常在较新的平台操作码不可用,因为JIT转换它认为必要堆栈命令16/32可用的CPU寄存器。 但是,在本机格式,你需要通过索引引用寄存器,并找出哪些寄存器可重复使用的时间和频率。

Answer 1:

Are ARM opcodes very different from x86 opcodes?

Yes, they are. You should assume that all instruction sets for different processor families are completely different and incompatible. An instruction set first defines an encoding, which specifies one or more of these:

  • the instruction opcode;
  • the addressing mode;
  • the operand size;
  • the address size;
  • the operands themselves.

The encoding further depends on how many registers it can address, whether it has to be backwards compatible, if it has to be decodable quickly, and how complex the instruction can be.

On the complexity: the ARM instruction set requires all operands to be loaded from memory to register and stored from register to memory using specialized load/store instructions, whereas x86 instructions can encode a single memory address as one of their operands and therefore do not have separate load/store instructions.

Then the instruction set itself: different processors will have specialized instructions to deal with specific situations. Even if two processors families have the same instruction for the same thing (e.g. an add instruction), they are encoded very differently and may have slightly different semantics.

As you see, since any CPU designer can decide on all these factors, this makes the instruction set architectures for different processor families completely different and incompatible.

Are registers, int/float/double and SIMD very different concepts on different architectures?

No they are very similar. Every modern architecture has registers and can handle integers, and most can handle IEEE 754 compatible floating-point instructions of some size. For example, the x86 architecture has 80-bit floating-point values that are truncated to fit the 32-bit or 64-bit floating-point values you know. The idea behind SIMD instructions is also the same on all architectures that support it, but many do not support it and most have different requirements or restrictions for them.

Are the opcodes common across all platforms like Windows, Mac and Unix?

Given three Intel x86 systems, one running Windows, one running Mac OS X and one running Unix/Linux, then yes the opcodes are exactly the same since they run on the same processor. However, each operating system is different. Many aspects such as memory allocation, graphics, device driver interfacing and threading require operating system specific code. So you generally can't run an executable compiled for Windows on Linux.

Does the PE format store the exact set of opcodes supported by the processor, or is it a more generic format that the OS converts to match the CPU?

No, the PE format does not store the set of opcodes. As explained earlier, the instruction set architectures of different processor families are simply too different to make this possible. A PE file usually stores machine code for one specific processor family and operating system family, and will only run on such processors and operating systems.

There is however one exception: .NET assemblies are also PE files but they contain generic instructions that are not specific to any processor or operating system. Such PE files can be 'run' on other systems, but not directly. For example, mono on Linux can run such .NET assemblies.

How does the EXE file indicate the instruction set extensions needed (like 3DNOW! or SSE/MMX?)

While the executable can indicate the instruction set for which it was built (see Chris Dodd's answer), I don't believe the executable can indicate the extensions that are required. However, the executable code, when run, can detect such extensions. For example, the x86 instruction set has a CPUID instruction that returns all the extensions and features supported by that particular CPU. The executable would just test that and abort when the processor does not meet the requirements.

.NET versus native code

You seem to know a thing or two about .NET assemblies and their instruction set, called CIL (Common Intermediate Language). Each CIL instruction follows a specific encoding and uses the evaluation stack for its operands. The CIL instruction set is kept very general and high-level. When it is run (on Windows by mscoree.dll, on Linux by mono) and a method is called, the Just-In-Time (JIT) compiler takes the method's CIL instructions and compiles them to machine code. Depending on the operating system and processor family the compiler has to decide which machine instructions to use and how to encode them. The compiled result is stored somewhere in memory. The next time the method is called the code jumps directly to the compiled machine code and can execute just as efficiently as a native executable.

ARM指令是如何编码的?

我从来没有与ARM合作,但是从文件看一眼我可以告诉你以下。 一个ARM指令的长度总是32位。 有许多特殊的编码(如分支和协处理器指令),而是ARM指令的一般格式是这样的:

31             28  27  26  25              21  20              16
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+--
|   Condition   | 0 | 0 |R/I|    Opcode     | S |   Operand 1   | ...
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+--

                   12                                               0
  --+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
... |  Destination  |               Operand 2                       |
  --+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

这些字段的含义如下:

  • 条件 :一个条件是,真当,导致被执行的指令。 这种着眼于零,进位,负和溢出标志。 当设置为1110,该指令总是执行。
  • R / I:为0时, 操作数2是寄存器。 为1时, 操作数2是一个恒定值。
  • 操作码 :指令的操作码。
  • S:1时,零,进位,负和溢出标志是根据该指令的结果集。
  • 操作数1:被用作第一个操作数的寄存器的索引。
  • 目的地 :其被用作目的操作数的寄存器的索引。
  • 操作数2:第二个操作数。 当R / I是0时,寄存器的索引。 当R / I是1时,一个无符号的8位的恒定值。 除了这些的任一个,在操作数2中的一些比特指示的值是否被移位/旋转。

如需更详细的信息,请阅读您想了解具体的ARM版本的文档。 我用这个ARM7TDMI-S数据表,第4章的这个例子。

请注意,每个ARM指令,不管多么简单,需要4个字节来编码。 由于可能的开销,现代ARM处理器允许您使用称为拇指替代16位指令集。 它不能表达的东西全部32位指令集可以,但它也是一半大。

在另一方面,x86-64的指令具有可变的长度编码,并使用各种改性剂来调整各指令的行为。 如果你想比较的x86和x86-64指令是如何编码的ARM指令,你应该阅读X86-64指令编码 ,我就写了OSDev.org文章。


你原来的问题是非常广泛的。 如果你想知道更多,你应该做一些研究,并创建一个你想知道具体的事情的新问题。



Answer 2:

PE文件格式(和ELF / COFF在非Windows机器的文件格式)定义了出现在文件的开头的报头,并且在该标头,有一个“机器”的代码。 在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)文件内有包含(二进制)机器代码的一个或多个“代码”部分。 该代码被加载到存储器中并由CPU直接执行。 操作系统或动态链接器/加载器(它的实际载荷)知道什么机器正在运行,所以它会检查“机”的代码在头,以确保它试图加载并执行代码之前匹配。 如果不匹配,可执行文件将被拒绝,因为它不能运行。



文章来源: How is code stored in the EXE format?