问题描述
我想我对 x86-64/i686 架构和 32 位/64 位操作系统感到困惑。
如果我在 32 位操作系统(例如 Ubuntu 14.04)上使用 g++
构建应用程序,则 -m64
不是有效选项,我无法构建 64 位二进制文件。但是,g++
不会抱怨使用 -march=x86-64
。 (硬件为酷睿 i7。)
然后,如果我在 64 位操作系统(例如 Ubuntu 20.04)上构建,我可以在 -m32
和 -m64
之间切换并构建 32 位或 64 位二进制文件。
所以,我的理解是...
-m32
和 -m64
决定了我可以在哪些操作系统上使用二进制文件。 (包括在 64 位操作系统上使用的 32 位二进制文件以及适当的库)
并且 -march
定义将用于代码生成的指令集 - 无论它注定要运行的操作系统是什么。
所以我的主要问题是...
鉴于我可以使用 -m32 -march=x86-64
在 32 位操作系统上构建和运行二进制文件,这是否意味着 x86-64 指令可以在 32 位操作系统上使用?位操作系统?
解决方法
不,64 位指令不能在 32 位操作系统下使用(甚至不能在 64 位内核下的 32 位用户空间中使用),这不是 -march=x86-64
的意思。
-march=x86-64
表示目标 CPU 支持 x86-64 保证的基线 ISA 扩展集:SSE2、P6 功能,如 CMOV、CPUID、RDTSC 等。但是如果没有 -m64
,代码仍然可以在 32 位模式下工作。
(对于 x86-64 的目标 ISA,它们本身并不是“扩展”。但我们仍然通常说 x86-64 意味着 SSE 和 SSE2,因为这是对那些在 XMM 上运行的指令进行分类的方便方法寄存器。如果我们将 x86-64 本身视为 x86 的扩展,这是有道理的。)
所以 -march=x86-64
与 -march=k8
或 -march=nocona
(特定于早期的 x86-64 CPU)非常相似。就像这些选项一样,它对 -m32
完全有效,并不意味着制作 64 位代码。
GCC 仍然知道(来自 -m32
)它的目标是 32 位模式(受保护或兼容模式),而不是 64 位长模式。
由于这些模式使用不兼容的机器代码格式,因此在制作 16 位代码以让编译器使用 32 位代码时,您可能期望 -march=i386
的 16 位到 32 位转换完全不同寄存器。在 x86-64 中,64 位寄存器只能用于长模式,不能用于兼容模式或遗留模式的任何子模式(例如保护模式)。见https://en.wikipedia.org/wiki/X86-64#Operating_modes
这里的关键点是,在兼容模式下(在 64 位内核下),32 位进程无法执行在保护模式下(在 32 位内核下)无法执行的任何操作。 x86-64 没有为 32 位进程提供在 64 位内核下运行的优势。 (除了将远 jmp 转换为 64 位代码段,但大多数操作系统不支持这一点,编译器肯定不会发出代码来做到这一点。当然使用完整的 4GB 地址空间,但就实际的机器代码指令什么都没有。)因此,没有任何您希望编译器使用的功能只能在 64 位内核下以 32 位模式运行。
TL:DR:-m32 -march=x86-64
可以解释为“通用 x86-64 CPU 的目标兼容/保护模式”。
实际上它实际上只是意味着“启用这组 ISA 扩展”,并且 该名称对于 GCC 来说没有比 -march=foobarbaz
或 -mtune=intel
更有意义,它只是一个文本字符串调整和 ISA 扩展设置表。