如何在 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 使代码可执行时,也会使其不可写,反之亦然。永远不要将可写和可执行结合起来。