在 64 位 linux 上从 32 位模式切换到 64 位(长模式)

Posted

技术标签:

【中文标题】在 64 位 linux 上从 32 位模式切换到 64 位(长模式)【英文标题】:Switch from 32bit mode to 64 bit (long mode) on 64bit linux 【发布时间】:2014-07-29 14:33:46 【问题描述】:

我的程序在 x86_64 CPU(64 位操作系统,ubuntu 8.04)上以 32 位模式运行。是否可以在用户模式下临时切换到 64 位模式(长模式)?如果有,怎么做?

背景故事:我正在编写一个与 32 位模式程序链接的库,所以它在启动时必须是 32 位模式。但是,我想使用更快的 x86_64 指令以获得更好的性能。所以我想切换到 64 位模式做一些纯计算(没有操作系统交互;不需要 64 位寻址)并在返回给调用者之前回到 32 位。

我发现有一些相关但不同的问题。例如,

run 32 bit code in 64 bit program run 64 bit code in 32 bit OS

我的问题是“在 32 位程序、64 位操作系统中运行 64 位代码”

【问题讨论】:

Ffmpeg 基于 cpu 信息做类似的事情。您可以构建具有相同功能但内部结构不同的单独模块,并根据 cpu 支持有条件地加载最佳模块。 【参考方案1】:

与其他答案相反,我断言原则上简短的答案是YES。这可能不以任何方式得到官方的支持,但它似乎有效。在这个答案的最后,我展示了一个演示。

在 Linux-x86_64 上,一个 32 位(以及 X32,根据 GDB 来源)进程获得等于 0x23CS 寄存器 — GDT 中定义的 32 位环 3 代码段的选择器(其基础是0)。并且 64 位进程得到另一个选择器:0x33 — 长模式(即 64 位)环 3 代码段的选择器(ESCSSSDS 的基础在64 位模式)。因此,如果我们对0x33 的目标段选择器进行远跳转、远调用或类似的操作,我们会将相应的描述符加载到CS 的影子部分,并以64 位段结束。

这个答案底部的演示使用jmp far 指令跳转到64位代码。请注意,我选择了一个特殊的常量来加载到rax,因此对于 32 位代码,该指令看起来像

dec eax
mov eax, 0xfafafafa
ud2
cli ; these two are unnecessary, but leaving them here for fun :)
hlt

如果我们在 CS 影子部分使用 32 位描述符执行它,这肯定会失败(将在 ud2 指令上引发 SIGILL)。

现在是演示(使用 fasm 编译)。

format ELF executable
segment readable executable

SYS_EXIT_32BIT=1
SYS_EXIT_64BIT=60
SYS_WRITE=4
STDERR=2

entry $
    mov ax,cs
    cmp ax,0x23 ; 32 bit process on 64 bit kernel has this selector in CS
    jne kernelIs32Bit
    jmp 0x33:start64 ; switch to 64-bit segment
start64:
use64
    mov rax, qword 0xf4fa0b0ffafafafa ; would crash inside this if executed as 32 bit code
    xor rdi,rdi
    mov eax, SYS_EXIT_64BIT
    syscall
    ud2

use32
kernelIs32Bit:
    mov edx, msgLen
    mov ecx, msg
    mov ebx, STDERR
    mov eax, SYS_WRITE
    int 0x80
    dec ebx
    mov eax, SYS_EXIT_32BIT
    int 0x80
msg:
    db "Kernel appears to be 32 bit, can't jump to long mode segment",10
msgLen = $-msg

【讨论】:

@LưuVĩnhPhúc 是的,与 32 位内核不同,64 位 Windows 使用与 Linux 相同的 GDT 布局(用于主要系统描述符)。似乎是由syscall指令的要求决定的。 要返回 32 位模式,请使用 retf,并将 jmp 0x33: 替换为 call 0x33: 你好,有windows版本吗? @blackshadow 在 Windows 上的跳转几乎是一样的(甚至选择器也是一样的)。但我还没有看到有人开发完整的演示,我自己也没有。但请注意,如果您想从 64 位段执行系统调用,您将依赖于 Windows 版本之间不同的系统调用号。【参考方案2】:

答案是否定的。仅仅因为您正在运行64bit code(可能是 64 位长度的数据类型,例如变量等),您并没有在 32 位机器上以 64 位模式运行。编译器有一些变通方法可以在 32 位机器上提供 64 位数据类型。例如 gcc 具有 unsigned long long 和 uin64_t,它们在 x86 和 x86_64 机器上都是 8 位数据类型。出于这个原因,数据类型可以在 x86 和 x86_64 之间移植。这并不意味着您在 32 位机器上获得 64 位地址空间。这意味着编译器可以处理 64 位数据类型。您将遇到无法在 32 位机器上运行某些 64 位代码的情况。在这种情况下,您将需要预处理指令来在 x86_64 上编译正确的 64 位代码和在 x86 上编译正确的 32 位代码。一个简单的例子是明确需要不同的数据类型。在这种情况下,您可以提供预处理器检查以确定主机是 64 位还是 32 位:

#if defined(__LP64__) || defined(_LP64)
# define BUILD_64   1
#endif

然后您可以提供条件来编译正确的代码,如下所示:

#ifdef BUILD_64
    printf (" x : %ld,  hex: %lx,\nfmtbinstr_64 (d, 4, \"-\"): %s\n",
            d, d, fmtbinstr_64 (d, 4, "-"));
#else
    printf (" x : %lld,  hex: %llx,\nfmtbinstr_64 (d, 4, \"-\"): %s\n",
            d, d, fmtbinstr_64 (d, 4, "-"));
#endif

希望这为您提供了一个起点。如果您有更具体的问题,请发布更多详细信息。

【讨论】:

以上是关于在 64 位 linux 上从 32 位模式切换到 64 位(长模式)的主要内容,如果未能解决你的问题,请参考以下文章

是否可以通过模式切换在 64 位进程中执行 32 位代码?

我何时需要使用64位Azure函数?

Cassandra安装失败64位检查。重新运行以在Windows 10上从32位获取版本

为啥 cx_Freeze 在 64 位 Debian Linux 上运行时使 32 位可执行?

Windows 7 上的 Java 7 64 位:如何切换 Java 版本

如何解决64位系统与32位软件不兼容