代码如何以EXE格式存储?

时间:2013-01-31 16:45:44

标签: format cpu executable opcodes

我的问题如下:

  1. Portable Executable格式(在windows / unix上)如何与x86 / x64指令集相关?
  2. PE格式是否存储处理器支持的确切操作码集,还是操作系统转换为与CPU匹配的更通用的格式?
  3. EXE文件如何指示所需的指令集扩展(如3DNOW!或SSE / MMX?)
  4. 所有平台上的操作码是否通用,如Windows,Mac和unix?
  5. Intel i386兼容的CPU芯片(如Intel和AMD的芯片)使用通用指令集。但我确信ARM驱动的CPU使用不同的操作码。这些非常非常不同或概念是否相似?寄存器,int / float / double,SIMD等?
  6. 在.NET,Java或Flash等较新的平台上,指令集是基于堆栈的操作码,JIT在运行时将其转换为本机格式。习惯于这种格式,我想知道如何执行和格式化“旧的”原生EXE格式。例如,“寄存器”通常在较新的平台操作码中不可用,因为JIT在其认为必要时将堆栈命令转换为16/32可用的CPU寄存器。但是在本机格式中,您需要通过索引引用寄存器,并确定可以重复使用哪些寄存器以及频率。

2 个答案:

答案 0 :(得分:9)

ARM操作码与x86操作码有很大不同吗?

是的,他们是。您应该假设不同处理器系列的所有指令集完全不同且不兼容。指令集首先定义编码,该编码指定其中的一个或多个:

  • 指令操作码;
  • 寻址模式;
  • 操作数大小;
  • 地址大小;
  • 操作数本身。

编码还取决于它可以解决的寄存器数量,是否必须向后兼容,是否必须快速解码,以及指令的复杂程度。

关于复杂性:ARM指令集要求所有操作数从内存加载到寄存器并使用专门的加载/存储指令从寄存器存储到存储器,而x86指令可以将单个存储器地址编码为其操作数之一,因此没有单独的加载/存储说明。

然后指令设置自己:不同的处理器将有专门的指令来处理特定的情况。即使两个处理器系列对同一事物具有相同的指令(例如add指令),它们的编码也会有很大不同,并且可能具有稍微不同的语义。

如您所见,由于任何CPU设计人员都可以决定所有这些因素,这使得不同处理器系列的指令集架构完全不同且不兼容。

寄存器,int / float / double和SIMD在不同架构上的概念截然不同吗?

不,他们非常相似。每个现代架构都有寄存器并且可以处理整数,并且大多数都可以处理某种大小的IEEE 754兼容浮点指令。例如,x86体系结构具有80位浮点值,这些值被截断以适合您知道的32位或64位浮点值。 SIMD指令背后的想法在支持它的所有架构上也是一样的,但是许多支持它并且大多数对它们有不同的要求或限制。

所有平台上的操作码是否通用,如Windows,Mac和Unix?

鉴于三个Intel x86系统,一个运行Windows,一个运行Mac OS X,一个运行Unix / Linux,然后操作码完全相同,因为它们在同一个处理器上运行。但是,每个操作系统都不同。内存分配,图形,设备驱动程序接口和线程等许多方面都需要特定于操作系统的代码。因此,您通常无法在Linux上运行为Windows编译的可执行文件。

PE格式是否存储处理器支持的确切操作码集,还是操作系统转换为与CPU匹配的更通用的格式?

不,PE格式不存储操作码集。如前所述,不同处理器系列的指令集架构太过不同,无法实现这一点。 PE文件通常存储一个特定处理器系列和操作系统系列的机器代码,并且只能在这样的处理器和操作系统上运行。

但有一个例外:.NET程序集也是PE文件,但它们包含非特定于任何处理器或操作系统的通用指令。这样的PE文件可以运行'在其他系统上,但不是直接。例如,Linux上的 mono 可以运行这样的.NET程序集。

EXE文件如何指示所需的指令集扩展(如3DNOW!或SSE / MMX?)

虽然可执行文件可以指示构建它的指令集(see Chris Dodd's answer),但我不相信可执行文件可以指示所需的扩展名。但是,可执行代码在运行时可以检测此类扩展。例如,x86指令集具有CPUID指令,该指令返回该特定CPU支持的所有扩展和功能。可执行文件只会在处理器不符合要求时测试并中止。

.NET与本机代码

您似乎对.NET程序集及其指令集了解了一两件事,称为CIL(通用中间语言)。每个CIL指令都遵循特定的编码,并将评估堆栈用于其操作数。 CIL指令集保持非常通用和高级。当它运行时(在Windows上由mscoree.dll,在Linux上由mono运行)并调用一个方法,即时(JIT)编译器采用方法的CIL指令并编译他们来机器代码。根据操作系统和处理器系列,编译器必须决定使用哪些机器指令以及如何对它们进行编码。编译结果存储在内存中的某处。下次调用该方法时,代码会直接跳转到已编译的机器代码,并且可以像本机可执行文件一样高效地执行。

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                       |
  --+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

字段表示以下内容:

  • 条件:一个条件,当为true时,会导致执行指令。这将查看Zero,Carry,Negative和Overflow标志。设置为1110时,始终执行指令。
  • R / I :当为0时,操作数2 是一个寄存器。当1时,操作数2 是一个常数值。
  • 操作码:指令的操作码。
  • S :当为1时,根据指令的结果设置零,进位,负和溢出标志。
  • Operand1 :用作第一个操作数的寄存器的索引。
  • 目的地:用作目标操作数的寄存器的索引。
  • 操作数2 :第二个操作数。当 R / I 为0时,寄存器的索引。当 R / I 为1时,为无符号8位常数值。除了这些中的任何一个之外,操作数2中的一些位指示值是否被移位/旋转。

有关更多详细信息,请阅读您想要了解的特定ARM版本的文档。我在这个例子中使用了ARM7TDMI-S Data Sheet, Chapter 4

请注意,每条ARM指令,无论多么简单,都需要4个字节进行编码。由于可能的开销,现代ARM处理器允许您使用名为 Thumb 的备用16位指令集。它不能表达32位指令集可以表达的所有内容,但它也只有一半大。

另一方面,x86-64指令具有可变长度编码,并使用各种修饰符来调整各个指令的行为。如果要将ARM指令与x86和x86-64指令的编码方式进行比较,则应阅读我在OSDev.org上撰写的x86-64 Instruction Encoding文章。


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

答案 1 :(得分:4)

PE文件格式(以及非Windows机器上的ELF / COFF文件格式)定义出现在文件开头的标题,在此标题中有一个“计算机”代码。在PE文件中,'Machine'代码是2个字节,spec为各种机器定义了一堆常量:

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直接执行。操作系统或动态链接器/加载器(执行实际加载)知道它正在运行什么机器,因此它会检查标头中的“机器”代码,以确保它在尝试加载和执行代码之前匹配。如果不匹配,则可执行文件将被拒绝,因为它无法运行。