13

这可能与 是否可以通过模式切换在 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
}

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

但是我可以在 Linux 中以用户模式进行此设置吗?(BSD/Darwin 的答案也将被接受。)这是我开始对这些概念产生真正模糊的地方。我认为解决方案包括向 GDT(或者是 LDT?)添加一个新的段描述符,然后通过lcall指令切换到该段。但是这一切都可以在用户模式下完成吗?

这是一个示例函数,在兼容性子模式下成功运行时应返回 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
};
4

1 回答 1

11

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

但是,您将面临各种混乱。堆栈指针的高位可能会被破坏。如果你真的想运行真正的代码,你可能需要一个数据段。您需要再进行一次远跳才能回到 64 位模式。

一个完整的例子在我的linux-clock-teststest_vsyscall.cc。(它在任何发布的内核上都有点损坏:int cc会崩溃。你应该把它改成更聪明的东西,比如“nop”。看看intcc32.

于 2012-11-13T04:38:38.823 回答