From: Jiri Olsa <hidden> Date: 2021-11-18 11:25:54
hi,
sending new version of batch attach support, previous post is in
here [1].
As discussed in previous post adding this version is adding generic
approach to attach program to multiple functions, meaning you are
now able to attach multiple trampolines on top of each other with
no limitations, which was not possible in previous version.
v5 changes:
- ftrace new direct batch api got merged, so it's not longer
part of this patchset (thanks Steven!), adding just few small
related fixes
- added code that allows attach trampolines on top of each other
as per previous version comments
- fixed btf__find_by_glob_kind as per Andrii's comments
- added new selftest to check on the trampoline's splitting
- added in bpf_arg/bpf_ret_value helpers with tests
I'm not completely sure about the approach, because it was quite
difficult to debug/finalize, but I can't think of anything better
for now.. so sending this as RFC ;)
Also available at:
https://git.kernel.org/pub/scm/linux/kernel/git/jolsa/perf.git
bpf/batch
thanks,
jirka
[1] https://lore.kernel.org/bpf/20210826193922.66204-1-jolsa@kernel.org/
Cc: Steven Rostedt <redacted>
---
Jiri Olsa (29):
ftrace: Use direct_ops hash in unregister_ftrace_direct
ftrace: Add cleanup to unregister_ftrace_direct_multi
ftrace: Add ftrace_set_filter_ips function
bpf: Factor bpf_check_attach_target function
bpf: Add bpf_check_attach_model function
bpf: Add bpf_arg/bpf_ret_value helpers for tracing programs
bpf, x64: Allow to use caller address from stack
bpf: Keep active attached trampoline in bpf_prog
bpf: Add support to load multi func tracing program
bpf: Add bpf_trampoline_id object
bpf: Add addr to bpf_trampoline_id object
bpf: Add struct bpf_tramp_node layer
bpf: Add bpf_tramp_attach layer for trampoline attachment
bpf: Add support to store multiple ids in bpf_tramp_id object
bpf: Add support to store multiple addrs in bpf_tramp_id object
bpf: Add bpf_tramp_id_single function
bpf: Resolve id in bpf_tramp_id_single
bpf: Add refcount_t to struct bpf_tramp_id
bpf: Add support to attach trampolines with multiple IDs
bpf: Add support for tracing multi link
libbpf: Add btf__find_by_glob_kind function
libbpf: Add support to link multi func tracing program
selftests/bpf: Add bpf_arg/bpf_ret_value test
selftests/bpf: Add fentry multi func test
selftests/bpf: Add fexit multi func test
selftests/bpf: Add fentry/fexit multi func test
selftests/bpf: Add mixed multi func test
selftests/bpf: Add ret_mod multi func test
selftests/bpf: Add attach multi func test
arch/x86/net/bpf_jit_comp.c | 31 ++-
include/linux/bpf.h | 76 +++++--
include/linux/bpf_verifier.h | 23 +--
include/linux/ftrace.h | 3 +
include/uapi/linux/bpf.h | 26 +++
kernel/bpf/core.c | 4 +-
kernel/bpf/syscall.c | 379 +++++++++++++++++++++++++++++++----
kernel/bpf/trampoline.c | 926 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
kernel/bpf/verifier.c | 173 +++++++++++++---
kernel/kallsyms.c | 2 +-
kernel/trace/bpf_trace.c | 38 +++-
kernel/trace/ftrace.c | 61 +++++-
tools/include/uapi/linux/bpf.h | 26 +++
tools/lib/bpf/bpf.c | 7 +
tools/lib/bpf/bpf.h | 6 +-
tools/lib/bpf/btf.c | 77 ++++++++
tools/lib/bpf/btf.h | 3 +
tools/lib/bpf/libbpf.c | 66 +++++++
tools/testing/selftests/bpf/Makefile | 10 +-
tools/testing/selftests/bpf/prog_tests/args_test.c | 34 ++++
tools/testing/selftests/bpf/prog_tests/modify_return.c | 114 ++++++++++-
tools/testing/selftests/bpf/prog_tests/multi_attach_test.c | 176 +++++++++++++++++
tools/testing/selftests/bpf/prog_tests/multi_fentry_fexit_test.c | 32 +++
tools/testing/selftests/bpf/prog_tests/multi_fentry_test.c | 30 +++
tools/testing/selftests/bpf/prog_tests/multi_fexit_test.c | 31 +++
tools/testing/selftests/bpf/prog_tests/multi_mixed_test.c | 34 ++++
tools/testing/selftests/bpf/progs/args_test.c | 30 +++
tools/testing/selftests/bpf/progs/multi_attach.c | 105 ++++++++++
tools/testing/selftests/bpf/progs/multi_check.c | 86 ++++++++
tools/testing/selftests/bpf/progs/multi_fentry.c | 17 ++
tools/testing/selftests/bpf/progs/multi_fentry_fexit.c | 28 +++
tools/testing/selftests/bpf/progs/multi_fexit.c | 20 ++
tools/testing/selftests/bpf/progs/multi_mixed.c | 43 ++++
tools/testing/selftests/bpf/progs/multi_modify_return.c | 17 ++
34 files changed, 2539 insertions(+), 195 deletions(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/args_test.c
create mode 100644 tools/testing/selftests/bpf/prog_tests/multi_attach_test.c
create mode 100644 tools/testing/selftests/bpf/prog_tests/multi_fentry_fexit_test.c
create mode 100644 tools/testing/selftests/bpf/prog_tests/multi_fentry_test.c
create mode 100644 tools/testing/selftests/bpf/prog_tests/multi_fexit_test.c
create mode 100644 tools/testing/selftests/bpf/prog_tests/multi_mixed_test.c
create mode 100644 tools/testing/selftests/bpf/progs/args_test.c
create mode 100644 tools/testing/selftests/bpf/progs/multi_attach.c
create mode 100644 tools/testing/selftests/bpf/progs/multi_check.c
create mode 100644 tools/testing/selftests/bpf/progs/multi_fentry.c
create mode 100644 tools/testing/selftests/bpf/progs/multi_fentry_fexit.c
create mode 100644 tools/testing/selftests/bpf/progs/multi_fexit.c
create mode 100644 tools/testing/selftests/bpf/progs/multi_mixed.c
create mode 100644 tools/testing/selftests/bpf/progs/multi_modify_return.c
From: Jiri Olsa <hidden> Date: 2021-11-18 11:26:08
Now when we have *direct_multi interface the direct_functions
hash is no longer owned just by direct_ops. It's also used by
any other ftrace_ops passed to *direct_multi interface.
Thus to find out that we are unregistering the last function
from direct_ops, we need to check directly direct_ops's hash.
Cc: Steven Rostedt <redacted>
Fixes: f64dd4627ec6 ("ftrace: Add multi direct register/unregister interface")
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
kernel/trace/ftrace.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
@@ -5217,6 +5217,7 @@ int unregister_ftrace_direct(unsigned long ip, unsigned long addr){structftrace_direct_func*direct;structftrace_func_entry*entry;+structftrace_hash*hash;intret=-ENODEV;mutex_lock(&direct_mutex);
@@ -5225,7 +5226,8 @@ int unregister_ftrace_direct(unsigned long ip, unsigned long addr)if(!entry)gotoout_unlock;-if(direct_functions->count==1)+hash=direct_ops.func_hash->filter_hash;+if(hash->count==1)unregister_ftrace_function(&direct_ops);ret=ftrace_set_filter_ip(&direct_ops,ip,1,0);
From: Jiri Olsa <hidden> Date: 2021-11-18 11:26:26
Adding ops cleanup to unregister_ftrace_direct_multi,
so it can be reused in another register call.
Cc: Steven Rostedt <redacted>
Fixes: f64dd4627ec6 ("ftrace: Add multi direct register/unregister interface")
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
kernel/trace/ftrace.c | 4 ++++
1 file changed, 4 insertions(+)
@@ -5542,6 +5542,10 @@ int unregister_ftrace_direct_multi(struct ftrace_ops *ops, unsigned long addr)err=unregister_ftrace_function(ops);remove_direct_functions_hash(hash,addr);mutex_unlock(&direct_mutex);++/* cleanup for possible another register call */+ops->func=NULL;+ops->trampoline=0;returnerr;}EXPORT_SYMBOL_GPL(unregister_ftrace_direct_multi);
From: Jiri Olsa <hidden> Date: 2021-11-18 11:26:27
Adding ftrace_set_filter_ips function to be able to set filter on
multiple ip addresses at once.
With the *direct_multi interface we have cases where we need to
initialize ftrace_ops object with thousands of functions, so having
single function diving into ftrace_hash_move_and_update_ops with
ftrace_lock is better.
The functions ips are passed as unsigned ong array with count.
Cc: Steven Rostedt <redacted>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
include/linux/ftrace.h | 3 +++
kernel/trace/ftrace.c | 53 +++++++++++++++++++++++++++++++++++-------
2 files changed, 47 insertions(+), 9 deletions(-)
From: Jiri Olsa <hidden> Date: 2021-11-18 11:26:30
Separating the check itself from model distilling and
address search into __bpf_check_attach_target function.
This way we can easily add function in following patch
that gets only function model without the address search,
while using the same code as bpf_check_attach_target.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
kernel/bpf/verifier.c | 79 ++++++++++++++++++++++++++++++++-----------
1 file changed, 59 insertions(+), 20 deletions(-)
@@ -13568,20 +13568,26 @@ static int check_non_sleepable_error_inject(u32 btf_id)returnbtf_id_set_contains(&btf_non_sleepable_error_inject,btf_id);}-intbpf_check_attach_target(structbpf_verifier_log*log,-conststructbpf_prog*prog,-conststructbpf_prog*tgt_prog,-u32btf_id,-structbpf_attach_target_info*tgt_info)+structattach_target{+conststructbtf_type*t;+constchar*tname;+intsubprog;+structbtf*btf;+};++staticint__bpf_check_attach_target(structbpf_verifier_log*log,+conststructbpf_prog*prog,+conststructbpf_prog*tgt_prog,+u32btf_id,+structattach_target*target){boolprog_extension=prog->type==BPF_PROG_TYPE_EXT;constcharprefix[]="btf_trace_";-intret=0,subprog=-1,i;+intsubprog=-1,i;conststructbtf_type*t;boolconservative=true;constchar*tname;structbtf*btf;-longaddr=0;if(!btf_id){bpf_log(log,"Tracing programs must provide btf_id\n");
@@ -13706,9 +13712,6 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,t=btf_type_by_id(btf,t->type);if(!btf_type_is_func_proto(t))return-EINVAL;-ret=btf_distill_func_proto(log,btf,t,tname,&tgt_info->fmodel);-if(ret)-returnret;break;default:if(!prog_extension)
@@ -13737,22 +13740,57 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,if(tgt_prog&&conservative)t=NULL;+}++target->t=t;+target->tname=tname;+target->subprog=subprog;+target->btf=btf;+return0;+}++intbpf_check_attach_target(structbpf_verifier_log*log,+conststructbpf_prog*prog,+conststructbpf_prog*tgt_prog,+u32btf_id,+structbpf_attach_target_info*tgt_info)+{+structattach_targettarget={};+longaddr=0;+intret;-ret=btf_distill_func_proto(log,btf,t,tname,&tgt_info->fmodel);+ret=__bpf_check_attach_target(log,prog,tgt_prog,btf_id,&target);+if(ret)+returnret;++switch(prog->expected_attach_type){+caseBPF_TRACE_RAW_TP:+break;+caseBPF_TRACE_ITER:+ret=btf_distill_func_proto(log,target.btf,target.t,target.tname,&tgt_info->fmodel);+if(ret)+returnret;+break;+default:+caseBPF_MODIFY_RETURN:+caseBPF_LSM_MAC:+caseBPF_TRACE_FENTRY:+caseBPF_TRACE_FEXIT:+ret=btf_distill_func_proto(log,target.btf,target.t,target.tname,&tgt_info->fmodel);if(ret<0)returnret;if(tgt_prog){-if(subprog==0)+if(target.subprog==0)addr=(long)tgt_prog->bpf_func;else-addr=(long)tgt_prog->aux->func[subprog]->bpf_func;+addr=(long)tgt_prog->aux->func[target.subprog]->bpf_func;}else{-addr=kallsyms_lookup_name(tname);+addr=kallsyms_lookup_name(target.tname);if(!addr){bpf_log(log,"The address of function %s cannot be found\n",-tname);+target.tname);return-ENOENT;}}
@@ -13779,7 +13817,7 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,break;}if(ret){-bpf_log(log,"%s is not sleepable\n",tname);+bpf_log(log,"%s is not sleepable\n",target.tname);returnret;}}elseif(prog->expected_attach_type==BPF_MODIFY_RETURN){
@@ -13787,18 +13825,19 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,bpf_log(log,"can't modify return codes of BPF programs\n");return-EINVAL;}-ret=check_attach_modify_return(addr,tname);+ret=check_attach_modify_return(addr,target.tname);if(ret){-bpf_log(log,"%s() is not modifiable\n",tname);+bpf_log(log,"%s() is not modifiable\n",target.tname);returnret;}}break;}+tgt_info->tgt_addr=addr;-tgt_info->tgt_name=tname;-tgt_info->tgt_type=t;+tgt_info->tgt_name=target.tname;+tgt_info->tgt_type=target.t;return0;}
From: Jiri Olsa <hidden> Date: 2021-11-18 11:26:32
Adding bpf_check_attach_model function that returns
model for function specified by btf_id. It will be
used in following patches.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
include/linux/bpf_verifier.h | 4 ++++
kernel/bpf/verifier.c | 29 +++++++++++++++++++++++++++++
2 files changed, 33 insertions(+)
From: Jiri Olsa <hidden> Date: 2021-11-18 11:26:35
Adding bpf_arg/bpf_ret_value helpers for tracing programs
that returns traced function arguments.
Get n-th argument of the traced function:
long bpf_arg(void *ctx, int n)
Get return value of the traced function:
long bpf_ret_value(void *ctx)
The trampoline now stores number of arguments on ctx-8
address, so it's easy to verify argument index and find
return value argument.
Moving function ip address on the trampoline stack behind
the number of functions arguments, so it's now stored
on ctx-16 address.
Both helpers are inlined by verifier.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
arch/x86/net/bpf_jit_comp.c | 18 +++++++++++---
include/uapi/linux/bpf.h | 14 +++++++++++
kernel/bpf/verifier.c | 45 ++++++++++++++++++++++++++++++++--
kernel/trace/bpf_trace.c | 38 +++++++++++++++++++++++++++-
tools/include/uapi/linux/bpf.h | 14 +++++++++++
5 files changed, 122 insertions(+), 7 deletions(-)
@@ -1987,12 +1987,22 @@ int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *iEMIT4(0x48,0x83,0xe8,X86_PATCH_SIZE);emit_stx(&prog,BPF_DW,BPF_REG_FP,BPF_REG_0,-stack_size);-/* Continue with stack_size for regs storage, stack will-*becorrectlyrestoredwith'leave'instruction.-*/+/* Continue with stack_size for 'nr_args' storage */stack_size-=8;}+/* Store number of arguments of the traced function:+*movrax,nr_args+*movQWORDPTR[rbp-stack_size],rax+*/+emit_mov_imm64(&prog,BPF_REG_0,0,(u32)nr_args);+emit_stx(&prog,BPF_DW,BPF_REG_FP,BPF_REG_0,-stack_size);++/* Continue with stack_size for regs storage, stack will+*becorrectlyrestoredwith'leave'instruction.+*/+stack_size-=8;+save_regs(m,&prog,nr_args,stack_size);if(flags&BPF_TRAMP_F_CALL_ORIG){
@@ -4957,6 +4957,18 @@ union bpf_attr {***-ENOENT**if*task->mm*isNULL,ornovmacontains*addr*.***-EBUSY**iffailedtotrylockmmap_lock.***-EINVAL**forinvalid**flags**.+*+*longbpf_arg(void*ctx,intn)+*Description+*Getn-thargumentofthetracedfunction(fortracingprograms).+*Return+*Valueoftheargument.+*+*longbpf_ret_value(void*ctx)+*Description+*Getreturnvalueofthetracedfunction(fortracingprograms).+*Return+*Returnvalueofthetracedfunction.*/#define __BPF_FUNC_MAPPER(FN) \FN(unspec),\
@@ -5140,6 +5152,8 @@ union bpf_attr {FN(skc_to_unix_sock),\FN(kallsyms_lookup_name),\FN(find_vma),\+FN(arg),\+FN(ret_value),\/* *//* integer value in 'imm' field of BPF_CALL instruction selects which helper
@@ -1012,7 +1012,7 @@ const struct bpf_func_proto bpf_snprintf_btf_proto = {BPF_CALL_1(bpf_get_func_ip_tracing,void*,ctx){/* This helper call is inlined by verifier. */-return((u64*)ctx)[-1];+return((u64*)ctx)[-2];}staticconststructbpf_func_protobpf_get_func_ip_proto_tracing={
@@ -1091,6 +1091,38 @@ static const struct bpf_func_proto bpf_get_branch_snapshot_proto = {.arg2_type=ARG_CONST_SIZE_OR_ZERO,};+BPF_CALL_2(bpf_arg,void*,ctx,int,n)+{+/* This helper call is inlined by verifier. */+u64nr_args=((u64*)ctx)[-1];++if((u64)n>=nr_args)+return0;+return((u64*)ctx)[n];+}++staticconststructbpf_func_protobpf_arg_proto={+.func=bpf_arg,+.gpl_only=true,+.ret_type=RET_INTEGER,+.arg1_type=ARG_PTR_TO_CTX,+.arg1_type=ARG_ANYTHING,+};++BPF_CALL_1(bpf_ret_value,void*,ctx)+{+/* This helper call is inlined by verifier. */+u64nr_args=((u64*)ctx)[-1];++return((u64*)ctx)[nr_args];+}++staticconststructbpf_func_protobpf_ret_value_proto={+.func=bpf_ret_value,+.gpl_only=true,+.ret_type=RET_INTEGER,+};+staticconststructbpf_func_proto*bpf_tracing_func_proto(enumbpf_func_idfunc_id,conststructbpf_prog*prog){
@@ -4957,6 +4957,18 @@ union bpf_attr {***-ENOENT**if*task->mm*isNULL,ornovmacontains*addr*.***-EBUSY**iffailedtotrylockmmap_lock.***-EINVAL**forinvalid**flags**.+*+*longbpf_arg(void*ctx,intn)+*Description+*Getn-thargumentofthetracedfunction(fortracingprograms).+*Return+*Valueoftheargument.+*+*longbpf_ret_value(void*ctx)+*Description+*Getreturnvalueofthetracedfunction(fortracingprograms).+*Return+*Returnvalueofthetracedfunction.*/#define __BPF_FUNC_MAPPER(FN) \FN(unspec),\
@@ -5140,6 +5152,8 @@ union bpf_attr {FN(skc_to_unix_sock),\FN(kallsyms_lookup_name),\FN(find_vma),\+FN(arg),\+FN(ret_value),\/* *//* integer value in 'imm' field of BPF_CALL instruction selects which helper
From: Jiri Olsa <hidden> Date: 2021-11-18 11:26:49
Currently we call the original function by using the absolute address
given at the JIT generation. That's not usable when having trampoline
attached to multiple functions. In this case we need to take the
return address from the stack.
Adding support to retrieve the original function address from the stack
by adding new BPF_TRAMP_F_ORIG_STACK flag for arch_prepare_bpf_trampoline
function.
Basically we take the return address of the 'fentry' call:
function + 0: call fentry # stores 'function + 5' address on stack
function + 5: ...
The 'function + 5' address will be used as the address for the
original function to call.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
arch/x86/net/bpf_jit_comp.c | 13 +++++++++----
include/linux/bpf.h | 5 +++++
2 files changed, 14 insertions(+), 4 deletions(-)
@@ -2035,10 +2035,15 @@ int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *iif(flags&BPF_TRAMP_F_CALL_ORIG){restore_regs(m,&prog,nr_args,stack_size);-/* call original function */-if(emit_call(&prog,orig_call,prog)){-ret=-EINVAL;-gotocleanup;+if(flags&BPF_TRAMP_F_ORIG_STACK){+emit_ldx(&prog,BPF_DW,BPF_REG_0,BPF_REG_FP,8);+EMIT2(0xff,0xd0);/* call *rax */+}else{+/* call original function */+if(emit_call(&prog,orig_call,prog)){+ret=-EINVAL;+gotocleanup;+}}/* remember return value in a stack for bpf prog to access */emit_stx(&prog,BPF_DW,BPF_REG_FP,BPF_REG_0,-8);
@@ -594,6 +594,11 @@ struct btf_func_model {/* Return the return value of fentry prog. Only used by bpf_struct_ops. */#define BPF_TRAMP_F_RET_FENTRY_RET BIT(4)+/* Get original function from stack instead of from provided direct address.+*Makessenseforfexitprogramsonly.+*/+#define BPF_TRAMP_F_ORIG_STACK BIT(5)+/* Each call __bpf_prog_enter + call bpf_func + call __bpf_prog_exit is ~50*bytesonx86.PickanumbertofitintoBPF_IMAGE_SIZE/2*/
From: Jiri Olsa <hidden> Date: 2021-11-18 11:26:53
Keeping active attached trampoline in bpf_prog so it can be used
in following changes to account for multiple functions attachments
in program.
As EXT programs are not going to be supported in multiple functions
attachment for now, I'm keeping them stored in link.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
include/linux/bpf.h | 1 +
kernel/bpf/syscall.c | 34 +++++++++++++++++++++++++++++-----
2 files changed, 30 insertions(+), 5 deletions(-)
@@ -2623,15 +2623,28 @@ struct bpf_tracing_link {structbpf_prog*tgt_prog;};+staticstructbpf_trampoline*link_trampoline(structbpf_tracing_link*link)+{+structbpf_prog*prog=link->link.prog;++if(prog->type==BPF_PROG_TYPE_EXT)+returnlink->trampoline;+else+returnprog->aux->trampoline;+}+staticvoidbpf_tracing_link_release(structbpf_link*link){structbpf_tracing_link*tr_link=container_of(link,structbpf_tracing_link,link);+structbpf_trampoline*tr=link_trampoline(tr_link);+structbpf_prog*prog=link->prog;-WARN_ON_ONCE(bpf_trampoline_unlink_prog(link->prog,-tr_link->trampoline));+WARN_ON_ONCE(bpf_trampoline_unlink_prog(link->prog,tr));-bpf_trampoline_put(tr_link->trampoline);+if(prog->type!=BPF_PROG_TYPE_EXT)+prog->aux->trampoline=NULL;+bpf_trampoline_put(tr);/* tgt_prog is NULL if target is a kernel function */if(tr_link->tgt_prog)
@@ -2662,9 +2675,10 @@ static int bpf_tracing_link_fill_link_info(const struct bpf_link *link,{structbpf_tracing_link*tr_link=container_of(link,structbpf_tracing_link,link);+structbpf_trampoline*tr=link_trampoline(tr_link);info->tracing.attach_type=tr_link->attach_type;-bpf_trampoline_unpack_key(tr_link->trampoline->key,+bpf_trampoline_unpack_key(tr->key,&info->tracing.target_obj_id,&info->tracing.target_btf_id);
@@ -2682,6 +2696,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,inttgt_prog_fd,u32btf_id){+boolprog_extension=prog->type==BPF_PROG_TYPE_EXT;structbpf_link_primerlink_primer;structbpf_prog*tgt_prog=NULL;structbpf_trampoline*tr=NULL;
@@ -2748,6 +2763,11 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,mutex_lock(&prog->aux->dst_mutex);+if(!prog_extension&&prog->aux->trampoline){+err=-EBUSY;+gotoout_unlock;+}+/* There are a few possible cases here:**-ifprog->aux->dst_trampolineisset,theprogramwasjustloaded
@@ -2824,7 +2844,11 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,}link->tgt_prog=tgt_prog;-link->trampoline=tr;++if(prog_extension)+link->trampoline=tr;+else+prog->aux->trampoline=tr;/* Always clear the trampoline and target prog from prog->aux to make*suretheoriginalattachdestinationisnotkeptaliveaftera
From: Jiri Olsa <hidden> Date: 2021-11-18 11:27:00
Adding support to load tracing program with new BPF_F_MULTI_FUNC flag,
that allows the program to be loaded without specific function to be
attached to.
Such program will be allowed to be attached to multiple functions
in following patches.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
include/linux/bpf.h | 1 +
include/uapi/linux/bpf.h | 7 +++++++
kernel/bpf/syscall.c | 35 +++++++++++++++++++++++++++++-----
kernel/bpf/verifier.c | 3 ++-
tools/include/uapi/linux/bpf.h | 7 +++++++
5 files changed, 47 insertions(+), 6 deletions(-)
@@ -1111,6 +1111,13 @@ enum bpf_link_type {*/#define BPF_F_SLEEPABLE (1U << 4)+/* If BPF_F_MULTI_FUNC is used in BPF_PROG_LOAD command, the verifier does+*notexpectBTFIDfortheprogram,insteaditassumesit'sfunction+*with6u64arguments.Notrampolineiscreatedfortheprogram.Such+*programcanbeattachedtomultiplefunctions.+*/+#define BPF_F_MULTI_FUNC (1U << 5)+/* When BPF ldimm64's insn[0].src_reg != 0 then this can have*thefollowingextensions:*
@@ -2183,6 +2193,16 @@ static bool is_perfmon_prog_type(enum bpf_prog_type prog_type)}}+#define DEFINE_BPF_MULTI_FUNC(args...) \+externintbpf_multi_func(args);\+int__initbpf_multi_func(args){return0;}++DEFINE_BPF_MULTI_FUNC(unsignedlonga1,unsignedlonga2,+unsignedlonga3,unsignedlonga4,+unsignedlonga5,unsignedlonga6)++BTF_ID_LIST_SINGLE(bpf_multi_func_btf_id,func,bpf_multi_func)+/* last field in 'union bpf_attr' used by this command */#define BPF_PROG_LOAD_LAST_FIELD fd_array
@@ -2233,6 +2255,8 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr)if(is_perfmon_prog_type(type)&&!perfmon_capable())return-EPERM;+multi_func=attr->prog_flags&BPF_F_MULTI_FUNC;+/* attach_prog_fd/attach_btf_obj_fd can specify fd of either bpf_prog*orbtf,weneedtocheckwhichoneitis*/
@@ -2251,7 +2275,7 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr)return-ENOTSUPP;}}-}elseif(attr->attach_btf_id){+}elseif(attr->attach_btf_id||multi_func){/* fall back to vmlinux BTF, if BTF type ID is specified */attach_btf=bpf_get_btf_vmlinux();if(IS_ERR(attach_btf))
@@ -1111,6 +1111,13 @@ enum bpf_link_type {*/#define BPF_F_SLEEPABLE (1U << 4)+/* If BPF_F_MULTI_FUNC is used in BPF_PROG_LOAD command, the verifier does+*notexpectBTFIDfortheprogram,insteaditassumesit'sfunction+*with6u64arguments.Notrampolineiscreatedfortheprogram.Such+*programcanbeattachedtomultiplefunctions.+*/+#define BPF_F_MULTI_FUNC (1U << 5)+/* When BPF ldimm64's insn[0].src_reg != 0 then this can have*thefollowingextensions:*
From: Jiri Olsa <hidden> Date: 2021-11-18 11:27:08
Replacing the bpf_trampoline's key with struct bpf_tramp_id object,
that currently holds only obj_id/btf_id, so same data as key.
Having the key in the struct will allow us to add more ids (functions)
to single trampoline in following patches.
No functional change is intended.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
include/linux/bpf.h | 18 +++++++++--
include/linux/bpf_verifier.h | 19 ------------
kernel/bpf/syscall.c | 30 +++++++++++++-----
kernel/bpf/trampoline.c | 59 +++++++++++++++++++++++++++++++-----
kernel/bpf/verifier.c | 14 ++++++---
5 files changed, 99 insertions(+), 41 deletions(-)
@@ -671,13 +671,18 @@ struct bpf_tramp_image {};};+structbpf_tramp_id{+u32obj_id;+u32btf_id;+};+structbpf_trampoline{/* hlist for trampoline_table */structhlist_nodehlist;/* serializes access to fields of this trampoline */structmutexmutex;refcount_trefcnt;-u64key;+structbpf_tramp_id*id;struct{structbtf_func_modelmodel;void*addr;
@@ -503,25 +503,6 @@ int check_ctx_reg(struct bpf_verifier_env *env,intcheck_mem_reg(structbpf_verifier_env*env,structbpf_reg_state*reg,u32regno,u32mem_size);-/* this lives here instead of in bpf.h because it needs to dereference tgt_prog */-staticinlineu64bpf_trampoline_compute_key(conststructbpf_prog*tgt_prog,-structbtf*btf,u32btf_id)-{-if(tgt_prog)-return((u64)tgt_prog->aux->id<<32)|btf_id;-else-return((u64)btf_obj_id(btf)<<32)|0x80000000|btf_id;-}--/* unpack the IDs from the key as constructed above */-staticinlinevoidbpf_trampoline_unpack_key(u64key,u32*obj_id,u32*btf_id)-{-if(obj_id)-*obj_id=key>>32;-if(btf_id)-*btf_id=key&0x7FFFFFFF;-}-intbpf_check_attach_target(structbpf_verifier_log*log,conststructbpf_prog*prog,conststructbpf_prog*tgt_prog,
@@ -2703,9 +2703,8 @@ static int bpf_tracing_link_fill_link_info(const struct bpf_link *link,structbpf_trampoline*tr=link_trampoline(tr_link);info->tracing.attach_type=tr_link->attach_type;-bpf_trampoline_unpack_key(tr->key,-&info->tracing.target_obj_id,-&info->tracing.target_btf_id);+info->tracing.target_obj_id=tr->id->obj_id;+info->tracing.target_btf_id=tr->id->btf_id;return0;}
@@ -2726,7 +2725,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,structbpf_prog*tgt_prog=NULL;structbpf_trampoline*tr=NULL;structbpf_tracing_link*link;-u64key=0;+structbpf_tramp_id*id=NULL;interr;switch(prog->type){
@@ -2767,6 +2766,12 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,gotoout_put_prog;}+id=bpf_tramp_id_alloc();+if(!id){+err=-ENOMEM;+gotoout_put_prog;+}+tgt_prog=bpf_prog_get(tgt_prog_fd);if(IS_ERR(tgt_prog)){err=PTR_ERR(tgt_prog);
@@ -2774,7 +2779,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,gotoout_put_prog;}-key=bpf_trampoline_compute_key(tgt_prog,NULL,btf_id);+bpf_tramp_id_init(id,tgt_prog,NULL,btf_id);}link=kzalloc(sizeof(*link),GFP_USER);
@@ -2823,12 +2828,20 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,err=-EINVAL;gotoout_unlock;}++id=bpf_tramp_id_alloc();+if(!id){+err=-ENOMEM;+gotoout_unlock;+}+btf_id=prog->aux->attach_btf_id;-key=bpf_trampoline_compute_key(NULL,prog->aux->attach_btf,btf_id);+bpf_tramp_id_init(id,NULL,prog->aux->attach_btf,btf_id);}if(!prog->aux->dst_trampoline||-(key&&key!=prog->aux->dst_trampoline->key)){+(!bpf_tramp_id_is_empty(id)&&+bpf_tramp_id_is_equal(id,prog->aux->dst_trampoline->id))){/* If there is no saved target, or the specified target is*differentfromthedestinationspecifiedatloadtime,we*needanewtrampolineandacheckforcompatibility
@@ -2840,7 +2853,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,if(err)gotoout_unlock;-tr=bpf_trampoline_get(key,&tgt_info);+tr=bpf_trampoline_get(id,&tgt_info);if(!tr){err=-ENOMEM;gotoout_unlock;
@@ -2900,6 +2913,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,out_put_prog:if(tgt_prog_fd&&tgt_prog)bpf_prog_put(tgt_prog);+bpf_tramp_id_free(id);returnerr;}
From: Jiri Olsa <hidden> Date: 2021-11-18 11:27:35
Adding addr to bpf_trampoline_id object so it's not associated
directly with trampoline directly. This will help us to easily
support multiple ids/addresses support for trampolines coming
in following changes.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
include/linux/bpf.h | 4 ++--
kernel/bpf/trampoline.c | 18 +++++++++---------
2 files changed, 11 insertions(+), 11 deletions(-)
@@ -685,11 +686,10 @@ struct bpf_trampoline {structbpf_tramp_id*id;struct{structbtf_func_modelmodel;-void*addr;boolftrace_managed;}func;/* if !NULL this is BPF_PROG_TYPE_EXT program that extends another BPF-*programbyreplacingoneofitsfunctions.func.addristheaddress+*programbyreplacingoneofitsfunctions.id->addristheaddress*ofthefunctionitreplaced.*/structbpf_prog*extension_prog;
From: Jiri Olsa <hidden> Date: 2021-11-18 11:28:04
Currently each trampoline holds a list of programs that
are attached to it. With multi func attach support we need
a way for a single program to be connected to multiple
trampolines.
Adding struct bpf_tramp_node object that holds bpf_prog
pointer, so it can be resolved directly. We can now
have multiple struct bpf_tramp_node being attached to
different trampolines pointing to single bpf_prog.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
include/linux/bpf.h | 15 ++++++++++-----
kernel/bpf/core.c | 1 +
kernel/bpf/syscall.c | 4 ++--
kernel/bpf/trampoline.c | 22 ++++++++++++----------
4 files changed, 25 insertions(+), 17 deletions(-)
@@ -894,7 +899,7 @@ struct bpf_prog_aux {boolsleepable;booltail_call_reachable;boolmulti_func;-structhlist_nodetramp_hlist;+structbpf_tramp_nodetramp_node;/* BTF_KIND_FUNC_PROTO for valid attach_btf_id */conststructbtf_type*attach_func_proto;/* function name for valid attach_btf_id */
From: Jiri Olsa <hidden> Date: 2021-11-18 11:28:15
Adding bpf_tramp_attach layer for trampoline attachment to
have extra layer on top of the trampoline. The reason is
that in following changes we will add multiple trampolines
for single program and we need entity to hold them.
The api in nutshell:
- each bpf_prog holds 'bpf_tramp_attach' object, which holds
list of 'struct bpf_tramp_node' objects:
struct bpf_tramp_attach {
struct bpf_tramp_id *id;
struct hlist_head nodes;
};
This allow us to hold multiple trampolines for each program.
- bpf_tramp_attach returns 'bpf_tramp_attach' object that
finds trampoline for given 'id' and adds it to the attach
object, no actuall program attachment is done, just trampoline
allocation
- bpf_tramp_attach_link does the actual attachment of the
program to trampoline
- bpf_tramp_attach_unlink unlinks all the trampolines present
in the attach object
- bpf_tramp_detach frees all the trampolines in attach object
Currently there'll be only single node added in attach object.
Following patches add support for multiple id trampolines,
and uses multiple nodes in attach object to hold trampoline
for given program.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
include/linux/bpf.h | 21 +++++--
kernel/bpf/core.c | 5 +-
kernel/bpf/syscall.c | 61 ++++++++++----------
kernel/bpf/trampoline.c | 122 ++++++++++++++++++++++++++++++++--------
kernel/bpf/verifier.c | 12 ++--
5 files changed, 156 insertions(+), 65 deletions(-)
@@ -888,8 +900,8 @@ struct bpf_prog_aux {conststructbpf_ctx_arg_aux*ctx_arg_info;structmutexdst_mutex;/* protects dst_* pointers below, *after* prog becomes visible */structbpf_prog*dst_prog;-structbpf_trampoline*dst_trampoline;-structbpf_trampoline*trampoline;+structbpf_tramp_attach*dst_attach;+structbpf_tramp_attach*attach;enumbpf_prog_typesaved_dst_prog_type;enumbpf_attach_typesaved_dst_attach_type;boolverifier_zext;/* Zero extensions has been inserted by verifier. */
@@ -899,7 +911,6 @@ struct bpf_prog_aux {boolsleepable;booltail_call_reachable;boolmulti_func;-structbpf_tramp_nodetramp_node;/* BTF_KIND_FUNC_PROTO for valid attach_btf_id */conststructbtf_type*attach_func_proto;/* function name for valid attach_btf_id */
@@ -2284,8 +2283,8 @@ static void bpf_prog_free_deferred(struct work_struct *work)if(aux->prog->has_callchain_buf)put_callchain_buffers();#endif-if(aux->dst_trampoline)-bpf_trampoline_put(aux->dst_trampoline);+if(aux->dst_attach)+bpf_tramp_detach(aux->dst_attach);for(i=0;i<aux->func_cnt;i++){/* We can just unlink the subprog poke descriptor table as*itwasoriginallylinkedtothemainprogramandisalso
@@ -2644,32 +2644,32 @@ struct bpf_link *bpf_link_get_from_fd(u32 ufd)structbpf_tracing_link{structbpf_linklink;enumbpf_attach_typeattach_type;-structbpf_trampoline*trampoline;+structbpf_tramp_attach*attach;structbpf_prog*tgt_prog;};-staticstructbpf_trampoline*link_trampoline(structbpf_tracing_link*link)+staticstructbpf_tramp_attach*link_attach(structbpf_tracing_link*link){structbpf_prog*prog=link->link.prog;if(prog->type==BPF_PROG_TYPE_EXT)-returnlink->trampoline;+returnlink->attach;else-returnprog->aux->trampoline;+returnprog->aux->attach;}staticvoidbpf_tracing_link_release(structbpf_link*link){structbpf_tracing_link*tr_link=container_of(link,structbpf_tracing_link,link);-structbpf_trampoline*tr=link_trampoline(tr_link);+structbpf_tramp_attach*attach=link_attach(tr_link);structbpf_prog*prog=link->prog;-WARN_ON_ONCE(bpf_trampoline_unlink_prog(&link->prog->aux->tramp_node,tr));+WARN_ON_ONCE(bpf_tramp_attach_unlink(attach));if(prog->type!=BPF_PROG_TYPE_EXT)-prog->aux->trampoline=NULL;-bpf_trampoline_put(tr);+prog->aux->attach=NULL;+bpf_tramp_detach(attach);/* tgt_prog is NULL if target is a kernel function */if(tr_link->tgt_prog)
@@ -2700,11 +2700,11 @@ static int bpf_tracing_link_fill_link_info(const struct bpf_link *link,{structbpf_tracing_link*tr_link=container_of(link,structbpf_tracing_link,link);-structbpf_trampoline*tr=link_trampoline(tr_link);+structbpf_tramp_attach*attach=link_attach(tr_link);info->tracing.attach_type=tr_link->attach_type;-info->tracing.target_obj_id=tr->id->obj_id;-info->tracing.target_btf_id=tr->id->btf_id;+info->tracing.target_obj_id=attach->id->obj_id;+info->tracing.target_btf_id=attach->id->btf_id;return0;}
@@ -2721,9 +2721,9 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,u32btf_id){boolprog_extension=prog->type==BPF_PROG_TYPE_EXT;+structbpf_tramp_attach*attach=NULL;structbpf_link_primerlink_primer;structbpf_prog*tgt_prog=NULL;-structbpf_trampoline*tr=NULL;structbpf_tracing_link*link;structbpf_tramp_id*id=NULL;interr;
@@ -2793,7 +2793,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,mutex_lock(&prog->aux->dst_mutex);-if(!prog_extension&&prog->aux->trampoline){+if(!prog_extension&&prog->aux->attach){err=-EBUSY;gotoout_unlock;}
@@ -2816,7 +2816,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,*-ifprog->aux->dst_trampolineandtgt_progisNULL,theprogram*wasdetachedandisgoingforre-attachment.*/-if(!prog->aux->dst_trampoline&&!tgt_prog){+if(!prog->aux->dst_attach&&!tgt_prog){/**Allowre-attachforTRACINGandLSMprograms.Ifit's*currentlylinked,bpf_trampoline_link_progwillfail.
@@ -2839,9 +2839,9 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,bpf_tramp_id_init(id,NULL,prog->aux->attach_btf,btf_id);}-if(!prog->aux->dst_trampoline||+if(!prog->aux->dst_attach||(!bpf_tramp_id_is_empty(id)&&-bpf_tramp_id_is_equal(id,prog->aux->dst_trampoline->id))){+bpf_tramp_id_is_equal(id,prog->aux->dst_attach->id))){/* If there is no saved target, or the specified target is*differentfromthedestinationspecifiedatloadtime,we*needanewtrampolineandacheckforcompatibility
@@ -2853,9 +2853,11 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,if(err)gotoout_unlock;-tr=bpf_trampoline_get(id,&tgt_info);-if(!tr){-err=-ENOMEM;+id->addr=(void*)tgt_info.tgt_addr;++attach=bpf_tramp_attach(id,tgt_prog,prog);+if(IS_ERR(attach)){+err=PTR_ERR(attach);gotoout_unlock;}}else{
@@ -2866,7 +2868,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,*canonlyhappenonceforanyprogram,asthesavedvaluesin*prog->auxareclearedbelow.*/-tr=prog->aux->dst_trampoline;+attach=prog->aux->dst_attach;tgt_prog=prog->aux->dst_prog;}
@@ -2874,7 +2876,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,if(err)gotoout_unlock;-err=bpf_trampoline_link_prog(&prog->aux->tramp_node,tr);+err=bpf_tramp_attach_link(attach);if(err){bpf_link_cleanup(&link_primer);link=NULL;
@@ -2882,32 +2884,31 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,}link->tgt_prog=tgt_prog;-if(prog_extension)-link->trampoline=tr;+link->attach=attach;else-prog->aux->trampoline=tr;+prog->aux->attach=attach;/* Always clear the trampoline and target prog from prog->aux to make*suretheoriginalattachdestinationisnotkeptaliveaftera*programis(re-)attachedtoanothertarget.*/if(prog->aux->dst_prog&&-(tgt_prog_fd||tr!=prog->aux->dst_trampoline))+(tgt_prog_fd||attach!=prog->aux->dst_attach))/* got extra prog ref from syscall, or attaching to different prog */bpf_prog_put(prog->aux->dst_prog);-if(prog->aux->dst_trampoline&&tr!=prog->aux->dst_trampoline)+if(prog->aux->dst_attach&&attach!=prog->aux->dst_attach)/* we allocated a new trampoline, so free the old one */-bpf_trampoline_put(prog->aux->dst_trampoline);+bpf_tramp_detach(prog->aux->dst_attach);prog->aux->dst_prog=NULL;-prog->aux->dst_trampoline=NULL;+prog->aux->dst_attach=NULL;mutex_unlock(&prog->aux->dst_mutex);returnbpf_link_settle(&link_primer);out_unlock:-if(tr&&tr!=prog->aux->dst_trampoline)-bpf_trampoline_put(tr);+if(attach&&attach!=prog->aux->dst_attach)+bpf_tramp_detach(attach);mutex_unlock(&prog->aux->dst_mutex);kfree(link);out_put_prog:
@@ -11,6 +11,7 @@#include<linux/rcupdate_wait.h>#include<linux/module.h>#include<linux/static_call.h>+#include<linux/bpf_verifier.h>/* dummy _ops. The verifier will operate on target program's ops. */conststructbpf_verifier_opsbpf_extension_verifier_ops={
From: Jiri Olsa <hidden> Date: 2021-11-18 11:28:29
Adding support to store multiple addrs in bpf_tramp_id object,
to provide address values for id values stored in the object.
The id->addr[idx] returns address value for id->id[idx] id.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
include/linux/bpf.h | 2 +-
kernel/bpf/syscall.c | 2 +-
kernel/bpf/trampoline.c | 20 ++++++++++++--------
kernel/bpf/verifier.c | 2 +-
4 files changed, 15 insertions(+), 11 deletions(-)
From: Jiri Olsa <hidden> Date: 2021-11-18 11:28:41
Adding bpf_tramp_id_single function as interface to
create trampoline with single ID and grouping together
the trampoline allocation with init that is used on
several places and save us few lines.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
include/linux/bpf.h | 5 ++---
kernel/bpf/syscall.c | 18 +++++++-----------
kernel/bpf/trampoline.c | 11 ++++++++---
kernel/bpf/verifier.c | 3 +--
4 files changed, 18 insertions(+), 19 deletions(-)
From: Jiri Olsa <hidden> Date: 2021-11-18 11:28:47
Moving the id resolving in the bpf_tramp_id_single function
so it's centralized together with the trampoline's allocation
and init.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
include/linux/bpf.h | 3 ++-
kernel/bpf/syscall.c | 21 ++++++---------------
kernel/bpf/trampoline.c | 18 +++++++++++++++---
kernel/bpf/verifier.c | 4 +---
4 files changed, 24 insertions(+), 22 deletions(-)
From: Jiri Olsa <hidden> Date: 2021-11-18 11:28:54
Adding refcount_t to struct bpf_tramp_id so we can
track its allocation and safely use one object on
more places in following changes.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
include/linux/bpf.h | 3 +++
kernel/bpf/syscall.c | 2 +-
kernel/bpf/trampoline.c | 16 +++++++++++++---
kernel/bpf/verifier.c | 2 +-
4 files changed, 18 insertions(+), 5 deletions(-)
From: Jiri Olsa <hidden> Date: 2021-11-18 11:29:01
Adding support to attach trampolines with multiple IDs.
This patch adds support to bpf_tramp_attach function to
attach given program to bpf_tramp_id object that holds
multiple BTF function IDs.
The process of attaching in bpf_tramp_attach is as follows:
- IDs in bpf_tramp_id object are sorted out to several new
bpf_tramp_id objects based on number of arguments of each
ID - so we end up with up to 6 bpf_tramp_id objects, that
we will create or find suitable trampoline for
- separating function IDs that have same number of arguments
save us troubles of handling different argument counts
within one trampoline
- now for each such bpf_tramp_id object we do following:
* search existing trampolines to find match or intersection
* if there's full match on IDs, we add program to existing
trampoline and we are done
* if there's intersection with existing trampoline,
we split it and add new program to the common part,
the rest of the IDs are attached to new trampoline
- we keep trampoline_table as place holder for all trampolines,
(while the has works only for single ID trampolines) so in case
there is no multi-id trampoline defined, we still use the fast
hash trampoline lookup
The bpf_tramp_attach assumes ID array is coming in sorted so it's
possible to run bsearch on it to do all the needed searches.
The splitting of the trampoline use the fact that we carry
'bpf_tramp_attach' object for each bpf_program, so when we split
trampoline that the program is attached to, we just add new
'bpf_tramp_node' object to the program's attach 'nodes'. This way
we keep track of all program's trampolines and it will be properly
detached when the program goes away.
The splitting of the trampoline is done with following steps:
- lock the trampoline
- unregister trampoline
- alloc the duplicate, which means that for all attached programs
of the original trampoline we create new bpf_tramp_node objects
and add them to these programs' attach objects
- then we assign new IDs (common and the rest) to both (original
and the duplicated) trampolines
- register both trampolines
- unlock the original trampoline
This patch only adds bpf_tramp_attach support to attach multiple
ID bpf_tramp_id object. The actual user interface for that comes
in following patch.
Now when each call to bpf_tramp_attach can change any program's attach
object, we need to take trampoline_mutex in both bpf_tramp_attach_link
and bpf_tramp_attach_unlink functions. Perhaps we could add new lock
to bpf_tramp_attach object to get rid of single lock for all.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
include/linux/bpf.h | 12 +-
kernel/bpf/trampoline.c | 717 +++++++++++++++++++++++++++++++++++++---
2 files changed, 674 insertions(+), 55 deletions(-)
@@ -703,6 +710,9 @@ struct bpf_trampoline {struct{structbtf_func_modelmodel;boolftrace_managed;+#ifdef CONFIG_FUNCTION_TRACER+structftrace_opsops;+#endif}func;/* if !NULL this is BPF_PROG_TYPE_EXT program that extends another BPF*programbyreplacingoneofitsfunctions.id->addristheaddress
@@ -12,6 +12,8 @@#include<linux/module.h>#include<linux/static_call.h>#include<linux/bpf_verifier.h>+#include<linux/bsearch.h>+#include<linux/minmax.h>/* dummy _ops. The verifier will operate on target program's ops. */conststructbpf_verifier_opsbpf_extension_verifier_ops={
@@ -435,17 +460,19 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr)structbpf_tramp_progs*tprogs;u32flags=BPF_TRAMP_F_RESTORE_REGS;boolip_arg=false;-interr,total;+interr=0,total;tprogs=bpf_trampoline_get_progs(tr,&total,&ip_arg);if(IS_ERR(tprogs))returnPTR_ERR(tprogs);if(total==0){-err=unregister_fentry(tr,tr->cur_image->image);-bpf_tramp_image_put(tr->cur_image);-tr->cur_image=NULL;-tr->selector=0;+if(tr->cur_image){+err=unregister_fentry(tr,tr->cur_image->image);+bpf_tramp_image_put(tr->cur_image);+tr->cur_image=NULL;+tr->selector=0;+}gotoout;}
@@ -456,8 +483,11 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr)}if(tprogs[BPF_TRAMP_FEXIT].nr_progs||-tprogs[BPF_TRAMP_MODIFY_RETURN].nr_progs)+tprogs[BPF_TRAMP_MODIFY_RETURN].nr_progs){flags=BPF_TRAMP_F_CALL_ORIG|BPF_TRAMP_F_SKIP_FRAME;+if(bpf_tramp_id_is_multi(tr->id))+flags|=BPF_TRAMP_F_ORIG_STACK;+}if(ip_arg)flags|=BPF_TRAMP_F_IP_ARG;
@@ -582,29 +612,29 @@ int bpf_trampoline_unlink_prog(struct bpf_tramp_node *node, struct bpf_trampolinreturnerr;}-voidbpf_trampoline_put(structbpf_trampoline*tr)+staticvoidbpf_trampoline_put(structbpf_trampoline*tr){if(!tr)return;-mutex_lock(&trampoline_mutex);if(!refcount_dec_and_test(&tr->refcnt))-gotoout;+return;WARN_ON_ONCE(mutex_is_locked(&tr->mutex));if(WARN_ON_ONCE(!hlist_empty(&tr->progs_hlist[BPF_TRAMP_FENTRY])))-gotoout;+return;if(WARN_ON_ONCE(!hlist_empty(&tr->progs_hlist[BPF_TRAMP_FEXIT])))-gotoout;+return;/* This code will be executed even when the last bpf_tramp_image*isalive.Allprogsaredetachedfromthetrampolineandthe*trampolineimageispatchedwithjmpintoepiloguetoskip*fexitprogs.Thefentry-onlytrampolinewillbefreedvia*multiplercucallbacks.*/+if(bpf_tramp_id_is_multi(tr->id))+nr_bpf_trampoline_multi--;hlist_del(&tr->hlist);bpf_tramp_id_put(tr->id);+ftrace_free_filter(&tr->func.ops);kfree(tr);-out:-mutex_unlock(&trampoline_mutex);}staticstructbpf_tramp_node*node_alloc(structbpf_trampoline*tr,structbpf_prog*prog)
@@ -628,18 +658,442 @@ static void node_free(struct bpf_tramp_node *node)kfree(node);}-structbpf_tramp_attach*bpf_tramp_attach(structbpf_tramp_id*id,-structbpf_prog*tgt_prog,-structbpf_prog*prog)+staticvoidbpf_func_model_nargs(structbtf_func_model*m,intnr_args)+{+inti;++for(i=0;i<nr_args;i++)+m->arg_size[i]=8;+m->ret_size=8;+m->nr_args=nr_args;+}++structattach_args{+intnr_args;+structbpf_prog*tgt_prog;+structbpf_prog*prog;+};++staticintbpf_trampoline_setup(structbpf_trampoline*tr,+structattach_args*att)+{+structbpf_tramp_id*id=tr->id;++if(bpf_tramp_id_is_multi(id)){+bpf_func_model_nargs(&tr->func.model,att->nr_args);+returnftrace_set_filter_ips(&tr->func.ops,(long*)id->addr,+id->cnt,0,1);+}else{+returnbpf_check_attach_model(att->prog,att->tgt_prog,+id->id[0],&tr->func.model);+}+}++staticint+bpf_trampoline_create(structbpf_tramp_attach*attach,+structbpf_tramp_id*id,structattach_args*att){structbpf_trampoline*tr=NULL;-structbpf_tramp_attach*attach;structbpf_tramp_node*node;interr;-attach=kzalloc(sizeof(*attach),GFP_KERNEL);-if(!attach)-returnERR_PTR(-ENOMEM);+tr=bpf_trampoline_alloc(id);+if(!tr){+err=-ENOMEM;+gotoout;+}++err=bpf_trampoline_setup(tr,att);+if(err)+gotoout;++node=node_alloc(tr,att->prog);+if(!node){+err=-ENOMEM;+gotoout;+}++hlist_add_head(&node->hlist_attach,&attach->nodes);+return0;++out:+bpf_trampoline_put(tr);+returnerr;+}++staticvoidbpf_trampoline_dup_destroy(structbpf_trampoline*tr)+{+structbpf_tramp_node*node;+structhlist_node*n;+intkind;++if(!tr)+return;++for(kind=0;kind<BPF_TRAMP_MAX;kind++){+hlist_for_each_entry_safe(node,n,&tr->progs_hlist[kind],+hlist_tramp){+hlist_del(&node->hlist_tramp);+hlist_del(&node->hlist_attach);+node_free(node);+}+}++WARN_ON_ONCE(refcount_read(&tr->refcnt)!=1);+bpf_trampoline_put(tr);+}++staticstructbpf_trampoline*+bpf_trampoline_dup(structbpf_trampoline*tr,structbpf_tramp_id*id)+{+structbpf_tramp_node*node,*iter;+structbpf_trampoline*dup;+intkind;++/* Allocate new trampoline and duplicate all+*theprogramattachmentsithas.+*/+dup=bpf_trampoline_alloc(id);+if(!dup)+returnNULL;++dup->refcnt=tr->refcnt;++for(kind=0;kind<BPF_TRAMP_MAX;kind++){+hlist_for_each_entry(iter,&tr->progs_hlist[kind],hlist_tramp){+structbpf_prog*prog=iter->prog;++node=node_alloc(dup,prog);+if(!node)+gotoout_free;+hlist_add_head(&node->hlist_tramp,&dup->progs_hlist[kind]);+hlist_add_head(&node->hlist_attach,&prog->aux->attach->nodes);+dup->progs_cnt[kind]++;+}+}+returndup;++out_free:+bpf_trampoline_dup_destroy(dup);+returnNULL;+}++staticintbtf_id_cmp(constvoid*a,constvoid*b)+{+constu32*x=a;+constu32*y=b;++if(*x==*y)+return0;+return*x<*y?-1:1;+}++staticvoidid_add(structbpf_tramp_id*id,u32btf_id,void*addr)+{+if(WARN_ON_ONCE(id->cnt>=id->max))+return;+id->id[id->cnt]=btf_id;+id->addr[id->cnt]=addr;+id->cnt++;+}++staticstructbpf_tramp_id*id_check(structbpf_tramp_id*id)+{+if(bpf_tramp_id_is_empty(id)){+bpf_tramp_id_put(id);+id=NULL;+}+returnid;+}++staticintid_and(structbpf_tramp_id*a,structbpf_tramp_id*b,+structbpf_tramp_id**pand,structbpf_tramp_id**pother)+{+structbpf_tramp_id*and,*other;+u32i,id;++and=bpf_tramp_id_alloc(min(a->cnt,b->cnt));+other=bpf_tramp_id_alloc(max(a->cnt,b->cnt));+if(!and||!other){+bpf_tramp_id_put(and);+bpf_tramp_id_put(other);+return-ENOMEM;+}++and->obj_id=a->obj_id;+other->obj_id=a->obj_id;++for(i=0;i<a->cnt;i++){+id=a->id[i];+if(bsearch(&id,b->id,b->cnt,sizeof(u32),btf_id_cmp))+id_add(and,id,a->addr[i]);+else+id_add(other,id,a->addr[i]);+}++*pand=id_check(and);+*pother=id_check(other);+return0;+}++staticintid_sub(structbpf_tramp_id*a,structbpf_tramp_id*b,+structbpf_tramp_id**psub)+{+structbpf_tramp_id*sub;+u32i,id;++sub=bpf_tramp_id_alloc(max(a->cnt,b->cnt));+if(!sub)+return-ENOMEM;++sub->obj_id=a->obj_id;++if(a->cnt<b->cnt)+swap(a,b);++for(i=0;i<a->cnt;i++){+id=a->id[i];+if(!bsearch(&id,b->id,b->cnt,sizeof(u32),btf_id_cmp))+id_add(sub,id,a->addr[i]);+}++*psub=id_check(sub);+return0;+}++structtramp_state{+structbpf_trampoline*tr_common;+structbpf_trampoline*tr_other;+structbpf_tramp_id*id_common;+structbpf_tramp_id*id_other;+structbpf_tramp_id*id;+};++#define MAX_TRAMP_STATE 20++structattach_state{+structtramp_statets[MAX_TRAMP_STATE];+intcnt;+};++staticstructtramp_state*tramp_state_get(structattach_state*state)+{+if(state->cnt==MAX_TRAMP_STATE)+returnNULL;+return&state->ts[state->cnt];+}++staticvoidstate_next(structattach_state*state)+{+state->cnt++;+}++staticvoidstate_cleanup(structattach_state*state)+{+structtramp_state*ts;+inti;++for(i=0;i<state->cnt;i++){+ts=&state->ts[state->cnt];+bpf_tramp_id_put(ts->id_common);+bpf_tramp_id_put(ts->id_other);+bpf_tramp_id_put(ts->id);+}+}++staticinttramp_state_compute(structattach_state*state,+structbpf_trampoline*tr,+structbpf_tramp_id*id,+structbpf_tramp_id**id_cont)+{+structbpf_tramp_id*id_new,*id_common,*id_other;+structtramp_state*ts;++ts=tramp_state_get(state);+if(!ts)+return-EBUSY;++/* different playground.. bail out */+if(tr->id->obj_id!=id->obj_id){+*id_cont=bpf_tramp_id_get(id);+return0;+}++/* complete match with trampoline */+if(bpf_tramp_id_is_equal(tr->id,id)){+ts->id_common=bpf_tramp_id_get(id);+*id_cont=NULL;+gotoout;+}++/* find out if there's common set of ids */+if(id_and(id,tr->id,&id_common,&id_new))+return-ENOMEM;++/* nothing in common, bail out */+if(!id_common){+bpf_tramp_id_put(id_new);+*id_cont=bpf_tramp_id_get(id);+return0;+}++/* we have common set, let's get the rest of the matched+*trampolineidsasnewidforsplittrampoline+*/+if(id_sub(id_common,tr->id,&id_other)){+bpf_tramp_id_put(id_common);+bpf_tramp_id_put(id_new);+return-ENOMEM;+}++ts->id_common=id_common;+ts->id_other=id_other;+ts->id=bpf_tramp_id_get(tr->id);+*id_cont=id_new;++out:+ts->tr_common=tr;+state_next(state);+return0;+}++staticintbpf_trampoline_register(structbpf_trampoline*tr)+{+returnbpf_trampoline_update(tr);+}++staticintbpf_trampoline_unregister(structbpf_trampoline*tr)+{+interr;++if(!tr->cur_image)+return0;+err=unregister_fentry(tr,tr->cur_image->image);+bpf_tramp_image_put(tr->cur_image);+tr->cur_image=NULL;+tr->selector=0;+returnerr;+}++staticvoidbpf_trampoline_id_assign(structbpf_trampoline*tr,structbpf_tramp_id*id)+{+boolmulti1=bpf_tramp_id_is_multi(tr->id);+boolmulti2=bpf_tramp_id_is_multi(id);++/* We can split into single ID trampolines and that+*mightaffectnr_bpf_trampoline_multiandthefast+*pathtrigger,soweneedtocheckonthat.+*/+if(multi1&&!multi2)+nr_bpf_trampoline_multi--;+if(!multi1&&multi2)+nr_bpf_trampoline_multi++;++tr->id=id;+}++staticintbpf_trampoline_split(structtramp_state*ts,structattach_args*att)+{+structbpf_trampoline*tr_other,*tr_common=ts->tr_common;+structbpf_tramp_id*id_common=ts->id_common;+structbpf_tramp_id*id_other=ts->id_other;+interr;++mutex_lock(&tr_common->mutex);++err=bpf_trampoline_unregister(tr_common);+if(err)+gotoout;++tr_other=bpf_trampoline_dup(tr_common,id_other);+if(!tr_other){+err=-ENOMEM;+gotoout_free;+}++err=bpf_trampoline_setup(tr_other,att);+if(err)+gotoout_free;++bpf_trampoline_id_assign(tr_common,id_common);++err=bpf_trampoline_setup(tr_common,att);+if(err)+gotoout_free;++ts->tr_other=tr_other;+WARN_ON_ONCE(bpf_trampoline_register(tr_common));+WARN_ON_ONCE(bpf_trampoline_register(tr_other));++mutex_unlock(&tr_common->mutex);+return0;++out_free:+bpf_trampoline_dup_destroy(tr_other);+tr_common->id=ts->id;+WARN_ON_ONCE(bpf_trampoline_register(tr_common));+out:+mutex_unlock(&tr_common->mutex);+returnerr;+}++staticinttramp_state_apply(structbpf_tramp_attach*attach,+structtramp_state*ts,structattach_args*att)+{+structbpf_tramp_node*node;+interr;++/* The program will be attached to the common part. */+node=node_alloc(ts->tr_common,att->prog);+if(!node)+return-ENOMEM;++refcount_inc(&ts->tr_common->refcnt);++/* If there are also 'other' IDs in the trampoline,+*weneedtodothesplit.*/+if(ts->id_other){+err=bpf_trampoline_split(ts,att);+if(err){+node_free(node);+returnerr;+}+}++hlist_add_head(&node->hlist_attach,&attach->nodes);+return0;+}++staticinttramp_state_revert(structtramp_state*ts,structattach_args*att)+{+structbpf_trampoline*tr_common=ts->tr_common;+interr;++bpf_trampoline_dup_destroy(ts->tr_other);++mutex_lock(&tr_common->mutex);+err=bpf_trampoline_unregister(tr_common);+if(err)+gotoout;++tr_common->id=ts->id;+err=bpf_trampoline_setup(tr_common,att);+if(err)+gotoout;++WARN_ON_ONCE(bpf_trampoline_register(tr_common));+out:+mutex_unlock(&tr_common->mutex);+returnerr;+}++staticint+bpf_tramp_attach_single(structbpf_tramp_attach*attach,+structbpf_tramp_id*id,structattach_args*att)+{+structbpf_trampoline*tr=NULL;+structbpf_tramp_node*node;+interr;tr=bpf_trampoline_get(id);if(!tr){
@@ -647,22 +1101,175 @@ struct bpf_tramp_attach *bpf_tramp_attach(struct bpf_tramp_id *id,gotoout;}-node=node_alloc(tr,prog);+node=node_alloc(tr,att->prog);if(!node)gotoout;-err=bpf_check_attach_model(prog,tgt_prog,id->id[0],&tr->func.model);+err=bpf_check_attach_model(att->prog,att->tgt_prog,+id->id[0],&tr->func.model);if(err)gotoout;-attach->id=id;hlist_add_head(&node->hlist_attach,&attach->nodes);-returnattach;+return0;out:bpf_trampoline_put(tr);-kfree(attach);-returnERR_PTR(err);+returnerr;+}++#define list_for_each_trampoline(tr, i) \+for(i=0;i<TRAMPOLINE_TABLE_SIZE;i++)\+hlist_for_each_entry(tr,&trampoline_table[i],hlist)++staticint__bpf_tramp_attach(structbpf_tramp_attach*attach,+structbpf_tramp_id*id,+structattach_args*att)+{+structattach_statestate={};+structbpf_tramp_id*id_cont;+structbpf_trampoline*tr;+boolid_put=false;+interr=0,i,j;++mutex_lock(&trampoline_mutex);++/* If we are ataching single ID trampoline and there's no multi ID+*trampolineregistered,there'snoneedtoiteratealltrampolines+*forintersection,wecandothefastpathandusehashsearch.+**/+if(!bpf_tramp_id_is_multi(id)&&!nr_bpf_trampoline_multi){+err=bpf_tramp_attach_single(attach,id,att);+gotoout;+}++/* Iterate all trampolines to find all the interesections. */+list_for_each_trampoline(tr,i){+err=tramp_state_compute(&state,tr,id,&id_cont);+if(err)+gotoout_multi;+id_put=true;+id=id_cont;+if(!id)+gotoout_break;+}+out_break:++/* Do the actuall trampoline splits if there's any .. */+for(i=0;i<state.cnt;i++){+err=tramp_state_apply(attach,&state.ts[i],att);+if(err)+gotorevert;+}++/* .. and create new trampoline if needed. */+if(id)+err=bpf_trampoline_create(attach,id,att);++revert:+/* Attach failed, let's revert already changed trampolines */+if(err){+for(j=0;j<i;j++)+WARN_ON_ONCE(tramp_state_revert(&state.ts[j],att));+}++out_multi:+if(id_put)+bpf_tramp_id_put(id);+out:+mutex_unlock(&trampoline_mutex);+state_cleanup(&state);+returnerr;+}++#define MAX_ARGS 7++staticvoidput_args(structbpf_tramp_id**args)+{+inti;++for(i=0;i<MAX_ARGS;i++)+bpf_tramp_id_put(args[i]);+}++staticintget_args(structbpf_tramp_id*id,structbpf_tramp_id**args,+structbpf_prog*tgt_prog,structbpf_prog*prog)+{+conststructbtf_type*t;+structbpf_tramp_id*a;+conststructbtf*btf;+interr=-EINVAL;+u32i,nargs;++btf=tgt_prog?tgt_prog->aux->btf:prog->aux->attach_btf;+if(!btf)+return-EINVAL;++for(i=0;i<id->cnt;i++){+t=btf_type_by_id(btf,id->id[i]);+if(!btf_type_is_func(t))+gotoout_free;+t=btf_type_by_id(btf,t->type);+if(!btf_type_is_func_proto(t))+gotoout_free;+nargs=btf_type_vlen(t);+if(nargs>=MAX_ARGS)+gotoout_free;+a=args[nargs];+if(!a){+a=bpf_tramp_id_alloc(id->cnt);+if(!a){+err=-ENOMEM;+gotoout_free;+}+a->obj_id=id->obj_id;+args[nargs]=a;+}+id_add(a,id->id[i],id->addr[i]);+}+err=0;+out_free:+if(err)+put_args(args);+returnerr;+}++structbpf_tramp_attach*bpf_tramp_attach(structbpf_tramp_id*id,+structbpf_prog*tgt_prog,+structbpf_prog*prog)+{+structbpf_tramp_id*args[MAX_ARGS]={};+structbpf_tramp_attach*attach;+structattach_argsatt={+.tgt_prog=tgt_prog,+.prog=prog,+};+inti,err;++err=get_args(id,args,tgt_prog,prog);+if(err)+returnERR_PTR(err);++attach=kzalloc(sizeof(*attach),GFP_KERNEL);+if(!attach)+returnERR_PTR(-ENOMEM);++for(i=0;i<MAX_ARGS;i++){+if(!args[i]||!args[i]->cnt)+continue;+att.nr_args=i;+err=__bpf_tramp_attach(attach,args[i],&att);+if(err)+break;+}++if(err)+bpf_tramp_detach(attach);+else+attach->id=id;++put_args(args);+returnerr?ERR_PTR(err):attach;}voidbpf_tramp_detach(structbpf_tramp_attach*attach)
From: Jiri Olsa <hidden> Date: 2021-11-18 11:29:09
Adding new link to allow to attach program to multiple
function BTF IDs.
New fields are added to bpf_attr::link_create to pass
array of BTF IDs:
struct {
__aligned_u64 btf_ids; /* addresses to attach */
__u32 btf_ids_cnt; /* addresses count */
} multi;
The new link code will load these IDs into bpf_tramp_id
and resolve their ips.
The resolve itself is done as per Andrii's suggestion:
- lookup all names for given IDs
- store and sort them by name
- go through all kallsyms symbols and use bsearch
to find it in provided names
- if name is found, store the address for the name
- resort the names array based on ID
If there are multi symbols of the same name the first one
will be used to resolve the address.
The new link will pass them to the bpf_tramp_attach that
does all the work of attaching.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
include/uapi/linux/bpf.h | 5 +
kernel/bpf/syscall.c | 252 +++++++++++++++++++++++++++++++++
kernel/kallsyms.c | 2 +-
tools/include/uapi/linux/bpf.h | 5 +
4 files changed, 263 insertions(+), 1 deletion(-)
@@ -2905,6 +2908,251 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog,returnerr;}+structbpf_tracing_multi_link{+structbpf_linklink;+enumbpf_attach_typeattach_type;+structbpf_tramp_attach*attach;+};++staticvoidbpf_tracing_multi_link_release(structbpf_link*link)+{+structbpf_prog*prog=link->prog;+structbpf_tramp_attach*attach=prog->aux->attach;++WARN_ON_ONCE(bpf_tramp_attach_unlink(attach));++if(prog->type!=BPF_PROG_TYPE_EXT)+prog->aux->attach=NULL;+bpf_tramp_detach(attach);+}++staticvoidbpf_tracing_multi_link_dealloc(structbpf_link*link)+{+structbpf_tracing_multi_link*tr_link=+container_of(link,structbpf_tracing_multi_link,link);++kfree(tr_link);+}++staticvoidbpf_tracing_multi_link_show_fdinfo(conststructbpf_link*link,+structseq_file*seq)+{+structbpf_tracing_multi_link*tr_link=+container_of(link,structbpf_tracing_multi_link,link);++seq_printf(seq,"attach_type:\t%d\n",tr_link->attach_type);+}++staticintbpf_tracing_multi_link_fill_link_info(conststructbpf_link*link,+structbpf_link_info*info)+{+structbpf_tracing_multi_link*tr_link=+container_of(link,structbpf_tracing_multi_link,link);++info->tracing.attach_type=tr_link->attach_type;+return0;+}++staticconststructbpf_link_opsbpf_tracing_multi_link_lops={+.release=bpf_tracing_multi_link_release,+.dealloc=bpf_tracing_multi_link_dealloc,+.show_fdinfo=bpf_tracing_multi_link_show_fdinfo,+.fill_link_info=bpf_tracing_multi_link_fill_link_info,+};++staticintcheck_multi_prog_type(structbpf_prog*prog)+{+if(prog->expected_attach_type!=BPF_TRACE_FENTRY&&+prog->expected_attach_type!=BPF_TRACE_FEXIT)+return-EINVAL;+return0;+}++staticintbtf_ids_cmp(constvoid*a,constvoid*b)+{+constu32*x=a;+constu32*y=b;++if(*x==*y)+return0;+return*x<*y?-1:1;+}++structresolve_id{+constchar*name;+void*addr;+u32id;+};++staticintrid_name_cmp(constvoid*a,constvoid*b)+{+conststructresolve_id*x=a;+conststructresolve_id*y=b;++returnstrcmp(x->name,y->name);+}++staticintrid_id_cmp(constvoid*a,constvoid*b)+{+conststructresolve_id*x=a;+conststructresolve_id*y=b;++if(x->id==y->id)+return0;+returnx->id<y->id?-1:1;+}++structkallsyms_data{+structresolve_id*rid;+u32cnt;+u32found;+};++staticintkallsyms_callback(void*data,constchar*name,+structmodule*mod,unsignedlongaddr)+{+structkallsyms_data*args=data;+structresolve_id*rid,id={+.name=name,+};++rid=bsearch(&id,args->rid,args->cnt,sizeof(*rid),rid_name_cmp);+if(rid&&!rid->addr){+rid->addr=(void*)addr;+args->found++;+}+returnargs->found==args->cnt?1:0;+}++staticintbpf_tramp_id_resolve(structbpf_tramp_id*id,structbpf_prog*prog)+{+structkallsyms_dataargs;+conststructbtf_type*t;+structresolve_id*rid;+constchar*name;+structbtf*btf;+interr=0;+u32i;++btf=prog->aux->attach_btf;+if(!btf)+return-EINVAL;++rid=kzalloc(id->cnt*sizeof(*rid),GFP_KERNEL);+if(!rid)+return-ENOMEM;++err=-EINVAL;+for(i=0;i<id->cnt;i++){+t=btf_type_by_id(btf,id->id[i]);+if(!t)+gotoout_free;++name=btf_name_by_offset(btf,t->name_off);+if(!name)+gotoout_free;++rid[i].name=name;+rid[i].id=id->id[i];+}++sort(rid,id->cnt,sizeof(*rid),rid_name_cmp,NULL);++args.rid=rid;+args.cnt=id->cnt;+args.found=0;+kallsyms_on_each_symbol(kallsyms_callback,&args);++sort(rid,id->cnt,sizeof(*rid),rid_id_cmp,NULL);++for(i=0;i<id->cnt;i++){+if(!rid[i].addr){+err=-EINVAL;+gotoout_free;+}+id->addr[i]=rid[i].addr;+}+err=0;+out_free:+kfree(rid);+returnerr;+}++staticintbpf_tracing_multi_attach(structbpf_prog*prog,+constunionbpf_attr*attr)+{+void__user*uids=u64_to_user_ptr(attr->link_create.multi.btf_ids);+u32cnt_size,cnt=attr->link_create.multi.btf_ids_cnt;+structbpf_tracing_multi_link*link=NULL;+structbpf_link_primerlink_primer;+structbpf_tramp_attach*attach;+structbpf_tramp_id*id=NULL;+interr=-EINVAL;++if(check_multi_prog_type(prog))+return-EINVAL;+if(!cnt||!uids)+return-EINVAL;++id=bpf_tramp_id_alloc(cnt);+if(!id)+return-ENOMEM;++err=-EFAULT;+cnt_size=cnt*sizeof(id->id[0]);+if(copy_from_user(id->id,uids,cnt_size))+gotoout_free_id;++id->cnt=cnt;+id->obj_id=btf_obj_id(prog->aux->attach_btf);++/* Sort user provided BTF ids, so we can use memcmp+*andbsearchontopofitlater.+*/+sort(id->id,cnt,sizeof(u32),btf_ids_cmp,NULL);++err=bpf_tramp_id_resolve(id,prog);+if(err)+gotoout_free_id;++attach=bpf_tramp_attach(id,NULL,prog);+if(IS_ERR(attach)){+err=PTR_ERR(attach);+gotoout_free_id;+}++link=kzalloc(sizeof(*link),GFP_KERNEL);+if(!link){+err=-ENOMEM;+gotoout_detach;+}++bpf_link_init(&link->link,BPF_LINK_TYPE_TRACING_MULTI,+&bpf_tracing_multi_link_lops,prog);+link->attach_type=prog->expected_attach_type;++err=bpf_link_prime(&link->link,&link_primer);+if(err){+kfree(link);+gotoout_detach;+}++err=bpf_tramp_attach_link(attach);+if(err){+bpf_link_cleanup(&link_primer);+gotoout_detach;+}+prog->aux->attach=attach;+returnbpf_link_settle(&link_primer);++out_detach:+bpf_tramp_detach(attach);+returnerr;+out_free_id:+bpf_tramp_id_put(id);+returnerr;+}+structbpf_raw_tp_link{structbpf_linklink;structbpf_raw_event_map*btp;
From: Jiri Olsa <hidden> Date: 2021-11-18 11:29:12
Adding btf__find_by_glob_kind function that returns array of
BTF ids that match given kind and allow/deny patterns.
int btf__find_by_glob_kind(const struct btf *btf, __u32 kind,
const char *allow_pattern,
const char *deny_pattern,
__u32 **__ids);
The __ids array is allocated and needs to be manually freed.
At the moment the supported pattern is '*' at the beginning or
the end of the pattern.
Kindly borrowed from retsnoop.
Suggested-by: Andrii Nakryiko <redacted>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
tools/lib/bpf/btf.c | 77 +++++++++++++++++++++++++++++++++++++++++++++
tools/lib/bpf/btf.h | 3 ++
2 files changed, 80 insertions(+)
@@ -749,6 +749,83 @@ __s32 btf__find_by_name_kind(const struct btf *btf, const char *type_name,returnbtf_find_by_name_kind(btf,1,type_name,kind);}+/* 'borrowed' from retsnoop */+staticboolglob_matches(constchar*glob,constchar*s)+{+intn=strlen(glob);++if(n==1&&glob[0]=='*')+returntrue;++if(glob[0]=='*'&&glob[n-1]=='*'){+constchar*subs;+/* substring match */++/* this is hacky, but we don't want to allocate for no good reason */+((char*)glob)[n-1]='\0';+subs=strstr(s,glob+1);+((char*)glob)[n-1]='*';++returnsubs!=NULL;+}elseif(glob[0]=='*'){+size_tnn=strlen(s);+/* suffix match */++/* too short for a given suffix */+if(nn<n-1)+returnfalse;++returnstrcmp(s+nn-(n-1),glob+1)==0;+}elseif(glob[n-1]=='*'){+/* prefix match */+returnstrncmp(s,glob,n-1)==0;+}else{+/* exact match */+returnstrcmp(glob,s)==0;+}+}++intbtf__find_by_glob_kind(conststructbtf*btf,__u32kind,+constchar*allow_pattern,constchar*deny_pattern,+__u32**__ids)+{+__u32i,nr_types=btf__get_nr_types(btf);+intcnt=0,alloc=0;+__u32*ids=NULL;++for(i=1;i<=nr_types;i++){+conststructbtf_type*t=btf__type_by_id(btf,i);+constchar*name;+__u32*p;++if(btf_kind(t)!=kind)+continue;+name=btf__name_by_offset(btf,t->name_off);+if(!name)+continue;++if(deny_pattern&&glob_matches(deny_pattern,name))+continue;+if(allow_pattern&&!glob_matches(allow_pattern,name))+continue;++if(cnt==alloc){+alloc=max(16,alloc*3/2);+p=libbpf_reallocarray(ids,alloc,sizeof(__u32));+if(!p){+free(ids);+return-ENOMEM;+}+ids=p;+}+ids[cnt]=i;+cnt++;+}++*__ids=ids;+returncnt;+}+staticboolbtf_is_modifiable(conststructbtf*btf){return(void*)btf->hdr!=btf->raw_data;
From: Jiri Olsa <hidden> Date: 2021-11-18 11:29:16
Adding support to link multi func tracing program
through link_create interface.
Adding special types for multi func programs:
fentry.multi
fexit.multi
so you can define multi func programs like:
SEC("fentry.multi/bpf_fentry_test*")
int BPF_PROG(test1, __u64 a, __u64 b, __u64 c, __u64 d, __u64 e, __u64 f)
that defines test1 to be attached to bpf_fentry_test* functions.
The test1 program is loaded with BPF_F_MULTI_FUNC flag.
If functions are not specified the program needs to be attached
manually.
Adding new btf_ids/btf_ids_cnt fields to bpf_link_create_opts,
that define functions to attach the program to.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
tools/lib/bpf/bpf.c | 7 +++++
tools/lib/bpf/bpf.h | 6 +++-
tools/lib/bpf/libbpf.c | 66 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 78 insertions(+), 1 deletion(-)
@@ -784,6 +784,13 @@ int bpf_link_create(int prog_fd, int target_fd,if(!OPTS_ZEROED(opts,perf_event))returnlibbpf_err(-EINVAL);break;+caseBPF_TRACE_FENTRY:+caseBPF_TRACE_FEXIT:+attr.link_create.multi.btf_ids=(__u64)OPTS_GET(opts,multi.btf_ids,0);+attr.link_create.multi.btf_ids_cnt=OPTS_GET(opts,multi.btf_ids_cnt,0);+if(!OPTS_ZEROED(opts,multi))+returnlibbpf_err(-EINVAL);+break;default:if(!OPTS_ZEROED(opts,flags))returnlibbpf_err(-EINVAL);
From: Jiri Olsa <hidden> Date: 2021-11-18 11:29:33
Adding selftest for fentry multi func test that attaches
to bpf_fentry_test* functions and checks argument values
based on the processed function.
We need to cast to real arguments types in multi_arg_check,
because the checked value can be shorter than u64.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
tools/testing/selftests/bpf/Makefile | 4 +-
.../bpf/prog_tests/multi_fentry_test.c | 30 +++++++++
.../testing/selftests/bpf/progs/multi_check.c | 63 +++++++++++++++++++
.../selftests/bpf/progs/multi_fentry.c | 17 +++++
4 files changed, 113 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/multi_fentry_test.c
create mode 100644 tools/testing/selftests/bpf/progs/multi_check.c
create mode 100644 tools/testing/selftests/bpf/progs/multi_fentry.c
From: Jiri Olsa <hidden> Date: 2021-11-18 11:29:51
Adding extra test to existing modify_return test to
test this with multi func program attached on top
of the modify return program.
Because the supported wildcards do not allow us to
match both bpf_fentry_test* and bpf_modify_return_test,
adding extra code to look it up in kernel's BTF.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
.../selftests/bpf/prog_tests/modify_return.c | 114 +++++++++++++++++-
.../selftests/bpf/progs/multi_modify_return.c | 17 +++
2 files changed, 128 insertions(+), 3 deletions(-)
create mode 100644 tools/testing/selftests/bpf/progs/multi_modify_return.c
From: Jiri Olsa <hidden> Date: 2021-11-18 11:30:00
Adding test code to check on trampolines spliting.
The tests attached various bpf_fetry_* functions in a way
so there's always non trivial IDs intersection, that leads
to trampoline splitting in kenrel code.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
tools/testing/selftests/bpf/Makefile | 4 +-
.../bpf/prog_tests/multi_attach_test.c | 176 ++++++++++++++++++
.../selftests/bpf/progs/multi_attach.c | 105 +++++++++++
3 files changed, 284 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/multi_attach_test.c
create mode 100644 tools/testing/selftests/bpf/progs/multi_attach.c
From: Jiri Olsa <hidden> Date: 2021-11-18 11:30:44
Adding support to store multiple ids in bpf_tramp_id object,
to have id for trampolines with multiple functions assigned.
Extra array of u32 values is allocated within bpf_tramp_id
object allocation.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
include/linux/bpf.h | 6 ++++--
kernel/bpf/syscall.c | 6 +++---
kernel/bpf/trampoline.c | 39 +++++++++++++++++++++++++++++++--------
kernel/bpf/verifier.c | 2 +-
4 files changed, 39 insertions(+), 14 deletions(-)
Just ignoring that was passed in uattr?
Maybe instead of ignoring dopr BPF_F_MULTI_FUNC and make libbpf
point to that btf_id instead?
Then multi or not can be checked with if (attr->attach_btf_id == bpf_multi_func_btf_id[0]).
On Thu, Nov 18, 2021 at 12:24:33PM +0100, Jiri Olsa wrote:
quoted hunk
Currently we call the original function by using the absolute address
given at the JIT generation. That's not usable when having trampoline
attached to multiple functions. In this case we need to take the
return address from the stack.
Adding support to retrieve the original function address from the stack
by adding new BPF_TRAMP_F_ORIG_STACK flag for arch_prepare_bpf_trampoline
function.
Basically we take the return address of the 'fentry' call:
function + 0: call fentry # stores 'function + 5' address on stack
function + 5: ...
The 'function + 5' address will be used as the address for the
original function to call.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
arch/x86/net/bpf_jit_comp.c | 13 +++++++++----
include/linux/bpf.h | 5 +++++
2 files changed, 14 insertions(+), 4 deletions(-)
From: Jiri Olsa <hidden> Date: 2021-11-19 21:47:01
On Thu, Nov 18, 2021 at 08:14:09PM -0800, Alexei Starovoitov wrote:
On Thu, Nov 18, 2021 at 12:24:33PM +0100, Jiri Olsa wrote:
quoted
Currently we call the original function by using the absolute address
given at the JIT generation. That's not usable when having trampoline
attached to multiple functions. In this case we need to take the
return address from the stack.
Adding support to retrieve the original function address from the stack
by adding new BPF_TRAMP_F_ORIG_STACK flag for arch_prepare_bpf_trampoline
function.
Basically we take the return address of the 'fentry' call:
function + 0: call fentry # stores 'function + 5' address on stack
function + 5: ...
The 'function + 5' address will be used as the address for the
original function to call.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
arch/x86/net/bpf_jit_comp.c | 13 +++++++++----
include/linux/bpf.h | 5 +++++
2 files changed, 14 insertions(+), 4 deletions(-)
Just ignoring that was passed in uattr?
Maybe instead of ignoring dopr BPF_F_MULTI_FUNC and make libbpf
point to that btf_id instead?
Then multi or not can be checked with if (attr->attach_btf_id == bpf_multi_func_btf_id[0]).
nice idea, it might fit better than the flag
thanks,
jirka
On Thu, Nov 18, 2021 at 3:25 AM Jiri Olsa [off-list ref] wrote:
Adding bpf_arg/bpf_ret_value helpers for tracing programs
that returns traced function arguments.
Get n-th argument of the traced function:
long bpf_arg(void *ctx, int n)
Get return value of the traced function:
long bpf_ret_value(void *ctx)
The trampoline now stores number of arguments on ctx-8
address, so it's easy to verify argument index and find
return value argument.
Moving function ip address on the trampoline stack behind
the number of functions arguments, so it's now stored
on ctx-16 address.
Both helpers are inlined by verifier.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
It would be great to land these changes separate from your huge patch
set. There are some upcoming BPF trampoline related changes that will
touch this (to add BPF cookie support for fentry/fexit progs), so
would be nice to minimize the interdependencies. So maybe post this
patch separately (probably after holidays ;) ).
this /* nr_args */ next to 8 is super confusing, would be better to
expand the comment; might be a good idea to have some sort of a
description of possible stack layouts (e.g., fexit has some extra
stuff on the stack, I think, but it's impossible to remember and need
to recover that knowledge from the assembly code, basically).
@@ -1987,12 +1987,22 @@ int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *i EMIT4(0x48, 0x83, 0xe8, X86_PATCH_SIZE); emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -stack_size);- /* Continue with stack_size for regs storage, stack will- * be correctly restored with 'leave' instruction.- */+ /* Continue with stack_size for 'nr_args' storage */
same, I don't think this comment really helps, just confuses some more
stack_size -= 8;
}
+ /* Store number of arguments of the traced function:
+ * mov rax, nr_args
+ * mov QWORD PTR [rbp - stack_size], rax
+ */
+ emit_mov_imm64(&prog, BPF_REG_0, 0, (u32) nr_args);
+ emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -stack_size);
+
+ /* Continue with stack_size for regs storage, stack will
+ * be correctly restored with 'leave' instruction.
+ */
+ stack_size -= 8;
I think "stack_size" as a name outlived itself and it just makes
everything harder to understand. It's used more like a stack offset
(relative to rsp or rbp) for different things. Would it make code
worse if we had few offset variables instead (or rather in addition,
we still need to calculate a full stack_size; it's just it's constant
re-adjustment is what's hard to keep track of), like regs_off,
ret_ip_off, arg_cnt_off, etc?
quoted hunk
+
save_regs(m, &prog, nr_args, stack_size);
if (flags & BPF_TRAMP_F_CALL_ORIG) {
@@ -4957,6 +4957,18 @@ union bpf_attr {***-ENOENT**if*task->mm*isNULL,ornovmacontains*addr*.***-EBUSY**iffailedtotrylockmmap_lock.***-EINVAL**forinvalid**flags**.+*+*longbpf_arg(void*ctx,intn)
__u32 n ?
+ * Description
+ * Get n-th argument of the traced function (for tracing programs).
+ * Return
+ * Value of the argument.
What about errors? those need to be documented.
+ *
+ * long bpf_ret_value(void *ctx)
+ * Description
+ * Get return value of the traced function (for tracing programs).
+ * Return
+ * Return value of the traced function.
Same, errors not documented. Also would be good to document what
happens when ret_value is requested in the context where there is no
ret value (e.g., fentry)
We already have bpf_get_func_ip, so why not continue a tradition and
call these bpf_get_func_arg() and bpf_get_func_ret(). Nice, short,
clean, consistent.
BTW, a wild thought. Wouldn't it be cool to have these functions work
with kprobe/kretprobe as well? Do you think it's possible?
quoted hunk
/* */
/* integer value in 'imm' field of BPF_CALL instruction selects which helper
@@ -13246,11 +13246,52 @@ static int do_misc_fixups(struct bpf_verifier_env *env)continue;}+/* Implement bpf_arg inline. */+if(prog_type==BPF_PROG_TYPE_TRACING&&+insn->imm==BPF_FUNC_arg){+/* Load nr_args from ctx - 8 */+insn_buf[0]=BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_1,-8);+insn_buf[1]=BPF_JMP32_REG(BPF_JGE,BPF_REG_2,BPF_REG_0,4);+insn_buf[2]=BPF_ALU64_IMM(BPF_MUL,BPF_REG_2,8);+insn_buf[3]=BPF_ALU64_REG(BPF_ADD,BPF_REG_2,BPF_REG_1);+insn_buf[4]=BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_2,0);+insn_buf[5]=BPF_JMP_A(1);+insn_buf[6]=BPF_MOV64_IMM(BPF_REG_0,0);++new_prog=bpf_patch_insn_data(env,i+delta,insn_buf,7);+if(!new_prog)+return-ENOMEM;++delta+=6;+env->prog=prog=new_prog;+insn=new_prog->insnsi+i+delta;+continue;
nit: this whole sequence of steps and calculations seems like
something that might be abstracted and hidden behind a macro or helper
func? Not related to your change, though. But wouldn't it be easier to
understand if it was just written as:
PATCH_INSNS(
BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);
BPF_JMP32_REG(BPF_JGE, BPF_REG_2, BPF_REG_0, 4);
BPF_ALU64_IMM(BPF_MUL, BPF_REG_2, 8);
BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_1);
BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_2, 0);
BPF_JMP_A(1);
BPF_MOV64_IMM(BPF_REG_0, 0));
continue;
?
@@ -1012,7 +1012,7 @@ const struct bpf_func_proto bpf_snprintf_btf_proto = {BPF_CALL_1(bpf_get_func_ip_tracing,void*,ctx){/* This helper call is inlined by verifier. */-return((u64*)ctx)[-1];+return((u64*)ctx)[-2];}staticconststructbpf_func_protobpf_get_func_ip_proto_tracing={
@@ -1091,6 +1091,38 @@ static const struct bpf_func_proto bpf_get_branch_snapshot_proto = {.arg2_type=ARG_CONST_SIZE_OR_ZERO,};+BPF_CALL_2(bpf_arg,void*,ctx,int,n)+{+/* This helper call is inlined by verifier. */+u64nr_args=((u64*)ctx)[-1];++if((u64)n>=nr_args)+return0;
We'll need bpf_get_func_arg_cnt() helper as well to be able to know
the actual number of arguments traced function has. It's impossible to
know whether the argument is zero or there is no argument, otherwise.
On Thu, Nov 18, 2021 at 3:25 AM Jiri Olsa [off-list ref] wrote:
Keeping active attached trampoline in bpf_prog so it can be used
in following changes to account for multiple functions attachments
in program.
As EXT programs are not going to be supported in multiple functions
attachment for now, I'm keeping them stored in link.
can the same EXT program be attached twice? If not, why can't you just
use the same prog->aux->trampoline instead of the if/else everywhere?
Just ignoring that was passed in uattr?
Maybe instead of ignoring dopr BPF_F_MULTI_FUNC and make libbpf
point to that btf_id instead?
Then multi or not can be checked with if (attr->attach_btf_id == bpf_multi_func_btf_id[0]).
nice idea, it might fit better than the flag
Instead of a flag we can also use a different expected_attach_type
(FENTRY vs FENTRY_MULTI, etc). As for attach_btf_id, why can't we just
enforce it as 0?
nit: this whole sequence of steps and calculations seems like
something that might be abstracted and hidden behind a macro or helper
func? Not related to your change, though. But wouldn't it be easier to
understand if it was just written as:
PATCH_INSNS(
BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);
BPF_JMP32_REG(BPF_JGE, BPF_REG_2, BPF_REG_0, 4);
BPF_ALU64_IMM(BPF_MUL, BPF_REG_2, 8);
BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_1);
BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_2, 0);
BPF_JMP_A(1);
BPF_MOV64_IMM(BPF_REG_0, 0));
Daniel and myself tried to do similar macro magic in the past,
but it suffers unnecessary stack increase and extra copies.
So eventually we got rid of it.
I suggest staying with Jiri's approach.
Independent from anything else...
Just noticed BPF_MUL in the above...
Please use BPF_LSH instead. JITs don't optimize such things.
It's a job of gcc/llvm to do so. JITs assume that all
normal optimizations were done by the compiler.
From: Jiri Olsa <hidden> Date: 2021-11-28 17:26:52
On Wed, Nov 24, 2021 at 01:48:09PM -0800, Andrii Nakryiko wrote:
On Thu, Nov 18, 2021 at 3:25 AM Jiri Olsa [off-list ref] wrote:
quoted
Keeping active attached trampoline in bpf_prog so it can be used
in following changes to account for multiple functions attachments
in program.
As EXT programs are not going to be supported in multiple functions
attachment for now, I'm keeping them stored in link.
can the same EXT program be attached twice? If not, why can't you just
use the same prog->aux->trampoline instead of the if/else everywhere?
I recall that was my initial change, but it was clashing with
fentry/fexit programs because extensions are special
I'll re-check and try to make this generic
jirka
Just ignoring that was passed in uattr?
Maybe instead of ignoring dopr BPF_F_MULTI_FUNC and make libbpf
point to that btf_id instead?
Then multi or not can be checked with if (attr->attach_btf_id == bpf_multi_func_btf_id[0]).
nice idea, it might fit better than the flag
Instead of a flag we can also use a different expected_attach_type
(FENTRY vs FENTRY_MULTI, etc).
right, you already asked for that - https://lore.kernel.org/bpf/YS9k26rRcUJVS%2Fvx@krava/
I still think it'd mean more code while this way we just use
current fentry/fexit code paths with few special handling
for multi programs
As for attach_btf_id, why can't we just
enforce it as 0?
there's prog->aux->attach_func_proto that needs to be set based
on attach_btf_id, and is checked later in btf_ctx_access
jirka
From: Jiri Olsa <hidden> Date: 2021-11-28 18:08:17
On Wed, Nov 24, 2021 at 01:43:22PM -0800, Andrii Nakryiko wrote:
On Thu, Nov 18, 2021 at 3:25 AM Jiri Olsa [off-list ref] wrote:
quoted
Adding bpf_arg/bpf_ret_value helpers for tracing programs
that returns traced function arguments.
Get n-th argument of the traced function:
long bpf_arg(void *ctx, int n)
Get return value of the traced function:
long bpf_ret_value(void *ctx)
The trampoline now stores number of arguments on ctx-8
address, so it's easy to verify argument index and find
return value argument.
Moving function ip address on the trampoline stack behind
the number of functions arguments, so it's now stored
on ctx-16 address.
Both helpers are inlined by verifier.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
It would be great to land these changes separate from your huge patch
set. There are some upcoming BPF trampoline related changes that will
touch this (to add BPF cookie support for fentry/fexit progs), so
would be nice to minimize the interdependencies. So maybe post this
patch separately (probably after holidays ;) ).
this /* nr_args */ next to 8 is super confusing, would be better to
expand the comment; might be a good idea to have some sort of a
description of possible stack layouts (e.g., fexit has some extra
stuff on the stack, I think, but it's impossible to remember and need
to recover that knowledge from the assembly code, basically).
@@ -1987,12 +1987,22 @@ int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *i EMIT4(0x48, 0x83, 0xe8, X86_PATCH_SIZE); emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -stack_size);- /* Continue with stack_size for regs storage, stack will- * be correctly restored with 'leave' instruction.- */+ /* Continue with stack_size for 'nr_args' storage */
same, I don't think this comment really helps, just confuses some more
ok, I'll put some more comments with list of possible the stack layouts
quoted
stack_size -= 8;
}
+ /* Store number of arguments of the traced function:
+ * mov rax, nr_args
+ * mov QWORD PTR [rbp - stack_size], rax
+ */
+ emit_mov_imm64(&prog, BPF_REG_0, 0, (u32) nr_args);
+ emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -stack_size);
+
+ /* Continue with stack_size for regs storage, stack will
+ * be correctly restored with 'leave' instruction.
+ */
+ stack_size -= 8;
I think "stack_size" as a name outlived itself and it just makes
everything harder to understand. It's used more like a stack offset
(relative to rsp or rbp) for different things. Would it make code
worse if we had few offset variables instead (or rather in addition,
we still need to calculate a full stack_size; it's just it's constant
re-adjustment is what's hard to keep track of), like regs_off,
ret_ip_off, arg_cnt_off, etc?
let's see, I'll try that
quoted
+
save_regs(m, &prog, nr_args, stack_size);
if (flags & BPF_TRAMP_F_CALL_ORIG) {
@@ -4957,6 +4957,18 @@ union bpf_attr {***-ENOENT**if*task->mm*isNULL,ornovmacontains*addr*.***-EBUSY**iffailedtotrylockmmap_lock.***-EINVAL**forinvalid**flags**.+*+*longbpf_arg(void*ctx,intn)
__u32 n ?
ok
quoted
+ * Description
+ * Get n-th argument of the traced function (for tracing programs).
+ * Return
+ * Value of the argument.
What about errors? those need to be documented.
ok
quoted
+ *
+ * long bpf_ret_value(void *ctx)
+ * Description
+ * Get return value of the traced function (for tracing programs).
+ * Return
+ * Return value of the traced function.
Same, errors not documented. Also would be good to document what
happens when ret_value is requested in the context where there is no
ret value (e.g., fentry)
ugh, that's not handled at the moment.. should we fail when
we see bpf_ret_value helper call in fentry program?
We already have bpf_get_func_ip, so why not continue a tradition and
call these bpf_get_func_arg() and bpf_get_func_ret(). Nice, short,
clean, consistent.
ok
BTW, a wild thought. Wouldn't it be cool to have these functions work
with kprobe/kretprobe as well? Do you think it's possible?
right, bpf_get_func_ip already works for kprobes
struct kprobe could have the btf_func_model of the traced function,
so in case we trace function directly on the entry point we could
read arguments registers based on the btf_func_model
I'll check with Massami
quoted
/* */
/* integer value in 'imm' field of BPF_CALL instruction selects which helper
@@ -13246,11 +13246,52 @@ static int do_misc_fixups(struct bpf_verifier_env *env)continue;}+/* Implement bpf_arg inline. */+if(prog_type==BPF_PROG_TYPE_TRACING&&+insn->imm==BPF_FUNC_arg){+/* Load nr_args from ctx - 8 */+insn_buf[0]=BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_1,-8);+insn_buf[1]=BPF_JMP32_REG(BPF_JGE,BPF_REG_2,BPF_REG_0,4);+insn_buf[2]=BPF_ALU64_IMM(BPF_MUL,BPF_REG_2,8);+insn_buf[3]=BPF_ALU64_REG(BPF_ADD,BPF_REG_2,BPF_REG_1);+insn_buf[4]=BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_2,0);+insn_buf[5]=BPF_JMP_A(1);+insn_buf[6]=BPF_MOV64_IMM(BPF_REG_0,0);++new_prog=bpf_patch_insn_data(env,i+delta,insn_buf,7);+if(!new_prog)+return-ENOMEM;++delta+=6;+env->prog=prog=new_prog;+insn=new_prog->insnsi+i+delta;+continue;
nit: this whole sequence of steps and calculations seems like
something that might be abstracted and hidden behind a macro or helper
func? Not related to your change, though. But wouldn't it be easier to
understand if it was just written as:
PATCH_INSNS(
BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);
BPF_JMP32_REG(BPF_JGE, BPF_REG_2, BPF_REG_0, 4);
BPF_ALU64_IMM(BPF_MUL, BPF_REG_2, 8);
BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_1);
BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_2, 0);
BPF_JMP_A(1);
BPF_MOV64_IMM(BPF_REG_0, 0));
continue;
@@ -1012,7 +1012,7 @@ const struct bpf_func_proto bpf_snprintf_btf_proto = {BPF_CALL_1(bpf_get_func_ip_tracing,void*,ctx){/* This helper call is inlined by verifier. */-return((u64*)ctx)[-1];+return((u64*)ctx)[-2];}staticconststructbpf_func_protobpf_get_func_ip_proto_tracing={
@@ -1091,6 +1091,38 @@ static const struct bpf_func_proto bpf_get_branch_snapshot_proto = {.arg2_type=ARG_CONST_SIZE_OR_ZERO,};+BPF_CALL_2(bpf_arg,void*,ctx,int,n)+{+/* This helper call is inlined by verifier. */+u64nr_args=((u64*)ctx)[-1];++if((u64)n>=nr_args)+return0;
We'll need bpf_get_func_arg_cnt() helper as well to be able to know
the actual number of arguments traced function has. It's impossible to
know whether the argument is zero or there is no argument, otherwise.
my idea was that the program will call those helpers based
on get_func_ip with proper argument indexes
but with bpf_get_func_arg_cnt we could make a simple program
that would just print function with all its arguments easily, ok ;-)
nit: this whole sequence of steps and calculations seems like
something that might be abstracted and hidden behind a macro or helper
func? Not related to your change, though. But wouldn't it be easier to
understand if it was just written as:
PATCH_INSNS(
BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);
BPF_JMP32_REG(BPF_JGE, BPF_REG_2, BPF_REG_0, 4);
BPF_ALU64_IMM(BPF_MUL, BPF_REG_2, 8);
BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_1);
BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_2, 0);
BPF_JMP_A(1);
BPF_MOV64_IMM(BPF_REG_0, 0));
Daniel and myself tried to do similar macro magic in the past,
but it suffers unnecessary stack increase and extra copies.
So eventually we got rid of it.
I suggest staying with Jiri's approach.
Independent from anything else...
Just noticed BPF_MUL in the above...
Please use BPF_LSH instead. JITs don't optimize such things.
It's a job of gcc/llvm to do so. JITs assume that all
normal optimizations were done by the compiler.
On Sun, Nov 28, 2021 at 10:06 AM Jiri Olsa [off-list ref] wrote:
On Wed, Nov 24, 2021 at 01:43:22PM -0800, Andrii Nakryiko wrote:
quoted
On Thu, Nov 18, 2021 at 3:25 AM Jiri Olsa [off-list ref] wrote:
quoted
Adding bpf_arg/bpf_ret_value helpers for tracing programs
that returns traced function arguments.
Get n-th argument of the traced function:
long bpf_arg(void *ctx, int n)
Get return value of the traced function:
long bpf_ret_value(void *ctx)
The trampoline now stores number of arguments on ctx-8
address, so it's easy to verify argument index and find
return value argument.
Moving function ip address on the trampoline stack behind
the number of functions arguments, so it's now stored
on ctx-16 address.
Both helpers are inlined by verifier.
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
It would be great to land these changes separate from your huge patch
set. There are some upcoming BPF trampoline related changes that will
touch this (to add BPF cookie support for fentry/fexit progs), so
would be nice to minimize the interdependencies. So maybe post this
patch separately (probably after holidays ;) ).
this /* nr_args */ next to 8 is super confusing, would be better to
expand the comment; might be a good idea to have some sort of a
description of possible stack layouts (e.g., fexit has some extra
stuff on the stack, I think, but it's impossible to remember and need
to recover that knowledge from the assembly code, basically).
@@ -1987,12 +1987,22 @@ int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *i EMIT4(0x48, 0x83, 0xe8, X86_PATCH_SIZE); emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -stack_size);- /* Continue with stack_size for regs storage, stack will- * be correctly restored with 'leave' instruction.- */+ /* Continue with stack_size for 'nr_args' storage */
same, I don't think this comment really helps, just confuses some more
ok, I'll put some more comments with list of possible the stack layouts
quoted
quoted
stack_size -= 8;
}
+ /* Store number of arguments of the traced function:
+ * mov rax, nr_args
+ * mov QWORD PTR [rbp - stack_size], rax
+ */
+ emit_mov_imm64(&prog, BPF_REG_0, 0, (u32) nr_args);
+ emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -stack_size);
+
+ /* Continue with stack_size for regs storage, stack will
+ * be correctly restored with 'leave' instruction.
+ */
+ stack_size -= 8;
I think "stack_size" as a name outlived itself and it just makes
everything harder to understand. It's used more like a stack offset
(relative to rsp or rbp) for different things. Would it make code
worse if we had few offset variables instead (or rather in addition,
we still need to calculate a full stack_size; it's just it's constant
re-adjustment is what's hard to keep track of), like regs_off,
ret_ip_off, arg_cnt_off, etc?
let's see, I'll try that
quoted
quoted
+
save_regs(m, &prog, nr_args, stack_size);
if (flags & BPF_TRAMP_F_CALL_ORIG) {
@@ -4957,6 +4957,18 @@ union bpf_attr {***-ENOENT**if*task->mm*isNULL,ornovmacontains*addr*.***-EBUSY**iffailedtotrylockmmap_lock.***-EINVAL**forinvalid**flags**.+*+*longbpf_arg(void*ctx,intn)
__u32 n ?
ok
quoted
quoted
+ * Description
+ * Get n-th argument of the traced function (for tracing programs).
+ * Return
+ * Value of the argument.
What about errors? those need to be documented.
ok
quoted
quoted
+ *
+ * long bpf_ret_value(void *ctx)
+ * Description
+ * Get return value of the traced function (for tracing programs).
+ * Return
+ * Return value of the traced function.
Same, errors not documented. Also would be good to document what
happens when ret_value is requested in the context where there is no
ret value (e.g., fentry)
ugh, that's not handled at the moment.. should we fail when
we see bpf_ret_value helper call in fentry program?
Well, two options, really. Either return zero or detect at
verification time and fail verifications. I find myself leaning
towards less restrictions at verification time, so I'd probably go
with runtime check and zero. This allows to have the same BPF
subprogram that can be called both from fentry/fexit with a proper
if() guard to not do anything with the result of bpf_ret_value (as one
example).
We already have bpf_get_func_ip, so why not continue a tradition and
call these bpf_get_func_arg() and bpf_get_func_ret(). Nice, short,
clean, consistent.
ok
quoted
BTW, a wild thought. Wouldn't it be cool to have these functions work
with kprobe/kretprobe as well? Do you think it's possible?
right, bpf_get_func_ip already works for kprobes
struct kprobe could have the btf_func_model of the traced function,
so in case we trace function directly on the entry point we could
read arguments registers based on the btf_func_model
I'll check with Massami
Hm... I'd actually try to keep kprobe BTF-free. We have fentry for
cases where BTF is present and the function is simple enough (like <=6
args, etc). Kprobe is an escape hatch mechanism when all the BTF
fanciness just gets in the way (retsnoop being a primary example from
my side). What I meant here was that bpf_get_arg(int n) would read
correct fields from pt_regs that map to first N arguments passed in
the registers. What we currently have with PT_REGS_PARM macros in
bpf_tracing.h, but with a proper unified BPF helper.
quoted
quoted
/* */
/* integer value in 'imm' field of BPF_CALL instruction selects which helper
@@ -13246,11 +13246,52 @@ static int do_misc_fixups(struct bpf_verifier_env *env)continue;}+/* Implement bpf_arg inline. */+if(prog_type==BPF_PROG_TYPE_TRACING&&+insn->imm==BPF_FUNC_arg){+/* Load nr_args from ctx - 8 */+insn_buf[0]=BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_1,-8);+insn_buf[1]=BPF_JMP32_REG(BPF_JGE,BPF_REG_2,BPF_REG_0,4);+insn_buf[2]=BPF_ALU64_IMM(BPF_MUL,BPF_REG_2,8);+insn_buf[3]=BPF_ALU64_REG(BPF_ADD,BPF_REG_2,BPF_REG_1);+insn_buf[4]=BPF_LDX_MEM(BPF_DW,BPF_REG_0,BPF_REG_2,0);+insn_buf[5]=BPF_JMP_A(1);+insn_buf[6]=BPF_MOV64_IMM(BPF_REG_0,0);++new_prog=bpf_patch_insn_data(env,i+delta,insn_buf,7);+if(!new_prog)+return-ENOMEM;++delta+=6;+env->prog=prog=new_prog;+insn=new_prog->insnsi+i+delta;+continue;
nit: this whole sequence of steps and calculations seems like
something that might be abstracted and hidden behind a macro or helper
func? Not related to your change, though. But wouldn't it be easier to
understand if it was just written as:
PATCH_INSNS(
BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);
BPF_JMP32_REG(BPF_JGE, BPF_REG_2, BPF_REG_0, 4);
BPF_ALU64_IMM(BPF_MUL, BPF_REG_2, 8);
BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_1);
BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_2, 0);
BPF_JMP_A(1);
BPF_MOV64_IMM(BPF_REG_0, 0));
continue;
yep, looks better ;-) I'll check
as Alexei mentioned, might not be possible, but if variadic
implementation turns out to be not too ugly, I think it might work.
Macro can assume that insn_buf and all the other variables are there,
so there shouldn't be any increase in stack size use, I think.
But this is just an item on a wishlist, so don't overstress about that.
@@ -1012,7 +1012,7 @@ const struct bpf_func_proto bpf_snprintf_btf_proto = {BPF_CALL_1(bpf_get_func_ip_tracing,void*,ctx){/* This helper call is inlined by verifier. */-return((u64*)ctx)[-1];+return((u64*)ctx)[-2];}staticconststructbpf_func_protobpf_get_func_ip_proto_tracing={
@@ -1091,6 +1091,38 @@ static const struct bpf_func_proto bpf_get_branch_snapshot_proto = {.arg2_type=ARG_CONST_SIZE_OR_ZERO,};+BPF_CALL_2(bpf_arg,void*,ctx,int,n)+{+/* This helper call is inlined by verifier. */+u64nr_args=((u64*)ctx)[-1];++if((u64)n>=nr_args)+return0;
We'll need bpf_get_func_arg_cnt() helper as well to be able to know
the actual number of arguments traced function has. It's impossible to
know whether the argument is zero or there is no argument, otherwise.
my idea was that the program will call those helpers based
on get_func_ip with proper argument indexes
see my comments on multi-attach kprobes, get_func_ip() is nice, but
BPF cookies are often much better. So I wouldn't design everything
with the assumption that user always has to use hashmap +
get_func_ip().
but with bpf_get_func_arg_cnt we could make a simple program
that would just print function with all its arguments easily, ok ;-)
right, and many other more complicated functions that don't have to do
runtime ip lookups ;)
we should return 0 for fentry or disable this helper for anything but
fexit? It's going to return garbage otherwise.
disabling seems like right choice to me
well, see above. I think we should prefer statically disabling
something if it's harmful to enable otherwise, but for more
flexibility and less headache with "proving to BPF verifier", I lean
more and more towards runtime checks, if they are safe and not overly
expensive or complicated.
Just ignoring that was passed in uattr?
Maybe instead of ignoring dopr BPF_F_MULTI_FUNC and make libbpf
point to that btf_id instead?
Then multi or not can be checked with if (attr->attach_btf_id == bpf_multi_func_btf_id[0]).
nice idea, it might fit better than the flag
Instead of a flag we can also use a different expected_attach_type
(FENTRY vs FENTRY_MULTI, etc).
right, you already asked for that - https://lore.kernel.org/bpf/YS9k26rRcUJVS%2Fvx@krava/
I still think it'd mean more code while this way we just use
current fentry/fexit code paths with few special handling
for multi programs
I don't see how it makes much difference for kernel implementation.
Checking expected_attach_type vs checking prog_flags is about the same
amount of code. The big advantage of new expected_attach_type (or
prog_type) is that it will be very obvious in all sorts of diagnostics
tooling (think bpftool prog show output, etc). prog_flags are almost
invisible and it will be the last thing that users will think about.
I'd try to minimize the usage of prog_flags overall.
quoted
As for attach_btf_id, why can't we just
enforce it as 0?
there's prog->aux->attach_func_proto that needs to be set based
on attach_btf_id, and is checked later in btf_ctx_access
right:
if (attach_btf_id == 0)
prog->aux->attach_func_proto =
&special_func_model_or_proto_or_whatever_that_does_not_have_to_be_known_to_libbpf_and_outside_world_ever;
;) let's keep implementation details as internal implementation
details, instead of dumping all that to UAPI
On Tue, Nov 30, 2021 at 11:13 PM Andrii Nakryiko
[off-list ref] wrote:
Hm... I'd actually try to keep kprobe BTF-free. We have fentry for
cases where BTF is present and the function is simple enough (like <=6
args, etc). Kprobe is an escape hatch mechanism when all the BTF
fanciness just gets in the way (retsnoop being a primary example from
my side). What I meant here was that bpf_get_arg(int n) would read
correct fields from pt_regs that map to first N arguments passed in
the registers. What we currently have with PT_REGS_PARM macros in
bpf_tracing.h, but with a proper unified BPF helper.
and these macros are arch specific.
which means that it won't be a trivial patch to add bpf_get_arg()
support for kprobes.
Plenty of things to consider. Like should it return an error
at run-time or verification time when a particular arch is not supported.
Or argument 6 might be available on one arch, but not on the other.
32-bit CPU regs vs 64-bit regs of BPF, etc.
I wouldn't attempt to mix this work with current patches.
On Wed, Dec 1, 2021 at 9:37 AM Alexei Starovoitov
[off-list ref] wrote:
On Tue, Nov 30, 2021 at 11:13 PM Andrii Nakryiko
[off-list ref] wrote:
quoted
Hm... I'd actually try to keep kprobe BTF-free. We have fentry for
cases where BTF is present and the function is simple enough (like <=6
args, etc). Kprobe is an escape hatch mechanism when all the BTF
fanciness just gets in the way (retsnoop being a primary example from
my side). What I meant here was that bpf_get_arg(int n) would read
correct fields from pt_regs that map to first N arguments passed in
the registers. What we currently have with PT_REGS_PARM macros in
bpf_tracing.h, but with a proper unified BPF helper.
and these macros are arch specific.
which means that it won't be a trivial patch to add bpf_get_arg()
support for kprobes.
no one suggested it would be trivial :) things worth doing are usually
non-trivial, as can be evidenced by Jiri's patch set
Plenty of things to consider. Like should it return an error
at run-time or verification time when a particular arch is not supported.
See my other replies to Jiri, I'm more and more convinced that dynamic
is the way to go for things like this, where the safety of the kernel
or BPF program are not compromised.
But you emphasized an important point, that it's probably good to
allow users to distinguish errors from reading actual value 0. There
are and will be situations where argument isn't available or some
combination of conditions are not supported. So I think, while it's a
bit more verbose, these forms are generally better:
int bpf_get_func_arg(int n, u64 *value);
int bpf_get_func_ret(u64 *value);
WDYT?
Or argument 6 might be available on one arch, but not on the other.
32-bit CPU regs vs 64-bit regs of BPF, etc.
I wouldn't attempt to mix this work with current patches.
Oh, I didn't suggest doing it as part of this already huge and
complicated set. But I think it's good to think a bit ahead and design
the helper API appropriately, at the very least.
And again, I think bpf_get_func_arg/bpf_get_func_ret deserve their own
patch set where we can discuss all this independently from
multi-attach.
On Wed, Dec 1, 2021 at 10:00 AM Andrii Nakryiko
[off-list ref] wrote:
But you emphasized an important point, that it's probably good to
allow users to distinguish errors from reading actual value 0. There
are and will be situations where argument isn't available or some
combination of conditions are not supported. So I think, while it's a
bit more verbose, these forms are generally better:
int bpf_get_func_arg(int n, u64 *value);
int bpf_get_func_ret(u64 *value);
WDYT?
Makes sense to me.
The verifier will be able to inline it just fine.
Two extra insns only compared to direct return.
From: Jiri Olsa <hidden> Date: 2021-12-01 21:17:38
On Wed, Dec 01, 2021 at 09:59:57AM -0800, Andrii Nakryiko wrote:
On Wed, Dec 1, 2021 at 9:37 AM Alexei Starovoitov
[off-list ref] wrote:
quoted
On Tue, Nov 30, 2021 at 11:13 PM Andrii Nakryiko
[off-list ref] wrote:
quoted
Hm... I'd actually try to keep kprobe BTF-free. We have fentry for
cases where BTF is present and the function is simple enough (like <=6
args, etc). Kprobe is an escape hatch mechanism when all the BTF
fanciness just gets in the way (retsnoop being a primary example from
my side). What I meant here was that bpf_get_arg(int n) would read
correct fields from pt_regs that map to first N arguments passed in
the registers. What we currently have with PT_REGS_PARM macros in
bpf_tracing.h, but with a proper unified BPF helper.
and these macros are arch specific.
which means that it won't be a trivial patch to add bpf_get_arg()
support for kprobes.
no one suggested it would be trivial :) things worth doing are usually
non-trivial, as can be evidenced by Jiri's patch set
quoted
Plenty of things to consider. Like should it return an error
at run-time or verification time when a particular arch is not supported.
See my other replies to Jiri, I'm more and more convinced that dynamic
is the way to go for things like this, where the safety of the kernel
or BPF program are not compromised.
But you emphasized an important point, that it's probably good to
allow users to distinguish errors from reading actual value 0. There
are and will be situations where argument isn't available or some
combination of conditions are not supported. So I think, while it's a
bit more verbose, these forms are generally better:
int bpf_get_func_arg(int n, u64 *value);
int bpf_get_func_ret(u64 *value);
WDYT?
ok, good preparation for kprobe code quirks described by Alexei
quoted
Or argument 6 might be available on one arch, but not on the other.
32-bit CPU regs vs 64-bit regs of BPF, etc.
I wouldn't attempt to mix this work with current patches.
Oh, I didn't suggest doing it as part of this already huge and
complicated set. But I think it's good to think a bit ahead and design
the helper API appropriately, at the very least.
And again, I think bpf_get_func_arg/bpf_get_func_ret deserve their own
patch set where we can discuss all this independently from
multi-attach.
Just ignoring that was passed in uattr?
Maybe instead of ignoring dopr BPF_F_MULTI_FUNC and make libbpf
point to that btf_id instead?
Then multi or not can be checked with if (attr->attach_btf_id == bpf_multi_func_btf_id[0]).
nice idea, it might fit better than the flag
Instead of a flag we can also use a different expected_attach_type
(FENTRY vs FENTRY_MULTI, etc).
right, you already asked for that - https://lore.kernel.org/bpf/YS9k26rRcUJVS%2Fvx@krava/
I still think it'd mean more code while this way we just use
current fentry/fexit code paths with few special handling
for multi programs
I don't see how it makes much difference for kernel implementation.
Checking expected_attach_type vs checking prog_flags is about the same
amount of code. The big advantage of new expected_attach_type (or
prog_type) is that it will be very obvious in all sorts of diagnostics
tooling (think bpftool prog show output, etc). prog_flags are almost
invisible and it will be the last thing that users will think about.
I'd try to minimize the usage of prog_flags overall.
ok, I'll check on that.. I recall adding this new type in
many expected_attach_type switches, which made me think
the new flag will be easier
quoted
quoted
As for attach_btf_id, why can't we just
enforce it as 0?
there's prog->aux->attach_func_proto that needs to be set based
on attach_btf_id, and is checked later in btf_ctx_access
right:
if (attach_btf_id == 0)
prog->aux->attach_func_proto =
&special_func_model_or_proto_or_whatever_that_does_not_have_to_be_known_to_libbpf_and_outside_world_ever;
;) let's keep implementation details as internal implementation
details, instead of dumping all that to UAPI