在 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 来源)进程获得等于 0x23
的 CS
寄存器 — GDT 中定义的 32 位环 3 代码段的选择器(其基础是0
)。并且 64 位进程得到另一个选择器:0x33
— 长模式(即 64 位)环 3 代码段的选择器(ES
、CS
、SS
、DS
的基础在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 位(长模式)的主要内容,如果未能解决你的问题,请参考以下文章
Cassandra安装失败64位检查。重新运行以在Windows 10上从32位获取版本
为啥 cx_Freeze 在 64 位 Debian Linux 上运行时使 32 位可执行?