如何在 C 中将一段内存标记为可执行?
How can you mark a segment of memory as executable in C?
最近在研究一些JIT编译器。据我所知,JIT 是一种将一些脚本语言代码即时(在执行之前)编译为本机代码的技术。当我想象这样一个编译器的内部时,我发现生成的本机代码所在的位置必须有一段动态分配的缓冲区。但是我们需要一种方法来从保存数据的缓冲区中开始 运行ning 代码。我的意思是,您不能只是将一些代码放入 char[]
中,然后跳转到执行,因为安全隐患,OS 必须阻止您这样做。必须有某种方法将缓冲区标记为可执行。考虑以下天真的方法:
#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.
因此该页面将具有可执行权限。
如果你想在堆中创建一个可执行区域,你可以使用mprotect。
int main() {
typedef void (*func_t)(void);
void *code = &some_jit_func;
int pagesize = getpagesize();
mprotect(code, pagesize,PROT_EXEC);
((func_t)code)();
}
您也可以将标志与 PROT_READ/PROT_WRITE
在您的代码中,您正在获取现有函数的地址。这自然会指向一个已经可执行的内存区域。但是同一区域在任何现代系统上都不可写。
另一方面,如果您 malloc() 一些内存,它将是可写但不可执行的。因此,您在 jit 编译器中构造的任何代码都将不可执行,并且尝试调用您在那里构造的函数将会失败。您必须首先使用 mprotect.
使内存可执行
出于安全原因,您应该遵循 W^X
原则。这意味着任何页面只能是可写或可执行的,但不能同时是可写或可执行的。当您使用 mprotect 使代码可执行时,也会使其不可写,反之亦然。永远不要将可写和可执行结合起来。
最近在研究一些JIT编译器。据我所知,JIT 是一种将一些脚本语言代码即时(在执行之前)编译为本机代码的技术。当我想象这样一个编译器的内部时,我发现生成的本机代码所在的位置必须有一段动态分配的缓冲区。但是我们需要一种方法来从保存数据的缓冲区中开始 运行ning 代码。我的意思是,您不能只是将一些代码放入 char[]
中,然后跳转到执行,因为安全隐患,OS 必须阻止您这样做。必须有某种方法将缓冲区标记为可执行。考虑以下天真的方法:
#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.
因此该页面将具有可执行权限。
如果你想在堆中创建一个可执行区域,你可以使用mprotect。
int main() {
typedef void (*func_t)(void);
void *code = &some_jit_func;
int pagesize = getpagesize();
mprotect(code, pagesize,PROT_EXEC);
((func_t)code)();
}
您也可以将标志与 PROT_READ/PROT_WRITE
在您的代码中,您正在获取现有函数的地址。这自然会指向一个已经可执行的内存区域。但是同一区域在任何现代系统上都不可写。
另一方面,如果您 malloc() 一些内存,它将是可写但不可执行的。因此,您在 jit 编译器中构造的任何代码都将不可执行,并且尝试调用您在那里构造的函数将会失败。您必须首先使用 mprotect.
使内存可执行出于安全原因,您应该遵循 W^X
原则。这意味着任何页面只能是可写或可执行的,但不能同时是可写或可执行的。当您使用 mprotect 使代码可执行时,也会使其不可写,反之亦然。永远不要将可写和可执行结合起来。