如何动态生成和运行本机代码?

时间:2011-02-06 06:32:51

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

我想为我编写的玩具语言处理器编写一个非常小的概念验证JIT编译器(纯粹是学术性的),但我在设计的中间高度遇到了一些麻烦。从概念上讲,我熟悉JIT的工作原理 - 您将字节码编译成(机器或汇编?)代码来运行。然而,在螺母和螺栓水平上,我并不是非常想要 你实际上那样。

我的(非常“新手”)膝跳反应,因为我没有第一个线索从哪里开始,将尝试类似以下内容:

  1. mmap()一块内存,设置对PROT_EXEC的访问
  2. 将本机代码写入块
  3. 存储当前寄存器(堆栈指针,等等)。
  4. 修改当前寄存器以指向映射区域中的本机代码块
  5. 本机代码现在由机器执行
  6. 恢复以前的寄存器
  7. 甚至关闭到/正确的算法?我已经尝试过阅读我知道有JIT编译器学习的不同项目(比如V8),但是由于它们的大小,这些代码库很难被消费,而我几乎不知道从哪里开始寻找。

7 个答案:

答案 0 :(得分:28)

不确定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 );

}

答案 1 :(得分:4)

Youmay想看看 libjit ,它提供了您正在寻找的基础设施:

  

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

http://freshmeat.net/projects/libjit

答案 2 :(得分:3)

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

如果情况越来越严重,那么查看LLVM可能也是一个不错的选择。

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

答案 3 :(得分:3)

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

答案 4 :(得分:2)

答案取决于您的编译器以及放置代码的位置。见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。

答案 5 :(得分:0)

除了到目前为止建议的技术之外,查看线程创建函数可能是值得的。如果创建一个新线程,并将起始地址设置为生成的代码,则可以确定没有需要保存或恢复的旧寄存器,并且OS会为您处理相关寄存器的设置。即,您删除了列表中的第3步,第4步和第6步。

答案 6 :(得分:0)

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

由于它在Potion's VM的上下文中运行,因此实现变得复杂。但是,不要让这吓到你。不久就能看到他的目标。基本上,使用一小组VM操作码可以将某些操作建模为optimized assembly