我在macOS 10.13上实现了一个简单的X86-64 JIT编译器,用于简单的编程语言我的问题是关于在JIT中调用函数的最佳方法。
所以我会写一下我如何克服这个问题:
和大多数编译器一样,我有一个AST来帮助代码生成 当代码生成器找到函数节点 (假设函数名称为func
)时,它将存储指向映射的可执行内存页面中当前位置的指针然后它将生成其余的指令,当其他函数调用此函数func
时,后端将计算Call
指令操作数。
我将更具体地说明我如何计算操作数:
让我们假设下面的代码是由JIT生成的
0000000100000f90 <_func>:
100000f90: 55 push %rbp
100000f91: 48 89 e5 mov %rsp,%rbp
100000f94: 5d pop %rbp
100000f95: c3 retq
100000f96: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
100000f9d: 00 00 00
0000000100000fa0 <_main>:
100000fa0: 55 push %rbp
100000fa1: 48 89 e5 mov %rsp,%rbp
100000fa4: e8 e7 ff ff ff callq 100000f90 <_func>
100000fa9: 31 c0 xor %eax,%eax
100000fab: 5d pop %rbp
100000fac: c3 retq
因为我已经存储了func
(0x100000f90)的地址
我可以使用公式计算call
指令的操作数
调用操作数=函数地址(0x100000f90) - 当前指令地址(0x100000fa4) - sizeof(Call
)(5) = 0xffffffe7因为它是英特尔CPU,所以它是小端(0xe7ffffff)。
并且对于将被调用的每个函数都将使用相同的步骤
最后是算法的简单C ++代码
#include <iostream>
#include <vector>
#include <sys/mman.h>
std::vector<u_int8_t> func{
0x55,
0x48,0x89,0xe5,
0xb8,0x5a,0x00,0x00,0x00,
0x5d,
0xc3
};
std::vector<u_int8_t> Main{
0x55,
0x48,0x89,0xe5,
0x48,0x83,0xec,0x10,
0xc7,0x45,0xfc,0x00,0x00,0x00,0x00,
0xe8,0x00,0x00,0x00,0x00,
0x48,0x83,0xc4,0x10,
0x5d,
0xc3
};
long calculateCallOperand(u_int8_t*funcAddress,u_int8_t*currentAddress){
return (funcAddress - currentAddress)-5;
}
int main(){
auto ptr = (u_int8_t*)mmap(nullptr,4096,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANONYMOUS|MAP_PRIVATE|MAP_JIT,-1,0);
u_int8_t*funcAddress = &ptr[0];
for(int i=0;i<11;++i)
ptr[i] = func[i];
auto operand = calculateCallOperand(funcAddress,&ptr[26]);
Main[16] = (u_int8_t)operand;
Main[17] = *((u_int8_t*)&operand+1);
Main[18] = *((u_int8_t*)&operand+2);
Main[19] = *((u_int8_t*)&operand+3);
for(int i=0,j=11;i<Main.size();++i,++j)
ptr[j]=Main[i];
auto offset = ptr+11;
auto call = (void(*)())offset;
call();
munmap(ptr,4096);
}
(我运行代码并且它正在运行(我不知道是否存在任何未定义的行为)。)
这是正确的方法,或者它不是
如果是,那么有更好的方法 如果不是这样做的正确方法
我很抱歉这个长期的问题但是我很努力地清楚地表明我的问题,提前谢谢。