【发布时间】:2019-12-18 22:55:30
【问题描述】:
我最近正在研究一些 JIT 编译器。据我所知,JIT 是一种即时(在执行之前)将一些脚本语言代码编译为本机代码的技术。正如我想象的这样一个编译器的内部,我发现生成的本机代码所在的地方肯定有一段动态分配的缓冲区。但是我们需要一种方法来从保存数据的缓冲区中开始运行代码。我的意思是,您不能只是将一些代码放入char[],然后因为安全隐患而跳入执行,为此操作系统必须阻止您这样做。必须有某种方法将缓冲区标记为可执行文件。考虑以下幼稚的方法:
#include <stdlib.h>
void *jit_some_native_code(void) {
void *code_segment = malloc(1024);
/*
* bla bla bla...
* Generate code into this code_segment.
*/
return code_segment;
}
int main(void) {
void *code = jit_some_native_code();
/*
* How can I start executing instruction in code?
*/
typedef void (*func_ptr_t)(void);
/*
* This won't work. OS bans you doing so.
*/
((func_ptr_t)code)();
}
在 Ubuntu 上,代码会运行,但会以状态码 26 退出。 鉴于 C 的类型不安全性质,代码可以编译,但对于 C++,编译器只会阻止您。这是否意味着 JIT 必须绕过编译器以及设置可执行标志?
编辑:除了mprotect,如果使用mmap,还可以指定要映射的页面的权限:
PROT_EXEC Pages may be executed.
PROT_READ Pages may be read.
PROT_WRITE Pages may be written.
PROT_NONE Pages may not be accessed.
因此该页面将具有可执行权限。
【问题讨论】:
-
((func_t) &code)- 你正在执行code指针的地址,而不是函数......它应该是((func_t)code)()。for C++, the compiler simply stops you?只需转换void *code = (void*)&some_jit_func即可使用 C++ 进行编译。 -
@KamilCuk 感谢您指出代码中的错误。但我记得 C++ 禁止将
void*转换为函数指针? -
神奇的是,
static_cast禁止这种转换,而 C 风格转换允许它! -
@ryyker 问题是关于如何标记内存区域可执行,而不是如何正确调用函数指针。我相信我的评论不是答案。
-
一个 void* 可能不足以容纳一个函数指针。转换为不同大小的指针是未定义的行为。所以,不,您不能只将 void * 大小写为函数指针。在 C 和 C++ 中都没有。为什么不将
code = &some_jit_func声明为函数指针呢? jit-me-this-bit-of-script 函数应该返回一个函数指针而不是 void*。
标签: c executable jit