在64位Linux上从32位模式转换到64位模式(长模式)

8
我的程序在32位模式下运行在x86_64 CPU上(64位操作系统,ubuntu 8.04)。是否可能在用户模式下临时切换到64位模式(长模式)?如果可以,如何实现?
背景故事:我正在编写一个与32位模式程序链接的库,因此它必须在开始时处于32位模式。但是,我想使用更快的x86_64指令以获得更好的性能。因此,我想在切换到64位模式后进行一些纯计算(没有操作系统交互;不需要64位寻址),然后在返回调用者之前回到32位。
我发现有一些相关但不同的问题。例如: 我的问题是“在32位程序、64位操作系统中运行64位代码”。

1
Ffmpeg 根据 CPU 信息执行类似的操作。您可以构建具有相同功能但不同内部的单独模块,并根据 CPU 支持条件性地加载最佳模块。 - technosaurus
2个回答

19
与其他答案相反,我认为原则上短答案是“是”。这可能没有得到任何官方支持,但它似乎可以工作。在本回答结束时,我会展示一个演示。
在Linux-x86_64上,32位进程(包括X32,根据GDB源代码)将CS寄存器设置为0x23——GDT中定义的32位ring 3代码段选择器(其基址为0)。64位进程会获得另一个选择器:0x33——长模式(即64位)ring 3代码段选择器(在64位模式下,对于ESCSSSDS,基地址无条件地视为零)。因此,如果我们使用目标段选择器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

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

-4
答案是否定的。仅仅因为你正在运行64位代码(假设是64位长度的数据类型,例如变量等),并不意味着你在32位系统上运行的是64位模式。编译器有解决方案来在32位机器上提供64位数据类型。例如,gcc具有无符号长整型和uin64_t,它们是x86和x86_64机器上的8位数据类型。由于这个原因,数据类型在x86和x86_64之间是可移植的。这并不意味着你在32位系统上获得了64位地址空间。这意味着编译器能够处理64位数据类型。你会遇到一些情况,无法在32位系统上运行某些64位代码。在这种情况下,你需要预处理指令,在x86_64上编译正确的64位代码,在x86上编译正确的32位代码。一个简单的例子是显式地需要不同的数据类型。在这种情况下,你可以提供一个预处理检查来确定主机计算机是64位还是32位:< / p>
#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

希望这为您提供了一个开始工作的起点。如果您有更具体的问题,请发布更多细节。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接