xref: /php-src/ext/opcache/jit/ir/ir_patch.c (revision caf102df)
1 /*
2  * IR - Lightweight JIT Compilation Framework
3  * (Native code patcher)
4  * Copyright (C) 2022 Zend by Perforce.
5  * Authors: Dmitry Stogov <dmitry@php.net>
6  *
7  * Based on Mike Pall's implementation for LuaJIT.
8  */
9 
10 #include "ir.h"
11 #include "ir_private.h"
12 
13 #if defined(IR_TARGET_X86) || defined(IR_TARGET_X64)
_asm_x86_inslen(const uint8_t * p)14 static uint32_t _asm_x86_inslen(const uint8_t* p)
15 {
16 	static const uint8_t map_op1[256] = {
17 		0x92,0x92,0x92,0x92,0x52,0x45,0x51,0x51,0x92,0x92,0x92,0x92,0x52,0x45,0x51,0x20,
18 		0x92,0x92,0x92,0x92,0x52,0x45,0x51,0x51,0x92,0x92,0x92,0x92,0x52,0x45,0x51,0x51,
19 		0x92,0x92,0x92,0x92,0x52,0x45,0x10,0x51,0x92,0x92,0x92,0x92,0x52,0x45,0x10,0x51,
20 		0x92,0x92,0x92,0x92,0x52,0x45,0x10,0x51,0x92,0x92,0x92,0x92,0x52,0x45,0x10,0x51,
21 #ifdef IR_TARGET_X64
22 		0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,
23 #else
24 		0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,
25 #endif
26 		0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,
27 		0x51,0x51,0x92,0x92,0x10,0x10,0x12,0x11,0x45,0x86,0x52,0x93,0x51,0x51,0x51,0x51,
28 		0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,
29 		0x93,0x86,0x93,0x93,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
30 		0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0x47,0x51,0x51,0x51,0x51,0x51,
31 #ifdef IR_TARGET_X64
32 		0x59,0x59,0x59,0x59,0x51,0x51,0x51,0x51,0x52,0x45,0x51,0x51,0x51,0x51,0x51,0x51,
33 #else
34 		0x55,0x55,0x55,0x55,0x51,0x51,0x51,0x51,0x52,0x45,0x51,0x51,0x51,0x51,0x51,0x51,
35 #endif
36 		0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x05,
37 		0x93,0x93,0x53,0x51,0x70,0x71,0x93,0x86,0x54,0x51,0x53,0x51,0x51,0x52,0x51,0x51,
38 		0x92,0x92,0x92,0x92,0x52,0x52,0x51,0x51,0x92,0x92,0x92,0x92,0x92,0x92,0x92,0x92,
39 		0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x45,0x45,0x47,0x52,0x51,0x51,0x51,0x51,
40 		0x10,0x51,0x10,0x10,0x51,0x51,0x63,0x66,0x51,0x51,0x51,0x51,0x51,0x51,0x92,0x92
41 	};
42 	static const uint8_t map_op2[256] = {
43 		0x93,0x93,0x93,0x93,0x52,0x52,0x52,0x52,0x52,0x52,0x51,0x52,0x51,0x93,0x52,0x94,
44 		0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
45 		0x53,0x53,0x53,0x53,0x53,0x53,0x53,0x53,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
46 		0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x34,0x51,0x35,0x51,0x51,0x51,0x51,0x51,
47 		0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
48 		0x53,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
49 		0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
50 		0x94,0x54,0x54,0x54,0x93,0x93,0x93,0x52,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
51 		0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,0x46,
52 		0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
53 		0x52,0x52,0x52,0x93,0x94,0x93,0x51,0x51,0x52,0x52,0x52,0x93,0x94,0x93,0x93,0x93,
54 		0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x94,0x93,0x93,0x93,0x93,0x93,
55 		0x93,0x93,0x94,0x93,0x94,0x94,0x94,0x93,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,
56 		0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
57 		0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,
58 		0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x93,0x52
59 	};
60 	uint32_t result = 0;
61 	uint32_t prefixes = 0;
62 	uint32_t x = map_op1[*p];
63 
64 	for (;;) {
65 		switch (x >> 4) {
66 			case 0:
67 				return result + x + (prefixes & 4);
68 			case 1:
69 				prefixes |= x;
70 				x = map_op1[*++p];
71 				result++;
72 				break;
73 			case 2:
74 				x = map_op2[*++p];
75 				break;
76 			case 3:
77 				p++;
78 				goto mrm;
79 			case 4:
80 				result -= (prefixes & 2);
81 				/* fallthrough */
82 			case 5:
83 				return result + (x & 15);
84 			case 6: /* Group 3. */
85 				if (p[1] & 0x38) {
86 					x = 2;
87 				} else if ((prefixes & 2) && (x == 0x66)) {
88 					x = 4;
89 				}
90 				goto mrm;
91 			case 7: /* VEX c4/c5. */
92 #ifdef IR_TARGET_X86
93 				if (p[1] < 0xc0) {
94 					x = 2;
95 					goto mrm;
96 				}
97 #endif
98 				if (x == 0x70) {
99 					x = *++p & 0x1f;
100 					result++;
101 					if (x >= 2) {
102 						p += 2;
103 						result += 2;
104 						goto mrm;
105 					}
106 				}
107 				p++;
108 				result++;
109 				x = map_op2[*++p];
110 				break;
111 			case 8:
112 				result -= (prefixes & 2);
113 				/* fallthrough */
114 			case 9:
115 mrm:
116 				/* ModR/M and possibly SIB. */
117 				result += (x & 15);
118 				x = *++p;
119 				switch (x >> 6) {
120 					case 0:
121 						if ((x & 7) == 5) {
122 							return result + 4;
123 						}
124 						break;
125 					case 1:
126 						result++;
127 						break;
128 					case 2:
129 						result += 4;
130 						break;
131 					case 3:
132 						return result;
133 				}
134 				if ((x & 7) == 4) {
135 					result++;
136 					if (x < 0x40 && (p[1] & 7) == 5) {
137 						result += 4;
138 					}
139 				}
140 				return result;
141 		}
142 	}
143 }
144 
145 typedef IR_SET_ALIGNED(1, uint16_t unaligned_uint16_t);
146 typedef IR_SET_ALIGNED(1, int32_t unaligned_int32_t);
147 
ir_patch_code(const void * code,size_t size,const void * from_addr,const void * to_addr)148 static int ir_patch_code(const void *code, size_t size, const void *from_addr, const void *to_addr)
149 {
150 	int ret = 0;
151 	uint8_t *p, *end;
152 
153 	p = (uint8_t*)code;
154 	end = p + size - 4;
155 	while (p < end) {
156 		if ((*(unaligned_uint16_t*)p & 0xf0ff) == 0x800f && p + *(unaligned_int32_t*)(p+2) == (uint8_t*)from_addr - 6) {
157 			*(unaligned_int32_t*)(p+2) = ((uint8_t*)to_addr - (p + 6));
158 			ret++;
159 		} else if (*p == 0xe9 && p + *(unaligned_int32_t*)(p+1) == (uint8_t*)from_addr - 5) {
160 			*(unaligned_int32_t*)(p+1) = ((uint8_t*)to_addr - (p + 5));
161 			ret++;
162 		}
163 		p += _asm_x86_inslen(p);
164 	}
165 	if (ret) {
166 		ir_mem_flush((void*)code, size);
167 	}
168 	return ret;
169 }
170 
171 #elif defined(IR_TARGET_AARCH64)
172 
ir_patch_code(const void * code,size_t size,const void * from_addr,const void * to_addr)173 static int ir_patch_code(const void *code, size_t size, const void *from_addr, const void *to_addr)
174 {
175 	int ret = 0;
176 	uint8_t *p, *end;
177 	const void *veneer = NULL;
178 	ptrdiff_t delta;
179 
180 	end = (uint8_t*)code;
181 	p = end + size;
182 	while (p > end) {
183 		uint32_t *ins_ptr;
184 		uint32_t ins;
185 
186 		p -= 4;
187 		ins_ptr = (uint32_t*)p;
188 		ins = *ins_ptr;
189 		if ((ins & 0xfc000000u) == 0x14000000u) {
190 			// B (imm26:0..25)
191 			delta = (uint32_t*)from_addr - ins_ptr;
192 			if (((ins ^ (uint32_t)delta) & 0x01ffffffu) == 0) {
193 				delta = (uint32_t*)to_addr - ins_ptr;
194 				if (((delta + 0x02000000) >> 26) != 0) {
195 					abort(); // branch target out of range
196 				}
197 				*ins_ptr = (ins & 0xfc000000u) | ((uint32_t)delta & 0x03ffffffu);
198 				ret++;
199 				if (!veneer) {
200 					veneer = p;
201 				}
202 			}
203 		} else if ((ins & 0xff000000u) == 0x54000000u ||
204 		           (ins & 0x7e000000u) == 0x34000000u) {
205 			// B.cond, CBZ, CBNZ (imm19:5..23)
206 			delta = (uint32_t*)from_addr - ins_ptr;
207 			if (((ins ^ ((uint32_t)delta << 5)) & 0x00ffffe0u) == 0) {
208 				delta = (uint32_t*)to_addr - ins_ptr;
209 				if (((delta + 0x40000) >> 19) != 0) {
210 					if (veneer) {
211 						delta = (uint32_t*)veneer - ins_ptr;
212 						if (((delta + 0x40000) >> 19) != 0) {
213 							abort(); // branch target out of range
214 						}
215 					} else {
216 						abort(); // branch target out of range
217 					}
218 				}
219 				*ins_ptr = (ins & 0xff00001fu) | (((uint32_t)delta & 0x7ffffu) << 5);
220 				ret++;
221 			}
222 		} else if ((ins & 0x7e000000u) == 0x36000000u) {
223 			// TBZ, TBNZ (imm14:5..18)
224 			delta = (uint32_t*)from_addr - ins_ptr;
225 			if (((ins ^ ((uint32_t)delta << 5)) & 0x0007ffe0u) == 0) {
226 				delta = (uint32_t*)to_addr - ins_ptr;
227 				if (((delta + 0x2000) >> 14) != 0) {
228 					if (veneer) {
229 						delta = (uint32_t*)veneer - ins_ptr;
230 						if (((delta + 0x2000) >> 14) != 0) {
231 							abort(); // branch target out of range
232 						}
233 					} else {
234 						abort(); // branch target out of range
235 					}
236 				}
237 				*ins_ptr = (ins & 0xfff8001fu) | (((uint32_t)delta & 0x3fffu) << 5);
238 				ret++;
239 			}
240 		}
241 	}
242 
243 	if (ret) {
244 		ir_mem_flush((void*)code, size);
245 	}
246 
247 	return ret;
248 }
249 #endif
250 
ir_patch(const void * code,size_t size,uint32_t jmp_table_size,const void * from_addr,const void * to_addr)251 int ir_patch(const void *code, size_t size, uint32_t jmp_table_size, const void *from_addr, const void *to_addr)
252 {
253 	int ret = 0;
254 
255 	if (jmp_table_size) {
256 		const void **jmp_slot = (const void **)((char*)code + IR_ALIGNED_SIZE(size, sizeof(void*)));
257 
258 		do {
259 			if (*jmp_slot == from_addr) {
260 				*jmp_slot = to_addr;
261 				ret++;
262 			}
263 			jmp_slot++;
264 		} while (--jmp_table_size);
265 	}
266 
267 	ret += ir_patch_code(code, size, from_addr, to_addr);
268 
269 	return ret;
270 }
271