root/tools/perf/util/dlfilter.c
// SPDX-License-Identifier: GPL-2.0
/*
 * dlfilter.c: Interface to perf script --dlfilter shared object
 * Copyright (c) 2021, Intel Corporation.
 */
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <subcmd/exec-cmd.h>
#include <linux/zalloc.h>
#include <linux/build_bug.h>
#include <linux/kernel.h>
#include <linux/string.h>

#include "debug.h"
#include "event.h"
#include "evsel.h"
#include "dso.h"
#include "map.h"
#include "thread.h"
#include "trace-event.h"
#include "symbol.h"
#include "srcline.h"
#include "dlfilter.h"
#include "../include/perf/perf_dlfilter.h"

static void al_to_d_al(struct addr_location *al, struct perf_dlfilter_al *d_al)
{
        struct symbol *sym = al->sym;

        d_al->size = sizeof(*d_al);
        if (al->map) {
                struct dso *dso = map__dso(al->map);

                if (symbol_conf.show_kernel_path && dso__long_name(dso))
                        d_al->dso = dso__long_name(dso);
                else
                        d_al->dso = dso__name(dso);
                d_al->is_64_bit = dso__is_64_bit(dso);
                d_al->buildid_size = dso__bid(dso)->size;
                d_al->buildid = dso__bid(dso)->data;
        } else {
                d_al->dso = NULL;
                d_al->is_64_bit = 0;
                d_al->buildid_size = 0;
                d_al->buildid = NULL;
        }
        if (sym) {
                d_al->sym = sym->name;
                d_al->sym_start = sym->start;
                d_al->sym_end = sym->end;
                if (al->addr < sym->end)
                        d_al->symoff = al->addr - sym->start;
                else if (al->map)
                        d_al->symoff = al->addr - map__start(al->map) - sym->start;
                else
                        d_al->symoff = 0;
                d_al->sym_binding = sym->binding;
        } else {
                d_al->sym = NULL;
                d_al->sym_start = 0;
                d_al->sym_end = 0;
                d_al->symoff = 0;
                d_al->sym_binding = 0;
        }
        d_al->addr = al->addr;
        d_al->comm = NULL;
        d_al->filtered = 0;
        d_al->priv = NULL;
}

static struct addr_location *get_al(struct dlfilter *d)
{
        struct addr_location *al = d->al;

        if (!al->thread && machine__resolve(d->machine, al, d->sample) < 0)
                return NULL;
        return al;
}

static struct thread *get_thread(struct dlfilter *d)
{
        struct addr_location *al = get_al(d);

        return al ? al->thread : NULL;
}

static const struct perf_dlfilter_al *dlfilter__resolve_ip(void *ctx)
{
        struct dlfilter *d = (struct dlfilter *)ctx;
        struct perf_dlfilter_al *d_al = d->d_ip_al;
        struct addr_location *al;

        if (!d->ctx_valid)
                return NULL;

        /* 'size' is also used to indicate already initialized */
        if (d_al->size)
                return d_al;

        al = get_al(d);
        if (!al)
                return NULL;

        al_to_d_al(al, d_al);

        d_al->is_kernel_ip = machine__kernel_ip(d->machine, d->sample->ip);
        d_al->comm = al->thread ? thread__comm_str(al->thread) : ":-1";
        d_al->filtered = al->filtered;

        return d_al;
}

static const struct perf_dlfilter_al *dlfilter__resolve_addr(void *ctx)
{
        struct dlfilter *d = (struct dlfilter *)ctx;
        struct perf_dlfilter_al *d_addr_al = d->d_addr_al;
        struct addr_location *addr_al = d->addr_al;

        if (!d->ctx_valid || !d->d_sample->addr_correlates_sym)
                return NULL;

        /* 'size' is also used to indicate already initialized */
        if (d_addr_al->size)
                return d_addr_al;

        if (!addr_al->thread) {
                struct thread *thread = get_thread(d);

                if (!thread)
                        return NULL;
                thread__resolve(thread, addr_al, d->sample);
        }

        al_to_d_al(addr_al, d_addr_al);

        d_addr_al->is_kernel_ip = machine__kernel_ip(d->machine, d->sample->addr);

        return d_addr_al;
}

static char **dlfilter__args(void *ctx, int *dlargc)
{
        struct dlfilter *d = (struct dlfilter *)ctx;

        if (dlargc)
                *dlargc = 0;
        else
                return NULL;

        if (!d->ctx_valid && !d->in_start && !d->in_stop)
                return NULL;

        *dlargc = d->dlargc;
        return d->dlargv;
}

static bool has_priv(struct perf_dlfilter_al *d_al_p)
{
        return d_al_p->size >= offsetof(struct perf_dlfilter_al, priv) + sizeof(d_al_p->priv);
}

static __s32 dlfilter__resolve_address(void *ctx, __u64 address, struct perf_dlfilter_al *d_al_p)
{
        struct dlfilter *d = (struct dlfilter *)ctx;
        struct perf_dlfilter_al d_al;
        struct addr_location al;
        struct thread *thread;
        __u32 sz;

        if (!d->ctx_valid || !d_al_p)
                return -1;

        thread = get_thread(d);
        if (!thread)
                return -1;

        addr_location__init(&al);
        thread__find_symbol_fb(thread, d->sample->cpumode, address, &al);

        al_to_d_al(&al, &d_al);

        d_al.is_kernel_ip = machine__kernel_ip(d->machine, address);

        sz = d_al_p->size;
        memcpy(d_al_p, &d_al, min((size_t)sz, sizeof(d_al)));
        d_al_p->size = sz;

        if (has_priv(d_al_p))
                d_al_p->priv = memdup(&al, sizeof(al));
        else /* Avoid leak for v0 API */
                addr_location__exit(&al);

        return 0;
}

static void dlfilter__al_cleanup(void *ctx __maybe_unused, struct perf_dlfilter_al *d_al_p)
{
        struct addr_location *al;

        /* Ensure backward compatibility */
        if (!has_priv(d_al_p) || !d_al_p->priv)
                return;

        al = d_al_p->priv;

        d_al_p->priv = NULL;

        addr_location__exit(al);

        free(al);
}

static const __u8 *dlfilter__insn(void *ctx, __u32 *len)
{
        struct dlfilter *d = (struct dlfilter *)ctx;

        if (!len)
                return NULL;

        *len = 0;

        if (!d->ctx_valid)
                return NULL;

        if (d->sample->ip && !d->sample->insn_len) {
                struct addr_location *al = d->al;

                if (!al->thread && machine__resolve(d->machine, al, d->sample) < 0)
                        return NULL;

                if (thread__maps(al->thread)) {
                        struct machine *machine = maps__machine(thread__maps(al->thread));

                        if (machine)
                                perf_sample__fetch_insn(d->sample, al->thread, machine);
                }
        }

        if (!d->sample->insn_len)
                return NULL;

        *len = d->sample->insn_len;

        return (__u8 *)d->sample->insn;
}

static const char *dlfilter__srcline(void *ctx, __u32 *line_no)
{
        struct dlfilter *d = (struct dlfilter *)ctx;
        struct addr_location *al;
        unsigned int line = 0;
        char *srcfile = NULL;
        struct map *map;
        struct dso *dso;
        u64 addr;

        if (!d->ctx_valid || !line_no)
                return NULL;

        al = get_al(d);
        if (!al)
                return NULL;

        map = al->map;
        addr = al->addr;
        dso = map ? map__dso(map) : NULL;

        if (dso)
                srcfile = get_srcline_split(dso, map__rip_2objdump(map, addr), &line);

        *line_no = line;
        return srcfile;
}

static struct perf_event_attr *dlfilter__attr(void *ctx)
{
        struct dlfilter *d = (struct dlfilter *)ctx;

        if (!d->ctx_valid)
                return NULL;

        return &d->evsel->core.attr;
}

static __s32 code_read(__u64 ip, struct map *map, struct machine *machine, void *buf, __u32 len)
{
        u64 offset = map__map_ip(map, ip);

        if (ip + len >= map__end(map))
                len = map__end(map) - ip;

        return dso__data_read_offset(map__dso(map), machine, offset, buf, len);
}

static __s32 dlfilter__object_code(void *ctx, __u64 ip, void *buf, __u32 len)
{
        struct dlfilter *d = (struct dlfilter *)ctx;
        struct addr_location *al;
        struct addr_location a;
        __s32 ret;

        if (!d->ctx_valid)
                return -1;

        al = get_al(d);
        if (!al)
                return -1;

        if (al->map && ip >= map__start(al->map) && ip < map__end(al->map) &&
            machine__kernel_ip(d->machine, ip) == machine__kernel_ip(d->machine, d->sample->ip))
                return code_read(ip, al->map, d->machine, buf, len);

        addr_location__init(&a);

        thread__find_map_fb(al->thread, d->sample->cpumode, ip, &a);
        ret = a.map ? code_read(ip, a.map, d->machine, buf, len) : -1;

        addr_location__exit(&a);

        return ret;
}

static const struct perf_dlfilter_fns perf_dlfilter_fns = {
        .resolve_ip      = dlfilter__resolve_ip,
        .resolve_addr    = dlfilter__resolve_addr,
        .args            = dlfilter__args,
        .resolve_address = dlfilter__resolve_address,
        .al_cleanup      = dlfilter__al_cleanup,
        .insn            = dlfilter__insn,
        .srcline         = dlfilter__srcline,
        .attr            = dlfilter__attr,
        .object_code     = dlfilter__object_code,
};

static char *find_dlfilter(const char *file)
{
        char path[PATH_MAX];
        char *exec_path;

        if (strchr(file, '/'))
                goto out;

        if (!access(file, R_OK)) {
                /*
                 * Prepend "./" so that dlopen will find the file in the
                 * current directory.
                 */
                snprintf(path, sizeof(path), "./%s", file);
                file = path;
                goto out;
        }

        exec_path = get_argv_exec_path();
        if (!exec_path)
                goto out;
        snprintf(path, sizeof(path), "%s/dlfilters/%s", exec_path, file);
        free(exec_path);
        if (!access(path, R_OK))
                file = path;
out:
        return strdup(file);
}

#define CHECK_FLAG(x) BUILD_BUG_ON((u64)PERF_DLFILTER_FLAG_ ## x != (u64)PERF_IP_FLAG_ ## x)

static int dlfilter__init(struct dlfilter *d, const char *file, int dlargc, char **dlargv)
{
        CHECK_FLAG(BRANCH);
        CHECK_FLAG(CALL);
        CHECK_FLAG(RETURN);
        CHECK_FLAG(CONDITIONAL);
        CHECK_FLAG(SYSCALLRET);
        CHECK_FLAG(ASYNC);
        CHECK_FLAG(INTERRUPT);
        CHECK_FLAG(TX_ABORT);
        CHECK_FLAG(TRACE_BEGIN);
        CHECK_FLAG(TRACE_END);
        CHECK_FLAG(IN_TX);
        CHECK_FLAG(VMENTRY);
        CHECK_FLAG(VMEXIT);

        memset(d, 0, sizeof(*d));
        d->file = find_dlfilter(file);
        if (!d->file)
                return -1;
        d->dlargc = dlargc;
        d->dlargv = dlargv;
        return 0;
}

static void dlfilter__exit(struct dlfilter *d)
{
        zfree(&d->file);
}

static int dlfilter__open(struct dlfilter *d)
{
        d->handle = dlopen(d->file, RTLD_NOW);
        if (!d->handle) {
                pr_err("dlopen failed for: '%s'\n", d->file);
                return -1;
        }
        d->start = dlsym(d->handle, "start");
        d->filter_event = dlsym(d->handle, "filter_event");
        d->filter_event_early = dlsym(d->handle, "filter_event_early");
        d->stop = dlsym(d->handle, "stop");
        d->fns = dlsym(d->handle, "perf_dlfilter_fns");
        if (d->fns)
                memcpy(d->fns, &perf_dlfilter_fns, sizeof(struct perf_dlfilter_fns));
        return 0;
}

static int dlfilter__close(struct dlfilter *d)
{
        return dlclose(d->handle);
}

struct dlfilter *dlfilter__new(const char *file, int dlargc, char **dlargv)
{
        struct dlfilter *d = malloc(sizeof(*d));

        if (!d)
                return NULL;

        if (dlfilter__init(d, file, dlargc, dlargv))
                goto err_free;

        if (dlfilter__open(d))
                goto err_exit;

        return d;

err_exit:
        dlfilter__exit(d);
err_free:
        free(d);
        return NULL;
}

static void dlfilter__free(struct dlfilter *d)
{
        if (d) {
                dlfilter__exit(d);
                free(d);
        }
}

int dlfilter__start(struct dlfilter *d, struct perf_session *session)
{
        if (d) {
                d->session = session;
                if (d->start) {
                        int ret;

                        d->in_start = true;
                        ret = d->start(&d->data, d);
                        d->in_start = false;
                        return ret;
                }
        }
        return 0;
}

static int dlfilter__stop(struct dlfilter *d)
{
        if (d && d->stop) {
                int ret;

                d->in_stop = true;
                ret = d->stop(d->data, d);
                d->in_stop = false;
                return ret;
        }
        return 0;
}

void dlfilter__cleanup(struct dlfilter *d)
{
        if (d) {
                dlfilter__stop(d);
                dlfilter__close(d);
                dlfilter__free(d);
        }
}

#define ASSIGN(x) d_sample.x = sample->x

int dlfilter__do_filter_event(struct dlfilter *d,
                              union perf_event *event,
                              struct perf_sample *sample,
                              struct evsel *evsel,
                              struct machine *machine,
                              struct addr_location *al,
                              struct addr_location *addr_al,
                              bool early)
{
        struct perf_dlfilter_sample d_sample;
        struct perf_dlfilter_al d_ip_al;
        struct perf_dlfilter_al d_addr_al;
        int ret;

        d->event       = event;
        d->sample      = sample;
        d->evsel       = evsel;
        d->machine     = machine;
        d->al          = al;
        d->addr_al     = addr_al;
        d->d_sample    = &d_sample;
        d->d_ip_al     = &d_ip_al;
        d->d_addr_al   = &d_addr_al;

        d_sample.size  = sizeof(d_sample);
        d_sample.p_stage_cyc = sample->weight3;
        d_ip_al.size   = 0; /* To indicate d_ip_al is not initialized */
        d_addr_al.size = 0; /* To indicate d_addr_al is not initialized */

        ASSIGN(ip);
        ASSIGN(pid);
        ASSIGN(tid);
        ASSIGN(time);
        ASSIGN(addr);
        ASSIGN(id);
        ASSIGN(stream_id);
        ASSIGN(period);
        ASSIGN(weight);
        ASSIGN(ins_lat);
        ASSIGN(transaction);
        ASSIGN(insn_cnt);
        ASSIGN(cyc_cnt);
        ASSIGN(cpu);
        ASSIGN(flags);
        ASSIGN(data_src);
        ASSIGN(phys_addr);
        ASSIGN(data_page_size);
        ASSIGN(code_page_size);
        ASSIGN(cgroup);
        ASSIGN(cpumode);
        ASSIGN(misc);
        ASSIGN(raw_size);
        ASSIGN(raw_data);
        ASSIGN(machine_pid);
        ASSIGN(vcpu);

        if (sample->branch_stack) {
                d_sample.brstack_nr = sample->branch_stack->nr;
                d_sample.brstack = (struct perf_branch_entry *)perf_sample__branch_entries(sample);
        } else {
                d_sample.brstack_nr = 0;
                d_sample.brstack = NULL;
        }

        if (sample->callchain) {
                d_sample.raw_callchain_nr = sample->callchain->nr;
                d_sample.raw_callchain = (__u64 *)sample->callchain->ips;
        } else {
                d_sample.raw_callchain_nr = 0;
                d_sample.raw_callchain = NULL;
        }

        d_sample.addr_correlates_sym =
                (evsel->core.attr.sample_type & PERF_SAMPLE_ADDR) &&
                sample_addr_correlates_sym(&evsel->core.attr);

        d_sample.event = evsel__name(evsel);

        d->ctx_valid = true;

        if (early)
                ret = d->filter_event_early(d->data, &d_sample, d);
        else
                ret = d->filter_event(d->data, &d_sample, d);

        d->ctx_valid = false;

        return ret;
}

bool get_filter_desc(const char *dirname, const char *name, char **desc,
                     char **long_desc)
{
        char path[PATH_MAX];
        void *handle;
        const char *(*desc_fn)(const char **long_description);

        snprintf(path, sizeof(path), "%s/%s", dirname, name);
        handle = dlopen(path, RTLD_NOW);
        if (!handle || !(dlsym(handle, "filter_event") || dlsym(handle, "filter_event_early")))
                return false;
        desc_fn = dlsym(handle, "filter_description");
        if (desc_fn) {
                const char *dsc;
                const char *long_dsc;

                dsc = desc_fn(&long_dsc);
                if (dsc)
                        *desc = strdup(dsc);
                if (long_dsc)
                        *long_desc = strdup(long_dsc);
        }
        dlclose(handle);
        return true;
}

static void list_filters(const char *dirname)
{
        struct dirent *entry;
        DIR *dir;

        dir = opendir(dirname);
        if (!dir)
                return;

        while ((entry = readdir(dir)) != NULL)
        {
                size_t n = strlen(entry->d_name);
                char *long_desc = NULL;
                char *desc = NULL;

                if (entry->d_type == DT_DIR || n < 4 ||
                    strcmp(".so", entry->d_name + n - 3))
                        continue;
                if (!get_filter_desc(dirname, entry->d_name, &desc, &long_desc))
                        continue;
                printf("  %-36s %s\n", entry->d_name, desc ? desc : "");
                if (verbose > 0) {
                        char *p = long_desc;
                        char *line;

                        while ((line = strsep(&p, "\n")) != NULL)
                                printf("%39s%s\n", "", line);
                }
                free(long_desc);
                free(desc);
        }

        closedir(dir);
}

int list_available_dlfilters(const struct option *opt __maybe_unused,
                             const char *s __maybe_unused,
                             int unset __maybe_unused)
{
        char path[PATH_MAX];
        char *exec_path;

        printf("List of available dlfilters:\n");

        list_filters(".");

        exec_path = get_argv_exec_path();
        if (!exec_path)
                goto out;
        snprintf(path, sizeof(path), "%s/dlfilters", exec_path);

        list_filters(path);

        free(exec_path);
out:
        exit(0);
}