663 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * dlfilter.c: Interface to perf script --dlfilter shared object4 * 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 28static 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(al->map);35 36 if (symbol_conf.show_kernel_path && dso__long_name(dso))37 d_al->dso = dso__long_name(dso);38 else39 d_al->dso = dso__name(dso);40 d_al->is_64_bit = dso__is_64_bit(dso);41 d_al->buildid_size = dso__bid(dso)->size;42 d_al->buildid = dso__bid(dso)->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(al->map) - sym->start;57 else58 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 73static struct addr_location *get_al(struct dlfilter *d)74{75 struct addr_location *al = d->al;76 77 if (!al->thread && machine__resolve(d->machine, al, d->sample) < 0)78 return NULL;79 return al;80}81 82static 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 89static 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(d->machine, d->sample->ip);109 d_al->comm = al->thread ? thread__comm_str(al->thread) : ":-1";110 d_al->filtered = al->filtered;111 112 return d_al;113}114 115static 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, addr_al, d->sample);134 }135 136 al_to_d_al(addr_al, d_addr_al);137 138 d_addr_al->is_kernel_ip = machine__kernel_ip(d->machine, d->sample->addr);139 140 return d_addr_al;141}142 143static char **dlfilter__args(void *ctx, int *dlargc)144{145 struct dlfilter *d = (struct dlfilter *)ctx;146 147 if (dlargc)148 *dlargc = 0;149 else150 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 159static 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 164static __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);180 thread__find_symbol_fb(thread, d->sample->cpumode, address, &al);181 182 al_to_d_al(&al, &d_al);183 184 d_al.is_kernel_ip = machine__kernel_ip(d->machine, 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);194 195 return 0;196}197 198static 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 215static 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(d->machine, al, d->sample) < 0)231 return NULL;232 233 if (thread__maps(al->thread)) {234 struct machine *machine = maps__machine(thread__maps(al->thread));235 236 if (machine)237 script_fetch_insn(d->sample, 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 249static 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, map__rip_2objdump(map, addr), &line);272 273 *line_no = line;274 return srcfile;275}276 277static 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 287static __s32 code_read(__u64 ip, struct map *map, struct machine *machine, void *buf, __u32 len)288{289 u64 offset = map__map_ip(map, ip);290 291 if (ip + len >= map__end(map))292 len = map__end(map) - ip;293 294 return dso__data_read_offset(map__dso(map), machine, offset, buf, len);295}296 297static __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(al->map) && ip < map__end(al->map) &&312 machine__kernel_ip(d->machine, ip) == machine__kernel_ip(d->machine, d->sample->ip))313 return code_read(ip, al->map, d->machine, buf, len);314 315 addr_location__init(&a);316 317 thread__find_map_fb(al->thread, d->sample->cpumode, ip, &a);318 ret = a.map ? code_read(ip, a.map, d->machine, buf, len) : -1;319 320 addr_location__exit(&a);321 322 return ret;323}324 325static 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 337static 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 the348 * current directory.349 */350 snprintf(path, sizeof(path), "./%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(path, sizeof(path), "%s/dlfilters/%s", exec_path, file);359 free(exec_path);360 if (!access(path, R_OK))361 file = path;362out: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 368static 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 393static void dlfilter__exit(struct dlfilter *d)394{395 zfree(&d->file);396}397 398static 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 415static int dlfilter__close(struct dlfilter *d)416{417 return dlclose(d->handle);418}419 420struct 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 435err_exit:436 dlfilter__exit(d);437err_free:438 free(d);439 return NULL;440}441 442static void dlfilter__free(struct dlfilter *d)443{444 if (d) {445 dlfilter__exit(d);446 free(d);447 }448}449 450int 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 466static 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 479void 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->x489 490int 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(&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 else573 ret = d->filter_event(d->data, &d_sample, d);574 575 d->ctx_valid = false;576 577 return ret;578}579 580bool 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(path, sizeof(path), "%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 606static 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, entry->d_name, &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 641int 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(".");651 652 exec_path = get_argv_exec_path();653 if (!exec_path)654 goto out;655 snprintf(path, sizeof(path), "%s/dlfilters", exec_path);656 657 list_filters(path);658 659 free(exec_path);660out:661 exit(0);662}663