可能是这个问题的确切重复,但那个问题是一年前的,只有一个答案没有给出任何源代码。我希望得到更详细的答案。
我正在运行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
}
现在,纯粹出于娱乐价值,我想用包含任意32位(ia32)代码的buffer
来做同样的事情,而不是64位代码。这个页面暗示您可以通过设置CS段描述符的位为LMA=1,L=0,D=1
进入“长兼容子模式”来在64位处理器上执行32位代码。我愿意用一个前奏/尾声来包装我的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
};
mprotect
的使用解决了http://stackoverflow.com/a/12712639/841108中回答的问题(如何获取新的可执行页面);我的主要问题是关于兼容子模式的兼容性。我相信x32 ABI完全不相关-x32只是由常规64位长模式中使用的奇怪系统使用的奇怪ABI,而我想要做的实际上是将解码器切换到32位兼容子模式。(换句话说,我的问题与ABI无关;它与处理器模式有关。) - Quuxplusonei386_set_ldt
,Linux有modify_ldt
,但我不明白它们的作用。 - Quuxplusonelcall
并设置你的数据描述符(ds
,ss
)即可。 - ughoavgfhwMAP_ANONYMOUS|MAP_32BIT
的mmap
来获取低(高)内存中的页面。关于LDT,我仍然希望有人能给我代码。我认为我有点理解实际的lgdt
和lldt
指令是做什么的,但我认为modify_ldt
是一些更高级别的东西,它不会清除整个表;您实际上可以以某种方式向现有LDT添加新条目。也许我应该研究这个示例代码。 - Quuxplusone