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