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
+---+---+---+---+---+---+---+---+---+---+---+---+- --+---+---+---+--
|条件 | 0 | 0 |R/I|操作码 |小号 |操作数 1 | ...
+---+---+---+---+---+---+---+---+---+---+---+---+- --+---+---+---+--
12 0
--+---+---+---+---+---+---+---+---+---+---+---+--- +---+---+---+---+
... |目的地 |操作数 2 |
--+---+---+---+---+---+---+---+---+---+---+---+--- +---+---+---+---+
这些字段的含义如下:
-
条件:当为真时,会导致指令执行的条件。这会查看零、进位、负数和溢出标志。设置为 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 文章。
您最初的问题非常广泛。如果你想知道更多,你应该做一些研究,并用你想知道的具体事情创建一个新问题。