【问题标题】:How to generate and run native code dynamically?如何动态生成和运行本机代码?
【发布时间】:2011-06-22 04:06:12
【问题描述】:

我想为我编写的玩具语言处理器编写一个非常小的概念验证 JIT 编译器(纯粹是学术性的),但我在设计的中等高度遇到了一些麻烦。从概念上讲,我熟悉 JIT 的工作原理——您将字节码编译成(机器或程序集?)代码以运行。然而,在具体细节层面,我不太了解如何你实际上是如何的。

我的(非常“新手”)下意识的反应,因为我没有第一个线索从哪里开始,会尝试如下:

  1. mmap() 一块内存,设置对 PROT_EXEC 的访问
  2. 将本机代码写入块中
  3. 在舒适的地方存储当前寄存器(堆栈指针等)
  4. 修改当前寄存器以指向映射区域中的本机代码块
  5. 本机代码现在将由机器执行
  6. 恢复以前的寄存器

这是否甚至 接近 一个/正确的算法?我尝试仔细阅读我知道有 JIT 编译器来研究的不同项目(例如 V8),但这些代码库由于它们的大小而难以使用,我不知道从哪里开始寻找。

【问题讨论】:

  • 您可能可以进一步简化事情:您通常可以在mmap'ed 块中获取代码的起始地址并将其转换为函数指针。在这种情况下,代码需要保存和恢复自己的寄存器等。您需要查看平台 ABI(应用程序二进制接口)中的调用约定,以了解您需要保存的确切内容(以及如何从 C 代码获取参数、调用 C 函数等)。
  • @Jeremiah Willcock:在我看来,这大致就是下面@Shelwien 展示的技术,我说的对吗?
  • mmap 到 PROT_EXEC 可能不起作用。我不相信当前版本的 Linux 允许任何内存同时可写和可执行。您需要将其映射为可写、写入,然后将其映射为可执行。至少我相信。
  • 实际上到目前为止,在我的实验中它似乎工作得很好。我正在使用最新版本的内核 (2.6.35) 并将我的 mmap() 访问权限设置为 PROT_READ | PROT_WRITE | PROT_EXEC
  • 并不是说您的目标是非 x86,但要注意自我修改代码(或即时生成的代码)需要在其他平台上显式缓存同步。它几乎只是透明地执行它的 x86(这意味着大量的硅)。只需在完成写入之后和执行之前在缓冲区上调用 msync()。

标签: c++ linux compiler-construction x86 jit


【解决方案1】:

Android Dalvik JIT 编译器可能也值得一看。它应该相当小且精益(不确定这是否有助于理解它或使事情变得更复杂)。它也以 Linux 为目标。

如果事情变得更严重,考虑 LLVM 可能也是一个不错的选择。

Jeremiah 建议的函数指针方法听起来不错。无论如何,您可能都想使用调用者的堆栈,并且可能只剩下几个寄存器(在 x86 上),您需要保留或不触摸它们。在这种情况下,如果您的编译代码(或入口存根)在继续之前将它们保存在堆栈中,这可能是最简单的。最后,这一切都归结为编写一个汇编程序函数并从 C 中与其接口。

【讨论】:

    【解决方案2】:

    不确定 linux,但这适用于 x86/windows。
    更新:http://codepad.org/sQoF6kR8

    #include <stdio.h>
    #include <windows.h>
    
    typedef unsigned char byte;
    
    int arg1;
    int arg2;
    int res1;
    
    typedef void (*pfunc)(void);
    
    union funcptr {
      pfunc x;
      byte* y;
    };
    
    int main( void ) {
    
      byte* buf = (byte*)VirtualAllocEx( GetCurrentProcess(), 0, 1<<16, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
    
      if( buf==0 ) return 0;
    
      byte* p = buf;
    
      *p++ = 0x50; // push eax
      *p++ = 0x52; // push edx
    
      *p++ = 0xA1; // mov eax, [arg2]
      (int*&)p[0] = &arg2; p+=sizeof(int*);
    
      *p++ = 0x92; // xchg edx,eax
    
      *p++ = 0xA1; // mov eax, [arg1]
      (int*&)p[0] = &arg1; p+=sizeof(int*);
    
      *p++ = 0xF7; *p++ = 0xEA; // imul edx
    
      *p++ = 0xA3; // mov [res1],eax
      (int*&)p[0] = &res1; p+=sizeof(int*);
    
      *p++ = 0x5A; // pop edx
      *p++ = 0x58; // pop eax
      *p++ = 0xC3; // ret
    
      funcptr func;
      func.y = buf;
    
      arg1 = 123; arg2 = 321; res1 = 0;
    
      func.x(); // call generated code
    
      printf( "arg1=%i arg2=%i arg1*arg2=%i func(arg1,arg2)=%i\n", arg1,arg2,arg1*arg2,res1 );
    
    }
    

    【讨论】:

    • @Shelwien:它以前绝对不能移植,因为大多数现代操作系统不接受堆栈的执行。
    • 感谢你的好例子!一开始我差点没注意到 funcptrunion - 之后它就变得非常有意义了。
    • 第一个版本在那里使用了简单的类型转换,例如 ((pfunc)(void*)buf)(); ,但是 codepad 说 ISO C++ 不允许将随机的东西转换为函数,所以我不得不用联合替换它。
    • 在 x64 上工作需要哪些修改?我不是组装专家,但我没有看到任何 x64 找不到反向兼容的东西。
    • 我的意思是生成的代码是 32 位的,x64 代码会有所不同。如有必要,我可以制作 x64 版本,但可以吗?
    【解决方案3】:

    您可能想看看 libjit,它提供了您正在寻找的基础架构:

    libjit 库实现 即时编译 功能。与其他 JIT 不同,这 一个被设计为独立于 任何特定的虚拟机 字节码格式或语言。

    http://freshmeat.net/projects/libjit

    【讨论】:

    • 有趣的发现。如果我决定我真的想要实现一个重要的 JIT,这可能很有用。
    【解决方案4】:

    除了目前建议的技术之外,可能值得研究线程创建函数。如果您创建一个新线程,并将起始地址设置为生成的代码,您肯定知道没有需要保存或恢复的旧寄存器,并且操作系统会为您处理相关寄存器的设置。即您消除了列表中的第 3、4 和 6 步。

    【讨论】:

    • 我唯一会犹豫的是,对于线程,你必须处理同步等问题。 - 否则(如果可以忽略和/或推迟同步)这是一个非常聪明的想法。
    【解决方案5】:

    您可能对why the lucky stiffPotion 编程语言感兴趣。它是一种小型、不完整的语言,具有即时编译功能。 Potion 的小尺寸使其更易于理解。存储库包含对 language's internals 的描述(JIT 内容从标题“~ the jit ~”开始)。

    由于它在Potion's VM 的上下文中运行,该实现变得复杂。不过,不要让这把你吓跑。不用多久就可以看到他在做什么。基本上,使用一小组 VM 操作码可以将一些操作建模为 optimized assembly

    【讨论】:

      【解决方案6】:

      答案取决于您的编译器以及您放置代码的位置。见http://encode.ru/threads/1273-Just-In-Time-Compilation-Improvement-For-ZPAQ?p=24902&posted=1#post24902

      在 32 位 Vista 中测试时,无论代码放在堆栈、堆还是静态内存中,Visual C++ 都会给出 DEP(数据执行保护)错误。 g++、Borland 和 Mars 有时可以工作。 JIT 代码访问的数据需要声明为 volatile。

      【讨论】:

        【解决方案7】:

        How to JIT - an introduction 是一篇新文章(从今天开始!),它解决了其中一些问题并描述了更大的图景。

        【讨论】:

          猜你喜欢
          • 2022-10-02
          • 2011-09-07
          • 1970-01-01
          • 1970-01-01
          • 2010-12-17
          • 2015-04-04
          • 2012-05-23
          • 2013-12-07
          • 2017-08-18
          相关资源
          最近更新 更多