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