bpf: permit multiple bpf attachments for a single perf tracepoint event
Subsystems (8)
v2 cover bpf: permit multiple bpf attachments for a single perf tracepoint event
This patch set adds support to permit multiple bpf prog attachments for a single perf tracepoint event. Patch 1 does some cleanup such that perf_event_{set|free}_bpf_handler is called under the same condition. Patch 2 has the core implementation, and Patch 3 adds a test case.
Changelogs:
v1 -> v2:
. fix a potential deadlock issue discovered by Daniel. . fix some coding style issues.
Yonghong Song (3):
bpf: use the same condition in perf event set/free bpf handler
bpf: permit multiple bpf attachments for a single perf event
bpf: add a test case to test single tp multiple bpf attachment
include/linux/bpf.h | 30 +++++++++++++--- include/linux/trace_events.h | 33 ++++++++++++++--- include/trace/perf.h | 6 ++-- kernel/bpf/core.c | 81 ++++++++++++++++++++++++++++++++++++++++++ kernel/events/core.c | 30 ++++++---------- kernel/trace/bpf_trace.c | 82 ++++++++++++++++++++++++++++++++++++++++--- kernel/trace/trace_kprobe.c | 6 ++-- kernel/trace/trace_syscalls.c | 34 ++++++++++-------- kernel/trace/trace_uprobe.c | 3 +- samples/bpf/syscall_tp_user.c | 66 +++++++++++++++++++++++++++------- 10 files changed, 300 insertions(+), 71 deletions(-)
Signature · 1 line
2.9.5
PATCH v2 1/3 bpf: use the same condition in perf event set/free bpf handler
This is a cleanup such that doing the same check in perf_event_free_bpf_prog as we already do in perf_event_set_bpf_prog step.
--- kernel/events/core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
kernel/events/core.c +3 -3
@@ -8191,10 +8191,10 @@ static void perf_event_free_bpf_prog(struct perf_event *event) {struct bpf_prog *prog; -perf_event_free_bpf_handler (event); - -if (!event->tp_event) +if (event->attr.type !=PERF_TYPE_TRACEPOINT ) { +perf_event_free_bpf_handler (event);return ; + } prog = event->tp_event->prog;if (prog && event->tp_event->bpf_prog_owner == event) {
Signature · 1 line
2.9.5
PATCH v2 2/3 bpf: permit multiple bpf attachments for a single perf event
In reply to Yonghong SongThis patch enables multiple bpf attachments for a kprobe/uprobe/tracepoint single trace event. Each trace_event keeps a list of attached perf events. When an event happens, all attached bpf programs will be executed based on the order of attachment.
A global bpf_event_mutex lock is introduced to protect prog_array attaching and detaching. An alternative will be introduce a mutex lock in every trace_event_call structure, but it takes a lot of extra memory. So a global bpf_event_mutex lock is a good compromise.
The bpf prog detachment involves allocation of memory. If the allocation fails, a dummy do-nothing program will replace to-be-detached program in-place.
--- include/linux/bpf.h | 30 +++++++++++++--- include/linux/trace_events.h | 33 ++++++++++++++--- include/trace/perf.h | 6 ++-- kernel/bpf/core.c | 81 ++++++++++++++++++++++++++++++++++++++++++ kernel/events/core.c | 26 +++++--------- kernel/trace/bpf_trace.c | 82 ++++++++++++++++++++++++++++++++++++++++--- kernel/trace/trace_kprobe.c | 6 ++-- kernel/trace/trace_syscalls.c | 34 ++++++++++-------- kernel/trace/trace_uprobe.c | 3 +- 9 files changed, 245 insertions(+), 56 deletions(-)
include/linux/bpf.h +25 -5
@@ -273,18 +273,38 @@ int bpf_prog_array_length(struct bpf_prog_array __rcu *progs);int bpf_prog_array_copy_to_user (struct bpf_prog_array __rcu *progs,__u32 __user *prog_ids,u32 cnt); -#define BPF_PROG_RUN_ARRAY (array, ctx, func) \ +void bpf_prog_array_delete_safe (struct bpf_prog_array __rcu *progs, +struct bpf_prog *old_prog); +int bpf_prog_array_copy (struct bpf_prog_array __rcu *old_array, +struct bpf_prog *exclude_prog, +struct bpf_prog *include_prog, +struct bpf_prog_array **new_array); + +#define __BPF_PROG_RUN_ARRAY (array, ctx, func, check_non_null) \ ({ \ - struct bpf_prog **_prog; \ + struct bpf_prog **_prog, *__prog; \ + struct bpf_prog_array *_array; \ u32 _ret = 1; \ rcu_read_lock(); \ - _prog = rcu_dereference(array)->progs; \ - for (; *_prog; _prog++) \ - _ret &= func(*_prog, ctx); \ + _array = rcu_dereference(array); \ + if (unlikely(check_non_null && !_array))\ + goto _out; \ + _prog = _array->progs; \ + while ((__prog = READ_ONCE(*_prog))) { \ + _ret &= func(__prog, ctx); \ + _prog++; \ + } \ +_out: \ rcu_read_unlock(); \ _ret; \ }) +#define BPF_PROG_RUN_ARRAY (array, ctx, func) \ + __BPF_PROG_RUN_ARRAY(array, ctx, func, false) + +#define BPF_PROG_RUN_ARRAY_CHECK (array, ctx, func) \ + __BPF_PROG_RUN_ARRAY(array, ctx, func, true) +#ifdef CONFIG_BPF_SYSCALL DECLARE_PER_CPU (int, bpf_prog_active);
include/linux/trace_events.h +29 -4
@@ -271,14 +271,37 @@ struct trace_event_call {#ifdef CONFIG_PERF_EVENTS int perf_refcount;struct hlist_head __percpu *perf_events; -struct bpf_prog *prog; -struct perf_event *bpf_prog_owner; +struct bpf_prog_array __rcu *prog_array;int (*perf_perm)(struct trace_event_call *,struct perf_event *);#endif }; +#ifdef CONFIG_PERF_EVENTS +static inline bool bpf_prog_array_valid (struct trace_event_call *call) +{ +/* +* This inline function checks whether call->prog_array +* is valid or not. The function is called in various places, +* outside rcu_read_lock/unlock, as a heuristic to speed up execution. +* +* If this function returns true, and later call->prog_array +* becomes false inside rcu_read_lock/unlock region, +* we bail out then. If this function return false, +* there is a risk that we might miss a few events if the checking +* were delayed until inside rcu_read_lock/unlock region and +* call->prog_array happened to become non-NULL then. +* +* Here, READ_ONCE() is used instead of rcu_access_pointer(). +* rcu_access_pointer() requires the actual definition of +* "struct bpf_prog_array" while READ_ONCE() only needs +* a declaration of the same type. +*/ +return !!READ_ONCE (call->prog_array); +} +#endif +static inline const char *trace_event_name (struct trace_event_call *call) {
@@ -435,9 +458,9 @@ trace_trigger_soft_disabled(struct trace_event_file *file) }#ifdef CONFIG_BPF_EVENTS -unsigned int trace_call_bpf (struct bpf_prog *prog,void *ctx); +unsigned int trace_call_bpf (struct trace_event_call *call,void *ctx);#else -static inline unsigned int trace_call_bpf (struct bpf_prog *prog,void *ctx) +static inline unsigned int trace_call_bpf (struct trace_event_call *call,void *ctx) {return 1 ; }
@@ -499,6 +522,8 @@ extern void ftrace_profile_free_filter(struct perf_event *event);void perf_trace_buf_update (void *record,u16 type);void *perf_trace_buf_alloc (int size,struct pt_regs **regs,int *rctxp); +int perf_event_attach_bpf_prog (struct perf_event *event,struct bpf_prog *prog); +void perf_event_detach_bpf_prog (struct perf_event *event); void perf_trace_run_bpf_submit(void *raw_data, int size, int rctx, struct trace_event_call *call, u64 count, struct pt_regs *regs, struct hlist_head *head,
include/trace/perf.h +3 -3
@@ -34,7 +34,6 @@ perf_trace_##call(void *__data, proto) \struct trace_event_call *event_call = __data; \ struct trace_event_data_offsets_##call __maybe_unused __data_offsets;\ struct trace_event_raw_##call *entry; \ - struct bpf_prog *prog = event_call->prog; \ struct pt_regs *__regs; \ u64 __count = 1; \ struct task_struct *__task = NULL; \
@@ -46,8 +45,9 @@ perf_trace_##call(void *__data, proto) \ __data_size = trace_event_get_offsets_##call(&__data_offsets, args); \ \ head = this_cpu_ptr(event_call->perf_events); \ - if (!prog && __builtin_constant_p(!__task) && !__task && \ - hlist_empty(head)) \ + if (!bpf_prog_array_valid(event_call) && \ + __builtin_constant_p(!__task) && !__task && \ + hlist_empty(head)) \ return; \ \ __entry_size = ALIGN(__data_size + sizeof(*entry) + sizeof(u32),\
kernel/bpf/core.c +81 -0
93 lines not on this page · raw message
kernel/events/core.c +8 -18
@@ -7954,11 +7954,9 @@ void perf_trace_run_bpf_submit(void *raw_data, int size, int rctx, struct pt_regs *regs, struct hlist_head *head, struct task_struct *task) { - struct bpf_prog *prog = call->prog; - -if (prog) { + if (bpf_prog_array_valid(call)) { *(struct pt_regs **)raw_data = regs; -if (!trace_call_bpf (prog, raw_data) ||hlist_empty (head)) { +if (!trace_call_bpf (call, raw_data) ||hlist_empty (head)) {perf_swevent_put_recursion_context (rctx);return ; }
@@ -8147,13 +8145,11 @@ static int perf_event_set_bpf_prog(struct perf_event *event, u32 prog_fd) {bool is_kprobe, is_tracepoint, is_syscall_tp;struct bpf_prog *prog; +int ret;if (event->attr.type !=PERF_TYPE_TRACEPOINT )return perf_event_set_bpf_handler (event, prog_fd); -if (event->tp_event->prog) -return -EEXIST ; - is_kprobe = event->tp_event->flags &TRACE_EVENT_FL_UKPROBE ; is_tracepoint = event->tp_event->flags &TRACE_EVENT_FL_TRACEPOINT ; is_syscall_tp =is_syscall_trace_event (event->tp_event);
@@ -8181,26 +8177,20 @@ static int perf_event_set_bpf_prog(struct perf_event *event, u32 prog_fd) return -EACCES; } } - event->tp_event->prog = prog; - event->tp_event->bpf_prog_owner = event; -return 0 ; + ret =perf_event_attach_bpf_prog (event, prog); +if (ret) +bpf_prog_put (prog); +return ret; }static void perf_event_free_bpf_prog (struct perf_event *event) { -struct bpf_prog *prog; -if (event->attr.type !=PERF_TYPE_TRACEPOINT ) {perf_event_free_bpf_handler (event);return ; } - - prog = event->tp_event->prog; -if (prog && event->tp_event->bpf_prog_owner == event) { - event->tp_event->prog =NULL ; -bpf_prog_put (prog); - } +perf_event_detach_bpf_prog (event); }#else
kernel/trace/bpf_trace.c +77 -5
103 lines not on this page · raw message
kernel/trace/trace_kprobe.c +2 -4
@@ -1174,13 +1174,12 @@ static void kprobe_perf_func(struct trace_kprobe *tk, struct pt_regs *regs) { struct trace_event_call *call = &tk->tp.call; -struct bpf_prog *prog = call->prog;struct kprobe_trace_entry_head *entry;struct hlist_head *head;int size, __size, dsize;int rctx; -if (prog && !trace_call_bpf (prog, regs)) +if (bpf_prog_array_valid (call) && !trace_call_bpf (call, regs))return ; head =this_cpu_ptr (call->perf_events);
@@ -1210,13 +1209,12 @@ kretprobe_perf_func(struct trace_kprobe *tk, struct kretprobe_instance *ri, struct pt_regs *regs) {struct trace_event_call *call = &tk->tp.call; -struct bpf_prog *prog = call->prog;struct kretprobe_trace_entry_head *entry;struct hlist_head *head;int size, __size, dsize;int rctx; -if (prog && !trace_call_bpf (prog, regs)) +if (bpf_prog_array_valid (call) && !trace_call_bpf (call, regs))return ; head =this_cpu_ptr (call->perf_events);
kernel/trace/trace_syscalls.c +19 -15
@@ -559,9 +559,10 @@ static DECLARE_BITMAP(enabled_perf_exit_syscalls, NR_syscalls);static int sys_perf_refcount_enter;static int sys_perf_refcount_exit; -static int perf_call_bpf_enter (struct bpf_prog *prog,struct pt_regs *regs, -struct syscall_metadata *sys_data, -struct syscall_trace_enter *rec) { +static int perf_call_bpf_enter (struct trace_event_call *call,struct pt_regs *regs, +struct syscall_metadata *sys_data, +struct syscall_trace_enter *rec) +{struct syscall_tp_t {unsigned long long regs;unsigned long syscall_nr;
@@ -573,7 +574,7 @@ static int perf_call_bpf_enter(struct bpf_prog *prog, struct pt_regs *regs, param.syscall_nr = rec->nr;for (i =0 ; i < sys_data->nb_args; i++) param.args[i] = rec->args[i]; -return trace_call_bpf (prog, ¶m); +return trace_call_bpf (call, ¶m); }static void perf_syscall_enter (void *ignore,struct pt_regs *regs,long id)
@@ -581,7 +582,7 @@ static void perf_syscall_enter(void *ignore, struct pt_regs *regs, long id)struct syscall_metadata *sys_data;struct syscall_trace_enter *rec;struct hlist_head *head; -struct bpf_prog *prog; +bool valid_prog_array;int syscall_nr;int rctx;int size;
@@ -596,9 +597,9 @@ static void perf_syscall_enter(void *ignore, struct pt_regs *regs, long id)if (!sys_data)return ; - prog =READ_ONCE (sys_data->enter_event->prog); head =this_cpu_ptr (sys_data->enter_event->perf_events); -if (!prog &&hlist_empty (head)) + valid_prog_array =bpf_prog_array_valid (sys_data->enter_event); +if (!valid_prog_array &&hlist_empty (head))return ;/* get the size after alignment with the u32 buffer size field */
@@ -614,7 +615,8 @@ static void perf_syscall_enter(void *ignore, struct pt_regs *regs, long id)syscall_get_arguments (current, regs,0 , sys_data->nb_args, (unsigned long *)&rec->args); -if ((prog && !perf_call_bpf_enter (prog, regs, sys_data, rec)) || +if ((valid_prog_array && + !perf_call_bpf_enter (sys_data->enter_event, regs, sys_data, rec)) ||hlist_empty (head)) {perf_swevent_put_recursion_context (rctx);return ;
@@ -659,8 +661,9 @@ static void perf_sysenter_disable(struct trace_event_call *call)mutex_unlock (&syscall_trace_lock); } -static int perf_call_bpf_exit (struct bpf_prog *prog,struct pt_regs *regs, -struct syscall_trace_exit *rec) { +static int perf_call_bpf_exit (struct trace_event_call *call,struct pt_regs *regs, +struct syscall_trace_exit *rec) +{struct syscall_tp_t {unsigned long long regs;unsigned long syscall_nr;
@@ -670,7 +673,7 @@ static int perf_call_bpf_exit(struct bpf_prog *prog, struct pt_regs *regs, *(struct pt_regs **)¶m = regs; param.syscall_nr = rec->nr; param.ret = rec->ret; -return trace_call_bpf (prog, ¶m); +return trace_call_bpf (call, ¶m); }static void perf_syscall_exit (void *ignore,struct pt_regs *regs,long ret)
@@ -678,7 +681,7 @@ static void perf_syscall_exit(void *ignore, struct pt_regs *regs, long ret)struct syscall_metadata *sys_data;struct syscall_trace_exit *rec;struct hlist_head *head; -struct bpf_prog *prog; +bool valid_prog_array;int syscall_nr;int rctx;int size;
@@ -693,9 +696,9 @@ static void perf_syscall_exit(void *ignore, struct pt_regs *regs, long ret)if (!sys_data)return ; - prog =READ_ONCE (sys_data->exit_event->prog); head =this_cpu_ptr (sys_data->exit_event->perf_events); -if (!prog &&hlist_empty (head)) + valid_prog_array =bpf_prog_array_valid (sys_data->exit_event); +if (!valid_prog_array &&hlist_empty (head))return ;/* We can probably do that at build time */
@@ -709,7 +712,8 @@ static void perf_syscall_exit(void *ignore, struct pt_regs *regs, long ret) rec->nr = syscall_nr; rec->ret =syscall_get_return_value (current, regs); -if ((prog && !perf_call_bpf_exit (prog, regs, rec)) || +if ((valid_prog_array && + !perf_call_bpf_exit (sys_data->exit_event, regs, rec)) ||hlist_empty (head)) {perf_swevent_put_recursion_context (rctx);return ;
kernel/trace/trace_uprobe.c +1 -2
@@ -1113,13 +1113,12 @@ static void __uprobe_perf_func(struct trace_uprobe *tu, {struct trace_event_call *call = &tu->tp.call;struct uprobe_trace_entry_head *entry; -struct bpf_prog *prog = call->prog;struct hlist_head *head;void *data;int size, esize;int rctx; -if (prog && !trace_call_bpf (prog, regs)) +if (bpf_prog_array_valid (call) && !trace_call_bpf (call, regs))return ; esize =SIZEOF_TRACE_ENTRY (is_ret_probe (tu));
Signature · 1 line
2.9.5
PATCH v2 3/3 bpf: add a test case to test single tp multiple bpf attachment
In reply to Yonghong SongThe bpf sample program syscall_tp is modified to show attachment of more than bpf programs for a particular kernel tracepoint.
--- samples/bpf/syscall_tp_user.c | 66 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 13 deletions(-)
samples/bpf/syscall_tp_user.c +53 -13
@@ -23,6 +23,13 @@* This requires kernel CONFIG_FTRACE_SYSCALLS to be set. */ +static void usage (const char *cmd) +{ +printf ("USAGE: %s [-i num_progs] [-h]\n" , cmd); +printf (" -i num_progs # number of progs of the test\n" ); +printf (" -h # help\n" ); +} +static void verify_map (int map_id) {__u32 key =0 ;
@@ -32,22 +39,29 @@ static void verify_map(int map_id)fprintf (stderr,"map_lookup failed: %s\n" ,strerror (errno));return ; } -if (val ==0 ) +if (val ==0 ) {fprintf (stderr,"failed: map #%d returns value 0\n" , map_id); +return ; + } + val =0 ; +if (bpf_map_update_elem (map_id, &key, &val,BPF_ANY ) !=0 ) { +fprintf (stderr,"map_update failed: %s\n" ,strerror (errno)); +return ; + } } -int main (int argc,char **argv) +static int test (char *filename,int num_progs) { -struct rlimit r = {RLIM_INFINITY ,RLIM_INFINITY }; -char filename[256 ]; -int fd; +int i, fd, map0_fds[num_progs], map1_fds[num_progs]; -setrlimit (RLIMIT_MEMLOCK , &r); -snprintf (filename,sizeof (filename),"%s_kern.o" , argv[0 ]); - -if (load_bpf_file (filename)) { -fprintf (stderr,"%s" , bpf_log_buf); -return 1 ; +for (i =0 ; i < num_progs; i++) { +if (load_bpf_file (filename)) { +fprintf (stderr,"%s" , bpf_log_buf); +return 1 ; + } +printf ("prog #%d: map ids %d %d\n" , i, map_fd[0 ], map_fd[1 ]); + map0_fds[i] = map_fd[0 ]; + map1_fds[i] = map_fd[1 ]; }/* current load_bpf_file has perf_event_open default pid = -1
@@ -64,8 +78,34 @@ int main(int argc, char **argv)close (fd);/* verify the map */ -verify_map (map_fd[0 ]); -verify_map (map_fd[1 ]); +for (i =0 ; i < num_progs; i++) { +verify_map (map0_fds[i]); +verify_map (map1_fds[i]); + }return 0 ; } + +int main (int argc,char **argv) +{ +struct rlimit r = {RLIM_INFINITY ,RLIM_INFINITY }; +int opt, num_progs =1 ; +char filename[256 ]; + +while ((opt =getopt (argc, argv,"i:h" )) !=-1 ) { +switch (opt) { +case 'i' : + num_progs =atoi (optarg); +break ; +case 'h' : +default : +usage (argv[0 ]); +return 0 ; + } + } + +setrlimit (RLIMIT_MEMLOCK , &r); +snprintf (filename,sizeof (filename),"%s_kern.o" , argv[0 ]); + +return test (filename, num_progs); +}
Signature · 1 line
2.9.5