JIT = just in time, simply dynamic compilation at run time. A program creates and runs brand new code while it is running, rather than the original code that was originally stored on the hard disk as part of the program. In fact, there are two concepts, one is dynamically generated code, and the other is dynamically run code.

As we all know, the computer is running machine code, and the full name of assembly language should be “machine code note language”, each assembly corresponds to a string of machine code. The principle of JIT is to generate and run a piece of code in memory.

The process of generating code is done by the compiler, but it can also be done manually. Running a piece of code in memory relies on the mmap syscall provided by the operating system.

For example, here is a summation machine code

Long add(long num) {return num + 2; X48 corresponding machine code 0} / / x55, 0, 0 x89, 0 xe5, 0 x48, 0 x89, 0 x7d, 0 xf8, 0 x48, 0 x8b, 0 x45, 0 xf8,, 0 x83, 0 xc0, 0 x02, 0 x5d, 0 xc3

Before we can dynamically create functions in memory, we need to allocate space in memory. When it comes to simulating dynamic creation functions, it is actually mapping the corresponding machine code into memory space. Here we use the C language and use the mmap function to achieve this. The underlying mmap function is an encapsulation of the operating system mmap syscall.

#include <unistd.h> #include <sys/mman.h> void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize); Start points to the corresponding memory starting address, usually set to NULL, which means that the system will automatically select the address, and the address will be returned after the corresponding success. Length represents how much of the file corresponds to memory. Where, PROT represents the protection mode of the mapping region, and the following combination of PROT_EXEC mapping region can be implemented; PROT_READ maps can be read; PROT_WRITE maps can be written to; Parameter flags: Various features that affect the mapped region. You must specify MAP_SHARED or MAP_PRIVATE when calling mmap()

We need this piece of code to be readable and executable, so we can create a space like this

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/mman.h> // allocate memory void* createSpace(size_t size) { void* ptr = mmap(0, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0); return ptr; }

We can try removing the “executable” PROT_EXEC permission and see what happens.

This gives us a block of space allocated for our code. The next step is to implement a way to copy the machine code into the space allocated to us. Just use memcpy.

Void copyCodeToM (unsigned char* addr) {unsigned char macCode[] = {0x55, 0x48,0x89,0xe5, 0xe5, 0xe5, 0xe5, 0xe5); 0x48,0x89, 0x7D, 0xF8, 0x48, 0x8B,0x45, 0xF8, 0x48, 0x83, 0xC0, 0x02, 0x5D, 0xC3}; memcpy(addr, macCode, sizeof(macCode)); }

The complete code is as follows:

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/mman.h> // allocate memory void* createSpace(size_t size) { void* ptr = mmap(0, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0); return ptr; } long add(long num) { return num + 2; } void copyCodeToM (unsigned char* addr) {unsigned char macCode[] = {0x55, 0x48,0x89,0xe5, 0xe5, 0xe5, 0xe5, 0xe5); 0x48,0x89, 0x7D, 0xF8, 0x48, 0x8B,0x45, 0xF8, 0x48, 0x83, 0xC0, 0x02, 0x5D, 0xC3}; memcpy(addr, macCode, sizeof(macCode)); } int main(int argc, char** argv) { const size_t SIZE = 1024; typedef long (*demo)(long); void* addr = createSpace(SIZE); copyCodeToMem(addr); demo d1 = addr; long result = d1(1); printf("result = %ld\n", result); return 0; }

The results of compilation and run are as follows:

[root@VM-0-7-centos develop]# gcc demo.c
[root@VM-0-7-centos develop]# ./a.out
result = 3

CopyCodeToM is the machine code that compiles the add function and then dumpsthe code.

[root@VM-0-7-centos develop]#startaddress=$(nm -n a.out |grep add| awk '{print "0x"$1; exit}') [root@VM-0-7-centos develop]#endaddress=$(nm -n a.out |grep -A1 add| awk '{getline; print "0x"$1; exit}') [root@VM-0-7-centos develop]#objdump -S a.out --start-address=$startaddress --stop-address=$endaddress a.out: file format elf64-x86-64 Disassembly of section .text: 0000000000400613 <add>: 400613: 55 push %rbp 400614: 48 89 e5 mov %rsp,%rbp 400617: 48 89 7d f8 mov %rdi,-0x8(%rbp) 40061b: 48 8b 45 f8 mov -0x8(%rbp),%rax 40061f: 48 83 c0 02 add $0x2,%rax 400623: 5d pop %rbp 400624: c3 retq

Instead of objdump, it is possible to use gcc-s to generate assembly code that can be retrieved from a table. Of course, different architectures have different tables.

Of course, a real JIT would not be that simple, but the basic principles are much the same, and the emphasis is also on dynamic code generation and optimization. In fact, getting the machine code of a program dynamically is not done by objdump. Modern compilers, such as LLVM, decouple each step from front end to back end

Mmap can be used to dynamically execute code or map a piece of memory, and the more common use is for file mapping to implement shared memory. The memfd_create() shared memory implementation, similar to mmap, is often used by script kids to hide processes or execute trojans in memory.