1 /*
2    +----------------------------------------------------------------------+
3    | Zend JIT                                                             |
4    +----------------------------------------------------------------------+
5    | Copyright (c) The PHP Group                                          |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | https://www.php.net/license/3_01.txt                                 |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Authors: Dmitry Stogov <dmitry@php.net>                              |
16    +----------------------------------------------------------------------+
17 */
18 
19 #define HAVE_PERFTOOLS 1
20 
21 #include <stdio.h>
22 #include <unistd.h>
23 #include <time.h>
24 #include <sys/mman.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28 
29 #if !defined(HAVE_OS_SIGNPOST_H)
30 #if defined(__linux__)
31 #include <sys/syscall.h>
32 #elif defined(__darwin__)
33 # include <pthread.h>
34 #elif defined(__FreeBSD__)
35 # include <sys/thr.h>
36 # include <sys/sysctl.h>
37 #elif defined(__NetBSD__)
38 # include <lwp.h>
39 #elif defined(__DragonFly__)
40 # include <sys/lwp.h>
41 # include <sys/sysctl.h>
42 #elif defined(__sun)
43 // avoiding thread.h inclusion as it conflicts with vtunes types.
44 extern unsigned int thr_self(void);
45 #elif defined(__HAIKU__)
46 #include <FindDirectory.h>
47 #endif
48 
49 #include "zend_elf.h"
50 #include "zend_mmap.h"
51 
52 /*
53  * 1) Profile using perf-<pid>.map
54  *
55  * perf record php -d opcache.huge_code_pages=0 -d opcache.jit_debug=0x10 bench.php
56  * perf report
57  *
58  * 2) Profile using jit-<pid>.dump
59  *
60  * perf record -k 1 php -d opcache.huge_code_pages=0 -d opcache.jit_debug=0x20 bench.php
61  * perf inject -j -i perf.data -o perf.data.jitted
62  * perf report -i perf.data.jitted
63  *
64  */
65 
66 
67 #define ZEND_PERF_JITDUMP_HEADER_MAGIC   0x4A695444
68 #define ZEND_PERF_JITDUMP_HEADER_VERSION 1
69 
70 #define ZEND_PERF_JITDUMP_RECORD_LOAD       0
71 #define ZEND_PERF_JITDUMP_RECORD_MOVE       1
72 #define ZEND_PERF_JITDUMP_RECORD_DEBUG_INFO 2
73 #define ZEND_PERF_JITDUMP_RECORD_CLOSE      3
74 #define ZEND_PERF_JITDUMP_UNWINDING_UNFO    4
75 
76 #define ALIGN8(size)   (((size) + 7) & ~7)
77 #define PADDING8(size) (ALIGN8(size) - (size))
78 
79 typedef struct zend_perf_jitdump_header {
80 	uint32_t magic;
81 	uint32_t version;
82 	uint32_t size;
83 	uint32_t elf_mach_target;
84 	uint32_t reserved;
85 	uint32_t process_id;
86 	uint64_t time_stamp;
87 	uint64_t flags;
88 } zend_perf_jitdump_header;
89 
90 typedef struct _zend_perf_jitdump_record {
91 	uint32_t event;
92 	uint32_t size;
93 	uint64_t time_stamp;
94 } zend_perf_jitdump_record;
95 
96 typedef struct _zend_perf_jitdump_load_record {
97 	zend_perf_jitdump_record hdr;
98 	uint32_t process_id;
99 	uint32_t thread_id;
100 	uint64_t vma;
101 	uint64_t code_address;
102 	uint64_t code_size;
103 	uint64_t code_id;
104 } zend_perf_jitdump_load_record;
105 
106 static int   jitdump_fd  = -1;
107 static void *jitdump_mem = MAP_FAILED;
108 
zend_perf_timestamp(void)109 static uint64_t zend_perf_timestamp(void)
110 {
111 	struct timespec ts;
112 
113 	if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
114 		return 0;
115 	}
116 	return ((uint64_t)ts.tv_sec * 1000000000) + ts.tv_nsec;
117 }
118 
zend_jit_perf_jitdump_open(void)119 static void zend_jit_perf_jitdump_open(void)
120 {
121 	char filename[64];
122 	int fd, ret;
123 	zend_elf_header elf_hdr;
124 	zend_perf_jitdump_header jit_hdr;
125 
126 	sprintf(filename, "/tmp/jit-%d.dump", getpid());
127 	if (!zend_perf_timestamp()) {
128 		return;
129 	}
130 
131 #if defined(__linux__)
132 	fd = open("/proc/self/exe", O_RDONLY);
133 #elif defined(__NetBSD__)
134 	fd = open("/proc/curproc/exe", O_RDONLY);
135 #elif defined(__FreeBSD__) || defined(__DragonFly__)
136 	char path[PATH_MAX];
137 	size_t pathlen = sizeof(path);
138 	int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
139 	if (sysctl(mib, 4, path, &pathlen, NULL, 0) == -1) {
140 		return;
141 	}
142 	fd = open(path, O_RDONLY);
143 #elif defined(__sun)
144 	fd = open("/proc/self/path/a.out", O_RDONLY);
145 #elif defined(__HAIKU__)
146 	char path[PATH_MAX];
147 	if (find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH,
148 		NULL, path, sizeof(path)) != B_OK) {
149 		return;
150 	}
151 
152 	fd = open(path, O_RDONLY);
153 #else
154 	fd = -1;
155 #endif
156 	if (fd < 0) {
157 		return;
158 	}
159 
160 	ret = read(fd, &elf_hdr, sizeof(elf_hdr));
161 	close(fd);
162 
163 	if (ret != sizeof(elf_hdr) ||
164 	    elf_hdr.emagic[0] != 0x7f ||
165 	    elf_hdr.emagic[1] != 'E' ||
166 	    elf_hdr.emagic[2] != 'L' ||
167 	    elf_hdr.emagic[3] != 'F') {
168 		return;
169 	}
170 
171 	jitdump_fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0666);
172 	if (jitdump_fd < 0) {
173 		return;
174 	}
175 
176 	const size_t page_size = sysconf(_SC_PAGESIZE);
177 	jitdump_mem = mmap(NULL,
178 			page_size,
179 			PROT_READ|PROT_EXEC,
180 			MAP_PRIVATE, jitdump_fd, 0);
181 
182 	if (jitdump_mem == MAP_FAILED) {
183 		close(jitdump_fd);
184 		jitdump_fd = -1;
185 		return;
186 	}
187 
188 	zend_mmap_set_name(jitdump_mem, page_size, "zend_jitdump");
189 
190 	memset(&jit_hdr, 0, sizeof(jit_hdr));
191 	jit_hdr.magic           = ZEND_PERF_JITDUMP_HEADER_MAGIC;
192 	jit_hdr.version         = ZEND_PERF_JITDUMP_HEADER_VERSION;
193 	jit_hdr.size            = sizeof(jit_hdr);
194 	jit_hdr.elf_mach_target = elf_hdr.machine;
195 	jit_hdr.process_id      = getpid();
196 	jit_hdr.time_stamp      = zend_perf_timestamp();
197 	jit_hdr.flags           = 0;
198 	zend_quiet_write(jitdump_fd, &jit_hdr, sizeof(jit_hdr));
199 }
200 
zend_jit_perf_jitdump_close(void)201 static void zend_jit_perf_jitdump_close(void)
202 {
203 	if (jitdump_fd >= 0) {
204 		zend_perf_jitdump_record rec;
205 
206 		rec.event      = ZEND_PERF_JITDUMP_RECORD_CLOSE;
207 		rec.size       = sizeof(rec);
208 		rec.time_stamp = zend_perf_timestamp();
209 		zend_quiet_write(jitdump_fd, &rec, sizeof(rec));
210 		close(jitdump_fd);
211 
212 		if (jitdump_mem != MAP_FAILED) {
213 			munmap(jitdump_mem, sysconf(_SC_PAGESIZE));
214 		}
215 	}
216 }
217 
zend_jit_perf_jitdump_register(const char * name,void * start,size_t size)218 static void zend_jit_perf_jitdump_register(const char *name, void *start, size_t size)
219 {
220 	if (jitdump_fd >= 0) {
221 		static uint64_t id = 1;
222 		zend_perf_jitdump_load_record rec;
223 		size_t len = strlen(name);
224 		uint32_t thread_id = 0;
225 #if defined(__linux__)
226 		thread_id = syscall(SYS_gettid);
227 #elif defined(__darwin__)
228 		uint64_t thread_id_u64;
229 		pthread_threadid_np(NULL, &thread_id_u64);
230 		thread_id = (uint32_t) thread_id_u64;
231 #elif defined(__FreeBSD__)
232 		long tid;
233 		thr_self(&tid);
234 		thread_id = (uint32_t)tid;
235 #elif defined(__OpenBSD__)
236 		thread_id = getthrid();
237 #elif defined(__NetBSD__)
238 		thread_id = _lwp_self();
239 #elif defined(__DragonFly__)
240 		thread_id = lwp_gettid();
241 #elif defined(__sun)
242 		thread_id = thr_self();
243 #endif
244 
245 		memset(&rec, 0, sizeof(rec));
246 		rec.hdr.event      = ZEND_PERF_JITDUMP_RECORD_LOAD;
247 		rec.hdr.size       = sizeof(rec) + len + 1 + size;
248 		rec.hdr.time_stamp = zend_perf_timestamp();
249 		rec.process_id     = getpid();
250 		rec.thread_id      = thread_id;
251 		rec.vma            = (uint64_t)(uintptr_t)start;
252 		rec.code_address   = (uint64_t)(uintptr_t)start;
253 		rec.code_size      = (uint64_t)size;
254 		rec.code_id        = id++;
255 
256 		zend_quiet_write(jitdump_fd, &rec, sizeof(rec));
257 		zend_quiet_write(jitdump_fd, name, len + 1);
258 		zend_quiet_write(jitdump_fd, start, size);
259 	}
260 }
261 
zend_jit_perf_map_register(const char * name,void * start,size_t size)262 static void zend_jit_perf_map_register(const char *name, void *start, size_t size)
263 {
264 	static FILE *fp = NULL;
265 
266 	if (!fp) {
267 		char filename[64];
268 
269 		sprintf(filename, "/tmp/perf-%d.map", getpid());
270 		fp = fopen(filename, "w");
271 		if (!fp) {
272 			return;
273 		}
274 	    setlinebuf(fp);
275 	}
276 	fprintf(fp, "%zx %zx %s\n", (size_t)(uintptr_t)start, size, name);
277 }
278 #else
279 #include <os/log.h>
280 #include <os/signpost.h>
281 
282 /*
283  * 1) To generate an Instrument tracing data:
284  * xcrun xctrace record --template <Template name> --launch -- php
285  * xcrun xctrace record --template Logging --launch -- php -dopcache.jit=1255 -dopcache.jit_debug=32 bench.php
286  * 2) An instrument trace folder is created:
287  * e.g. open Launch_php_2022-07-03_15.41.05_5F96D825.trace
288  */
289 
290 static os_log_t jitdump_fd;
291 static os_signpost_id_t jitdump_sp = OS_SIGNPOST_ID_NULL;
292 
zend_jit_perf_jitdump_open(void)293 static void zend_jit_perf_jitdump_open(void)
294 {
295 	/**
296 	 *  The `os_log_t` list per namespace is maintained by the os
297 	 *  and are not deallocated by (and not deallocatable)
298 	 *  but are reusable.
299 	 */
300 	jitdump_fd = os_log_create("net.php.opcache.jit", OS_LOG_CATEGORY_POINTS_OF_INTEREST);
301 	jitdump_sp = os_signpost_id_generate(jitdump_fd);
302 
303 	if (jitdump_sp != OS_SIGNPOST_ID_NULL && jitdump_sp != OS_SIGNPOST_ID_INVALID) {
304 		os_signpost_interval_begin(jitdump_fd, jitdump_sp, "zend_jitdump");
305 	}
306 }
307 
zend_jit_perf_jitdump_close(void)308 static void zend_jit_perf_jitdump_close(void)
309 {
310 	if (jitdump_sp != OS_SIGNPOST_ID_NULL && jitdump_sp != OS_SIGNPOST_ID_INVALID) {
311 		os_signpost_interval_end(jitdump_fd, jitdump_sp, "zend_jitdump");
312 	}
313 }
314 
zend_jit_perf_jitdump_register(const char * name,void * start,size_t size)315 static void zend_jit_perf_jitdump_register(const char *name, void *start, size_t size)
316 {
317 }
318 
zend_jit_perf_map_register(const char * name,void * start,size_t size)319 static void zend_jit_perf_map_register(const char *name, void *start, size_t size)
320 {
321 	os_signpost_id_t map = os_signpost_id_make_with_pointer(jitdump_fd, start);
322 	if (map != OS_SIGNPOST_ID_NULL && map != OS_SIGNPOST_ID_INVALID) {
323 		os_signpost_event_emit(jitdump_fd, map, "zend_jitdump_name", "%s", name);
324 		os_signpost_event_emit(jitdump_fd, map, "zend_jitdump_size", "%lu", size);
325 	}
326 }
327 #endif
328