什么是 JIT
名如其特点,JIT —— just in time,即时编译。
什么?这就是你要告诉大家伙的?这不是人人都知道的嘛?而且网上一搜也全都是 JIT = just in time 了事。好吧好吧,我知错啦。那就认真的定义一下JIT:
一个程序在它运行的时候创建并且运行了全新的代码,而并非那些最初作为这个程序的一部分保存在硬盘上的固有的代码。就叫 JIT。
几个点:
- 程序需要运行
- 生成的代码是新的代码,并非作为原始程序的一部分被存在磁盘上的那些代码
- 不光生成代码,还要运行。
需要提醒的是第三点,也就是 JIT不光是生成新的代码,它还会运行新生成的代码。
模拟一下JIT的过程
JIT这么好,那它是如何实现既生成新代码,又能运行新代码的呢?
编译器如何生成代码很多文章都有涉及,我就不多在此着墨了。下面我就着重和各位聊聊,如何运行新生成的代码。
首先我们要知道生成的所谓机器码到底是神马东西。一行看上去只是处理几个数字的代码,蕴含着的就是机器码。
| unsigned char[] macCode = {0x48, 0x8b, 0x07};
|
macCode对应的汇编指令就是:
其实可以看出机器码就是比特流,所以将它加载进内存并不困难。而问题是应该如何执行。
好啦。下面我们就模拟一下执行新生成的机器码的过程。假设JIT已经为我们编译出了新的机器码,是一个求和函数的机器码:
| long add(long num) { return num + 1; }
0x48, 0x83, 0xc0, 0x01, 0xc3
|
首先,动态的在内存上创建函数之前,我们需要在内存上分配空间。具体到模拟动态创建函数,其实就是将对应的机器码映射到内存空间中。这里我们使用c语言做实验,利用 mmap函数 来实现这一点。
头文件:
| #include <unistd.h> #include <sys/mman.h>
|
定义函数:
| void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize)
|
函数说明:
因为我们想要把已经是 比特流的“求和函数”在内存中创建出来,同时还要运行它。所以mmap有几个参数需要注意一下。
代表映射区域的保护方式,有下列组合:
- PROT_EXEC 映射区域可被执行;
- PROT_READ 映射区域可被读取;
- PROT_WRITE 映射区域可被写入;
| #include<stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/mman.h>
void* create_space(size_t size) { void* ptr = mmap(0, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0); return ptr; }
|
这样我们就获得了一块分配给我们存放代码的空间。下一步就是实现一个方法将机器码,也就是比特流拷贝到分配给我们的那块空间上去。使用 memcpy 即可。
| void copy_code_2_space(unsigned char* m) { unsigned char macCode[] = { 0x48, 0x83, 0xc0, 0x01, c3 }; memcpy(m, macCode, sizeof(macCode)); }
|
然后我们在写一个main函数来处理整个逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include<stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/mman.h>
void* create_space(size_t size) { void* ptr = mmap(0, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0); return ptr; }
void copy_code_2_space(unsigned char* addr) { unsigned char macCode[] = { 0x48, 0x83, 0xc0, 0x01, 0xc3 }; memcpy(addr, macCode, sizeof(macCode)); }
int main(int argc, char** argv) { const size_t SIZE = 1024; typedef long (*TestFun)(long); void* addr = create_space(SIZE); copy_code_2_space(addr); TestFun test = addr; int result = test(1); printf("result = %d\n", result); return 0; }
|
编译运行一下看下结果:
为什么iOS不能使用JIT?
OK,到此为止。这个例子模拟了动态代码在内存上的生成,和之后的运行。似乎没有什么问题呀?可不知道各位是否忽略了一个前提?那就是我们为这块区域设置的保护模式可是:可读,可写,可执行的啊!如果没有内存可读写可执行的权限,我们的实验还能成功吗?
让我们把create_space函数中的“可执行”PROT_EXEC权限去掉,看看结果会是怎样的一番景象。
修改代码,同时将刚才生成的可执行文件a.out删除重新生成运行。
| rm a.out vim testFun.c gcc testFun.c ./a.out 1
|
结果。。。报错了!
所以,IOS并非把JIT禁止了。或者换个句式讲,IOS封了内存(或者堆)的可执行权限,相当于变相的封锁了JIT这种编译方式。
本文来自:https://www.jianshu.com/p/1f0c10c4af0c
本文参考:https://www.cnblogs.com/murongxiaopifu/p/4278947.html