Merge branch 'bpf: Speed up symbol resolving in kprobe multi link'
Jiri Olsa says: ==================== hi, sending additional fix for symbol resolving in kprobe multi link requested by Alexei and Andrii [1]. This speeds up bpftrace kprobe attachment, when using pure symbols (3344 symbols) to attach: Before: # perf stat -r 5 -e cycles ./src/bpftrace -e 'kprobe:x* { } i:ms:1 { exit(); }' ... 6.5681 +- 0.0225 seconds time elapsed ( +- 0.34% ) After: # perf stat -r 5 -e cycles ./src/bpftrace -e 'kprobe:x* { } i:ms:1 { exit(); }' ... 0.5661 +- 0.0275 seconds time elapsed ( +- 4.85% ) v6 changes: - rewrote patch 1 changelog and fixed the line length [Christoph] v5 changes: - added acks [Masami] - workaround in selftest for RCU warning by filtering out several functions to attach v4 changes: - fix compile issue [kernel test robot] - added acks [Andrii] v3 changes: - renamed kallsyms_lookup_names to ftrace_lookup_symbols and moved it to ftrace.c [Masami] - added ack [Andrii] - couple small test fixes [Andrii] v2 changes (first version [2]): - removed the 2 seconds check [Alexei] - moving/forcing symbols sorting out of kallsyms_lookup_names function [Alexei] - skipping one array allocation and copy_from_user [Andrii] - several small fixes [Masami,Andrii] - build fix [kernel test robot] thanks, jirka [1] https://lore.kernel.org/bpf/CAEf4BzZtQaiUxQ-sm_hH2qKPRaqGHyOfEsW96DxtBHRaKLoL3Q@mail.gmail.com/ [2] https://lore.kernel.org/bpf/20220407125224.310255-1-jolsa@kernel.org/ ==================== Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
commit
cb41154530
@ -303,6 +303,8 @@ int unregister_ftrace_function(struct ftrace_ops *ops);
|
||||
extern void ftrace_stub(unsigned long a0, unsigned long a1,
|
||||
struct ftrace_ops *op, struct ftrace_regs *fregs);
|
||||
|
||||
|
||||
int ftrace_lookup_symbols(const char **sorted_syms, size_t cnt, unsigned long *addrs);
|
||||
#else /* !CONFIG_FUNCTION_TRACER */
|
||||
/*
|
||||
* (un)register_ftrace_function must be a macro since the ops parameter
|
||||
@ -313,6 +315,10 @@ extern void ftrace_stub(unsigned long a0, unsigned long a1,
|
||||
static inline void ftrace_kill(void) { }
|
||||
static inline void ftrace_free_init_mem(void) { }
|
||||
static inline void ftrace_free_mem(struct module *mod, void *start, void *end) { }
|
||||
static inline int ftrace_lookup_symbols(const char **sorted_syms, size_t cnt, unsigned long *addrs)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
#endif /* CONFIG_FUNCTION_TRACER */
|
||||
|
||||
struct ftrace_func_entry {
|
||||
|
@ -65,11 +65,11 @@ static inline void *dereference_symbol_descriptor(void *ptr)
|
||||
return ptr;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KALLSYMS
|
||||
int kallsyms_on_each_symbol(int (*fn)(void *, const char *, struct module *,
|
||||
unsigned long),
|
||||
void *data);
|
||||
|
||||
#ifdef CONFIG_KALLSYMS
|
||||
/* Lookup the address for a symbol. Returns 0 if not found. */
|
||||
unsigned long kallsyms_lookup_name(const char *name);
|
||||
|
||||
@ -163,6 +163,11 @@ static inline bool kallsyms_show_value(const struct cred *cred)
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline int kallsyms_on_each_symbol(int (*fn)(void *, const char *, struct module *,
|
||||
unsigned long), void *data)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
#endif /*CONFIG_KALLSYMS*/
|
||||
|
||||
static inline void print_ip_sym(const char *loglvl, unsigned long ip)
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/bsearch.h>
|
||||
|
||||
/*
|
||||
* These will be re-linked against their real values
|
||||
@ -228,7 +229,6 @@ unsigned long kallsyms_lookup_name(const char *name)
|
||||
return module_kallsyms_lookup_name(name);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_LIVEPATCH
|
||||
/*
|
||||
* Iterate over all symbols in vmlinux. For symbols from modules use
|
||||
* module_kallsyms_on_each_symbol instead.
|
||||
@ -251,7 +251,6 @@ int kallsyms_on_each_symbol(int (*fn)(void *, const char *, struct module *,
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_LIVEPATCH */
|
||||
|
||||
static unsigned long get_symbol_pos(unsigned long addr,
|
||||
unsigned long *symbolsize,
|
||||
|
@ -2229,6 +2229,59 @@ struct bpf_kprobe_multi_run_ctx {
|
||||
unsigned long entry_ip;
|
||||
};
|
||||
|
||||
struct user_syms {
|
||||
const char **syms;
|
||||
char *buf;
|
||||
};
|
||||
|
||||
static int copy_user_syms(struct user_syms *us, unsigned long __user *usyms, u32 cnt)
|
||||
{
|
||||
unsigned long __user usymbol;
|
||||
const char **syms = NULL;
|
||||
char *buf = NULL, *p;
|
||||
int err = -ENOMEM;
|
||||
unsigned int i;
|
||||
|
||||
syms = kvmalloc(cnt * sizeof(*syms), GFP_KERNEL);
|
||||
if (!syms)
|
||||
goto error;
|
||||
|
||||
buf = kvmalloc(cnt * KSYM_NAME_LEN, GFP_KERNEL);
|
||||
if (!buf)
|
||||
goto error;
|
||||
|
||||
for (p = buf, i = 0; i < cnt; i++) {
|
||||
if (__get_user(usymbol, usyms + i)) {
|
||||
err = -EFAULT;
|
||||
goto error;
|
||||
}
|
||||
err = strncpy_from_user(p, (const char __user *) usymbol, KSYM_NAME_LEN);
|
||||
if (err == KSYM_NAME_LEN)
|
||||
err = -E2BIG;
|
||||
if (err < 0)
|
||||
goto error;
|
||||
syms[i] = p;
|
||||
p += err + 1;
|
||||
}
|
||||
|
||||
us->syms = syms;
|
||||
us->buf = buf;
|
||||
return 0;
|
||||
|
||||
error:
|
||||
if (err) {
|
||||
kvfree(syms);
|
||||
kvfree(buf);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static void free_user_syms(struct user_syms *us)
|
||||
{
|
||||
kvfree(us->syms);
|
||||
kvfree(us->buf);
|
||||
}
|
||||
|
||||
static void bpf_kprobe_multi_link_release(struct bpf_link *link)
|
||||
{
|
||||
struct bpf_kprobe_multi_link *kmulti_link;
|
||||
@ -2349,53 +2402,12 @@ kprobe_multi_link_handler(struct fprobe *fp, unsigned long entry_ip,
|
||||
kprobe_multi_link_prog_run(link, entry_ip, regs);
|
||||
}
|
||||
|
||||
static int
|
||||
kprobe_multi_resolve_syms(const void __user *usyms, u32 cnt,
|
||||
unsigned long *addrs)
|
||||
static int symbols_cmp(const void *a, const void *b)
|
||||
{
|
||||
unsigned long addr, size;
|
||||
const char __user **syms;
|
||||
int err = -ENOMEM;
|
||||
unsigned int i;
|
||||
char *func;
|
||||
const char **str_a = (const char **) a;
|
||||
const char **str_b = (const char **) b;
|
||||
|
||||
size = cnt * sizeof(*syms);
|
||||
syms = kvzalloc(size, GFP_KERNEL);
|
||||
if (!syms)
|
||||
return -ENOMEM;
|
||||
|
||||
func = kmalloc(KSYM_NAME_LEN, GFP_KERNEL);
|
||||
if (!func)
|
||||
goto error;
|
||||
|
||||
if (copy_from_user(syms, usyms, size)) {
|
||||
err = -EFAULT;
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (i = 0; i < cnt; i++) {
|
||||
err = strncpy_from_user(func, syms[i], KSYM_NAME_LEN);
|
||||
if (err == KSYM_NAME_LEN)
|
||||
err = -E2BIG;
|
||||
if (err < 0)
|
||||
goto error;
|
||||
err = -EINVAL;
|
||||
addr = kallsyms_lookup_name(func);
|
||||
if (!addr)
|
||||
goto error;
|
||||
if (!kallsyms_lookup_size_offset(addr, &size, NULL))
|
||||
goto error;
|
||||
addr = ftrace_location_range(addr, addr + size - 1);
|
||||
if (!addr)
|
||||
goto error;
|
||||
addrs[i] = addr;
|
||||
}
|
||||
|
||||
err = 0;
|
||||
error:
|
||||
kvfree(syms);
|
||||
kfree(func);
|
||||
return err;
|
||||
return strcmp(*str_a, *str_b);
|
||||
}
|
||||
|
||||
int bpf_kprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
|
||||
@ -2441,7 +2453,15 @@ int bpf_kprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *pr
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
err = kprobe_multi_resolve_syms(usyms, cnt, addrs);
|
||||
struct user_syms us;
|
||||
|
||||
err = copy_user_syms(&us, usyms, cnt);
|
||||
if (err)
|
||||
goto error;
|
||||
|
||||
sort(us.syms, cnt, sizeof(*us.syms), symbols_cmp, NULL);
|
||||
err = ftrace_lookup_symbols(us.syms, cnt, addrs);
|
||||
free_user_syms(&us);
|
||||
if (err)
|
||||
goto error;
|
||||
}
|
||||
|
@ -85,39 +85,31 @@ static void fprobe_exit_handler(struct rethook_node *rh, void *data,
|
||||
}
|
||||
NOKPROBE_SYMBOL(fprobe_exit_handler);
|
||||
|
||||
static int symbols_cmp(const void *a, const void *b)
|
||||
{
|
||||
const char **str_a = (const char **) a;
|
||||
const char **str_b = (const char **) b;
|
||||
|
||||
return strcmp(*str_a, *str_b);
|
||||
}
|
||||
|
||||
/* Convert ftrace location address from symbols */
|
||||
static unsigned long *get_ftrace_locations(const char **syms, int num)
|
||||
{
|
||||
unsigned long addr, size;
|
||||
unsigned long *addrs;
|
||||
int i;
|
||||
|
||||
/* Convert symbols to symbol address */
|
||||
addrs = kcalloc(num, sizeof(*addrs), GFP_KERNEL);
|
||||
if (!addrs)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
addr = kallsyms_lookup_name(syms[i]);
|
||||
if (!addr) /* Maybe wrong symbol */
|
||||
goto error;
|
||||
/* ftrace_lookup_symbols expects sorted symbols */
|
||||
sort(syms, num, sizeof(*syms), symbols_cmp, NULL);
|
||||
|
||||
/* Convert symbol address to ftrace location. */
|
||||
if (!kallsyms_lookup_size_offset(addr, &size, NULL) || !size)
|
||||
goto error;
|
||||
if (!ftrace_lookup_symbols(syms, num, addrs))
|
||||
return addrs;
|
||||
|
||||
addr = ftrace_location_range(addr, addr + size - 1);
|
||||
if (!addr) /* No dynamic ftrace there. */
|
||||
goto error;
|
||||
|
||||
addrs[i] = addr;
|
||||
}
|
||||
|
||||
return addrs;
|
||||
|
||||
error:
|
||||
kfree(addrs);
|
||||
|
||||
return ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
|
@ -7964,3 +7964,65 @@ ftrace_enable_sysctl(struct ctl_table *table, int write,
|
||||
mutex_unlock(&ftrace_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int symbols_cmp(const void *a, const void *b)
|
||||
{
|
||||
const char **str_a = (const char **) a;
|
||||
const char **str_b = (const char **) b;
|
||||
|
||||
return strcmp(*str_a, *str_b);
|
||||
}
|
||||
|
||||
struct kallsyms_data {
|
||||
unsigned long *addrs;
|
||||
const char **syms;
|
||||
size_t cnt;
|
||||
size_t found;
|
||||
};
|
||||
|
||||
static int kallsyms_callback(void *data, const char *name,
|
||||
struct module *mod, unsigned long addr)
|
||||
{
|
||||
struct kallsyms_data *args = data;
|
||||
|
||||
if (!bsearch(&name, args->syms, args->cnt, sizeof(*args->syms), symbols_cmp))
|
||||
return 0;
|
||||
|
||||
addr = ftrace_location(addr);
|
||||
if (!addr)
|
||||
return 0;
|
||||
|
||||
args->addrs[args->found++] = addr;
|
||||
return args->found == args->cnt ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ftrace_lookup_symbols - Lookup addresses for array of symbols
|
||||
*
|
||||
* @sorted_syms: array of symbols pointers symbols to resolve,
|
||||
* must be alphabetically sorted
|
||||
* @cnt: number of symbols/addresses in @syms/@addrs arrays
|
||||
* @addrs: array for storing resulting addresses
|
||||
*
|
||||
* This function looks up addresses for array of symbols provided in
|
||||
* @syms array (must be alphabetically sorted) and stores them in
|
||||
* @addrs array, which needs to be big enough to store at least @cnt
|
||||
* addresses.
|
||||
*
|
||||
* This function returns 0 if all provided symbols are found,
|
||||
* -ESRCH otherwise.
|
||||
*/
|
||||
int ftrace_lookup_symbols(const char **sorted_syms, size_t cnt, unsigned long *addrs)
|
||||
{
|
||||
struct kallsyms_data args;
|
||||
int err;
|
||||
|
||||
args.addrs = addrs;
|
||||
args.syms = sorted_syms;
|
||||
args.cnt = cnt;
|
||||
args.found = 0;
|
||||
err = kallsyms_on_each_symbol(kallsyms_callback, &args);
|
||||
if (err < 0)
|
||||
return err;
|
||||
return args.found == args.cnt ? 0 : -ESRCH;
|
||||
}
|
||||
|
@ -2,6 +2,9 @@
|
||||
#include <test_progs.h>
|
||||
#include "kprobe_multi.skel.h"
|
||||
#include "trace_helpers.h"
|
||||
#include "kprobe_multi_empty.skel.h"
|
||||
#include "bpf/libbpf_internal.h"
|
||||
#include "bpf/hashmap.h"
|
||||
|
||||
static void kprobe_multi_test_run(struct kprobe_multi *skel, bool test_return)
|
||||
{
|
||||
@ -301,6 +304,144 @@ cleanup:
|
||||
kprobe_multi__destroy(skel);
|
||||
}
|
||||
|
||||
static inline __u64 get_time_ns(void)
|
||||
{
|
||||
struct timespec t;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
return (__u64) t.tv_sec * 1000000000 + t.tv_nsec;
|
||||
}
|
||||
|
||||
static size_t symbol_hash(const void *key, void *ctx __maybe_unused)
|
||||
{
|
||||
return str_hash((const char *) key);
|
||||
}
|
||||
|
||||
static bool symbol_equal(const void *key1, const void *key2, void *ctx __maybe_unused)
|
||||
{
|
||||
return strcmp((const char *) key1, (const char *) key2) == 0;
|
||||
}
|
||||
|
||||
static int get_syms(char ***symsp, size_t *cntp)
|
||||
{
|
||||
size_t cap = 0, cnt = 0, i;
|
||||
char *name, **syms = NULL;
|
||||
struct hashmap *map;
|
||||
char buf[256];
|
||||
FILE *f;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* The available_filter_functions contains many duplicates,
|
||||
* but other than that all symbols are usable in kprobe multi
|
||||
* interface.
|
||||
* Filtering out duplicates by using hashmap__add, which won't
|
||||
* add existing entry.
|
||||
*/
|
||||
f = fopen("/sys/kernel/debug/tracing/available_filter_functions", "r");
|
||||
if (!f)
|
||||
return -EINVAL;
|
||||
|
||||
map = hashmap__new(symbol_hash, symbol_equal, NULL);
|
||||
if (IS_ERR(map))
|
||||
goto error;
|
||||
|
||||
while (fgets(buf, sizeof(buf), f)) {
|
||||
/* skip modules */
|
||||
if (strchr(buf, '['))
|
||||
continue;
|
||||
if (sscanf(buf, "%ms$*[^\n]\n", &name) != 1)
|
||||
continue;
|
||||
/*
|
||||
* We attach to almost all kernel functions and some of them
|
||||
* will cause 'suspicious RCU usage' when fprobe is attached
|
||||
* to them. Filter out the current culprits - arch_cpu_idle
|
||||
* and rcu_* functions.
|
||||
*/
|
||||
if (!strcmp(name, "arch_cpu_idle"))
|
||||
continue;
|
||||
if (!strncmp(name, "rcu_", 4))
|
||||
continue;
|
||||
err = hashmap__add(map, name, NULL);
|
||||
if (err) {
|
||||
free(name);
|
||||
if (err == -EEXIST)
|
||||
continue;
|
||||
goto error;
|
||||
}
|
||||
err = libbpf_ensure_mem((void **) &syms, &cap,
|
||||
sizeof(*syms), cnt + 1);
|
||||
if (err) {
|
||||
free(name);
|
||||
goto error;
|
||||
}
|
||||
syms[cnt] = name;
|
||||
cnt++;
|
||||
}
|
||||
|
||||
*symsp = syms;
|
||||
*cntp = cnt;
|
||||
|
||||
error:
|
||||
fclose(f);
|
||||
hashmap__free(map);
|
||||
if (err) {
|
||||
for (i = 0; i < cnt; i++)
|
||||
free(syms[cnt]);
|
||||
free(syms);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static void test_bench_attach(void)
|
||||
{
|
||||
LIBBPF_OPTS(bpf_kprobe_multi_opts, opts);
|
||||
struct kprobe_multi_empty *skel = NULL;
|
||||
long attach_start_ns, attach_end_ns;
|
||||
long detach_start_ns, detach_end_ns;
|
||||
double attach_delta, detach_delta;
|
||||
struct bpf_link *link = NULL;
|
||||
char **syms = NULL;
|
||||
size_t cnt, i;
|
||||
|
||||
if (!ASSERT_OK(get_syms(&syms, &cnt), "get_syms"))
|
||||
return;
|
||||
|
||||
skel = kprobe_multi_empty__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "kprobe_multi_empty__open_and_load"))
|
||||
goto cleanup;
|
||||
|
||||
opts.syms = (const char **) syms;
|
||||
opts.cnt = cnt;
|
||||
|
||||
attach_start_ns = get_time_ns();
|
||||
link = bpf_program__attach_kprobe_multi_opts(skel->progs.test_kprobe_empty,
|
||||
NULL, &opts);
|
||||
attach_end_ns = get_time_ns();
|
||||
|
||||
if (!ASSERT_OK_PTR(link, "bpf_program__attach_kprobe_multi_opts"))
|
||||
goto cleanup;
|
||||
|
||||
detach_start_ns = get_time_ns();
|
||||
bpf_link__destroy(link);
|
||||
detach_end_ns = get_time_ns();
|
||||
|
||||
attach_delta = (attach_end_ns - attach_start_ns) / 1000000000.0;
|
||||
detach_delta = (detach_end_ns - detach_start_ns) / 1000000000.0;
|
||||
|
||||
printf("%s: found %lu functions\n", __func__, cnt);
|
||||
printf("%s: attached in %7.3lfs\n", __func__, attach_delta);
|
||||
printf("%s: detached in %7.3lfs\n", __func__, detach_delta);
|
||||
|
||||
cleanup:
|
||||
kprobe_multi_empty__destroy(skel);
|
||||
if (syms) {
|
||||
for (i = 0; i < cnt; i++)
|
||||
free(syms[i]);
|
||||
free(syms);
|
||||
}
|
||||
}
|
||||
|
||||
void test_kprobe_multi_test(void)
|
||||
{
|
||||
if (!ASSERT_OK(load_kallsyms(), "load_kallsyms"))
|
||||
@ -320,4 +461,6 @@ void test_kprobe_multi_test(void)
|
||||
test_attach_api_syms();
|
||||
if (test__start_subtest("attach_api_fails"))
|
||||
test_attach_api_fails();
|
||||
if (test__start_subtest("bench_attach"))
|
||||
test_bench_attach();
|
||||
}
|
||||
|
12
tools/testing/selftests/bpf/progs/kprobe_multi_empty.c
Normal file
12
tools/testing/selftests/bpf/progs/kprobe_multi_empty.c
Normal file
@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
SEC("kprobe.multi/")
|
||||
int test_kprobe_empty(struct pt_regs *ctx)
|
||||
{
|
||||
return 0;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user