你能在内核模式之外输入x64 32位"长兼容子模式"吗?

Quu*_*one 12 linux kernel x86-64 compatibility-mode

这可能完全重复 是否可以通过模式切换在64位进程中执行32位代码?,但这个问题是从一年前开始的,只有一个答案没有给出任何源代码.我希望得到更详细的答案.

我正在运行64位Linux(Ubuntu 12.04,如果重要的话).这里有一些代码可以分配页面,将一些64位代码写入其中,然后执行该代码.

#include <assert.h>
#include <malloc.h>
#include <stdio.h>
#include <sys/mman.h>  // mprotect
#include <unistd.h>  // sysconf

unsigned char test_function[] = { 0xC3 };  // RET
int main()
{
    int pagesize = sysconf(_SC_PAGE_SIZE);
    unsigned char *buffer = memalign(pagesize, pagesize);
    void (*func)() = (void (*)())buffer;

    memcpy(buffer, test_function, sizeof test_function);

    // func();  // will segfault 
    mprotect(buffer, pagesize, PROT_EXEC);
    func();  // works fine
}
Run Code Online (Sandbox Code Playgroud)

现在,纯粹是为了娱乐价值,我想做同样的事情,但buffer包含任意32位(ia32)代码,而不是64位代码.此页面意味着您可以通过将CS段描述符的位设置为"长兼容性子模式"来在64位处理器上执行32位代码LMA=1, L=0, D=1.我愿意将我的32位代码包装在执行此设置的序言/结尾中.

但我可以在Linux中以usermode进行此设置吗?(BSD/Darwin的答案也将被接受.)这是我开始对这些概念感到朦胧的地方.我认为解决方案涉及向GDT添加新的段描述符(或者是LDT?),然后通过lcall指令切换到该段.但是所有这些都可以在usermode中完成吗?

这是一个示例函数,在兼容性子模式下成功运行时应返回4,在长模式下运行时应返回8.我的目标是获取指令指针以获取此代码路径并从另一端出来%rax=4,而不会进入内核模式(或仅通过记录的系统调用执行此操作).

unsigned char behave_differently_depending_on_processor_mode[] = {
    0x89, 0xE0,  // movl %esp, %eax
    0x56,        // push %{e,r}si
    0x29, 0xE0,  // subl %esp, %eax
    0x5E,        // pop %{e,r}si
    0xC3         // ret
};
Run Code Online (Sandbox Code Playgroud)

And*_*ski 11

是的你可以.甚至可以使用完全支持的接口.使用modify_ldt将一个32位代码段安装到LDT中,然后设置一个指向32位代码的远指针,然后使用AT&T表示法中的"ljumpl*(%eax)"间接跳转到它.

不过,你会遇到各种各样的混乱.堆栈指针的高位可能会被破坏.如果您确实想要运行实际代码,则可能需要一个数据段.而你需要做另一个远程跳转才能回到64位模式.

一个完全成熟的例子是在test_vsyscall.cc中的linux-clock-tests中.(在任何已发布的内核上都有点破坏:int cc会崩溃.你应该把它更改为更聪明的东西,比如"nop".查看intcc32.