C++ 如何动态生成和运行本机代码?
我想为我编写的玩具语言处理器(纯学术性)编写一个非常小的概念验证JIT编译器,但我在设计的中间阶段遇到了一些麻烦。从概念上讲,我熟悉JIT的工作原理——将字节码编译成(机器或程序集?)代码来运行。然而,在螺母和螺栓的层面上,我不太清楚你实际上是如何做到这一点的 我(非常“新手”)的下意识反应是,因为我不知道从哪里开始,我会尝试下面的方法:C++ 如何动态生成和运行本机代码?,c++,linux,compiler-construction,x86,jit,C++,Linux,Compiler Construction,X86,Jit,我想为我编写的玩具语言处理器(纯学术性)编写一个非常小的概念验证JIT编译器,但我在设计的中间阶段遇到了一些麻烦。从概念上讲,我熟悉JIT的工作原理——将字节码编译成(机器或程序集?)代码来运行。然而,在螺母和螺栓的层面上,我不太清楚你实际上是如何做到这一点的 我(非常“新手”)的下意识反应是,因为我不知道从哪里开始,我会尝试下面的方法: mmap()内存块,设置对PROT_EXEC的访问 将本机代码写入块中 将当前寄存器(堆栈指针等)存储在舒适的地方 修改当前寄存器以指向映射区域中的本机代码块
这是否接近正确的算法?我试过阅读我知道有JIT编译器要研究的不同项目(例如),但这些代码库由于其大小而很难使用,我不知道从哪里开始寻找。Android Dalvik JIT编译器可能也值得一看。它应该是相当小和精益的(不确定这是否有助于理解它或使事情更加复杂)。它也针对Linux 如果事情变得越来越严重,那么查看LLVM也可能是一个不错的选择
Jeremiah建议的函数指针方法听起来不错。您可能希望使用调用方的堆栈,并且可能只剩下少数寄存器(在x86上)需要保留或不接触。在这种情况下,如果编译后的代码(或条目存根)在继续之前将它们保存在堆栈上,这可能是最简单的。最后,这一切归结为编写汇编函数并从C与之接口。不确定linux,但这在x86/windows上是可行的。
更新:
#包括
#包括
typedef无符号字符字节;
int arg1;
int-arg2;
int res1;
类型定义无效(*pfunc)(无效);
联合函数{
pfunc-x;
字节*y;
};
内部主(空){
byte*buf=(byte*)VirtualAllocEx(GetCurrentProcess(),0,1您可能想看看libjit,它正好提供了您想要的基础结构:
libjit库实现了
即时编译
与其他JIT不同,此
一个是设计成独立于
任何特定的虚拟机
字节码格式或语言
除了目前建议的技术外,还值得研究线程创建功能。如果您创建了一个新线程,并将起始地址设置为生成的代码,您肯定知道没有需要保存或还原的旧寄存器,并且操作系统会为您处理相关寄存器的设置u、 例如,您删除了列表中的步骤3、4和6。您可能对的编程语言感兴趣。这是一种小型、不完整的语言,具有即时编译功能。Potion的小型使其更易于理解。存储库中包含对(JIT内容从标题“~JIT~”开始)的描述
由于它是在的上下文中运行的,所以实现很复杂。不过,不要让这件事吓跑你。很快就会知道他在干什么。基本上,使用一小组VM操作码可以将一些操作建模为。答案取决于你的编译器和你把代码放在哪里。请参阅
在32位Vista中,VisualC++提供了DEP(数据执行防止)错误,无论代码是否放在堆栈、堆或静态内存上。G++、Borland和Mars有时都可以工作。JIT代码访问的数据需要声明为易失性。
是一篇新文章(从今天开始!)这解决了其中一些问题,也描述了更大的情况。
您可能可以进一步简化:您通常可以在mmap
“ed块中获取代码的起始地址,并将其强制转换为函数指针。在这种情况下,代码需要保存和恢复自己的寄存器等。您可以想要查看平台ABI(应用程序二进制接口)中的调用约定,以了解需要保存的内容(以及如何从C代码获取参数、调用C函数等)@Jeremiah Willcock:在我看来,这大概就是下面@Shelwien演示的技术,对吗?mmap到PROT_EXEC可能不起作用。我认为当前版本的Linux不允许任何内存同时具有可写性和可执行性。你需要将其映射为可写、可写,然后映射为可执行。或者我认为是这样。事实上,到目前为止,它在我的实验中似乎工作得很好。我使用的是最新的内核版本(2.6.35),并使用PROT_READ | PROT_WRITE | PROT_EXEC
设置我的mmap()
访问权限,这并不是因为你针对的是非x86,而是要注意那些自我修改的代码(或动态生成的代码)在其他平台上需要显式缓存同步。它几乎只有x86可以透明地进行同步(这意味着硅负载)。只需调用msync()Shelwien:它以前绝对不可移植,因为大多数现代操作系统都不接受堆栈的执行。感谢这个伟大的例子!我几乎没有意识到funcptr
最初是一个联合体,之后它就有了完美的意义SED是一个简单的类型,比如((pFunc)(Vult*)BUF)();但是CODEAD说了一些关于ISO C++的不允许将随机的东西随机转换成函数,所以我必须用一个联合来替换它。需要在x64上工作?我不是组装专家,但我看不到任何x64不能反向兼容的东西。我的意思是
#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 );
}