Commit d5651838 authored by Alexei Starovoitov's avatar Alexei Starovoitov

Merge branch 'bpf-add-multi-uprobe-link'

Jiri Olsa says:

====================
bpf: Add multi uprobe link

hi,
this patchset is adding support to attach multiple uprobes and usdt probes
through new uprobe_multi link.

The current uprobe is attached through the perf event and attaching many
uprobes takes a lot of time because of that.

The main reason is that we need to install perf event for each probed function
and profile shows perf event installation (perf_install_in_context) as culprit.

The new uprobe_multi link just creates raw uprobes and attaches the bpf
program to them without perf event being involved.

In addition to being faster we also save file descriptors. For the current
uprobe attach we use extra perf event fd for each probed function. The new
link just need one fd that covers all the functions we are attaching to.

v7 changes:
  - fixed task release on error path and re-org the error
    path to be more straightforward [Yonghong]
  - re-organized uprobe_prog_run locking to follow general pattern
    and removed might_fault check as it's not needed in uprobe/task
    context [Yonghong]

There's support for bpftrace [2] and tetragon [1].

Also available at:
  https://git.kernel.org/pub/scm/linux/kernel/git/jolsa/perf.git
  uprobe_multi

thanks,
jirka

[1] https://github.com/cilium/tetragon/pull/936
[2] https://github.com/iovisor/bpftrace/compare/master...olsajiri:bpftrace:uprobe_multi
[3] https://lore.kernel.org/bpf/20230628115329.248450-1-laoar.shao@gmail.com/
---
====================

Link: https://lore.kernel.org/r/20230809083440.3209381-1-jolsa@kernel.orgSigned-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parents acfadf25 8909a939
......@@ -752,6 +752,7 @@ int bpf_get_perf_event_info(const struct perf_event *event, u32 *prog_id,
u32 *fd_type, const char **buf,
u64 *probe_offset, u64 *probe_addr);
int bpf_kprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *prog);
int bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *prog);
#else
static inline unsigned int trace_call_bpf(struct trace_event_call *call, void *ctx)
{
......@@ -798,6 +799,11 @@ bpf_kprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
{
return -EOPNOTSUPP;
}
static inline int
bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
{
return -EOPNOTSUPP;
}
#endif
enum {
......
......@@ -1039,6 +1039,7 @@ enum bpf_attach_type {
BPF_NETFILTER,
BPF_TCX_INGRESS,
BPF_TCX_EGRESS,
BPF_TRACE_UPROBE_MULTI,
__MAX_BPF_ATTACH_TYPE
};
......@@ -1057,6 +1058,7 @@ enum bpf_link_type {
BPF_LINK_TYPE_STRUCT_OPS = 9,
BPF_LINK_TYPE_NETFILTER = 10,
BPF_LINK_TYPE_TCX = 11,
BPF_LINK_TYPE_UPROBE_MULTI = 12,
MAX_BPF_LINK_TYPE,
};
......@@ -1186,7 +1188,16 @@ enum bpf_perf_event_type {
/* link_create.kprobe_multi.flags used in LINK_CREATE command for
* BPF_TRACE_KPROBE_MULTI attach type to create return probe.
*/
#define BPF_F_KPROBE_MULTI_RETURN (1U << 0)
enum {
BPF_F_KPROBE_MULTI_RETURN = (1U << 0)
};
/* link_create.uprobe_multi.flags used in LINK_CREATE command for
* BPF_TRACE_UPROBE_MULTI attach type to create return probe.
*/
enum {
BPF_F_UPROBE_MULTI_RETURN = (1U << 0)
};
/* link_create.netfilter.flags used in LINK_CREATE command for
* BPF_PROG_TYPE_NETFILTER to enable IP packet defragmentation.
......@@ -1624,6 +1635,15 @@ union bpf_attr {
};
__u64 expected_revision;
} tcx;
struct {
__aligned_u64 path;
__aligned_u64 offsets;
__aligned_u64 ref_ctr_offsets;
__aligned_u64 cookies;
__u32 cnt;
__u32 flags;
__u32 pid;
} uprobe_multi;
};
} link_create;
......
......@@ -2815,10 +2815,12 @@ static void bpf_link_free_id(int id)
/* Clean up bpf_link and corresponding anon_inode file and FD. After
* anon_inode is created, bpf_link can't be just kfree()'d due to deferred
* anon_inode's release() call. This helper marksbpf_link as
* anon_inode's release() call. This helper marks bpf_link as
* defunct, releases anon_inode file and puts reserved FD. bpf_prog's refcnt
* is not decremented, it's the responsibility of a calling code that failed
* to complete bpf_link initialization.
* This helper eventually calls link's dealloc callback, but does not call
* link's release callback.
*/
void bpf_link_cleanup(struct bpf_link_primer *primer)
{
......@@ -3655,34 +3657,6 @@ static int bpf_raw_tracepoint_open(const union bpf_attr *attr)
return fd;
}
static int bpf_prog_attach_check_attach_type(const struct bpf_prog *prog,
enum bpf_attach_type attach_type)
{
switch (prog->type) {
case BPF_PROG_TYPE_CGROUP_SOCK:
case BPF_PROG_TYPE_CGROUP_SOCK_ADDR:
case BPF_PROG_TYPE_CGROUP_SOCKOPT:
case BPF_PROG_TYPE_SK_LOOKUP:
return attach_type == prog->expected_attach_type ? 0 : -EINVAL;
case BPF_PROG_TYPE_CGROUP_SKB:
if (!capable(CAP_NET_ADMIN))
/* cg-skb progs can be loaded by unpriv user.
* check permissions at attach time.
*/
return -EPERM;
return prog->enforce_expected_attach_type &&
prog->expected_attach_type != attach_type ?
-EINVAL : 0;
case BPF_PROG_TYPE_KPROBE:
if (prog->expected_attach_type == BPF_TRACE_KPROBE_MULTI &&
attach_type != BPF_TRACE_KPROBE_MULTI)
return -EINVAL;
return 0;
default:
return 0;
}
}
static enum bpf_prog_type
attach_type_to_prog_type(enum bpf_attach_type attach_type)
{
......@@ -3749,6 +3723,62 @@ attach_type_to_prog_type(enum bpf_attach_type attach_type)
}
}
static int bpf_prog_attach_check_attach_type(const struct bpf_prog *prog,
enum bpf_attach_type attach_type)
{
enum bpf_prog_type ptype;
switch (prog->type) {
case BPF_PROG_TYPE_CGROUP_SOCK:
case BPF_PROG_TYPE_CGROUP_SOCK_ADDR:
case BPF_PROG_TYPE_CGROUP_SOCKOPT:
case BPF_PROG_TYPE_SK_LOOKUP:
return attach_type == prog->expected_attach_type ? 0 : -EINVAL;
case BPF_PROG_TYPE_CGROUP_SKB:
if (!capable(CAP_NET_ADMIN))
/* cg-skb progs can be loaded by unpriv user.
* check permissions at attach time.
*/
return -EPERM;
return prog->enforce_expected_attach_type &&
prog->expected_attach_type != attach_type ?
-EINVAL : 0;
case BPF_PROG_TYPE_EXT:
return 0;
case BPF_PROG_TYPE_NETFILTER:
if (attach_type != BPF_NETFILTER)
return -EINVAL;
return 0;
case BPF_PROG_TYPE_PERF_EVENT:
case BPF_PROG_TYPE_TRACEPOINT:
if (attach_type != BPF_PERF_EVENT)
return -EINVAL;
return 0;
case BPF_PROG_TYPE_KPROBE:
if (prog->expected_attach_type == BPF_TRACE_KPROBE_MULTI &&
attach_type != BPF_TRACE_KPROBE_MULTI)
return -EINVAL;
if (prog->expected_attach_type == BPF_TRACE_UPROBE_MULTI &&
attach_type != BPF_TRACE_UPROBE_MULTI)
return -EINVAL;
if (attach_type != BPF_PERF_EVENT &&
attach_type != BPF_TRACE_KPROBE_MULTI &&
attach_type != BPF_TRACE_UPROBE_MULTI)
return -EINVAL;
return 0;
case BPF_PROG_TYPE_SCHED_CLS:
if (attach_type != BPF_TCX_INGRESS &&
attach_type != BPF_TCX_EGRESS)
return -EINVAL;
return 0;
default:
ptype = attach_type_to_prog_type(attach_type);
if (ptype == BPF_PROG_TYPE_UNSPEC || ptype != prog->type)
return -EINVAL;
return 0;
}
}
#define BPF_PROG_ATTACH_LAST_FIELD expected_revision
#define BPF_F_ATTACH_MASK_BASE \
......@@ -4852,10 +4882,9 @@ static int bpf_map_do_batch(const union bpf_attr *attr,
return err;
}
#define BPF_LINK_CREATE_LAST_FIELD link_create.kprobe_multi.cookies
#define BPF_LINK_CREATE_LAST_FIELD link_create.uprobe_multi.pid
static int link_create(union bpf_attr *attr, bpfptr_t uattr)
{
enum bpf_prog_type ptype;
struct bpf_prog *prog;
int ret;
......@@ -4874,45 +4903,6 @@ static int link_create(union bpf_attr *attr, bpfptr_t uattr)
if (ret)
goto out;
switch (prog->type) {
case BPF_PROG_TYPE_EXT:
break;
case BPF_PROG_TYPE_NETFILTER:
if (attr->link_create.attach_type != BPF_NETFILTER) {
ret = -EINVAL;
goto out;
}
break;
case BPF_PROG_TYPE_PERF_EVENT:
case BPF_PROG_TYPE_TRACEPOINT:
if (attr->link_create.attach_type != BPF_PERF_EVENT) {
ret = -EINVAL;
goto out;
}
break;
case BPF_PROG_TYPE_KPROBE:
if (attr->link_create.attach_type != BPF_PERF_EVENT &&
attr->link_create.attach_type != BPF_TRACE_KPROBE_MULTI) {
ret = -EINVAL;
goto out;
}
break;
case BPF_PROG_TYPE_SCHED_CLS:
if (attr->link_create.attach_type != BPF_TCX_INGRESS &&
attr->link_create.attach_type != BPF_TCX_EGRESS) {
ret = -EINVAL;
goto out;
}
break;
default:
ptype = attach_type_to_prog_type(attr->link_create.attach_type);
if (ptype == BPF_PROG_TYPE_UNSPEC || ptype != prog->type) {
ret = -EINVAL;
goto out;
}
break;
}
switch (prog->type) {
case BPF_PROG_TYPE_CGROUP_SKB:
case BPF_PROG_TYPE_CGROUP_SOCK:
......@@ -4969,8 +4959,10 @@ static int link_create(union bpf_attr *attr, bpfptr_t uattr)
case BPF_PROG_TYPE_KPROBE:
if (attr->link_create.attach_type == BPF_PERF_EVENT)
ret = bpf_perf_link_attach(attr, prog);
else
else if (attr->link_create.attach_type == BPF_TRACE_KPROBE_MULTI)
ret = bpf_kprobe_multi_link_attach(attr, prog);
else if (attr->link_create.attach_type == BPF_TRACE_UPROBE_MULTI)
ret = bpf_uprobe_multi_link_attach(attr, prog);
break;
default:
ret = -EINVAL;
......
......@@ -23,6 +23,7 @@
#include <linux/sort.h>
#include <linux/key.h>
#include <linux/verification.h>
#include <linux/namei.h>
#include <net/bpf_sk_storage.h>
......@@ -86,6 +87,9 @@ static int bpf_btf_printf_prepare(struct btf_ptr *ptr, u32 btf_ptr_size,
static u64 bpf_kprobe_multi_cookie(struct bpf_run_ctx *ctx);
static u64 bpf_kprobe_multi_entry_ip(struct bpf_run_ctx *ctx);
static u64 bpf_uprobe_multi_cookie(struct bpf_run_ctx *ctx);
static u64 bpf_uprobe_multi_entry_ip(struct bpf_run_ctx *ctx);
/**
* trace_call_bpf - invoke BPF program
* @call: tracepoint event
......@@ -1103,6 +1107,30 @@ static const struct bpf_func_proto bpf_get_attach_cookie_proto_kmulti = {
.arg1_type = ARG_PTR_TO_CTX,
};
BPF_CALL_1(bpf_get_func_ip_uprobe_multi, struct pt_regs *, regs)
{
return bpf_uprobe_multi_entry_ip(current->bpf_ctx);
}
static const struct bpf_func_proto bpf_get_func_ip_proto_uprobe_multi = {
.func = bpf_get_func_ip_uprobe_multi,
.gpl_only = false,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
};
BPF_CALL_1(bpf_get_attach_cookie_uprobe_multi, struct pt_regs *, regs)
{
return bpf_uprobe_multi_cookie(current->bpf_ctx);
}
static const struct bpf_func_proto bpf_get_attach_cookie_proto_umulti = {
.func = bpf_get_attach_cookie_uprobe_multi,
.gpl_only = false,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
};
BPF_CALL_1(bpf_get_attach_cookie_trace, void *, ctx)
{
struct bpf_trace_run_ctx *run_ctx;
......@@ -1545,13 +1573,17 @@ kprobe_prog_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
return &bpf_override_return_proto;
#endif
case BPF_FUNC_get_func_ip:
return prog->expected_attach_type == BPF_TRACE_KPROBE_MULTI ?
&bpf_get_func_ip_proto_kprobe_multi :
&bpf_get_func_ip_proto_kprobe;
if (prog->expected_attach_type == BPF_TRACE_KPROBE_MULTI)
return &bpf_get_func_ip_proto_kprobe_multi;
if (prog->expected_attach_type == BPF_TRACE_UPROBE_MULTI)
return &bpf_get_func_ip_proto_uprobe_multi;
return &bpf_get_func_ip_proto_kprobe;
case BPF_FUNC_get_attach_cookie:
return prog->expected_attach_type == BPF_TRACE_KPROBE_MULTI ?
&bpf_get_attach_cookie_proto_kmulti :
&bpf_get_attach_cookie_proto_trace;
if (prog->expected_attach_type == BPF_TRACE_KPROBE_MULTI)
return &bpf_get_attach_cookie_proto_kmulti;
if (prog->expected_attach_type == BPF_TRACE_UPROBE_MULTI)
return &bpf_get_attach_cookie_proto_umulti;
return &bpf_get_attach_cookie_proto_trace;
default:
return bpf_tracing_func_proto(func_id, prog);
}
......@@ -2970,3 +3002,301 @@ static u64 bpf_kprobe_multi_entry_ip(struct bpf_run_ctx *ctx)
return 0;
}
#endif
#ifdef CONFIG_UPROBES
struct bpf_uprobe_multi_link;
struct bpf_uprobe {
struct bpf_uprobe_multi_link *link;
loff_t offset;
u64 cookie;
struct uprobe_consumer consumer;
};
struct bpf_uprobe_multi_link {
struct path path;
struct bpf_link link;
u32 cnt;
struct bpf_uprobe *uprobes;
struct task_struct *task;
};
struct bpf_uprobe_multi_run_ctx {
struct bpf_run_ctx run_ctx;
unsigned long entry_ip;
struct bpf_uprobe *uprobe;
};
static void bpf_uprobe_unregister(struct path *path, struct bpf_uprobe *uprobes,
u32 cnt)
{
u32 i;
for (i = 0; i < cnt; i++) {
uprobe_unregister(d_real_inode(path->dentry), uprobes[i].offset,
&uprobes[i].consumer);
}
}
static void bpf_uprobe_multi_link_release(struct bpf_link *link)
{
struct bpf_uprobe_multi_link *umulti_link;
umulti_link = container_of(link, struct bpf_uprobe_multi_link, link);
bpf_uprobe_unregister(&umulti_link->path, umulti_link->uprobes, umulti_link->cnt);
}
static void bpf_uprobe_multi_link_dealloc(struct bpf_link *link)
{
struct bpf_uprobe_multi_link *umulti_link;
umulti_link = container_of(link, struct bpf_uprobe_multi_link, link);
if (umulti_link->task)
put_task_struct(umulti_link->task);
path_put(&umulti_link->path);
kvfree(umulti_link->uprobes);
kfree(umulti_link);
}
static const struct bpf_link_ops bpf_uprobe_multi_link_lops = {
.release = bpf_uprobe_multi_link_release,
.dealloc = bpf_uprobe_multi_link_dealloc,
};
static int uprobe_prog_run(struct bpf_uprobe *uprobe,
unsigned long entry_ip,
struct pt_regs *regs)
{
struct bpf_uprobe_multi_link *link = uprobe->link;
struct bpf_uprobe_multi_run_ctx run_ctx = {
.entry_ip = entry_ip,
.uprobe = uprobe,
};
struct bpf_prog *prog = link->link.prog;
bool sleepable = prog->aux->sleepable;
struct bpf_run_ctx *old_run_ctx;
int err = 0;
if (link->task && current != link->task)
return 0;
if (sleepable)
rcu_read_lock_trace();
else
rcu_read_lock();
migrate_disable();
old_run_ctx = bpf_set_run_ctx(&run_ctx.run_ctx);
err = bpf_prog_run(link->link.prog, regs);
bpf_reset_run_ctx(old_run_ctx);
migrate_enable();
if (sleepable)
rcu_read_unlock_trace();
else
rcu_read_unlock();
return err;
}
static bool
uprobe_multi_link_filter(struct uprobe_consumer *con, enum uprobe_filter_ctx ctx,
struct mm_struct *mm)
{
struct bpf_uprobe *uprobe;
uprobe = container_of(con, struct bpf_uprobe, consumer);
return uprobe->link->task->mm == mm;
}
static int
uprobe_multi_link_handler(struct uprobe_consumer *con, struct pt_regs *regs)
{
struct bpf_uprobe *uprobe;
uprobe = container_of(con, struct bpf_uprobe, consumer);
return uprobe_prog_run(uprobe, instruction_pointer(regs), regs);
}
static int
uprobe_multi_link_ret_handler(struct uprobe_consumer *con, unsigned long func, struct pt_regs *regs)
{
struct bpf_uprobe *uprobe;
uprobe = container_of(con, struct bpf_uprobe, consumer);
return uprobe_prog_run(uprobe, func, regs);
}
static u64 bpf_uprobe_multi_entry_ip(struct bpf_run_ctx *ctx)
{
struct bpf_uprobe_multi_run_ctx *run_ctx;
run_ctx = container_of(current->bpf_ctx, struct bpf_uprobe_multi_run_ctx, run_ctx);
return run_ctx->entry_ip;
}
static u64 bpf_uprobe_multi_cookie(struct bpf_run_ctx *ctx)
{
struct bpf_uprobe_multi_run_ctx *run_ctx;
run_ctx = container_of(current->bpf_ctx, struct bpf_uprobe_multi_run_ctx, run_ctx);
return run_ctx->uprobe->cookie;
}
int bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
{
struct bpf_uprobe_multi_link *link = NULL;
unsigned long __user *uref_ctr_offsets;
unsigned long *ref_ctr_offsets = NULL;
struct bpf_link_primer link_primer;
struct bpf_uprobe *uprobes = NULL;
struct task_struct *task = NULL;
unsigned long __user *uoffsets;
u64 __user *ucookies;
void __user *upath;
u32 flags, cnt, i;
struct path path;
char *name;
pid_t pid;
int err;
/* no support for 32bit archs yet */
if (sizeof(u64) != sizeof(void *))
return -EOPNOTSUPP;
if (prog->expected_attach_type != BPF_TRACE_UPROBE_MULTI)
return -EINVAL;
flags = attr->link_create.uprobe_multi.flags;
if (flags & ~BPF_F_UPROBE_MULTI_RETURN)
return -EINVAL;
/*
* path, offsets and cnt are mandatory,
* ref_ctr_offsets and cookies are optional
*/
upath = u64_to_user_ptr(attr->link_create.uprobe_multi.path);
uoffsets = u64_to_user_ptr(attr->link_create.uprobe_multi.offsets);
cnt = attr->link_create.uprobe_multi.cnt;
if (!upath || !uoffsets || !cnt)
return -EINVAL;
uref_ctr_offsets = u64_to_user_ptr(attr->link_create.uprobe_multi.ref_ctr_offsets);
ucookies = u64_to_user_ptr(attr->link_create.uprobe_multi.cookies);
name = strndup_user(upath, PATH_MAX);
if (IS_ERR(name)) {
err = PTR_ERR(name);
return err;
}
err = kern_path(name, LOOKUP_FOLLOW, &path);
kfree(name);
if (err)
return err;
if (!d_is_reg(path.dentry)) {
err = -EBADF;
goto error_path_put;
}
pid = attr->link_create.uprobe_multi.pid;
if (pid) {
rcu_read_lock();
task = get_pid_task(find_vpid(pid), PIDTYPE_PID);
rcu_read_unlock();
if (!task)
goto error_path_put;
}
err = -ENOMEM;
link = kzalloc(sizeof(*link), GFP_KERNEL);
uprobes = kvcalloc(cnt, sizeof(*uprobes), GFP_KERNEL);
if (!uprobes || !link)
goto error_free;
if (uref_ctr_offsets) {
ref_ctr_offsets = kvcalloc(cnt, sizeof(*ref_ctr_offsets), GFP_KERNEL);
if (!ref_ctr_offsets)
goto error_free;
}
for (i = 0; i < cnt; i++) {
if (ucookies && __get_user(uprobes[i].cookie, ucookies + i)) {
err = -EFAULT;
goto error_free;
}
if (uref_ctr_offsets && __get_user(ref_ctr_offsets[i], uref_ctr_offsets + i)) {
err = -EFAULT;
goto error_free;
}
if (__get_user(uprobes[i].offset, uoffsets + i)) {
err = -EFAULT;
goto error_free;
}
uprobes[i].link = link;
if (flags & BPF_F_UPROBE_MULTI_RETURN)
uprobes[i].consumer.ret_handler = uprobe_multi_link_ret_handler;
else
uprobes[i].consumer.handler = uprobe_multi_link_handler;
if (pid)
uprobes[i].consumer.filter = uprobe_multi_link_filter;
}
link->cnt = cnt;
link->uprobes = uprobes;
link->path = path;
link->task = task;
bpf_link_init(&link->link, BPF_LINK_TYPE_UPROBE_MULTI,
&bpf_uprobe_multi_link_lops, prog);
for (i = 0; i < cnt; i++) {
err = uprobe_register_refctr(d_real_inode(link->path.dentry),
uprobes[i].offset,
ref_ctr_offsets ? ref_ctr_offsets[i] : 0,
&uprobes[i].consumer);
if (err) {
bpf_uprobe_unregister(&path, uprobes, i);
goto error_free;
}
}
err = bpf_link_prime(&link->link, &link_primer);
if (err)
goto error_free;
kvfree(ref_ctr_offsets);
return bpf_link_settle(&link_primer);
error_free:
kvfree(ref_ctr_offsets);
kvfree(uprobes);
kfree(link);
if (task)
put_task_struct(task);
error_path_put:
path_put(&path);
return err;
}
#else /* !CONFIG_UPROBES */
int bpf_uprobe_multi_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
{
return -EOPNOTSUPP;
}
static u64 bpf_uprobe_multi_cookie(struct bpf_run_ctx *ctx)
{
return 0;
}
static u64 bpf_uprobe_multi_entry_ip(struct bpf_run_ctx *ctx)
{
return 0;
}
#endif /* CONFIG_UPROBES */
......@@ -1039,6 +1039,7 @@ enum bpf_attach_type {
BPF_NETFILTER,
BPF_TCX_INGRESS,
BPF_TCX_EGRESS,
BPF_TRACE_UPROBE_MULTI,
__MAX_BPF_ATTACH_TYPE
};
......@@ -1057,6 +1058,7 @@ enum bpf_link_type {
BPF_LINK_TYPE_STRUCT_OPS = 9,
BPF_LINK_TYPE_NETFILTER = 10,
BPF_LINK_TYPE_TCX = 11,
BPF_LINK_TYPE_UPROBE_MULTI = 12,
MAX_BPF_LINK_TYPE,
};
......@@ -1186,7 +1188,16 @@ enum bpf_perf_event_type {
/* link_create.kprobe_multi.flags used in LINK_CREATE command for
* BPF_TRACE_KPROBE_MULTI attach type to create return probe.
*/
#define BPF_F_KPROBE_MULTI_RETURN (1U << 0)
enum {
BPF_F_KPROBE_MULTI_RETURN = (1U << 0)
};
/* link_create.uprobe_multi.flags used in LINK_CREATE command for
* BPF_TRACE_UPROBE_MULTI attach type to create return probe.
*/
enum {
BPF_F_UPROBE_MULTI_RETURN = (1U << 0)
};
/* link_create.netfilter.flags used in LINK_CREATE command for
* BPF_PROG_TYPE_NETFILTER to enable IP packet defragmentation.
......@@ -1624,6 +1635,15 @@ union bpf_attr {
};
__u64 expected_revision;
} tcx;
struct {
__aligned_u64 path;
__aligned_u64 offsets;
__aligned_u64 ref_ctr_offsets;
__aligned_u64 cookies;
__u32 cnt;
__u32 flags;
__u32 pid;
} uprobe_multi;
};
} link_create;
......
libbpf-y := libbpf.o bpf.o nlattr.o btf.o libbpf_errno.o str_error.o \
netlink.o bpf_prog_linfo.o libbpf_probes.o hashmap.o \
btf_dump.o ringbuf.o strset.o linker.o gen_loader.o relo_core.o \
usdt.o zip.o
usdt.o zip.o elf.o
......@@ -767,6 +767,17 @@ int bpf_link_create(int prog_fd, int target_fd,
if (!OPTS_ZEROED(opts, kprobe_multi))
return libbpf_err(-EINVAL);
break;
case BPF_TRACE_UPROBE_MULTI:
attr.link_create.uprobe_multi.flags = OPTS_GET(opts, uprobe_multi.flags, 0);
attr.link_create.uprobe_multi.cnt = OPTS_GET(opts, uprobe_multi.cnt, 0);
attr.link_create.uprobe_multi.path = ptr_to_u64(OPTS_GET(opts, uprobe_multi.path, 0));
attr.link_create.uprobe_multi.offsets = ptr_to_u64(OPTS_GET(opts, uprobe_multi.offsets, 0));
attr.link_create.uprobe_multi.ref_ctr_offsets = ptr_to_u64(OPTS_GET(opts, uprobe_multi.ref_ctr_offsets, 0));
attr.link_create.uprobe_multi.cookies = ptr_to_u64(OPTS_GET(opts, uprobe_multi.cookies, 0));
attr.link_create.uprobe_multi.pid = OPTS_GET(opts, uprobe_multi.pid, 0);
if (!OPTS_ZEROED(opts, uprobe_multi))
return libbpf_err(-EINVAL);
break;
case BPF_TRACE_FENTRY:
case BPF_TRACE_FEXIT:
case BPF_MODIFY_RETURN:
......
......@@ -392,6 +392,15 @@ struct bpf_link_create_opts {
const unsigned long *addrs;
const __u64 *cookies;
} kprobe_multi;
struct {
__u32 flags;
__u32 cnt;
const char *path;
const unsigned long *offsets;
const unsigned long *ref_ctr_offsets;
const __u64 *cookies;
__u32 pid;
} uprobe_multi;
struct {
__u64 cookie;
} tracing;
......@@ -409,7 +418,7 @@ struct bpf_link_create_opts {
};
size_t :0;
};
#define bpf_link_create_opts__last_field kprobe_multi.cookies
#define bpf_link_create_opts__last_field uprobe_multi.pid
LIBBPF_API int bpf_link_create(int prog_fd, int target_fd,
enum bpf_attach_type attach_type,
......
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
#include <libelf.h>
#include <gelf.h>
#include <fcntl.h>
#include <linux/kernel.h>
#include "libbpf_internal.h"
#include "str_error.h"
#define STRERR_BUFSIZE 128
int elf_open(const char *binary_path, struct elf_fd *elf_fd)
{
char errmsg[STRERR_BUFSIZE];
int fd, ret;
Elf *elf;
if (elf_version(EV_CURRENT) == EV_NONE) {
pr_warn("elf: failed to init libelf for %s\n", binary_path);
return -LIBBPF_ERRNO__LIBELF;
}
fd = open(binary_path, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
ret = -errno;
pr_warn("elf: failed to open %s: %s\n", binary_path,
libbpf_strerror_r(ret, errmsg, sizeof(errmsg)));
return ret;
}
elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
if (!elf) {
pr_warn("elf: could not read elf from %s: %s\n", binary_path, elf_errmsg(-1));
close(fd);
return -LIBBPF_ERRNO__FORMAT;
}
elf_fd->fd = fd;
elf_fd->elf = elf;
return 0;
}
void elf_close(struct elf_fd *elf_fd)
{
if (!elf_fd)
return;
elf_end(elf_fd->elf);
close(elf_fd->fd);
}
/* Return next ELF section of sh_type after scn, or first of that type if scn is NULL. */
static Elf_Scn *elf_find_next_scn_by_type(Elf *elf, int sh_type, Elf_Scn *scn)
{
while ((scn = elf_nextscn(elf, scn)) != NULL) {
GElf_Shdr sh;
if (!gelf_getshdr(scn, &sh))
continue;
if (sh.sh_type == sh_type)
return scn;
}
return NULL;
}
struct elf_sym {
const char *name;
GElf_Sym sym;
GElf_Shdr sh;
};
struct elf_sym_iter {
Elf *elf;
Elf_Data *syms;
size_t nr_syms;
size_t strtabidx;
size_t next_sym_idx;
struct elf_sym sym;
int st_type;
};
static int elf_sym_iter_new(struct elf_sym_iter *iter,
Elf *elf, const char *binary_path,
int sh_type, int st_type)
{
Elf_Scn *scn = NULL;
GElf_Ehdr ehdr;
GElf_Shdr sh;
memset(iter, 0, sizeof(*iter));
if (!gelf_getehdr(elf, &ehdr)) {
pr_warn("elf: failed to get ehdr from %s: %s\n", binary_path, elf_errmsg(-1));
return -EINVAL;
}
scn = elf_find_next_scn_by_type(elf, sh_type, NULL);
if (!scn) {
pr_debug("elf: failed to find symbol table ELF sections in '%s'\n",
binary_path);
return -ENOENT;
}
if (!gelf_getshdr(scn, &sh))
return -EINVAL;
iter->strtabidx = sh.sh_link;
iter->syms = elf_getdata(scn, 0);
if (!iter->syms) {
pr_warn("elf: failed to get symbols for symtab section in '%s': %s\n",
binary_path, elf_errmsg(-1));
return -EINVAL;
}
iter->nr_syms = iter->syms->d_size / sh.sh_entsize;
iter->elf = elf;
iter->st_type = st_type;
return 0;
}
static struct elf_sym *elf_sym_iter_next(struct elf_sym_iter *iter)
{
struct elf_sym *ret = &iter->sym;
GElf_Sym *sym = &ret->sym;
const char *name = NULL;
Elf_Scn *sym_scn;
size_t idx;
for (idx = iter->next_sym_idx; idx < iter->nr_syms; idx++) {
if (!gelf_getsym(iter->syms, idx, sym))
continue;
if (GELF_ST_TYPE(sym->st_info) != iter->st_type)
continue;
name = elf_strptr(iter->elf, iter->strtabidx, sym->st_name);
if (!name)
continue;
sym_scn = elf_getscn(iter->elf, sym->st_shndx);
if (!sym_scn)
continue;
if (!gelf_getshdr(sym_scn, &ret->sh))
continue;
iter->next_sym_idx = idx + 1;
ret->name = name;
return ret;
}
return NULL;
}
/* Transform symbol's virtual address (absolute for binaries and relative
* for shared libs) into file offset, which is what kernel is expecting
* for uprobe/uretprobe attachment.
* See Documentation/trace/uprobetracer.rst for more details. This is done
* by looking up symbol's containing section's header and using iter's virtual
* address (sh_addr) and corresponding file offset (sh_offset) to transform
* sym.st_value (virtual address) into desired final file offset.
*/
static unsigned long elf_sym_offset(struct elf_sym *sym)
{
return sym->sym.st_value - sym->sh.sh_addr + sym->sh.sh_offset;
}
/* Find offset of function name in the provided ELF object. "binary_path" is
* the path to the ELF binary represented by "elf", and only used for error
* reporting matters. "name" matches symbol name or name@@LIB for library
* functions.
*/
long elf_find_func_offset(Elf *elf, const char *binary_path, const char *name)
{
int i, sh_types[2] = { SHT_DYNSYM, SHT_SYMTAB };
bool is_shared_lib, is_name_qualified;
long ret = -ENOENT;
size_t name_len;
GElf_Ehdr ehdr;
if (!gelf_getehdr(elf, &ehdr)) {
pr_warn("elf: failed to get ehdr from %s: %s\n", binary_path, elf_errmsg(-1));
ret = -LIBBPF_ERRNO__FORMAT;
goto out;
}
/* for shared lib case, we do not need to calculate relative offset */
is_shared_lib = ehdr.e_type == ET_DYN;
name_len = strlen(name);
/* Does name specify "@@LIB"? */
is_name_qualified = strstr(name, "@@") != NULL;
/* Search SHT_DYNSYM, SHT_SYMTAB for symbol. This search order is used because if
* a binary is stripped, it may only have SHT_DYNSYM, and a fully-statically
* linked binary may not have SHT_DYMSYM, so absence of a section should not be
* reported as a warning/error.
*/
for (i = 0; i < ARRAY_SIZE(sh_types); i++) {
struct elf_sym_iter iter;
struct elf_sym *sym;
int last_bind = -1;
int cur_bind;
ret = elf_sym_iter_new(&iter, elf, binary_path, sh_types[i], STT_FUNC);
if (ret == -ENOENT)
continue;
if (ret)
goto out;
while ((sym = elf_sym_iter_next(&iter))) {
/* User can specify func, func@@LIB or func@@LIB_VERSION. */
if (strncmp(sym->name, name, name_len) != 0)
continue;
/* ...but we don't want a search for "foo" to match 'foo2" also, so any
* additional characters in sname should be of the form "@@LIB".
*/
if (!is_name_qualified && sym->name[name_len] != '\0' && sym->name[name_len] != '@')
continue;
cur_bind = GELF_ST_BIND(sym->sym.st_info);
if (ret > 0) {
/* handle multiple matches */
if (last_bind != STB_WEAK && cur_bind != STB_WEAK) {
/* Only accept one non-weak bind. */
pr_warn("elf: ambiguous match for '%s', '%s' in '%s'\n",
sym->name, name, binary_path);
ret = -LIBBPF_ERRNO__FORMAT;
goto out;
} else if (cur_bind == STB_WEAK) {
/* already have a non-weak bind, and
* this is a weak bind, so ignore.
*/
continue;
}
}
ret = elf_sym_offset(sym);
last_bind = cur_bind;
}
if (ret > 0)
break;
}
if (ret > 0) {
pr_debug("elf: symbol address match for '%s' in '%s': 0x%lx\n", name, binary_path,
ret);
} else {
if (ret == 0) {
pr_warn("elf: '%s' is 0 in symtab for '%s': %s\n", name, binary_path,
is_shared_lib ? "should not be 0 in a shared library" :
"try using shared library path instead");
ret = -ENOENT;
} else {
pr_warn("elf: failed to find symbol '%s' in '%s'\n", name, binary_path);
}
}
out:
return ret;
}
/* Find offset of function name in ELF object specified by path. "name" matches
* symbol name or name@@LIB for library functions.
*/
long elf_find_func_offset_from_file(const char *binary_path, const char *name)
{
struct elf_fd elf_fd;
long ret = -ENOENT;
ret = elf_open(binary_path, &elf_fd);
if (ret)
return ret;
ret = elf_find_func_offset(elf_fd.elf, binary_path, name);
elf_close(&elf_fd);
return ret;
}
struct symbol {
const char *name;
int bind;
int idx;
};
static int symbol_cmp(const void *a, const void *b)
{
const struct symbol *sym_a = a;
const struct symbol *sym_b = b;
return strcmp(sym_a->name, sym_b->name);
}
/*
* Return offsets in @poffsets for symbols specified in @syms array argument.
* On success returns 0 and offsets are returned in allocated array with @cnt
* size, that needs to be released by the caller.
*/
int elf_resolve_syms_offsets(const char *binary_path, int cnt,
const char **syms, unsigned long **poffsets)
{
int sh_types[2] = { SHT_DYNSYM, SHT_SYMTAB };
int err = 0, i, cnt_done = 0;
unsigned long *offsets;
struct symbol *symbols;
struct elf_fd elf_fd;
err = elf_open(binary_path, &elf_fd);
if (err)
return err;
offsets = calloc(cnt, sizeof(*offsets));
symbols = calloc(cnt, sizeof(*symbols));
if (!offsets || !symbols) {
err = -ENOMEM;
goto out;
}
for (i = 0; i < cnt; i++) {
symbols[i].name = syms[i];
symbols[i].idx = i;
}
qsort(symbols, cnt, sizeof(*symbols), symbol_cmp);
for (i = 0; i < ARRAY_SIZE(sh_types); i++) {
struct elf_sym_iter iter;
struct elf_sym *sym;
err = elf_sym_iter_new(&iter, elf_fd.elf, binary_path, sh_types[i], STT_FUNC);
if (err == -ENOENT)
continue;
if (err)
goto out;
while ((sym = elf_sym_iter_next(&iter))) {
unsigned long sym_offset = elf_sym_offset(sym);
int bind = GELF_ST_BIND(sym->sym.st_info);
struct symbol *found, tmp = {
.name = sym->name,
};
unsigned long *offset;
found = bsearch(&tmp, symbols, cnt, sizeof(*symbols), symbol_cmp);
if (!found)
continue;
offset = &offsets[found->idx];
if (*offset > 0) {
/* same offset, no problem */
if (*offset == sym_offset)
continue;
/* handle multiple matches */
if (found->bind != STB_WEAK && bind != STB_WEAK) {
/* Only accept one non-weak bind. */
pr_warn("elf: ambiguous match found '%s@%lu' in '%s' previous offset %lu\n",
sym->name, sym_offset, binary_path, *offset);
err = -ESRCH;
goto out;
} else if (bind == STB_WEAK) {
/* already have a non-weak bind, and
* this is a weak bind, so ignore.
*/
continue;
}
} else {
cnt_done++;
}
*offset = sym_offset;
found->bind = bind;
}
}
if (cnt != cnt_done) {
err = -ENOENT;
goto out;
}
*poffsets = offsets;
out:
free(symbols);
if (err)
free(offsets);
elf_close(&elf_fd);
return err;
}
/*
* Return offsets in @poffsets for symbols specified by @pattern argument.
* On success returns 0 and offsets are returned in allocated @poffsets
* array with the @pctn size, that needs to be released by the caller.
*/
int elf_resolve_pattern_offsets(const char *binary_path, const char *pattern,
unsigned long **poffsets, size_t *pcnt)
{
int sh_types[2] = { SHT_SYMTAB, SHT_DYNSYM };
unsigned long *offsets = NULL;
size_t cap = 0, cnt = 0;
struct elf_fd elf_fd;
int err = 0, i;
err = elf_open(binary_path, &elf_fd);
if (err)
return err;
for (i = 0; i < ARRAY_SIZE(sh_types); i++) {
struct elf_sym_iter iter;
struct elf_sym *sym;
err = elf_sym_iter_new(&iter, elf_fd.elf, binary_path, sh_types[i], STT_FUNC);
if (err == -ENOENT)
continue;
if (err)
goto out;
while ((sym = elf_sym_iter_next(&iter))) {
if (!glob_match(sym->name, pattern))
continue;
err = libbpf_ensure_mem((void **) &offsets, &cap, sizeof(*offsets),
cnt + 1);
if (err)
goto out;
offsets[cnt++] = elf_sym_offset(sym);
}
/* If we found anything in the first symbol section,
* do not search others to avoid duplicates.
*/
if (cnt)
break;
}
if (cnt) {
*poffsets = offsets;
*pcnt = cnt;
} else {
err = -ENOENT;
}
out:
if (err)
free(offsets);
elf_close(&elf_fd);
return err;
}
......@@ -120,6 +120,7 @@ static const char * const attach_type_name[] = {
[BPF_NETFILTER] = "netfilter",
[BPF_TCX_INGRESS] = "tcx_ingress",
[BPF_TCX_EGRESS] = "tcx_egress",
[BPF_TRACE_UPROBE_MULTI] = "trace_uprobe_multi",
};
static const char * const link_type_name[] = {
......@@ -135,6 +136,7 @@ static const char * const link_type_name[] = {
[BPF_LINK_TYPE_STRUCT_OPS] = "struct_ops",
[BPF_LINK_TYPE_NETFILTER] = "netfilter",
[BPF_LINK_TYPE_TCX] = "tcx",
[BPF_LINK_TYPE_UPROBE_MULTI] = "uprobe_multi",
};
static const char * const map_type_name[] = {
......@@ -365,6 +367,8 @@ enum sec_def_flags {
SEC_SLEEPABLE = 8,
/* BPF program support non-linear XDP buffer */
SEC_XDP_FRAGS = 16,
/* Setup proper attach type for usdt probes. */
SEC_USDT = 32,
};
struct bpf_sec_def {
......@@ -4827,6 +4831,39 @@ static int probe_perf_link(void)
return link_fd < 0 && err == -EBADF;
}
static int probe_uprobe_multi_link(void)
{
LIBBPF_OPTS(bpf_prog_load_opts, load_opts,
.expected_attach_type = BPF_TRACE_UPROBE_MULTI,
);
LIBBPF_OPTS(bpf_link_create_opts, link_opts);
struct bpf_insn insns[] = {
BPF_MOV64_IMM(BPF_REG_0, 0),
BPF_EXIT_INSN(),
};
int prog_fd, link_fd, err;
unsigned long offset = 0;
prog_fd = bpf_prog_load(BPF_PROG_TYPE_KPROBE, NULL, "GPL",
insns, ARRAY_SIZE(insns), &load_opts);
if (prog_fd < 0)
return -errno;
/* Creating uprobe in '/' binary should fail with -EBADF. */
link_opts.uprobe_multi.path = "/";
link_opts.uprobe_multi.offsets = &offset;
link_opts.uprobe_multi.cnt = 1;
link_fd = bpf_link_create(prog_fd, -1, BPF_TRACE_UPROBE_MULTI, &link_opts);
err = -errno; /* close() can clobber errno */
if (link_fd >= 0)
close(link_fd);
close(prog_fd);
return link_fd < 0 && err == -EBADF;
}
static int probe_kern_bpf_cookie(void)
{
struct bpf_insn insns[] = {
......@@ -4923,6 +4960,9 @@ static struct kern_feature_desc {
[FEAT_SYSCALL_WRAPPER] = {
"Kernel using syscall wrapper", probe_kern_syscall_wrapper,
},
[FEAT_UPROBE_MULTI_LINK] = {
"BPF multi-uprobe link support", probe_uprobe_multi_link,
},
};
bool kernel_supports(const struct bpf_object *obj, enum kern_feature_id feat_id)
......@@ -6790,6 +6830,10 @@ static int libbpf_prepare_prog_load(struct bpf_program *prog,
if (prog->type == BPF_PROG_TYPE_XDP && (def & SEC_XDP_FRAGS))
opts->prog_flags |= BPF_F_XDP_HAS_FRAGS;
/* special check for usdt to use uprobe_multi link */
if ((def & SEC_USDT) && kernel_supports(prog->obj, FEAT_UPROBE_MULTI_LINK))
prog->expected_attach_type = BPF_TRACE_UPROBE_MULTI;
if ((def & SEC_ATTACH_BTF) && !prog->attach_btf_id) {
int btf_obj_fd = 0, btf_type_id = 0, err;
const char *attach_name;
......@@ -6858,7 +6902,6 @@ static int bpf_object_load_prog(struct bpf_object *obj, struct bpf_program *prog
if (!insns || !insns_cnt)
return -EINVAL;
load_attr.expected_attach_type = prog->expected_attach_type;
if (kernel_supports(obj, FEAT_PROG_NAME))
prog_name = prog->name;
load_attr.attach_prog_fd = prog->attach_prog_fd;
......@@ -6894,6 +6937,9 @@ static int bpf_object_load_prog(struct bpf_object *obj, struct bpf_program *prog
insns_cnt = prog->insns_cnt;
}
/* allow prog_prepare_load_fn to change expected_attach_type */
load_attr.expected_attach_type = prog->expected_attach_type;
if (obj->gen_loader) {
bpf_gen__prog_load(obj->gen_loader, prog->type, prog->name,
license, insns, insns_cnt, &load_attr,
......@@ -8699,6 +8745,7 @@ static int attach_tp(const struct bpf_program *prog, long cookie, struct bpf_lin
static int attach_raw_tp(const struct bpf_program *prog, long cookie, struct bpf_link **link);
static int attach_trace(const struct bpf_program *prog, long cookie, struct bpf_link **link);
static int attach_kprobe_multi(const struct bpf_program *prog, long cookie, struct bpf_link **link);
static int attach_uprobe_multi(const struct bpf_program *prog, long cookie, struct bpf_link **link);
static int attach_lsm(const struct bpf_program *prog, long cookie, struct bpf_link **link);
static int attach_iter(const struct bpf_program *prog, long cookie, struct bpf_link **link);
......@@ -8714,9 +8761,14 @@ static const struct bpf_sec_def section_defs[] = {
SEC_DEF("uretprobe.s+", KPROBE, 0, SEC_SLEEPABLE, attach_uprobe),
SEC_DEF("kprobe.multi+", KPROBE, BPF_TRACE_KPROBE_MULTI, SEC_NONE, attach_kprobe_multi),
SEC_DEF("kretprobe.multi+", KPROBE, BPF_TRACE_KPROBE_MULTI, SEC_NONE, attach_kprobe_multi),
SEC_DEF("uprobe.multi+", KPROBE, BPF_TRACE_UPROBE_MULTI, SEC_NONE, attach_uprobe_multi),
SEC_DEF("uretprobe.multi+", KPROBE, BPF_TRACE_UPROBE_MULTI, SEC_NONE, attach_uprobe_multi),
SEC_DEF("uprobe.multi.s+", KPROBE, BPF_TRACE_UPROBE_MULTI, SEC_SLEEPABLE, attach_uprobe_multi),
SEC_DEF("uretprobe.multi.s+", KPROBE, BPF_TRACE_UPROBE_MULTI, SEC_SLEEPABLE, attach_uprobe_multi),
SEC_DEF("ksyscall+", KPROBE, 0, SEC_NONE, attach_ksyscall),
SEC_DEF("kretsyscall+", KPROBE, 0, SEC_NONE, attach_ksyscall),
SEC_DEF("usdt+", KPROBE, 0, SEC_NONE, attach_usdt),
SEC_DEF("usdt+", KPROBE, 0, SEC_USDT, attach_usdt),
SEC_DEF("usdt.s+", KPROBE, 0, SEC_USDT | SEC_SLEEPABLE, attach_usdt),
SEC_DEF("tc/ingress", SCHED_CLS, BPF_TCX_INGRESS, SEC_NONE), /* alias for tcx */
SEC_DEF("tc/egress", SCHED_CLS, BPF_TCX_EGRESS, SEC_NONE), /* alias for tcx */
SEC_DEF("tcx/ingress", SCHED_CLS, BPF_TCX_INGRESS, SEC_NONE),
......@@ -10567,7 +10619,7 @@ struct bpf_link *bpf_program__attach_ksyscall(const struct bpf_program *prog,
}
/* Adapted from perf/util/string.c */
static bool glob_match(const char *str, const char *pat)
bool glob_match(const char *str, const char *pat)
{
while (*str && *pat && *pat != '*') {
if (*pat == '?') { /* Matches any single character */
......@@ -10920,6 +10972,37 @@ static int attach_kprobe_multi(const struct bpf_program *prog, long cookie, stru
return libbpf_get_error(*link);
}
static int attach_uprobe_multi(const struct bpf_program *prog, long cookie, struct bpf_link **link)
{
char *probe_type = NULL, *binary_path = NULL, *func_name = NULL;
LIBBPF_OPTS(bpf_uprobe_multi_opts, opts);
int n, ret = -EINVAL;
*link = NULL;
n = sscanf(prog->sec_name, "%m[^/]/%m[^:]:%ms",
&probe_type, &binary_path, &func_name);
switch (n) {
case 1:
/* handle SEC("u[ret]probe") - format is valid, but auto-attach is impossible. */
ret = 0;
break;
case 3:
opts.retprobe = strcmp(probe_type, "uretprobe.multi") == 0;
*link = bpf_program__attach_uprobe_multi(prog, -1, binary_path, func_name, &opts);
ret = libbpf_get_error(*link);
break;
default:
pr_warn("prog '%s': invalid format of section definition '%s'\n", prog->name,
prog->sec_name);
break;
}
free(probe_type);
free(binary_path);
free(func_name);
return ret;
}
static void gen_uprobe_legacy_event_name(char *buf, size_t buf_sz,
const char *binary_path, uint64_t offset)
{
......@@ -11002,191 +11085,6 @@ static int perf_event_uprobe_open_legacy(const char *probe_name, bool retprobe,
return err;
}
/* Return next ELF section of sh_type after scn, or first of that type if scn is NULL. */
static Elf_Scn *elf_find_next_scn_by_type(Elf *elf, int sh_type, Elf_Scn *scn)
{
while ((scn = elf_nextscn(elf, scn)) != NULL) {
GElf_Shdr sh;
if (!gelf_getshdr(scn, &sh))
continue;
if (sh.sh_type == sh_type)
return scn;
}
return NULL;
}
/* Find offset of function name in the provided ELF object. "binary_path" is
* the path to the ELF binary represented by "elf", and only used for error
* reporting matters. "name" matches symbol name or name@@LIB for library
* functions.
*/
static long elf_find_func_offset(Elf *elf, const char *binary_path, const char *name)
{
int i, sh_types[2] = { SHT_DYNSYM, SHT_SYMTAB };
bool is_shared_lib, is_name_qualified;
long ret = -ENOENT;
size_t name_len;
GElf_Ehdr ehdr;
if (!gelf_getehdr(elf, &ehdr)) {
pr_warn("elf: failed to get ehdr from %s: %s\n", binary_path, elf_errmsg(-1));
ret = -LIBBPF_ERRNO__FORMAT;
goto out;
}
/* for shared lib case, we do not need to calculate relative offset */
is_shared_lib = ehdr.e_type == ET_DYN;
name_len = strlen(name);
/* Does name specify "@@LIB"? */
is_name_qualified = strstr(name, "@@") != NULL;
/* Search SHT_DYNSYM, SHT_SYMTAB for symbol. This search order is used because if
* a binary is stripped, it may only have SHT_DYNSYM, and a fully-statically
* linked binary may not have SHT_DYMSYM, so absence of a section should not be
* reported as a warning/error.
*/
for (i = 0; i < ARRAY_SIZE(sh_types); i++) {
size_t nr_syms, strtabidx, idx;
Elf_Data *symbols = NULL;
Elf_Scn *scn = NULL;
int last_bind = -1;
const char *sname;
GElf_Shdr sh;
scn = elf_find_next_scn_by_type(elf, sh_types[i], NULL);
if (!scn) {
pr_debug("elf: failed to find symbol table ELF sections in '%s'\n",
binary_path);
continue;
}
if (!gelf_getshdr(scn, &sh))
continue;
strtabidx = sh.sh_link;
symbols = elf_getdata(scn, 0);
if (!symbols) {
pr_warn("elf: failed to get symbols for symtab section in '%s': %s\n",
binary_path, elf_errmsg(-1));
ret = -LIBBPF_ERRNO__FORMAT;
goto out;
}
nr_syms = symbols->d_size / sh.sh_entsize;
for (idx = 0; idx < nr_syms; idx++) {
int curr_bind;
GElf_Sym sym;
Elf_Scn *sym_scn;
GElf_Shdr sym_sh;
if (!gelf_getsym(symbols, idx, &sym))
continue;
if (GELF_ST_TYPE(sym.st_info) != STT_FUNC)
continue;
sname = elf_strptr(elf, strtabidx, sym.st_name);
if (!sname)
continue;
curr_bind = GELF_ST_BIND(sym.st_info);
/* User can specify func, func@@LIB or func@@LIB_VERSION. */
if (strncmp(sname, name, name_len) != 0)
continue;
/* ...but we don't want a search for "foo" to match 'foo2" also, so any
* additional characters in sname should be of the form "@@LIB".
*/
if (!is_name_qualified && sname[name_len] != '\0' && sname[name_len] != '@')
continue;
if (ret >= 0) {
/* handle multiple matches */
if (last_bind != STB_WEAK && curr_bind != STB_WEAK) {
/* Only accept one non-weak bind. */
pr_warn("elf: ambiguous match for '%s', '%s' in '%s'\n",
sname, name, binary_path);
ret = -LIBBPF_ERRNO__FORMAT;
goto out;
} else if (curr_bind == STB_WEAK) {
/* already have a non-weak bind, and
* this is a weak bind, so ignore.
*/
continue;
}
}
/* Transform symbol's virtual address (absolute for
* binaries and relative for shared libs) into file
* offset, which is what kernel is expecting for
* uprobe/uretprobe attachment.
* See Documentation/trace/uprobetracer.rst for more
* details.
* This is done by looking up symbol's containing
* section's header and using it's virtual address
* (sh_addr) and corresponding file offset (sh_offset)
* to transform sym.st_value (virtual address) into
* desired final file offset.
*/
sym_scn = elf_getscn(elf, sym.st_shndx);
if (!sym_scn)
continue;
if (!gelf_getshdr(sym_scn, &sym_sh))
continue;
ret = sym.st_value - sym_sh.sh_addr + sym_sh.sh_offset;
last_bind = curr_bind;
}
if (ret > 0)
break;
}
if (ret > 0) {
pr_debug("elf: symbol address match for '%s' in '%s': 0x%lx\n", name, binary_path,
ret);
} else {
if (ret == 0) {
pr_warn("elf: '%s' is 0 in symtab for '%s': %s\n", name, binary_path,
is_shared_lib ? "should not be 0 in a shared library" :
"try using shared library path instead");
ret = -ENOENT;
} else {
pr_warn("elf: failed to find symbol '%s' in '%s'\n", name, binary_path);
}
}
out:
return ret;
}
/* Find offset of function name in ELF object specified by path. "name" matches
* symbol name or name@@LIB for library functions.
*/
static long elf_find_func_offset_from_file(const char *binary_path, const char *name)
{
char errmsg[STRERR_BUFSIZE];
long ret = -ENOENT;
Elf *elf;
int fd;
fd = open(binary_path, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
ret = -errno;
pr_warn("failed to open %s: %s\n", binary_path,
libbpf_strerror_r(ret, errmsg, sizeof(errmsg)));
return ret;
}
elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
if (!elf) {
pr_warn("elf: could not read elf from %s: %s\n", binary_path, elf_errmsg(-1));
close(fd);
return -LIBBPF_ERRNO__FORMAT;
}
ret = elf_find_func_offset(elf, binary_path, name);
elf_end(elf);
close(fd);
return ret;
}
/* Find offset of function name in archive specified by path. Currently
* supported are .zip files that do not compress their contents, as used on
* Android in the form of APKs, for example. "file_name" is the name of the ELF
......@@ -11329,6 +11227,120 @@ static int resolve_full_path(const char *file, char *result, size_t result_sz)
return -ENOENT;
}
struct bpf_link *
bpf_program__attach_uprobe_multi(const struct bpf_program *prog,
pid_t pid,
const char *path,
const char *func_pattern,
const struct bpf_uprobe_multi_opts *opts)
{
const unsigned long *ref_ctr_offsets = NULL, *offsets = NULL;
LIBBPF_OPTS(bpf_link_create_opts, lopts);
unsigned long *resolved_offsets = NULL;
int err = 0, link_fd, prog_fd;
struct bpf_link *link = NULL;
char errmsg[STRERR_BUFSIZE];
char full_path[PATH_MAX];
const __u64 *cookies;
const char **syms;
size_t cnt;
if (!OPTS_VALID(opts, bpf_uprobe_multi_opts))
return libbpf_err_ptr(-EINVAL);
syms = OPTS_GET(opts, syms, NULL);
offsets = OPTS_GET(opts, offsets, NULL);
ref_ctr_offsets = OPTS_GET(opts, ref_ctr_offsets, NULL);
cookies = OPTS_GET(opts, cookies, NULL);
cnt = OPTS_GET(opts, cnt, 0);
/*
* User can specify 2 mutually exclusive set of inputs:
*
* 1) use only path/func_pattern/pid arguments
*
* 2) use path/pid with allowed combinations of:
* syms/offsets/ref_ctr_offsets/cookies/cnt
*
* - syms and offsets are mutually exclusive
* - ref_ctr_offsets and cookies are optional
*
* Any other usage results in error.
*/
if (!path)
return libbpf_err_ptr(-EINVAL);
if (!func_pattern && cnt == 0)
return libbpf_err_ptr(-EINVAL);
if (func_pattern) {
if (syms || offsets || ref_ctr_offsets || cookies || cnt)
return libbpf_err_ptr(-EINVAL);
} else {
if (!!syms == !!offsets)
return libbpf_err_ptr(-EINVAL);
}
if (func_pattern) {
if (!strchr(path, '/')) {
err = resolve_full_path(path, full_path, sizeof(full_path));
if (err) {
pr_warn("prog '%s': failed to resolve full path for '%s': %d\n",
prog->name, path, err);
return libbpf_err_ptr(err);
}
path = full_path;
}
err = elf_resolve_pattern_offsets(path, func_pattern,
&resolved_offsets, &cnt);
if (err < 0)
return libbpf_err_ptr(err);
offsets = resolved_offsets;
} else if (syms) {
err = elf_resolve_syms_offsets(path, cnt, syms, &resolved_offsets);
if (err < 0)
return libbpf_err_ptr(err);
offsets = resolved_offsets;
}
lopts.uprobe_multi.path = path;
lopts.uprobe_multi.offsets = offsets;
lopts.uprobe_multi.ref_ctr_offsets = ref_ctr_offsets;
lopts.uprobe_multi.cookies = cookies;
lopts.uprobe_multi.cnt = cnt;
lopts.uprobe_multi.flags = OPTS_GET(opts, retprobe, false) ? BPF_F_UPROBE_MULTI_RETURN : 0;
if (pid == 0)
pid = getpid();
if (pid > 0)
lopts.uprobe_multi.pid = pid;
link = calloc(1, sizeof(*link));
if (!link) {
err = -ENOMEM;
goto error;
}
link->detach = &bpf_link__detach_fd;
prog_fd = bpf_program__fd(prog);
link_fd = bpf_link_create(prog_fd, 0, BPF_TRACE_UPROBE_MULTI, &lopts);
if (link_fd < 0) {
err = -errno;
pr_warn("prog '%s': failed to attach multi-uprobe: %s\n",
prog->name, libbpf_strerror_r(err, errmsg, sizeof(errmsg)));
goto error;
}
link->fd = link_fd;
free(resolved_offsets);
return link;
error:
free(resolved_offsets);
free(link);
return libbpf_err_ptr(err);
}
LIBBPF_API struct bpf_link *
bpf_program__attach_uprobe_opts(const struct bpf_program *prog, pid_t pid,
const char *binary_path, size_t func_offset,
......
......@@ -529,6 +529,57 @@ bpf_program__attach_kprobe_multi_opts(const struct bpf_program *prog,
const char *pattern,
const struct bpf_kprobe_multi_opts *opts);
struct bpf_uprobe_multi_opts {
/* size of this struct, for forward/backward compatibility */
size_t sz;
/* array of function symbols to attach to */
const char **syms;
/* array of function addresses to attach to */
const unsigned long *offsets;
/* optional, array of associated ref counter offsets */
const unsigned long *ref_ctr_offsets;
/* optional, array of associated BPF cookies */
const __u64 *cookies;
/* number of elements in syms/addrs/cookies arrays */
size_t cnt;
/* create return uprobes */
bool retprobe;
size_t :0;
};
#define bpf_uprobe_multi_opts__last_field retprobe
/**
* @brief **bpf_program__attach_uprobe_multi()** attaches a BPF program
* to multiple uprobes with uprobe_multi link.
*
* User can specify 2 mutually exclusive set of inputs:
*
* 1) use only path/func_pattern/pid arguments
*
* 2) use path/pid with allowed combinations of
* syms/offsets/ref_ctr_offsets/cookies/cnt
*
* - syms and offsets are mutually exclusive
* - ref_ctr_offsets and cookies are optional
*
*
* @param prog BPF program to attach
* @param pid Process ID to attach the uprobe to, 0 for self (own process),
* -1 for all processes
* @param binary_path Path to binary
* @param func_pattern Regular expression to specify functions to attach
* BPF program to
* @param opts Additional options (see **struct bpf_uprobe_multi_opts**)
* @return 0, on success; negative error code, otherwise
*/
LIBBPF_API struct bpf_link *
bpf_program__attach_uprobe_multi(const struct bpf_program *prog,
pid_t pid,
const char *binary_path,
const char *func_pattern,
const struct bpf_uprobe_multi_opts *opts);
struct bpf_ksyscall_opts {
/* size of this struct, for forward/backward compatibility */
size_t sz;
......
......@@ -398,4 +398,5 @@ LIBBPF_1.3.0 {
bpf_prog_detach_opts;
bpf_program__attach_netfilter;
bpf_program__attach_tcx;
bpf_program__attach_uprobe_multi;
} LIBBPF_1.2.0;
......@@ -15,6 +15,7 @@
#include <linux/err.h>
#include <fcntl.h>
#include <unistd.h>
#include <libelf.h>
#include "relo_core.h"
/* make sure libbpf doesn't use kernel-only integer typedefs */
......@@ -354,6 +355,8 @@ enum kern_feature_id {
FEAT_BTF_ENUM64,
/* Kernel uses syscall wrapper (CONFIG_ARCH_HAS_SYSCALL_WRAPPER) */
FEAT_SYSCALL_WRAPPER,
/* BPF multi-uprobe link support */
FEAT_UPROBE_MULTI_LINK,
__FEAT_CNT,
};
......@@ -577,4 +580,22 @@ static inline bool is_pow_of_2(size_t x)
#define PROG_LOAD_ATTEMPTS 5
int sys_bpf_prog_load(union bpf_attr *attr, unsigned int size, int attempts);
bool glob_match(const char *str, const char *pat);
long elf_find_func_offset(Elf *elf, const char *binary_path, const char *name);
long elf_find_func_offset_from_file(const char *binary_path, const char *name);
struct elf_fd {
Elf *elf;
int fd;
};
int elf_open(const char *binary_path, struct elf_fd *elf_fd);
void elf_close(struct elf_fd *elf_fd);
int elf_resolve_syms_offsets(const char *binary_path, int cnt,
const char **syms, unsigned long **poffsets);
int elf_resolve_pattern_offsets(const char *binary_path, const char *pattern,
unsigned long **poffsets, size_t *pcnt);
#endif /* __LIBBPF_LIBBPF_INTERNAL_H */
......@@ -250,6 +250,7 @@ struct usdt_manager {
bool has_bpf_cookie;
bool has_sema_refcnt;
bool has_uprobe_multi;
};
struct usdt_manager *usdt_manager_new(struct bpf_object *obj)
......@@ -284,6 +285,11 @@ struct usdt_manager *usdt_manager_new(struct bpf_object *obj)
*/
man->has_sema_refcnt = faccessat(AT_FDCWD, ref_ctr_sysfs_path, F_OK, AT_EACCESS) == 0;
/*
* Detect kernel support for uprobe multi link to be used for attaching
* usdt probes.
*/
man->has_uprobe_multi = kernel_supports(obj, FEAT_UPROBE_MULTI_LINK);
return man;
}
......@@ -808,6 +814,8 @@ struct bpf_link_usdt {
long abs_ip;
struct bpf_link *link;
} *uprobes;
struct bpf_link *multi_link;
};
static int bpf_link_usdt_detach(struct bpf_link *link)
......@@ -816,6 +824,9 @@ static int bpf_link_usdt_detach(struct bpf_link *link)
struct usdt_manager *man = usdt_link->usdt_man;
int i;
bpf_link__destroy(usdt_link->multi_link);
/* When having multi_link, uprobe_cnt is 0 */
for (i = 0; i < usdt_link->uprobe_cnt; i++) {
/* detach underlying uprobe link */
bpf_link__destroy(usdt_link->uprobes[i].link);
......@@ -946,32 +957,24 @@ struct bpf_link *usdt_manager_attach_usdt(struct usdt_manager *man, const struct
const char *usdt_provider, const char *usdt_name,
__u64 usdt_cookie)
{
int i, fd, err, spec_map_fd, ip_map_fd;
unsigned long *offsets = NULL, *ref_ctr_offsets = NULL;
int i, err, spec_map_fd, ip_map_fd;
LIBBPF_OPTS(bpf_uprobe_opts, opts);
struct hashmap *specs_hash = NULL;
struct bpf_link_usdt *link = NULL;
struct usdt_target *targets = NULL;
__u64 *cookies = NULL;
struct elf_fd elf_fd;
size_t target_cnt;
Elf *elf;
spec_map_fd = bpf_map__fd(man->specs_map);
ip_map_fd = bpf_map__fd(man->ip_to_spec_id_map);
fd = open(path, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
err = -errno;
pr_warn("usdt: failed to open ELF binary '%s': %d\n", path, err);
err = elf_open(path, &elf_fd);
if (err)
return libbpf_err_ptr(err);
}
elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
if (!elf) {
err = -EBADF;
pr_warn("usdt: failed to parse ELF binary '%s': %s\n", path, elf_errmsg(-1));
goto err_out;
}
err = sanity_check_usdt_elf(elf, path);
err = sanity_check_usdt_elf(elf_fd.elf, path);
if (err)
goto err_out;
......@@ -984,7 +987,7 @@ struct bpf_link *usdt_manager_attach_usdt(struct usdt_manager *man, const struct
/* discover USDT in given binary, optionally limiting
* activations to a given PID, if pid > 0
*/
err = collect_usdt_targets(man, elf, path, pid, usdt_provider, usdt_name,
err = collect_usdt_targets(man, elf_fd.elf, path, pid, usdt_provider, usdt_name,
usdt_cookie, &targets, &target_cnt);
if (err <= 0) {
err = (err == 0) ? -ENOENT : err;
......@@ -1007,11 +1010,22 @@ struct bpf_link *usdt_manager_attach_usdt(struct usdt_manager *man, const struct
link->link.detach = &bpf_link_usdt_detach;
link->link.dealloc = &bpf_link_usdt_dealloc;
if (man->has_uprobe_multi) {
offsets = calloc(target_cnt, sizeof(*offsets));
cookies = calloc(target_cnt, sizeof(*cookies));
ref_ctr_offsets = calloc(target_cnt, sizeof(*ref_ctr_offsets));
if (!offsets || !ref_ctr_offsets || !cookies) {
err = -ENOMEM;
goto err_out;
}
} else {
link->uprobes = calloc(target_cnt, sizeof(*link->uprobes));
if (!link->uprobes) {
err = -ENOMEM;
goto err_out;
}
}
for (i = 0; i < target_cnt; i++) {
struct usdt_target *target = &targets[i];
......@@ -1051,6 +1065,11 @@ struct bpf_link *usdt_manager_attach_usdt(struct usdt_manager *man, const struct
goto err_out;
}
if (man->has_uprobe_multi) {
offsets[i] = target->rel_ip;
ref_ctr_offsets[i] = target->sema_off;
cookies[i] = spec_id;
} else {
opts.ref_ctr_offset = target->sema_off;
opts.bpf_cookie = man->has_bpf_cookie ? spec_id : 0;
uprobe_link = bpf_program__attach_uprobe_opts(prog, pid, path,
......@@ -1066,22 +1085,45 @@ struct bpf_link *usdt_manager_attach_usdt(struct usdt_manager *man, const struct
link->uprobes[i].abs_ip = target->abs_ip;
link->uprobe_cnt++;
}
}
if (man->has_uprobe_multi) {
LIBBPF_OPTS(bpf_uprobe_multi_opts, opts_multi,
.ref_ctr_offsets = ref_ctr_offsets,
.offsets = offsets,
.cookies = cookies,
.cnt = target_cnt,
);
link->multi_link = bpf_program__attach_uprobe_multi(prog, pid, path,
NULL, &opts_multi);
if (!link->multi_link) {
err = -errno;
pr_warn("usdt: failed to attach uprobe multi for '%s:%s' in '%s': %d\n",
usdt_provider, usdt_name, path, err);
goto err_out;
}
free(offsets);
free(ref_ctr_offsets);
free(cookies);
}
free(targets);
hashmap__free(specs_hash);
elf_end(elf);
close(fd);
elf_close(&elf_fd);
return &link->link;
err_out:
free(offsets);
free(ref_ctr_offsets);
free(cookies);
if (link)
bpf_link__destroy(&link->link);
free(targets);
hashmap__free(specs_hash);
if (elf)
elf_end(elf);
close(fd);
elf_close(&elf_fd);
return libbpf_err_ptr(err);
}
......
......@@ -585,6 +585,7 @@ TRUNNER_EXTRA_FILES := $(OUTPUT)/urandom_read $(OUTPUT)/bpf_testmod.ko \
$(OUTPUT)/liburandom_read.so \
$(OUTPUT)/xdp_synproxy \
$(OUTPUT)/sign-file \
$(OUTPUT)/uprobe_multi \
ima_setup.sh \
verify_sig_setup.sh \
$(wildcard progs/btf_dump_test_case_*.c) \
......@@ -698,6 +699,10 @@ $(OUTPUT)/veristat: $(OUTPUT)/veristat.o
$(call msg,BINARY,,$@)
$(Q)$(CC) $(CFLAGS) $(LDFLAGS) $(filter %.a %.o,$^) $(LDLIBS) -o $@
$(OUTPUT)/uprobe_multi: uprobe_multi.c
$(call msg,BINARY,,$@)
$(Q)$(CC) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@
EXTRA_CLEAN := $(TEST_CUSTOM_PROGS) $(SCRATCH_DIR) $(HOST_SCRATCH_DIR) \
prog_tests/tests.h map_tests/tests.h verifier/tests.h \
feature bpftool \
......
......@@ -81,15 +81,6 @@ void grace_period_latency_basic_stats(struct bench_res res[], int res_cnt,
void grace_period_ticks_basic_stats(struct bench_res res[], int res_cnt,
struct basic_stats *gp_stat);
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 inline void atomic_inc(long *value)
{
(void)__atomic_add_fetch(value, 1, __ATOMIC_RELAXED);
......
......@@ -11,6 +11,7 @@
#include <bpf/btf.h>
#include "test_bpf_cookie.skel.h"
#include "kprobe_multi.skel.h"
#include "uprobe_multi.skel.h"
/* uprobe attach point */
static noinline void trigger_func(void)
......@@ -239,6 +240,81 @@ static void kprobe_multi_attach_api_subtest(void)
bpf_link__destroy(link1);
kprobe_multi__destroy(skel);
}
/* defined in prog_tests/uprobe_multi_test.c */
void uprobe_multi_func_1(void);
void uprobe_multi_func_2(void);
void uprobe_multi_func_3(void);
static void uprobe_multi_test_run(struct uprobe_multi *skel)
{
skel->bss->uprobe_multi_func_1_addr = (__u64) uprobe_multi_func_1;
skel->bss->uprobe_multi_func_2_addr = (__u64) uprobe_multi_func_2;
skel->bss->uprobe_multi_func_3_addr = (__u64) uprobe_multi_func_3;
skel->bss->pid = getpid();
skel->bss->test_cookie = true;
uprobe_multi_func_1();
uprobe_multi_func_2();
uprobe_multi_func_3();
ASSERT_EQ(skel->bss->uprobe_multi_func_1_result, 1, "uprobe_multi_func_1_result");
ASSERT_EQ(skel->bss->uprobe_multi_func_2_result, 1, "uprobe_multi_func_2_result");
ASSERT_EQ(skel->bss->uprobe_multi_func_3_result, 1, "uprobe_multi_func_3_result");
ASSERT_EQ(skel->bss->uretprobe_multi_func_1_result, 1, "uretprobe_multi_func_1_result");
ASSERT_EQ(skel->bss->uretprobe_multi_func_2_result, 1, "uretprobe_multi_func_2_result");
ASSERT_EQ(skel->bss->uretprobe_multi_func_3_result, 1, "uretprobe_multi_func_3_result");
}
static void uprobe_multi_attach_api_subtest(void)
{
struct bpf_link *link1 = NULL, *link2 = NULL;
struct uprobe_multi *skel = NULL;
LIBBPF_OPTS(bpf_uprobe_multi_opts, opts);
const char *syms[3] = {
"uprobe_multi_func_1",
"uprobe_multi_func_2",
"uprobe_multi_func_3",
};
__u64 cookies[3];
cookies[0] = 3; /* uprobe_multi_func_1 */
cookies[1] = 1; /* uprobe_multi_func_2 */
cookies[2] = 2; /* uprobe_multi_func_3 */
opts.syms = syms;
opts.cnt = ARRAY_SIZE(syms);
opts.cookies = &cookies[0];
skel = uprobe_multi__open_and_load();
if (!ASSERT_OK_PTR(skel, "uprobe_multi"))
goto cleanup;
link1 = bpf_program__attach_uprobe_multi(skel->progs.uprobe, -1,
"/proc/self/exe", NULL, &opts);
if (!ASSERT_OK_PTR(link1, "bpf_program__attach_uprobe_multi"))
goto cleanup;
cookies[0] = 2; /* uprobe_multi_func_1 */
cookies[1] = 3; /* uprobe_multi_func_2 */
cookies[2] = 1; /* uprobe_multi_func_3 */
opts.retprobe = true;
link2 = bpf_program__attach_uprobe_multi(skel->progs.uretprobe, -1,
"/proc/self/exe", NULL, &opts);
if (!ASSERT_OK_PTR(link2, "bpf_program__attach_uprobe_multi_retprobe"))
goto cleanup;
uprobe_multi_test_run(skel);
cleanup:
bpf_link__destroy(link2);
bpf_link__destroy(link1);
uprobe_multi__destroy(skel);
}
static void uprobe_subtest(struct test_bpf_cookie *skel)
{
DECLARE_LIBBPF_OPTS(bpf_uprobe_opts, opts);
......@@ -515,6 +591,8 @@ void test_bpf_cookie(void)
kprobe_multi_attach_api_subtest();
if (test__start_subtest("uprobe"))
uprobe_subtest(skel);
if (test__start_subtest("multi_uprobe_attach_api"))
uprobe_multi_attach_api_subtest();
if (test__start_subtest("tracepoint"))
tp_subtest(skel);
if (test__start_subtest("perf_event"))
......
......@@ -304,14 +304,6 @@ static void test_attach_api_fails(void)
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(long key, void *ctx __maybe_unused)
{
return str_hash((const char *) key);
......
// SPDX-License-Identifier: GPL-2.0
#include <unistd.h>
#include <test_progs.h>
#include "uprobe_multi.skel.h"
#include "uprobe_multi_bench.skel.h"
#include "uprobe_multi_usdt.skel.h"
#include "bpf/libbpf_internal.h"
#include "testing_helpers.h"
static char test_data[] = "test_data";
noinline void uprobe_multi_func_1(void)
{
asm volatile ("");
}
noinline void uprobe_multi_func_2(void)
{
asm volatile ("");
}
noinline void uprobe_multi_func_3(void)
{
asm volatile ("");
}
struct child {
int go[2];
int pid;
};
static void release_child(struct child *child)
{
int child_status;
if (!child)
return;
close(child->go[1]);
close(child->go[0]);
if (child->pid > 0)
waitpid(child->pid, &child_status, 0);
}
static void kick_child(struct child *child)
{
char c = 1;
if (child) {
write(child->go[1], &c, 1);
release_child(child);
}
fflush(NULL);
}
static struct child *spawn_child(void)
{
static struct child child;
int err;
int c;
/* pipe to notify child to execute the trigger functions */
if (pipe(child.go))
return NULL;
child.pid = fork();
if (child.pid < 0) {
release_child(&child);
errno = EINVAL;
return NULL;
}
/* child */
if (child.pid == 0) {
close(child.go[1]);
/* wait for parent's kick */
err = read(child.go[0], &c, 1);
if (err != 1)
exit(err);
uprobe_multi_func_1();
uprobe_multi_func_2();
uprobe_multi_func_3();
exit(errno);
}
return &child;
}
static void uprobe_multi_test_run(struct uprobe_multi *skel, struct child *child)
{
skel->bss->uprobe_multi_func_1_addr = (__u64) uprobe_multi_func_1;
skel->bss->uprobe_multi_func_2_addr = (__u64) uprobe_multi_func_2;
skel->bss->uprobe_multi_func_3_addr = (__u64) uprobe_multi_func_3;
skel->bss->user_ptr = test_data;
/*
* Disable pid check in bpf program if we are pid filter test,
* because the probe should be executed only by child->pid
* passed at the probe attach.
*/
skel->bss->pid = child ? 0 : getpid();
if (child)
kick_child(child);
/* trigger all probes */
uprobe_multi_func_1();
uprobe_multi_func_2();
uprobe_multi_func_3();
/*
* There are 2 entry and 2 exit probe called for each uprobe_multi_func_[123]
* function and each slepable probe (6) increments uprobe_multi_sleep_result.
*/
ASSERT_EQ(skel->bss->uprobe_multi_func_1_result, 2, "uprobe_multi_func_1_result");
ASSERT_EQ(skel->bss->uprobe_multi_func_2_result, 2, "uprobe_multi_func_2_result");
ASSERT_EQ(skel->bss->uprobe_multi_func_3_result, 2, "uprobe_multi_func_3_result");
ASSERT_EQ(skel->bss->uretprobe_multi_func_1_result, 2, "uretprobe_multi_func_1_result");
ASSERT_EQ(skel->bss->uretprobe_multi_func_2_result, 2, "uretprobe_multi_func_2_result");
ASSERT_EQ(skel->bss->uretprobe_multi_func_3_result, 2, "uretprobe_multi_func_3_result");
ASSERT_EQ(skel->bss->uprobe_multi_sleep_result, 6, "uprobe_multi_sleep_result");
if (child)
ASSERT_EQ(skel->bss->child_pid, child->pid, "uprobe_multi_child_pid");
}
static void test_skel_api(void)
{
struct uprobe_multi *skel = NULL;
int err;
skel = uprobe_multi__open_and_load();
if (!ASSERT_OK_PTR(skel, "uprobe_multi__open_and_load"))
goto cleanup;
err = uprobe_multi__attach(skel);
if (!ASSERT_OK(err, "uprobe_multi__attach"))
goto cleanup;
uprobe_multi_test_run(skel, NULL);
cleanup:
uprobe_multi__destroy(skel);
}
static void
__test_attach_api(const char *binary, const char *pattern, struct bpf_uprobe_multi_opts *opts,
struct child *child)
{
pid_t pid = child ? child->pid : -1;
struct uprobe_multi *skel = NULL;
skel = uprobe_multi__open_and_load();
if (!ASSERT_OK_PTR(skel, "uprobe_multi__open_and_load"))
goto cleanup;
opts->retprobe = false;
skel->links.uprobe = bpf_program__attach_uprobe_multi(skel->progs.uprobe, pid,
binary, pattern, opts);
if (!ASSERT_OK_PTR(skel->links.uprobe, "bpf_program__attach_uprobe_multi"))
goto cleanup;
opts->retprobe = true;
skel->links.uretprobe = bpf_program__attach_uprobe_multi(skel->progs.uretprobe, pid,
binary, pattern, opts);
if (!ASSERT_OK_PTR(skel->links.uretprobe, "bpf_program__attach_uprobe_multi"))
goto cleanup;
opts->retprobe = false;
skel->links.uprobe_sleep = bpf_program__attach_uprobe_multi(skel->progs.uprobe_sleep, pid,
binary, pattern, opts);
if (!ASSERT_OK_PTR(skel->links.uprobe_sleep, "bpf_program__attach_uprobe_multi"))
goto cleanup;
opts->retprobe = true;
skel->links.uretprobe_sleep = bpf_program__attach_uprobe_multi(skel->progs.uretprobe_sleep,
pid, binary, pattern, opts);
if (!ASSERT_OK_PTR(skel->links.uretprobe_sleep, "bpf_program__attach_uprobe_multi"))
goto cleanup;
opts->retprobe = false;
skel->links.uprobe_extra = bpf_program__attach_uprobe_multi(skel->progs.uprobe_extra, -1,
binary, pattern, opts);
if (!ASSERT_OK_PTR(skel->links.uprobe_extra, "bpf_program__attach_uprobe_multi"))
goto cleanup;
uprobe_multi_test_run(skel, child);
cleanup:
uprobe_multi__destroy(skel);
}
static void
test_attach_api(const char *binary, const char *pattern, struct bpf_uprobe_multi_opts *opts)
{
struct child *child;
/* no pid filter */
__test_attach_api(binary, pattern, opts, NULL);
/* pid filter */
child = spawn_child();
if (!ASSERT_OK_PTR(child, "spawn_child"))
return;
__test_attach_api(binary, pattern, opts, child);
}
static void test_attach_api_pattern(void)
{
LIBBPF_OPTS(bpf_uprobe_multi_opts, opts);
test_attach_api("/proc/self/exe", "uprobe_multi_func_*", &opts);
test_attach_api("/proc/self/exe", "uprobe_multi_func_?", &opts);
}
static void test_attach_api_syms(void)
{
LIBBPF_OPTS(bpf_uprobe_multi_opts, opts);
const char *syms[3] = {
"uprobe_multi_func_1",
"uprobe_multi_func_2",
"uprobe_multi_func_3",
};
opts.syms = syms;
opts.cnt = ARRAY_SIZE(syms);
test_attach_api("/proc/self/exe", NULL, &opts);
}
static void __test_link_api(struct child *child)
{
int prog_fd, link1_fd = -1, link2_fd = -1, link3_fd = -1, link4_fd = -1;
LIBBPF_OPTS(bpf_link_create_opts, opts);
const char *path = "/proc/self/exe";
struct uprobe_multi *skel = NULL;
unsigned long *offsets = NULL;
const char *syms[3] = {
"uprobe_multi_func_1",
"uprobe_multi_func_2",
"uprobe_multi_func_3",
};
int link_extra_fd = -1;
int err;
err = elf_resolve_syms_offsets(path, 3, syms, (unsigned long **) &offsets);
if (!ASSERT_OK(err, "elf_resolve_syms_offsets"))
return;
opts.uprobe_multi.path = path;
opts.uprobe_multi.offsets = offsets;
opts.uprobe_multi.cnt = ARRAY_SIZE(syms);
opts.uprobe_multi.pid = child ? child->pid : 0;
skel = uprobe_multi__open_and_load();
if (!ASSERT_OK_PTR(skel, "uprobe_multi__open_and_load"))
goto cleanup;
opts.kprobe_multi.flags = 0;
prog_fd = bpf_program__fd(skel->progs.uprobe);
link1_fd = bpf_link_create(prog_fd, 0, BPF_TRACE_UPROBE_MULTI, &opts);
if (!ASSERT_GE(link1_fd, 0, "link1_fd"))
goto cleanup;
opts.kprobe_multi.flags = BPF_F_UPROBE_MULTI_RETURN;
prog_fd = bpf_program__fd(skel->progs.uretprobe);
link2_fd = bpf_link_create(prog_fd, 0, BPF_TRACE_UPROBE_MULTI, &opts);
if (!ASSERT_GE(link2_fd, 0, "link2_fd"))
goto cleanup;
opts.kprobe_multi.flags = 0;
prog_fd = bpf_program__fd(skel->progs.uprobe_sleep);
link3_fd = bpf_link_create(prog_fd, 0, BPF_TRACE_UPROBE_MULTI, &opts);
if (!ASSERT_GE(link3_fd, 0, "link3_fd"))
goto cleanup;
opts.kprobe_multi.flags = BPF_F_UPROBE_MULTI_RETURN;
prog_fd = bpf_program__fd(skel->progs.uretprobe_sleep);
link4_fd = bpf_link_create(prog_fd, 0, BPF_TRACE_UPROBE_MULTI, &opts);
if (!ASSERT_GE(link4_fd, 0, "link4_fd"))
goto cleanup;
opts.kprobe_multi.flags = 0;
opts.uprobe_multi.pid = 0;
prog_fd = bpf_program__fd(skel->progs.uprobe_extra);
link_extra_fd = bpf_link_create(prog_fd, 0, BPF_TRACE_UPROBE_MULTI, &opts);
if (!ASSERT_GE(link_extra_fd, 0, "link_extra_fd"))
goto cleanup;
uprobe_multi_test_run(skel, child);
cleanup:
if (link1_fd >= 0)
close(link1_fd);
if (link2_fd >= 0)
close(link2_fd);
if (link3_fd >= 0)
close(link3_fd);
if (link4_fd >= 0)
close(link4_fd);
if (link_extra_fd >= 0)
close(link_extra_fd);
uprobe_multi__destroy(skel);
free(offsets);
}
void test_link_api(void)
{
struct child *child;
/* no pid filter */
__test_link_api(NULL);
/* pid filter */
child = spawn_child();
if (!ASSERT_OK_PTR(child, "spawn_child"))
return;
__test_link_api(child);
}
static void test_bench_attach_uprobe(void)
{
long attach_start_ns = 0, attach_end_ns = 0;
struct uprobe_multi_bench *skel = NULL;
long detach_start_ns, detach_end_ns;
double attach_delta, detach_delta;
int err;
skel = uprobe_multi_bench__open_and_load();
if (!ASSERT_OK_PTR(skel, "uprobe_multi_bench__open_and_load"))
goto cleanup;
attach_start_ns = get_time_ns();
err = uprobe_multi_bench__attach(skel);
if (!ASSERT_OK(err, "uprobe_multi_bench__attach"))
goto cleanup;
attach_end_ns = get_time_ns();
system("./uprobe_multi bench");
ASSERT_EQ(skel->bss->count, 50000, "uprobes_count");
cleanup:
detach_start_ns = get_time_ns();
uprobe_multi_bench__destroy(skel);
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: attached in %7.3lfs\n", __func__, attach_delta);
printf("%s: detached in %7.3lfs\n", __func__, detach_delta);
}
static void test_bench_attach_usdt(void)
{
long attach_start_ns = 0, attach_end_ns = 0;
struct uprobe_multi_usdt *skel = NULL;
long detach_start_ns, detach_end_ns;
double attach_delta, detach_delta;
skel = uprobe_multi_usdt__open_and_load();
if (!ASSERT_OK_PTR(skel, "uprobe_multi__open"))
goto cleanup;
attach_start_ns = get_time_ns();
skel->links.usdt0 = bpf_program__attach_usdt(skel->progs.usdt0, -1, "./uprobe_multi",
"test", "usdt", NULL);
if (!ASSERT_OK_PTR(skel->links.usdt0, "bpf_program__attach_usdt"))
goto cleanup;
attach_end_ns = get_time_ns();
system("./uprobe_multi usdt");
ASSERT_EQ(skel->bss->count, 50000, "usdt_count");
cleanup:
detach_start_ns = get_time_ns();
uprobe_multi_usdt__destroy(skel);
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: attached in %7.3lfs\n", __func__, attach_delta);
printf("%s: detached in %7.3lfs\n", __func__, detach_delta);
}
void test_uprobe_multi_test(void)
{
if (test__start_subtest("skel_api"))
test_skel_api();
if (test__start_subtest("attach_api_pattern"))
test_attach_api_pattern();
if (test__start_subtest("attach_api_syms"))
test_attach_api_syms();
if (test__start_subtest("link_api"))
test_link_api();
if (test__start_subtest("bench_uprobe"))
test_bench_attach_uprobe();
if (test__start_subtest("bench_usdt"))
test_bench_attach_usdt();
}
// SPDX-License-Identifier: GPL-2.0
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <stdbool.h>
char _license[] SEC("license") = "GPL";
__u64 uprobe_multi_func_1_addr = 0;
__u64 uprobe_multi_func_2_addr = 0;
__u64 uprobe_multi_func_3_addr = 0;
__u64 uprobe_multi_func_1_result = 0;
__u64 uprobe_multi_func_2_result = 0;
__u64 uprobe_multi_func_3_result = 0;
__u64 uretprobe_multi_func_1_result = 0;
__u64 uretprobe_multi_func_2_result = 0;
__u64 uretprobe_multi_func_3_result = 0;
__u64 uprobe_multi_sleep_result = 0;
int pid = 0;
int child_pid = 0;
bool test_cookie = false;
void *user_ptr = 0;
static __always_inline bool verify_sleepable_user_copy(void)
{
char data[9];
bpf_copy_from_user(data, sizeof(data), user_ptr);
return bpf_strncmp(data, sizeof(data), "test_data") == 0;
}
static void uprobe_multi_check(void *ctx, bool is_return, bool is_sleep)
{
child_pid = bpf_get_current_pid_tgid() >> 32;
if (pid && child_pid != pid)
return;
__u64 cookie = test_cookie ? bpf_get_attach_cookie(ctx) : 0;
__u64 addr = bpf_get_func_ip(ctx);
#define SET(__var, __addr, __cookie) ({ \
if (addr == __addr && \
(!test_cookie || (cookie == __cookie))) \
__var += 1; \
})
if (is_return) {
SET(uretprobe_multi_func_1_result, uprobe_multi_func_1_addr, 2);
SET(uretprobe_multi_func_2_result, uprobe_multi_func_2_addr, 3);
SET(uretprobe_multi_func_3_result, uprobe_multi_func_3_addr, 1);
} else {
SET(uprobe_multi_func_1_result, uprobe_multi_func_1_addr, 3);
SET(uprobe_multi_func_2_result, uprobe_multi_func_2_addr, 1);
SET(uprobe_multi_func_3_result, uprobe_multi_func_3_addr, 2);
}
#undef SET
if (is_sleep && verify_sleepable_user_copy())
uprobe_multi_sleep_result += 1;
}
SEC("uprobe.multi//proc/self/exe:uprobe_multi_func_*")
int uprobe(struct pt_regs *ctx)
{
uprobe_multi_check(ctx, false, false);
return 0;
}
SEC("uretprobe.multi//proc/self/exe:uprobe_multi_func_*")
int uretprobe(struct pt_regs *ctx)
{
uprobe_multi_check(ctx, true, false);
return 0;
}
SEC("uprobe.multi.s//proc/self/exe:uprobe_multi_func_*")
int uprobe_sleep(struct pt_regs *ctx)
{
uprobe_multi_check(ctx, false, true);
return 0;
}
SEC("uretprobe.multi.s//proc/self/exe:uprobe_multi_func_*")
int uretprobe_sleep(struct pt_regs *ctx)
{
uprobe_multi_check(ctx, true, true);
return 0;
}
SEC("uprobe.multi//proc/self/exe:uprobe_multi_func_*")
int uprobe_extra(struct pt_regs *ctx)
{
return 0;
}
// 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";
int count;
SEC("uprobe.multi/./uprobe_multi:uprobe_multi_func_*")
int uprobe_bench(struct pt_regs *ctx)
{
count++;
return 0;
}
// SPDX-License-Identifier: GPL-2.0
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/usdt.bpf.h>
char _license[] SEC("license") = "GPL";
int count;
SEC("usdt")
int usdt0(struct pt_regs *ctx)
{
count++;
return 0;
}
......@@ -7,6 +7,7 @@
#include <stdbool.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <time.h>
int parse_num_list(const char *s, bool **set, int *set_len);
__u32 link_info_prog_id(const struct bpf_link *link, struct bpf_link_info *info);
......@@ -33,4 +34,13 @@ int load_bpf_testmod(bool verbose);
int unload_bpf_testmod(bool verbose);
int kern_sync_rcu(void);
static inline __u64 get_time_ns(void)
{
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (u64)t.tv_sec * 1000000000 + t.tv_nsec;
}
#endif /* __TESTING_HELPERS_H */
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <string.h>
#include <sdt.h>
#define __PASTE(a, b) a##b
#define PASTE(a, b) __PASTE(a, b)
#define NAME(name, idx) PASTE(name, idx)
#define DEF(name, idx) int NAME(name, idx)(void) { return 0; }
#define CALL(name, idx) NAME(name, idx)();
#define F(body, name, idx) body(name, idx)
#define F10(body, name, idx) \
F(body, PASTE(name, idx), 0) F(body, PASTE(name, idx), 1) F(body, PASTE(name, idx), 2) \
F(body, PASTE(name, idx), 3) F(body, PASTE(name, idx), 4) F(body, PASTE(name, idx), 5) \
F(body, PASTE(name, idx), 6) F(body, PASTE(name, idx), 7) F(body, PASTE(name, idx), 8) \
F(body, PASTE(name, idx), 9)
#define F100(body, name, idx) \
F10(body, PASTE(name, idx), 0) F10(body, PASTE(name, idx), 1) F10(body, PASTE(name, idx), 2) \
F10(body, PASTE(name, idx), 3) F10(body, PASTE(name, idx), 4) F10(body, PASTE(name, idx), 5) \
F10(body, PASTE(name, idx), 6) F10(body, PASTE(name, idx), 7) F10(body, PASTE(name, idx), 8) \
F10(body, PASTE(name, idx), 9)
#define F1000(body, name, idx) \
F100(body, PASTE(name, idx), 0) F100(body, PASTE(name, idx), 1) F100(body, PASTE(name, idx), 2) \
F100(body, PASTE(name, idx), 3) F100(body, PASTE(name, idx), 4) F100(body, PASTE(name, idx), 5) \
F100(body, PASTE(name, idx), 6) F100(body, PASTE(name, idx), 7) F100(body, PASTE(name, idx), 8) \
F100(body, PASTE(name, idx), 9)
#define F10000(body, name, idx) \
F1000(body, PASTE(name, idx), 0) F1000(body, PASTE(name, idx), 1) F1000(body, PASTE(name, idx), 2) \
F1000(body, PASTE(name, idx), 3) F1000(body, PASTE(name, idx), 4) F1000(body, PASTE(name, idx), 5) \
F1000(body, PASTE(name, idx), 6) F1000(body, PASTE(name, idx), 7) F1000(body, PASTE(name, idx), 8) \
F1000(body, PASTE(name, idx), 9)
F10000(DEF, uprobe_multi_func_, 0)
F10000(DEF, uprobe_multi_func_, 1)
F10000(DEF, uprobe_multi_func_, 2)
F10000(DEF, uprobe_multi_func_, 3)
F10000(DEF, uprobe_multi_func_, 4)
static int bench(void)
{
F10000(CALL, uprobe_multi_func_, 0)
F10000(CALL, uprobe_multi_func_, 1)
F10000(CALL, uprobe_multi_func_, 2)
F10000(CALL, uprobe_multi_func_, 3)
F10000(CALL, uprobe_multi_func_, 4)
return 0;
}
#define PROBE STAP_PROBE(test, usdt);
#define PROBE10 PROBE PROBE PROBE PROBE PROBE \
PROBE PROBE PROBE PROBE PROBE
#define PROBE100 PROBE10 PROBE10 PROBE10 PROBE10 PROBE10 \
PROBE10 PROBE10 PROBE10 PROBE10 PROBE10
#define PROBE1000 PROBE100 PROBE100 PROBE100 PROBE100 PROBE100 \
PROBE100 PROBE100 PROBE100 PROBE100 PROBE100
#define PROBE10000 PROBE1000 PROBE1000 PROBE1000 PROBE1000 PROBE1000 \
PROBE1000 PROBE1000 PROBE1000 PROBE1000 PROBE1000
static int usdt(void)
{
PROBE10000
PROBE10000
PROBE10000
PROBE10000
PROBE10000
return 0;
}
int main(int argc, char **argv)
{
if (argc != 2)
goto error;
if (!strcmp("bench", argv[1]))
return bench();
if (!strcmp("usdt", argv[1]))
return usdt();
error:
fprintf(stderr, "usage: %s <bench|usdt>\n", argv[0]);
return -1;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment