问题描述
我的程序在 x86_64 CPU(64 位操作系统,ubuntu 8.04)上以 32 位模式运行.是否可以在用户模式下暂时切换到 64 位模式(长模式)?如果是,怎么办?
My program is in 32bit mode running on x86_64 CPU (64bit OS, ubuntu 8.04). Is it possible to switch to 64bit mode (long mode) in user mode temporarily? If so, how?
背景故事:我正在编写一个与 32 位模式程序链接的库,因此它在启动时必须是 32 位模式.但是,我想使用更快的 x86_64 指令以获得更好的性能.所以我想切换到 64 位模式做一些纯计算(没有操作系统交互;不需要 64 位寻址)并在返回调用者之前回到 32 位.
Background story: I'm writing a library linked with 32bit mode program, so it must be 32bit mode at start. However, I'd like to use faster x86_64 intructions for better performance. So I want to switch to 64bit mode do some pure computation (no OS interaction; no need 64bit addressing) and come back to 32bit before returning to caller.
我发现有一些相关但不同的问题.例如,
I found there are some related but different questions. For example,
我的问题是在 32 位程序、64 位操作系统中运行 64 位代码"
My question is "run 64 bit code in 32 bit program, 64 bit OS"
推荐答案
与其他答案相反,我断言原则上简短的答案是YES.这可能不会以任何方式得到官方支持,但它似乎有效.在这个答案的最后,我展示了一个演示.
Contrary to the other answers, I assert that in principle the short answer is YES. This is likely not supported officially in any way, but it appears to work. At the end of this answer I present a demo.
在 Linux-x86_64 上,一个 32 位(和 X32 也是如此,根据 GDB 来源)进程获得 CS
寄存器等于 0x23
— 32 位环的选择器3 GDT 中定义的代码段(它的基数是0
).而 64 位进程得到另一个选择器:0x33
— 长模式(即 64 位)环 3 代码段的选择器(ES
、CS
的基础)>、SS
、DS
在 64 位模式下被无条件地视为零).因此,如果我们对 0x33
的目标段选择器进行远跳、远调用或类似的操作,我们会将相应的描述符加载到 CS
的影子部分并结束在 64 位段中.
On Linux-x86_64, a 32 bit (and X32 too, according to GDB sources) process gets CS
register equal to 0x23
— a selector of 32-bit ring 3 code segment defined in GDT (its base is 0
). And 64 bit processes get another selector: 0x33
— a selector of long mode (i.e. 64 bit) ring 3 code segment (bases for ES
, CS
, SS
, DS
are treated unconditionally as zeros in 64 bit mode). Thus if we do far jump, far call or something similar with target segment selector of 0x33
, we'll load the corresponding descriptor to the shadow part of CS
and will end up in a 64 bit segment.
此答案底部的演示使用 jmp far
指令跳转到 64 位代码.请注意,我选择了一个特殊的常量加载到 rax
中,因此对于 32 位代码,该指令看起来像
The demo at the bottom of this answer uses jmp far
instruction to jump to 64 bit code. Note that I've chosen a special constant to load into rax
, so that for 32 bit code that instruction looks like
dec eax
mov eax, 0xfafafafa
ud2
cli ; these two are unnecessary, but leaving them here for fun :)
hlt
如果我们在 CS 影子部分使用 32 位描述符执行它,这一定会失败(将在 ud2
指令上引发 SIGILL).
This must fail if we execute it having 32 bit descriptor in CS shadow part (will raise SIGILL on ud2
instruction).
现在是演示(用 fasm 编译).
Now here's the demo (compile it with 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
这篇关于在 64 位 linux 上从 32 位模式切换到 64 位(长模式)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!