机器代码如何存储在EXE文件中?

问题描述 投票:6回答:2

我的问题如下:

  1. [Portable Executable格式(在Windows / Unix上)与一般的x86 / x64指令集有何关系?
  2. PE格式存储的是处理器支持的确切操作码集,还是OS转换为与CPU匹配的更通用的格式?
  3. EXE文件如何指示所需的指令集扩展名(如3DNOW!或SSE / MMX?]
  4. 操作码在Windows,Mac和Unix等所有平台上通用吗?
  5. Intel i386兼容的CPU芯片(例如Intel和AMD的CPU芯片使用通用的指令集。但是我确定ARM驱动的CPU使用不同的操作码。这些非常非常不同还是概念相似?寄存器,整数/浮点数/双精度,SIMD等?

在.NET,Java或Flash等较新的平台上,指令集是基于堆栈的操作码,JIT在运行时将其转换为本机格式。习惯了这种格式后,我想知道“旧”本机EXE格式是如何执行和格式化的。例如,“寄存器”通常在较新的平台操作码中不可用,因为JIT视需要将堆栈命令转换为16/32可用的CPU寄存器。但是,在本机格式中,您需要按索引引用寄存器,并确定哪些寄存器可以重复使用以及使用频率。

x86 portable-executable machine-code instruction-set opcode
2个回答
10
投票

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

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

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

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

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

然后,指令集本身:不同的处理器将具有专门的指令来处理特定的情况。即使两个处理器系列针对相同的事物使用相同的指令(例如add指令),它们的编码方式也非常不同,并且语义可能略有不同。

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

在不同的体系结构上,寄存器,整数/浮点/双精度和SIMD的概念是否完全不同?

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

操作码在Windows,Mac和Unix等所有平台上通用吗?

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

PE格式存储的是处理器支持的确切操作码集,还是OS转换为与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+ --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- +- -+ --- + --- + --- +-|条件| 0 | 0 | R / I |操作码| S |操作数1 | ...+ --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- +- -+ --- + --- + --- +-12 0-+ --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- +- + --- + --- + --- + --- +... |目的地|操作数2 |-+ --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- +- + --- + --- + --- + --- +

这些字段表示以下内容:

  • 条件:条件为true时,将导致执行指令。这将查看“零”,“进位”,“负”和“溢出”标志。设置为1110时,指令始终执行。
  • R / I:当为0时,操作数2为寄存器。当1时,操作数2为常数。
  • Opcode:指令的操作码。
  • S:为1时,将根据指令的结果设置零,进位,负和溢出标志。
  • Operand1:用作第一个操作数的寄存器的索引。
  • 目标:用作目标操作数的寄存器的索引。
  • Operand 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文章。


您最初的问题非常广泛。如果您想了解更多,您应该进行一些研究,并针对您要了解的特定问题创建一个新问题。


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)文件中,有一个或多个包含(二进制)机器代码的“代码”部分。该代码被加载到内存中并直接由CPU执行。操作系统或动态链接器/加载器(进行实际加载)知道运行的计算机,因此在尝试加载和执行代码之前,它会检查标头中的“机器”代码以确保其匹配。如果不匹配,则可执行文件将被拒绝,因为它无法运行。

© www.soinside.com 2019 - 2024. All rights reserved.