首页 > 生活

JIT原理 - 简介

更新时间:2025-05-17 15:00:29 阅读: 评论:0

说明:本文翻译自eli.thegreenplace/2013/11/05/how-to-JIT-an-introduction,并做了适当增减。

JIT是“Just In Time”的首字母缩写。每当一个程序在运行时创建并运行一些新的可执行代码,而这些代码在存储于磁盘上时不属于该程序的一部分,它就是一个JIT。

我认为JIT技术在分为两个不同的阶段时更容易解释:

阶段1:在程序运行时创建机器代码。

阶段2:在程序运行时也执行该机器代码。

第1阶段是JITing 99%的挑战所在,但它也是这个过程中不那么神秘的部分,因为这正是编译器所做的。众所周知的编译器,如gcc和clang,将C/C++源代码转换为机器代码。机器代码被发送到输出流中,但它很可能只保存在内存中(实际上,gcc和clang / llvm都有构建块用于将代码保存在内存中以便执行JIT)。第2阶段是我想在本文中关注的内容。

运行动态生成的代码

现代操作系统对于允许程序在运行时执行的操作可以说是非常挑剔。过去“海阔凭鱼跃,天高任鸟飞”的日子随着保护模式的出现而不复存在,保护模式允许操作系统以各种权限对虚拟内存块的使用做出限制。因此,在“普通”代码中,你可以在堆上动态创建新数据,但是你不能在没有操作系统明确允许的情况下从堆中运行其内容。

在这一点上,我希望机器代码只是数据 - 一个字节流,比如:

unsigned char[] code = {0x48, 0x89, 0xf8};

不同的人会有不同的视角,对某些人而言,0x48, 0x89, 0xf8只是一些可以代表任何事物qq原创表情的数据。 对于其他人来说,它是真实早泄能治好有效的机器代码的二进制编码,其对应的x86-64汇金钱龟养殖编代码如下:

mov %rdi, %rax

将机器代码放入内存是容易的,但是如何让它获得可执行权限,然后运行它呢?

首托管中心先我们创建一个函数:

long add4(long num) { return num + 4;}

然后在内存中动态地执行它:

#include <stdio.h>#include <stdlib.h>#include <string.h>#include &每日邮报lt;sys/mman.h>// Allocates RWX memory of given size and returns a pointer to it. On failure,// prints out 带电粒子在电场中的运动the error and returns NULL.void* alloc_executable_memory(size_t size) { void* ptr =甄嬛传分集剧情 m唐山师范学院怎么样map(0, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (ptr == (void*)-1) { perror("mmap"); return NULL; } return ptr;}void emit_code_into_memory(unsigned char* m) { 巨蛇unsigned char code[] = { 0x48, 0x89, 0xf8, // mov %rdi, %rax 0x48, 0x83, 0xc0, 0x04, // add $4, %rax 0xc3 // ret }; memcpy(m, code, sizeof(code));}const size_t 千股跌停SIZE = 1024;typedef long (*JittedFunc)(long);// Allocates RWX memory directly.void run_from_rwx() { void静海军* m = alloc_executable_memory李清彪(SIZE); emit_code_into_memory(m); JittedFunc func = m; int result = func(2); printf("result = %d\n", result);}

此代码执行的主要3个步骤是:

使用mmap在堆上分配可读,可写和可执行的内存块。将实现add4函数的汇编/机器代码复制到此内存块中。将该内存块首地址转换为函数指针,并通过调用这一函数指针来执行此内存块中的代码。

请注意,步骤3能发生是因为包含机器代码的内存块是可执行的,如果没有设置正确的权限,该调用将导致OS的运行时错误(很可能是segmentation fault)。如果我们通过对malloc的常规调用来分配内存块,则会发生这种情况,malloc分配可读写但不广深驾校可执行的内存。而通过mmap来分配内存块,则可以自行设置该内存块的属性【1】。

安全问题

上面显示的代码其实有一个安全漏洞,那就是它所分配的RWX(可读,可写,可执行)大块内存,这种内存对于漏洞攻击者来说可大崎娜娜是可以大展身手,兴风作浪的天堂。所以让我们对它更负责任,进行一些略微的修改:

// Allocates RW memory of given size 性界and returns a pointer to it. On failure,// prints out th丰年虾e error and returns NULL花钱上大学. Unlike malloc, the memory is allocated// on a page boundary so it's suitable for calling mprotect.void* alloc_writable_memory(size_t size) { void* 孙兴敏ptr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (ptr == (void*)-1) { perror("mmap"); return NULL; } return ptr;}// Sets a RX permission on the given memory, which must be page-aligned. Returns// 0 on success. On failure, prints out the error and returns -1.int make_memory_executable(void* m, size_t size) { if (mprotect(m, size, PROT_READ | PROT_EXEC) == -1) { perror("mprotect"); return -1; } return 0;}// Allocates RW memory, emits the code into it and sets it to RX before// executing.void emit_to_rw_run_from_rx() { void* m = alloc_writable_mem朝鲜空军ory(SIZE); emit_code_into_memory(m); make_memory_executable(m, SIZE); JittedFunc func = m; int result = func(2); printf("result = %d\n", result);}

内存块首先被分配了RW权限,因为我们需要将函数的机器代码写入该内存块。然后我们使用mprotect将块的权限从RW更改为RX,使其可执行但不再可写,所以最终效果是一样的,但是在我们的程序执行过程中,没有任何一个时间点,该内存块是同时可写的和可执行的。

本文介绍的这种技术几米币乎是真正的JIT引擎(例如LLVM和libjit)从内存中发出和运行可执行机器代码的方式,剩下的只是从其他东西合成机器代码的问题。LLVM有一个完整的编译器,所以它实际上可以在运行时将C和C ++代码(通过LLVM IR)转换为机器码,然后执行它。

注【1】:传统上(即很久以前)malloc使用sbrk系统调用,但是现在大多数malloc的实现在很多情况下使用的是mmap,通常mmap用于大块内存,sbrk用于小块内存,这取决于从OS请求更多内存的两种方法的相对效率。

原创翻译,转载请注明出处。

本文发布于:2023-06-02 10:41:39,感谢您对本站的认可!

本文链接:http://www.ranqi119.com/ge/85/189903.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:原理   简介   JIT
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 站长QQ:55-9-10-26|友情:优美诗词|电脑我帮您|扬州装修|369文学|学编程|软件玩家|水木编程|编程频道