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,那么yes操作码完全相同,因为它们在同一处理器上运行。然而,每个操作系统都是不同的。内存分配、图形、设备驱动程序接口和线程等许多方面都需要操作系统特定的代码。因此,通常无法在 Linux 上运行为 Windows 编译的可执行文件。
PE 格式是否存储处理器支持的确切操作码集,或者是操作系统转换以匹配 CPU 的更通用格式?
不,PE 格式不存储操作码集。如前所述,不同处理器系列的指令集架构差异太大,无法实现这一点。 PE 文件通常存储特定处理器系列和操作系统系列的机器代码,并且只能在此类处理器和操作系统上运行。
但有一个例外:.NET 程序集也是 PE 文件,但它们包含不特定于任何处理器或操作系统的通用指令。这样的PE文件can可以在其他系统上“运行”,但不能直接运行。例如,mono在 Linux 上可以运行此类 .NET 程序集。
EXE 文件如何指示所需的指令集扩展(例如 3DNOW!或 SSE/MMX?)
虽然可执行文件可以指示其构建的指令集(看看克里斯·多德的回答),我不相信可执行文件可以指示所需的扩展。然而,可执行代码在运行时可以检测到此类扩展。例如,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 |
--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
这些字段的含义如下:
-
健康)状况:条件为真时,会导致执行指令。它着眼于零、进位、负数和溢出标志。当设置为 1110 时,始终执行该指令。
-
R/I:当 0 时,操作数2是一个寄存器。当 1 时,操作数2是一个常数值。
-
Opcode:指令的操作码。
-
S:当为 1 时,根据指令的结果设置零、进位、负数和溢出标志。
-
Operand1:用作第一个操作数的寄存器的索引。
-
目的地:用作目标操作数的寄存器的索引。
-
操作数2:第二个操作数。什么时候R/I是0,寄存器的索引。什么时候R/I为 1,无符号 8 位常量值。除了其中任何一个之外,操作数 2 中的一些位还指示该值是否移位/循环。
有关更详细的信息,您应该阅读您想要了解的特定 ARM 版本的文档。我用过这个ARM7TDMI-S 数据表,第 4 章对于这个例子。
请注意,每个 ARM 指令,无论多么简单,都需要 4 个字节进行编码。由于可能存在开销,现代 ARM 处理器允许您使用替代的 16 位指令集,称为Thumb。它无法表达 32 位指令集可以表达的所有内容,但它的大小也只有 32 位指令集的一半。
另一方面,x86-64 指令具有可变长度编码,并使用各种修饰符来调整各个指令的行为。如果您想将 ARM 指令与 x86 和 x86-64 指令的编码方式进行比较,您应该阅读x86-64 指令编码我在 OSDev.org 上写的文章。
你原来的问题非常广泛。如果您想了解更多,您应该做一些研究并针对您想了解的具体内容提出一个新问题。