/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 | | #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 | 0 | #define IR_PERF_JITDUMP_HEADER_MAGIC 0x4A695444 |
50 | 0 | #define IR_PERF_JITDUMP_HEADER_VERSION 1 |
51 | | |
52 | 0 | #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 | 0 | #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 | | |
91 | | static uint64_t ir_perf_timestamp(void) |
92 | 0 | { |
93 | 0 | struct timespec ts; |
94 | |
|
95 | 0 | if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { |
96 | 0 | return 0; |
97 | 0 | } |
98 | 0 | return ((uint64_t)ts.tv_sec * 1000000000) + ts.tv_nsec; |
99 | 0 | } |
100 | | |
101 | | int ir_perf_jitdump_open(void) |
102 | 0 | { |
103 | 0 | char filename[64]; |
104 | 0 | int fd, ret; |
105 | 0 | ir_elf_header elf_hdr; |
106 | 0 | ir_perf_jitdump_header jit_hdr; |
107 | |
|
108 | 0 | snprintf(filename, sizeof(filename), "/tmp/jit-%d.dump", getpid()); |
109 | 0 | if (!ir_perf_timestamp()) { |
110 | 0 | return 0; |
111 | 0 | } |
112 | | |
113 | 0 | #if defined(__linux__) |
114 | 0 | 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 | 0 | if (fd < 0) { |
139 | 0 | return 0; |
140 | 0 | } |
141 | | |
142 | 0 | ret = read(fd, &elf_hdr, sizeof(elf_hdr)); |
143 | 0 | close(fd); |
144 | |
|
145 | 0 | if (ret != sizeof(elf_hdr) || |
146 | 0 | elf_hdr.emagic[0] != 0x7f || |
147 | 0 | elf_hdr.emagic[1] != 'E' || |
148 | 0 | elf_hdr.emagic[2] != 'L' || |
149 | 0 | elf_hdr.emagic[3] != 'F') { |
150 | 0 | return 0; |
151 | 0 | } |
152 | | |
153 | 0 | jitdump_fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0666); |
154 | 0 | if (jitdump_fd < 0) { |
155 | 0 | return 0; |
156 | 0 | } |
157 | | |
158 | 0 | jitdump_mem = mmap(NULL, |
159 | 0 | sysconf(_SC_PAGESIZE), |
160 | 0 | PROT_READ|PROT_EXEC, |
161 | 0 | MAP_PRIVATE, jitdump_fd, 0); |
162 | |
|
163 | 0 | if (jitdump_mem == MAP_FAILED) { |
164 | 0 | close(jitdump_fd); |
165 | 0 | jitdump_fd = -1; |
166 | 0 | return 0; |
167 | 0 | } |
168 | | |
169 | 0 | memset(&jit_hdr, 0, sizeof(jit_hdr)); |
170 | 0 | jit_hdr.magic = IR_PERF_JITDUMP_HEADER_MAGIC; |
171 | 0 | jit_hdr.version = IR_PERF_JITDUMP_HEADER_VERSION; |
172 | 0 | jit_hdr.size = sizeof(jit_hdr); |
173 | 0 | jit_hdr.elf_mach_target = elf_hdr.machine; |
174 | 0 | jit_hdr.process_id = getpid(); |
175 | 0 | jit_hdr.time_stamp = ir_perf_timestamp(); |
176 | 0 | jit_hdr.flags = 0; |
177 | 0 | if (write(jitdump_fd, &jit_hdr, sizeof(jit_hdr)) != sizeof(jit_hdr)) { |
178 | 0 | return 0; |
179 | 0 | } |
180 | 0 | return 1; |
181 | 0 | } |
182 | | |
183 | | int ir_perf_jitdump_close(void) |
184 | 0 | { |
185 | 0 | int ret = 1; |
186 | |
|
187 | 0 | if (jitdump_fd >= 0) { |
188 | 0 | ir_perf_jitdump_record rec; |
189 | |
|
190 | 0 | rec.event = IR_PERF_JITDUMP_RECORD_CLOSE; |
191 | 0 | rec.size = sizeof(rec); |
192 | 0 | rec.time_stamp = ir_perf_timestamp(); |
193 | 0 | if (write(jitdump_fd, &rec, sizeof(rec)) != sizeof(rec)) { |
194 | 0 | ret = 0; |
195 | 0 | } |
196 | 0 | close(jitdump_fd); |
197 | |
|
198 | 0 | if (jitdump_mem != MAP_FAILED) { |
199 | 0 | munmap(jitdump_mem, sysconf(_SC_PAGESIZE)); |
200 | 0 | } |
201 | 0 | } |
202 | 0 | return ret; |
203 | 0 | } |
204 | | |
205 | | int ir_perf_jitdump_register(const char *name, const void *start, size_t size) |
206 | 0 | { |
207 | 0 | if (jitdump_fd >= 0) { |
208 | 0 | static uint64_t id = 1; |
209 | 0 | ir_perf_jitdump_load_record rec; |
210 | 0 | size_t len = strlen(name); |
211 | 0 | uint32_t thread_id = 0; |
212 | 0 | #if defined(__linux__) |
213 | 0 | 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 | 0 | memset(&rec, 0, sizeof(rec)); |
233 | 0 | rec.hdr.event = IR_PERF_JITDUMP_RECORD_LOAD; |
234 | 0 | rec.hdr.size = sizeof(rec) + len + 1 + size; |
235 | 0 | rec.hdr.time_stamp = ir_perf_timestamp(); |
236 | 0 | rec.process_id = getpid(); |
237 | 0 | rec.thread_id = thread_id; |
238 | 0 | rec.vma = (uint64_t)(uintptr_t)start; |
239 | 0 | rec.code_address = (uint64_t)(uintptr_t)start; |
240 | 0 | rec.code_size = (uint64_t)size; |
241 | 0 | rec.code_id = id++; |
242 | |
|
243 | 0 | if (write(jitdump_fd, &rec, sizeof(rec)) != sizeof(rec) |
244 | 0 | || write(jitdump_fd, name, len + 1) < 0 |
245 | 0 | || write(jitdump_fd, start, size) < 0) { |
246 | 0 | return 0; |
247 | 0 | } |
248 | 0 | } |
249 | 0 | return 1; |
250 | 0 | } |
251 | | |
252 | | void ir_perf_map_register(const char *name, const void *start, size_t size) |
253 | 0 | { |
254 | 0 | static FILE *fp = NULL; |
255 | |
|
256 | 0 | if (!fp) { |
257 | 0 | char filename[64]; |
258 | |
|
259 | 0 | snprintf(filename, sizeof(filename), "/tmp/perf-%d.map", getpid()); |
260 | 0 | fp = fopen(filename, "w"); |
261 | 0 | if (!fp) { |
262 | 0 | return; |
263 | 0 | } |
264 | 0 | setlinebuf(fp); |
265 | 0 | } |
266 | 0 | fprintf(fp, "%zx %zx %s\n", (size_t)(uintptr_t)start, size, name); |
267 | 0 | } |