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(__linux__)
30 #include <sys/syscall.h>
31 #elif defined(__darwin__)
32 # include <pthread.h>
33 #elif defined(__FreeBSD__)
34 # include <sys/thr.h>
35 # include <sys/sysctl.h>
36 #elif defined(__NetBSD__)
37 # include <lwp.h>
38 #elif defined(__DragonFly__)
39 # include <sys/lwp.h>
40 # include <sys/sysctl.h>
41 #elif defined(__sun)
42 // avoiding thread.h inclusion as it conflicts with vtunes types.
43 extern unsigned int thr_self(void);
44 #elif defined(__HAIKU__)
45 #include <FindDirectory.h>
46 #endif
47 
48 #include "zend_elf.h"
49 
50 /*
51  * 1) Profile using perf-<pid>.map
52  *
53  * perf record php -d opcache.huge_code_pages=0 -d opcache.jit_debug=0x10 bench.php
54  * perf report
55  *
56  * 2) Profile using jit-<pid>.dump
57  *
58  * perf record -k 1 php -d opcache.huge_code_pages=0 -d opcache.jit_debug=0x20 bench.php
59  * perf inject -j -i perf.data -o perf.data.jitted
60  * perf report -i perf.data.jitted
61  *
62  */
63 
64 
65 #define ZEND_PERF_JITDUMP_HEADER_MAGIC   0x4A695444
66 #define ZEND_PERF_JITDUMP_HEADER_VERSION 1
67 
68 #define ZEND_PERF_JITDUMP_RECORD_LOAD       0
69 #define ZEND_PERF_JITDUMP_RECORD_MOVE       1
70 #define ZEND_PERF_JITDUMP_RECORD_DEBUG_INFO 2
71 #define ZEND_PERF_JITDUMP_RECORD_CLOSE      3
72 #define ZEND_PERF_JITDUMP_UNWINDING_UNFO    4
73 
74 #define ALIGN8(size)   (((size) + 7) & ~7)
75 #define PADDING8(size) (ALIGN8(size) - (size))
76 
77 typedef struct zend_perf_jitdump_header {
78 	uint32_t magic;
79 	uint32_t version;
80 	uint32_t size;
81 	uint32_t elf_mach_target;
82 	uint32_t reserved;
83 	uint32_t process_id;
84 	uint64_t time_stamp;
85 	uint64_t flags;
86 } zend_perf_jitdump_header;
87 
88 typedef struct _zend_perf_jitdump_record {
89 	uint32_t event;
90 	uint32_t size;
91 	uint64_t time_stamp;
92 } zend_perf_jitdump_record;
93 
94 typedef struct _zend_perf_jitdump_load_record {
95 	zend_perf_jitdump_record hdr;
96 	uint32_t process_id;
97 	uint32_t thread_id;
98 	uint64_t vma;
99 	uint64_t code_address;
100 	uint64_t code_size;
101 	uint64_t code_id;
102 } zend_perf_jitdump_load_record;
103 
104 static int   jitdump_fd  = -1;
105 static void *jitdump_mem = MAP_FAILED;
106 
zend_perf_timestamp(void)107 static uint64_t zend_perf_timestamp(void)
108 {
109 	struct timespec ts;
110 
111 	if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
112 		return 0;
113 	}
114 	return ((uint64_t)ts.tv_sec * 1000000000) + ts.tv_nsec;
115 }
116 
zend_jit_perf_jitdump_open(void)117 static void zend_jit_perf_jitdump_open(void)
118 {
119 	char filename[64];
120 	int fd, ret;
121 	zend_elf_header elf_hdr;
122 	zend_perf_jitdump_header jit_hdr;
123 
124 	sprintf(filename, "/tmp/jit-%d.dump", getpid());
125 	if (!zend_perf_timestamp()) {
126 		return;
127 	}
128 
129 #if defined(__linux__)
130 	fd = open("/proc/self/exe", O_RDONLY);
131 #elif defined(__NetBSD__)
132 	fd = open("/proc/curproc/exe", O_RDONLY);
133 #elif defined(__FreeBSD__) || defined(__DragonFly__)
134 	char path[PATH_MAX];
135 	size_t pathlen = sizeof(path);
136 	int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
137 	if (sysctl(mib, 4, path, &pathlen, NULL, 0) == -1) {
138 		return;
139 	}
140 	fd = open(path, O_RDONLY);
141 #elif defined(__sun)
142 	fd = open("/proc/self/path/a.out", O_RDONLY);
143 #elif defined(__HAIKU__)
144 	char path[PATH_MAX];
145 	if (find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH,
146 		NULL, path, sizeof(path)) != B_OK) {
147 		return;
148 	}
149 
150 	fd = open(path, O_RDONLY);
151 #else
152 	fd = -1;
153 #endif
154 	if (fd < 0) {
155 		return;
156 	}
157 
158 	ret = read(fd, &elf_hdr, sizeof(elf_hdr));
159 	close(fd);
160 
161 	if (ret != sizeof(elf_hdr) ||
162 	    elf_hdr.emagic[0] != 0x7f ||
163 	    elf_hdr.emagic[1] != 'E' ||
164 	    elf_hdr.emagic[2] != 'L' ||
165 	    elf_hdr.emagic[3] != 'F') {
166 		return;
167 	}
168 
169 	jitdump_fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0666);
170 	if (jitdump_fd < 0) {
171 		return;
172 	}
173 
174 	jitdump_mem = mmap(NULL,
175 			sysconf(_SC_PAGESIZE),
176 			PROT_READ|PROT_EXEC,
177 			MAP_PRIVATE, jitdump_fd, 0);
178 
179 	if (jitdump_mem == MAP_FAILED) {
180 		close(jitdump_fd);
181 		jitdump_fd = -1;
182 		return;
183 	}
184 
185 	memset(&jit_hdr, 0, sizeof(jit_hdr));
186 	jit_hdr.magic           = ZEND_PERF_JITDUMP_HEADER_MAGIC;
187 	jit_hdr.version         = ZEND_PERF_JITDUMP_HEADER_VERSION;
188 	jit_hdr.size            = sizeof(jit_hdr);
189 	jit_hdr.elf_mach_target = elf_hdr.machine;
190 	jit_hdr.process_id      = getpid();
191 	jit_hdr.time_stamp      = zend_perf_timestamp();
192 	jit_hdr.flags           = 0;
193 	zend_quiet_write(jitdump_fd, &jit_hdr, sizeof(jit_hdr));
194 }
195 
zend_jit_perf_jitdump_close(void)196 static void zend_jit_perf_jitdump_close(void)
197 {
198 	if (jitdump_fd >= 0) {
199 		zend_perf_jitdump_record rec;
200 
201 		rec.event      = ZEND_PERF_JITDUMP_RECORD_CLOSE;
202 		rec.size       = sizeof(rec);
203 		rec.time_stamp = zend_perf_timestamp();
204 		zend_quiet_write(jitdump_fd, &rec, sizeof(rec));
205 		close(jitdump_fd);
206 
207 		if (jitdump_mem != MAP_FAILED) {
208 			munmap(jitdump_mem, sysconf(_SC_PAGESIZE));
209 		}
210 	}
211 }
212 
zend_jit_perf_jitdump_register(const char * name,void * start,size_t size)213 static void zend_jit_perf_jitdump_register(const char *name, void *start, size_t size)
214 {
215 	if (jitdump_fd >= 0) {
216 		static uint64_t id = 1;
217 		zend_perf_jitdump_load_record rec;
218 		size_t len = strlen(name);
219 		uint32_t thread_id = 0;
220 #if defined(__linux__)
221 		thread_id = syscall(SYS_gettid);
222 #elif defined(__darwin__)
223 		uint64_t thread_id_u64;
224 		pthread_threadid_np(NULL, &thread_id_u64);
225 		thread_id = (uint32_t) thread_id_u64;
226 #elif defined(__FreeBSD__)
227 		long tid;
228 		thr_self(&tid);
229 		thread_id = (uint32_t)tid;
230 #elif defined(__OpenBSD__)
231 		thread_id = getthrid();
232 #elif defined(__NetBSD__)
233 		thread_id = _lwp_self();
234 #elif defined(__DragonFly__)
235 		thread_id = lwp_gettid();
236 #elif defined(__sun)
237 		thread_id = thr_self();
238 #endif
239 
240 		memset(&rec, 0, sizeof(rec));
241 		rec.hdr.event      = ZEND_PERF_JITDUMP_RECORD_LOAD;
242 		rec.hdr.size       = sizeof(rec) + len + 1 + size;
243 		rec.hdr.time_stamp = zend_perf_timestamp();
244 		rec.process_id     = getpid();
245 		rec.thread_id      = thread_id;
246 		rec.vma            = (uint64_t)(uintptr_t)start;
247 		rec.code_address   = (uint64_t)(uintptr_t)start;
248 		rec.code_size      = (uint64_t)size;
249 		rec.code_id        = id++;
250 
251 		zend_quiet_write(jitdump_fd, &rec, sizeof(rec));
252 		zend_quiet_write(jitdump_fd, name, len + 1);
253 		zend_quiet_write(jitdump_fd, start, size);
254 	}
255 }
256 
zend_jit_perf_map_register(const char * name,void * start,size_t size)257 static void zend_jit_perf_map_register(const char *name, void *start, size_t size)
258 {
259 	static FILE *fp = NULL;
260 
261 	if (!fp) {
262 		char filename[64];
263 
264 		sprintf(filename, "/tmp/perf-%d.map", getpid());
265 		fp = fopen(filename, "w");
266 		if (!fp) {
267 			return;
268 		}
269 	    setlinebuf(fp);
270 	}
271 	fprintf(fp, "%zx %zx %s\n", (size_t)(uintptr_t)start, size, name);
272 }
273