1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * dlfilter.c: Interface to perf script --dlfilter shared object |
4 | * Copyright (c) 2021, Intel Corporation. |
5 | */ |
6 | #include <dlfcn.h> |
7 | #include <stdlib.h> |
8 | #include <string.h> |
9 | #include <dirent.h> |
10 | #include <subcmd/exec-cmd.h> |
11 | #include <linux/zalloc.h> |
12 | #include <linux/build_bug.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/string.h> |
15 | |
16 | #include "debug.h" |
17 | #include "event.h" |
18 | #include "evsel.h" |
19 | #include "dso.h" |
20 | #include "map.h" |
21 | #include "thread.h" |
22 | #include "trace-event.h" |
23 | #include "symbol.h" |
24 | #include "srcline.h" |
25 | #include "dlfilter.h" |
26 | #include "../include/perf/perf_dlfilter.h" |
27 | |
28 | static void al_to_d_al(struct addr_location *al, struct perf_dlfilter_al *d_al) |
29 | { |
30 | struct symbol *sym = al->sym; |
31 | |
32 | d_al->size = sizeof(*d_al); |
33 | if (al->map) { |
34 | struct dso *dso = map__dso(map: al->map); |
35 | |
36 | if (symbol_conf.show_kernel_path && dso->long_name) |
37 | d_al->dso = dso->long_name; |
38 | else |
39 | d_al->dso = dso->name; |
40 | d_al->is_64_bit = dso->is_64_bit; |
41 | d_al->buildid_size = dso->bid.size; |
42 | d_al->buildid = dso->bid.data; |
43 | } else { |
44 | d_al->dso = NULL; |
45 | d_al->is_64_bit = 0; |
46 | d_al->buildid_size = 0; |
47 | d_al->buildid = NULL; |
48 | } |
49 | if (sym) { |
50 | d_al->sym = sym->name; |
51 | d_al->sym_start = sym->start; |
52 | d_al->sym_end = sym->end; |
53 | if (al->addr < sym->end) |
54 | d_al->symoff = al->addr - sym->start; |
55 | else if (al->map) |
56 | d_al->symoff = al->addr - map__start(map: al->map) - sym->start; |
57 | else |
58 | d_al->symoff = 0; |
59 | d_al->sym_binding = sym->binding; |
60 | } else { |
61 | d_al->sym = NULL; |
62 | d_al->sym_start = 0; |
63 | d_al->sym_end = 0; |
64 | d_al->symoff = 0; |
65 | d_al->sym_binding = 0; |
66 | } |
67 | d_al->addr = al->addr; |
68 | d_al->comm = NULL; |
69 | d_al->filtered = 0; |
70 | d_al->priv = NULL; |
71 | } |
72 | |
73 | static struct addr_location *get_al(struct dlfilter *d) |
74 | { |
75 | struct addr_location *al = d->al; |
76 | |
77 | if (!al->thread && machine__resolve(machine: d->machine, al, sample: d->sample) < 0) |
78 | return NULL; |
79 | return al; |
80 | } |
81 | |
82 | static struct thread *get_thread(struct dlfilter *d) |
83 | { |
84 | struct addr_location *al = get_al(d); |
85 | |
86 | return al ? al->thread : NULL; |
87 | } |
88 | |
89 | static const struct perf_dlfilter_al *dlfilter__resolve_ip(void *ctx) |
90 | { |
91 | struct dlfilter *d = (struct dlfilter *)ctx; |
92 | struct perf_dlfilter_al *d_al = d->d_ip_al; |
93 | struct addr_location *al; |
94 | |
95 | if (!d->ctx_valid) |
96 | return NULL; |
97 | |
98 | /* 'size' is also used to indicate already initialized */ |
99 | if (d_al->size) |
100 | return d_al; |
101 | |
102 | al = get_al(d); |
103 | if (!al) |
104 | return NULL; |
105 | |
106 | al_to_d_al(al, d_al); |
107 | |
108 | d_al->is_kernel_ip = machine__kernel_ip(machine: d->machine, ip: d->sample->ip); |
109 | d_al->comm = al->thread ? thread__comm_str(thread: al->thread) : ":-1" ; |
110 | d_al->filtered = al->filtered; |
111 | |
112 | return d_al; |
113 | } |
114 | |
115 | static const struct perf_dlfilter_al *dlfilter__resolve_addr(void *ctx) |
116 | { |
117 | struct dlfilter *d = (struct dlfilter *)ctx; |
118 | struct perf_dlfilter_al *d_addr_al = d->d_addr_al; |
119 | struct addr_location *addr_al = d->addr_al; |
120 | |
121 | if (!d->ctx_valid || !d->d_sample->addr_correlates_sym) |
122 | return NULL; |
123 | |
124 | /* 'size' is also used to indicate already initialized */ |
125 | if (d_addr_al->size) |
126 | return d_addr_al; |
127 | |
128 | if (!addr_al->thread) { |
129 | struct thread *thread = get_thread(d); |
130 | |
131 | if (!thread) |
132 | return NULL; |
133 | thread__resolve(thread, al: addr_al, sample: d->sample); |
134 | } |
135 | |
136 | al_to_d_al(al: addr_al, d_al: d_addr_al); |
137 | |
138 | d_addr_al->is_kernel_ip = machine__kernel_ip(machine: d->machine, ip: d->sample->addr); |
139 | |
140 | return d_addr_al; |
141 | } |
142 | |
143 | static char **dlfilter__args(void *ctx, int *dlargc) |
144 | { |
145 | struct dlfilter *d = (struct dlfilter *)ctx; |
146 | |
147 | if (dlargc) |
148 | *dlargc = 0; |
149 | else |
150 | return NULL; |
151 | |
152 | if (!d->ctx_valid && !d->in_start && !d->in_stop) |
153 | return NULL; |
154 | |
155 | *dlargc = d->dlargc; |
156 | return d->dlargv; |
157 | } |
158 | |
159 | static bool has_priv(struct perf_dlfilter_al *d_al_p) |
160 | { |
161 | return d_al_p->size >= offsetof(struct perf_dlfilter_al, priv) + sizeof(d_al_p->priv); |
162 | } |
163 | |
164 | static __s32 dlfilter__resolve_address(void *ctx, __u64 address, struct perf_dlfilter_al *d_al_p) |
165 | { |
166 | struct dlfilter *d = (struct dlfilter *)ctx; |
167 | struct perf_dlfilter_al d_al; |
168 | struct addr_location al; |
169 | struct thread *thread; |
170 | __u32 sz; |
171 | |
172 | if (!d->ctx_valid || !d_al_p) |
173 | return -1; |
174 | |
175 | thread = get_thread(d); |
176 | if (!thread) |
177 | return -1; |
178 | |
179 | addr_location__init(al: &al); |
180 | thread__find_symbol_fb(thread, cpumode: d->sample->cpumode, addr: address, al: &al); |
181 | |
182 | al_to_d_al(al: &al, d_al: &d_al); |
183 | |
184 | d_al.is_kernel_ip = machine__kernel_ip(machine: d->machine, ip: address); |
185 | |
186 | sz = d_al_p->size; |
187 | memcpy(d_al_p, &d_al, min((size_t)sz, sizeof(d_al))); |
188 | d_al_p->size = sz; |
189 | |
190 | if (has_priv(d_al_p)) |
191 | d_al_p->priv = memdup(&al, sizeof(al)); |
192 | else /* Avoid leak for v0 API */ |
193 | addr_location__exit(al: &al); |
194 | |
195 | return 0; |
196 | } |
197 | |
198 | static void dlfilter__al_cleanup(void *ctx __maybe_unused, struct perf_dlfilter_al *d_al_p) |
199 | { |
200 | struct addr_location *al; |
201 | |
202 | /* Ensure backward compatibility */ |
203 | if (!has_priv(d_al_p) || !d_al_p->priv) |
204 | return; |
205 | |
206 | al = d_al_p->priv; |
207 | |
208 | d_al_p->priv = NULL; |
209 | |
210 | addr_location__exit(al); |
211 | |
212 | free(al); |
213 | } |
214 | |
215 | static const __u8 *dlfilter__insn(void *ctx, __u32 *len) |
216 | { |
217 | struct dlfilter *d = (struct dlfilter *)ctx; |
218 | |
219 | if (!len) |
220 | return NULL; |
221 | |
222 | *len = 0; |
223 | |
224 | if (!d->ctx_valid) |
225 | return NULL; |
226 | |
227 | if (d->sample->ip && !d->sample->insn_len) { |
228 | struct addr_location *al = d->al; |
229 | |
230 | if (!al->thread && machine__resolve(machine: d->machine, al, sample: d->sample) < 0) |
231 | return NULL; |
232 | |
233 | if (thread__maps(thread: al->thread)) { |
234 | struct machine *machine = maps__machine(maps: thread__maps(thread: al->thread)); |
235 | |
236 | if (machine) |
237 | script_fetch_insn(sample: d->sample, thread: al->thread, machine); |
238 | } |
239 | } |
240 | |
241 | if (!d->sample->insn_len) |
242 | return NULL; |
243 | |
244 | *len = d->sample->insn_len; |
245 | |
246 | return (__u8 *)d->sample->insn; |
247 | } |
248 | |
249 | static const char *dlfilter__srcline(void *ctx, __u32 *line_no) |
250 | { |
251 | struct dlfilter *d = (struct dlfilter *)ctx; |
252 | struct addr_location *al; |
253 | unsigned int line = 0; |
254 | char *srcfile = NULL; |
255 | struct map *map; |
256 | struct dso *dso; |
257 | u64 addr; |
258 | |
259 | if (!d->ctx_valid || !line_no) |
260 | return NULL; |
261 | |
262 | al = get_al(d); |
263 | if (!al) |
264 | return NULL; |
265 | |
266 | map = al->map; |
267 | addr = al->addr; |
268 | dso = map ? map__dso(map) : NULL; |
269 | |
270 | if (dso) |
271 | srcfile = get_srcline_split(dso, addr: map__rip_2objdump(map, rip: addr), line: &line); |
272 | |
273 | *line_no = line; |
274 | return srcfile; |
275 | } |
276 | |
277 | static struct perf_event_attr *dlfilter__attr(void *ctx) |
278 | { |
279 | struct dlfilter *d = (struct dlfilter *)ctx; |
280 | |
281 | if (!d->ctx_valid) |
282 | return NULL; |
283 | |
284 | return &d->evsel->core.attr; |
285 | } |
286 | |
287 | static __s32 code_read(__u64 ip, struct map *map, struct machine *machine, void *buf, __u32 len) |
288 | { |
289 | u64 offset = map__map_ip(map, ip_or_rip: ip); |
290 | |
291 | if (ip + len >= map__end(map)) |
292 | len = map__end(map) - ip; |
293 | |
294 | return dso__data_read_offset(dso: map__dso(map), machine, offset, data: buf, size: len); |
295 | } |
296 | |
297 | static __s32 dlfilter__object_code(void *ctx, __u64 ip, void *buf, __u32 len) |
298 | { |
299 | struct dlfilter *d = (struct dlfilter *)ctx; |
300 | struct addr_location *al; |
301 | struct addr_location a; |
302 | __s32 ret; |
303 | |
304 | if (!d->ctx_valid) |
305 | return -1; |
306 | |
307 | al = get_al(d); |
308 | if (!al) |
309 | return -1; |
310 | |
311 | if (al->map && ip >= map__start(map: al->map) && ip < map__end(map: al->map) && |
312 | machine__kernel_ip(machine: d->machine, ip) == machine__kernel_ip(machine: d->machine, ip: d->sample->ip)) |
313 | return code_read(ip, map: al->map, machine: d->machine, buf, len); |
314 | |
315 | addr_location__init(al: &a); |
316 | |
317 | thread__find_map_fb(thread: al->thread, cpumode: d->sample->cpumode, addr: ip, al: &a); |
318 | ret = a.map ? code_read(ip, map: a.map, machine: d->machine, buf, len) : -1; |
319 | |
320 | addr_location__exit(al: &a); |
321 | |
322 | return ret; |
323 | } |
324 | |
325 | static const struct perf_dlfilter_fns perf_dlfilter_fns = { |
326 | .resolve_ip = dlfilter__resolve_ip, |
327 | .resolve_addr = dlfilter__resolve_addr, |
328 | .args = dlfilter__args, |
329 | .resolve_address = dlfilter__resolve_address, |
330 | .al_cleanup = dlfilter__al_cleanup, |
331 | .insn = dlfilter__insn, |
332 | .srcline = dlfilter__srcline, |
333 | .attr = dlfilter__attr, |
334 | .object_code = dlfilter__object_code, |
335 | }; |
336 | |
337 | static char *find_dlfilter(const char *file) |
338 | { |
339 | char path[PATH_MAX]; |
340 | char *exec_path; |
341 | |
342 | if (strchr(file, '/')) |
343 | goto out; |
344 | |
345 | if (!access(file, R_OK)) { |
346 | /* |
347 | * Prepend "./" so that dlopen will find the file in the |
348 | * current directory. |
349 | */ |
350 | snprintf(buf: path, size: sizeof(path), fmt: "./%s" , file); |
351 | file = path; |
352 | goto out; |
353 | } |
354 | |
355 | exec_path = get_argv_exec_path(); |
356 | if (!exec_path) |
357 | goto out; |
358 | snprintf(buf: path, size: sizeof(path), fmt: "%s/dlfilters/%s" , exec_path, file); |
359 | free(exec_path); |
360 | if (!access(path, R_OK)) |
361 | file = path; |
362 | out: |
363 | return strdup(file); |
364 | } |
365 | |
366 | #define CHECK_FLAG(x) BUILD_BUG_ON((u64)PERF_DLFILTER_FLAG_ ## x != (u64)PERF_IP_FLAG_ ## x) |
367 | |
368 | static int dlfilter__init(struct dlfilter *d, const char *file, int dlargc, char **dlargv) |
369 | { |
370 | CHECK_FLAG(BRANCH); |
371 | CHECK_FLAG(CALL); |
372 | CHECK_FLAG(RETURN); |
373 | CHECK_FLAG(CONDITIONAL); |
374 | CHECK_FLAG(SYSCALLRET); |
375 | CHECK_FLAG(ASYNC); |
376 | CHECK_FLAG(INTERRUPT); |
377 | CHECK_FLAG(TX_ABORT); |
378 | CHECK_FLAG(TRACE_BEGIN); |
379 | CHECK_FLAG(TRACE_END); |
380 | CHECK_FLAG(IN_TX); |
381 | CHECK_FLAG(VMENTRY); |
382 | CHECK_FLAG(VMEXIT); |
383 | |
384 | memset(d, 0, sizeof(*d)); |
385 | d->file = find_dlfilter(file); |
386 | if (!d->file) |
387 | return -1; |
388 | d->dlargc = dlargc; |
389 | d->dlargv = dlargv; |
390 | return 0; |
391 | } |
392 | |
393 | static void dlfilter__exit(struct dlfilter *d) |
394 | { |
395 | zfree(&d->file); |
396 | } |
397 | |
398 | static int dlfilter__open(struct dlfilter *d) |
399 | { |
400 | d->handle = dlopen(d->file, RTLD_NOW); |
401 | if (!d->handle) { |
402 | pr_err("dlopen failed for: '%s'\n" , d->file); |
403 | return -1; |
404 | } |
405 | d->start = dlsym(d->handle, "start" ); |
406 | d->filter_event = dlsym(d->handle, "filter_event" ); |
407 | d->filter_event_early = dlsym(d->handle, "filter_event_early" ); |
408 | d->stop = dlsym(d->handle, "stop" ); |
409 | d->fns = dlsym(d->handle, "perf_dlfilter_fns" ); |
410 | if (d->fns) |
411 | memcpy(d->fns, &perf_dlfilter_fns, sizeof(struct perf_dlfilter_fns)); |
412 | return 0; |
413 | } |
414 | |
415 | static int dlfilter__close(struct dlfilter *d) |
416 | { |
417 | return dlclose(d->handle); |
418 | } |
419 | |
420 | struct dlfilter *dlfilter__new(const char *file, int dlargc, char **dlargv) |
421 | { |
422 | struct dlfilter *d = malloc(sizeof(*d)); |
423 | |
424 | if (!d) |
425 | return NULL; |
426 | |
427 | if (dlfilter__init(d, file, dlargc, dlargv)) |
428 | goto err_free; |
429 | |
430 | if (dlfilter__open(d)) |
431 | goto err_exit; |
432 | |
433 | return d; |
434 | |
435 | err_exit: |
436 | dlfilter__exit(d); |
437 | err_free: |
438 | free(d); |
439 | return NULL; |
440 | } |
441 | |
442 | static void dlfilter__free(struct dlfilter *d) |
443 | { |
444 | if (d) { |
445 | dlfilter__exit(d); |
446 | free(d); |
447 | } |
448 | } |
449 | |
450 | int dlfilter__start(struct dlfilter *d, struct perf_session *session) |
451 | { |
452 | if (d) { |
453 | d->session = session; |
454 | if (d->start) { |
455 | int ret; |
456 | |
457 | d->in_start = true; |
458 | ret = d->start(&d->data, d); |
459 | d->in_start = false; |
460 | return ret; |
461 | } |
462 | } |
463 | return 0; |
464 | } |
465 | |
466 | static int dlfilter__stop(struct dlfilter *d) |
467 | { |
468 | if (d && d->stop) { |
469 | int ret; |
470 | |
471 | d->in_stop = true; |
472 | ret = d->stop(d->data, d); |
473 | d->in_stop = false; |
474 | return ret; |
475 | } |
476 | return 0; |
477 | } |
478 | |
479 | void dlfilter__cleanup(struct dlfilter *d) |
480 | { |
481 | if (d) { |
482 | dlfilter__stop(d); |
483 | dlfilter__close(d); |
484 | dlfilter__free(d); |
485 | } |
486 | } |
487 | |
488 | #define ASSIGN(x) d_sample.x = sample->x |
489 | |
490 | int dlfilter__do_filter_event(struct dlfilter *d, |
491 | union perf_event *event, |
492 | struct perf_sample *sample, |
493 | struct evsel *evsel, |
494 | struct machine *machine, |
495 | struct addr_location *al, |
496 | struct addr_location *addr_al, |
497 | bool early) |
498 | { |
499 | struct perf_dlfilter_sample d_sample; |
500 | struct perf_dlfilter_al d_ip_al; |
501 | struct perf_dlfilter_al d_addr_al; |
502 | int ret; |
503 | |
504 | d->event = event; |
505 | d->sample = sample; |
506 | d->evsel = evsel; |
507 | d->machine = machine; |
508 | d->al = al; |
509 | d->addr_al = addr_al; |
510 | d->d_sample = &d_sample; |
511 | d->d_ip_al = &d_ip_al; |
512 | d->d_addr_al = &d_addr_al; |
513 | |
514 | d_sample.size = sizeof(d_sample); |
515 | d_ip_al.size = 0; /* To indicate d_ip_al is not initialized */ |
516 | d_addr_al.size = 0; /* To indicate d_addr_al is not initialized */ |
517 | |
518 | ASSIGN(ip); |
519 | ASSIGN(pid); |
520 | ASSIGN(tid); |
521 | ASSIGN(time); |
522 | ASSIGN(addr); |
523 | ASSIGN(id); |
524 | ASSIGN(stream_id); |
525 | ASSIGN(period); |
526 | ASSIGN(weight); |
527 | ASSIGN(ins_lat); |
528 | ASSIGN(p_stage_cyc); |
529 | ASSIGN(transaction); |
530 | ASSIGN(insn_cnt); |
531 | ASSIGN(cyc_cnt); |
532 | ASSIGN(cpu); |
533 | ASSIGN(flags); |
534 | ASSIGN(data_src); |
535 | ASSIGN(phys_addr); |
536 | ASSIGN(data_page_size); |
537 | ASSIGN(code_page_size); |
538 | ASSIGN(cgroup); |
539 | ASSIGN(cpumode); |
540 | ASSIGN(misc); |
541 | ASSIGN(raw_size); |
542 | ASSIGN(raw_data); |
543 | ASSIGN(machine_pid); |
544 | ASSIGN(vcpu); |
545 | |
546 | if (sample->branch_stack) { |
547 | d_sample.brstack_nr = sample->branch_stack->nr; |
548 | d_sample.brstack = (struct perf_branch_entry *)perf_sample__branch_entries(sample); |
549 | } else { |
550 | d_sample.brstack_nr = 0; |
551 | d_sample.brstack = NULL; |
552 | } |
553 | |
554 | if (sample->callchain) { |
555 | d_sample.raw_callchain_nr = sample->callchain->nr; |
556 | d_sample.raw_callchain = (__u64 *)sample->callchain->ips; |
557 | } else { |
558 | d_sample.raw_callchain_nr = 0; |
559 | d_sample.raw_callchain = NULL; |
560 | } |
561 | |
562 | d_sample.addr_correlates_sym = |
563 | (evsel->core.attr.sample_type & PERF_SAMPLE_ADDR) && |
564 | sample_addr_correlates_sym(attr: &evsel->core.attr); |
565 | |
566 | d_sample.event = evsel__name(evsel); |
567 | |
568 | d->ctx_valid = true; |
569 | |
570 | if (early) |
571 | ret = d->filter_event_early(d->data, &d_sample, d); |
572 | else |
573 | ret = d->filter_event(d->data, &d_sample, d); |
574 | |
575 | d->ctx_valid = false; |
576 | |
577 | return ret; |
578 | } |
579 | |
580 | bool get_filter_desc(const char *dirname, const char *name, char **desc, |
581 | char **long_desc) |
582 | { |
583 | char path[PATH_MAX]; |
584 | void *handle; |
585 | const char *(*desc_fn)(const char **long_description); |
586 | |
587 | snprintf(buf: path, size: sizeof(path), fmt: "%s/%s" , dirname, name); |
588 | handle = dlopen(path, RTLD_NOW); |
589 | if (!handle || !(dlsym(handle, "filter_event" ) || dlsym(handle, "filter_event_early" ))) |
590 | return false; |
591 | desc_fn = dlsym(handle, "filter_description" ); |
592 | if (desc_fn) { |
593 | const char *dsc; |
594 | const char *long_dsc; |
595 | |
596 | dsc = desc_fn(&long_dsc); |
597 | if (dsc) |
598 | *desc = strdup(dsc); |
599 | if (long_dsc) |
600 | *long_desc = strdup(long_dsc); |
601 | } |
602 | dlclose(handle); |
603 | return true; |
604 | } |
605 | |
606 | static void list_filters(const char *dirname) |
607 | { |
608 | struct dirent *entry; |
609 | DIR *dir; |
610 | |
611 | dir = opendir(dirname); |
612 | if (!dir) |
613 | return; |
614 | |
615 | while ((entry = readdir(dir)) != NULL) |
616 | { |
617 | size_t n = strlen(entry->d_name); |
618 | char *long_desc = NULL; |
619 | char *desc = NULL; |
620 | |
621 | if (entry->d_type == DT_DIR || n < 4 || |
622 | strcmp(".so" , entry->d_name + n - 3)) |
623 | continue; |
624 | if (!get_filter_desc(dirname, name: entry->d_name, desc: &desc, long_desc: &long_desc)) |
625 | continue; |
626 | printf(" %-36s %s\n" , entry->d_name, desc ? desc : "" ); |
627 | if (verbose > 0) { |
628 | char *p = long_desc; |
629 | char *line; |
630 | |
631 | while ((line = strsep(&p, "\n" )) != NULL) |
632 | printf("%39s%s\n" , "" , line); |
633 | } |
634 | free(long_desc); |
635 | free(desc); |
636 | } |
637 | |
638 | closedir(dir); |
639 | } |
640 | |
641 | int list_available_dlfilters(const struct option *opt __maybe_unused, |
642 | const char *s __maybe_unused, |
643 | int unset __maybe_unused) |
644 | { |
645 | char path[PATH_MAX]; |
646 | char *exec_path; |
647 | |
648 | printf("List of available dlfilters:\n" ); |
649 | |
650 | list_filters(dirname: "." ); |
651 | |
652 | exec_path = get_argv_exec_path(); |
653 | if (!exec_path) |
654 | goto out; |
655 | snprintf(buf: path, size: sizeof(path), fmt: "%s/dlfilters" , exec_path); |
656 | |
657 | list_filters(dirname: path); |
658 | |
659 | free(exec_path); |
660 | out: |
661 | exit(0); |
662 | } |
663 | |