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