diff mbox series

[bpf-next] bpf: fix bpf_ksym_exists in GCC

Message ID 20240428112559.10518-1-jose.marchesi@oracle.com (mailing list archive)
State Accepted
Commit cf9bea94f6b2934d409511c05337010b137316a3
Delegated to: BPF
Headers show
Series [bpf-next] bpf: fix bpf_ksym_exists in GCC | expand

Checks

Context Check Description
netdev/series_format success Single patches do not need cover letters
netdev/tree_selection success Clearly marked for bpf-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 8 this patch: 8
netdev/build_tools success Errors and warnings before: 1 this patch: 1
netdev/cc_maintainers warning 11 maintainers not CCed: kpsingh@kernel.org andrii@kernel.org john.fastabend@gmail.com jolsa@kernel.org eddyz87@gmail.com song@kernel.org yonghong.song@linux.dev daniel@iogearbox.net martin.lau@linux.dev haoluo@google.com sdf@google.com
netdev/build_clang success Errors and warnings before: 8 this patch: 8
netdev/verify_signedoff fail author Signed-off-by missing
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 8 this patch: 8
netdev/checkpatch fail CHECK: Macro argument 'sym' may be better as '(sym)' to avoid precedence issues ERROR: Macros with complex values should be enclosed in parentheses WARNING: Non-standard signature: 'Signed-of-by:' - perhaps 'Signed-off-by:'? WARNING: line length of 105 exceeds 80 columns WARNING: line length of 97 exceeds 80 columns WARNING: space prohibited between function name and open parenthesis '('
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-VM_Test-5 success Logs for aarch64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Unittests
bpf/vmtest-bpf-next-VM_Test-3 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-VM_Test-10 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-4 success Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-12 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-11 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-9 success Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-17 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-19 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-18 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-20 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-29 success Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17 and -O2 optimization
bpf/vmtest-bpf-next-VM_Test-34 success Logs for x86_64-llvm-17 / veristat
bpf/vmtest-bpf-next-VM_Test-28 success Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-36 success Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18 and -O2 optimization
bpf/vmtest-bpf-next-VM_Test-42 success Logs for x86_64-llvm-18 / veristat
bpf/vmtest-bpf-next-VM_Test-35 success Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-6 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-7 success Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-8 success Logs for aarch64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-13 success Logs for s390x-gcc / test (test_maps, false, 360) / test_maps on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-16 success Logs for s390x-gcc / test (test_verifier, false, 360) / test_verifier on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-15 success Logs for s390x-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-14 success Logs for s390x-gcc / test (test_progs, false, 360) / test_progs on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-21 success Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-22 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-23 success Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-24 success Logs for x86_64-gcc / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-25 success Logs for x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-26 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-27 success Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-30 success Logs for x86_64-llvm-17 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-31 success Logs for x86_64-llvm-17 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-33 success Logs for x86_64-llvm-17 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-32 success Logs for x86_64-llvm-17 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-37 success Logs for x86_64-llvm-18 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-40 success Logs for x86_64-llvm-18 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-41 success Logs for x86_64-llvm-18 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-38 success Logs for x86_64-llvm-18 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-39 success Logs for x86_64-llvm-18 / test (test_progs_cpuv4, false, 360) / test_progs_cpuv4 on x86_64 with llvm-18
bpf/vmtest-bpf-next-PR fail merge-conflict

Commit Message

Jose E. Marchesi April 28, 2024, 11:25 a.m. UTC
The macro bpf_ksym_exists is defined in bpf_helpers.h as:

  #define bpf_ksym_exists(sym) ({								\
  	_Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");	\
  	!!sym;											\
  })

The purpose of the macro is to determine whether a given symbol has
been defined, given the address of the object associated with the
symbol.  It also has a compile-time check to make sure the object
whose address is passed to the macro has been declared as weak, which
makes the check on `sym' meaningful.

As it happens, the check for weak doesn't work in GCC in all cases,
because __builtin_constant_p not always folds at parse time when
optimizing.  This is because optimizations that happen later in the
compilation process, like inlining, may make a previously non-constant
expression a constant.  This results in errors like the following when
building the selftests with GCC:

  bpf_helpers.h:190:24: error: expression in static assertion is not constant
  190 |         _Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");       \
      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~

Fortunately recent versions of GCC support a __builtin_has_attribute
that can be used to directly check for the __weak__ attribute.  This
patch changes bpf_helpers.h to use that builtin when building with a
recent enough GCC, and to omit the check if GCC is too old to support
the builtin.

The macro used for GCC becomes:

  #define bpf_ksym_exists(sym) ({									\
	_Static_assert(__builtin_has_attribute (*sym, __weak__), #sym " should be marked as __weak");	\
	!!sym;												\
  })

Note that since bpf_ksym_exists is designed to get the address of the
object associated with symbol SYM, we pass *sym to
__builtin_has_attribute instead of sym.  When an expression is passed
to __builtin_has_attribute then it is the type of the passed
expression that is checked for the specified attribute.  The
expression itself is not evaluated.  This accommodates well with the
existing usages of the macro:

- For function objects:

  struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym __weak;
  [...]
  bpf_ksym_exists(bpf_task_acquire)

- For variable objects:

  extern const struct rq runqueues __ksym __weak; /* typed */
  [...]
  bpf_ksym_exists(&runqueues)

Note also that BPF support was added in GCC 10 and support for
__builtin_has_attribute in GCC 9.

Locally tested in bpf-next master branch.
No regressions.

Signed-of-by: Jose E. Marchesi <jose.marchesi@oracle.com>
Cc: Alexei Starovoitov <alexei.starovoitov@gmail.com>
Cc: david.faust@oracle.com
Cc: cupertino.miranda@oracle.com
---
 tools/lib/bpf/bpf_helpers.h | 9 +++++++++
 1 file changed, 9 insertions(+)

Comments

Yonghong Song April 29, 2024, 8:52 p.m. UTC | #1
On 4/28/24 4:25 AM, Jose E. Marchesi wrote:
> The macro bpf_ksym_exists is defined in bpf_helpers.h as:
>
>    #define bpf_ksym_exists(sym) ({								\
>    	_Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");	\
>    	!!sym;											\
>    })
>
> The purpose of the macro is to determine whether a given symbol has
> been defined, given the address of the object associated with the
> symbol.  It also has a compile-time check to make sure the object
> whose address is passed to the macro has been declared as weak, which
> makes the check on `sym' meaningful.
>
> As it happens, the check for weak doesn't work in GCC in all cases,
> because __builtin_constant_p not always folds at parse time when
> optimizing.  This is because optimizations that happen later in the
> compilation process, like inlining, may make a previously non-constant
> expression a constant.  This results in errors like the following when
> building the selftests with GCC:
>
>    bpf_helpers.h:190:24: error: expression in static assertion is not constant
>    190 |         _Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");       \
>        |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> Fortunately recent versions of GCC support a __builtin_has_attribute
> that can be used to directly check for the __weak__ attribute.  This
> patch changes bpf_helpers.h to use that builtin when building with a
> recent enough GCC, and to omit the check if GCC is too old to support
> the builtin.
>
> The macro used for GCC becomes:
>
>    #define bpf_ksym_exists(sym) ({									\
> 	_Static_assert(__builtin_has_attribute (*sym, __weak__), #sym " should be marked as __weak");	\
> 	!!sym;												\
>    })
>
> Note that since bpf_ksym_exists is designed to get the address of the
> object associated with symbol SYM, we pass *sym to
> __builtin_has_attribute instead of sym.  When an expression is passed
> to __builtin_has_attribute then it is the type of the passed
> expression that is checked for the specified attribute.  The
> expression itself is not evaluated.  This accommodates well with the
> existing usages of the macro:
>
> - For function objects:
>
>    struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym __weak;
>    [...]
>    bpf_ksym_exists(bpf_task_acquire)
>
> - For variable objects:
>
>    extern const struct rq runqueues __ksym __weak; /* typed */
>    [...]
>    bpf_ksym_exists(&runqueues)
>
> Note also that BPF support was added in GCC 10 and support for
> __builtin_has_attribute in GCC 9.

It would be great if you can share details with asm code and
BTF so we can understand better. I am not 100% sure about
whether __builtin_has_attribute builtin can help to do
run-time ksym resolution with libbpf.

The following is what clang does:

For example, for progs/test_ksyms_weak.c, we have
  43         if (rq && bpf_ksym_exists(&runqueues))
  44                 out__existing_typed = rq->cpu;
...
  56         if (!bpf_ksym_exists(bpf_task_acquire))
  57                 /* dead code won't be seen by the verifier */
  58                 bpf_task_acquire(0);

The asm code:

         .loc    0 42 20 prologue_end            # progs/test_ksyms_weak.c:42:20
.Ltmp0:
         r6 = runqueues ll
         r1 = runqueues ll
         w2 = 0
         call 153
.Ltmp1:
.Ltmp2:
         #DEBUG_VALUE: pass_handler:rq <- $r0
         .loc    0 43 9                          # progs/test_ksyms_weak.c:43:9
.Ltmp3:
         if r0 == 0 goto LBB0_3
.Ltmp4:
.Ltmp5:
# %bb.1:                                # %entry
         #DEBUG_VALUE: pass_handler:rq <- $r0
         if r6 == 0 goto LBB0_3
...
LBB0_5:                                 # %if.end4
         .loc    0 56 6 is_stmt 1                # progs/test_ksyms_weak.c:56:6
.Ltmp25:
         r1 = bpf_task_acquire ll
         if r1 != 0 goto LBB0_7
# %bb.6:                                # %if.then9

Here, 'runqueues' and 'bpf_task_acquire' will be changed by libbpf
based on the *current* kernel state. The BTF datasec encodes such ksym
information like below which will be used by libbpf:

         .long   13079                           # BTF_KIND_DATASEC(id = 395)
         .long   251658247                       # 0xf000007
         .long   0
         .long   377
         .long   bpf_task_acquire
         .long   0
         .long   379
         .long   bpf_testmod_test_mod_kfunc
         .long   0
         .long   381
         .long   invalid_kfunc
         .long   0
         .long   387
         .long   runqueues
         .long   3264
         .long   388
         .long   bpf_prog_active
         .long   1
         .long   389
         .long   bpf_link_fops1
         .long   1
         .long   391
         .long   bpf_link_fops2
         .long   4

What gcc generates for the above example? It would be great
if this can be put in the commit message.

>
> Locally tested in bpf-next master branch.
> No regressions.
>
> Signed-of-by: Jose E. Marchesi <jose.marchesi@oracle.com>
> Cc: Alexei Starovoitov <alexei.starovoitov@gmail.com>
> Cc: david.faust@oracle.com
> Cc: cupertino.miranda@oracle.com
> ---
>   tools/lib/bpf/bpf_helpers.h | 9 +++++++++
>   1 file changed, 9 insertions(+)
>
> diff --git a/tools/lib/bpf/bpf_helpers.h b/tools/lib/bpf/bpf_helpers.h
> index 62e1c0cc4a59..a720636a87d9 100644
> --- a/tools/lib/bpf/bpf_helpers.h
> +++ b/tools/lib/bpf/bpf_helpers.h
> @@ -186,10 +186,19 @@ enum libbpf_tristate {
>   #define __kptr __attribute__((btf_type_tag("kptr")))
>   #define __percpu_kptr __attribute__((btf_type_tag("percpu_kptr")))
>   
> +#if defined (__clang__)
>   #define bpf_ksym_exists(sym) ({									\
>   	_Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");	\
>   	!!sym;											\
>   })
> +#elif __GNUC__ > 8

| +#define bpf_ksym_exists(sym) ({									\

> +	_Static_assert(__builtin_has_attribute (*sym, __weak__), #sym " should be marked as __weak");	\
> +	!!sym;												\
> +})
> +#else
> +#define bpf_ksym_exists(sym) !!sym
> +#endif
>   
>   #define __arg_ctx __attribute__((btf_decl_tag("arg:ctx")))
>   #define __arg_nonnull __attribute((btf_decl_tag("arg:nonnull")))
Jose E. Marchesi May 2, 2024, 5:44 p.m. UTC | #2
> On 4/28/24 4:25 AM, Jose E. Marchesi wrote:
>> The macro bpf_ksym_exists is defined in bpf_helpers.h as:
>>
>>    #define bpf_ksym_exists(sym) ({								\
>>    	_Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");	\
>>    	!!sym;											\
>>    })
>>
>> The purpose of the macro is to determine whether a given symbol has
>> been defined, given the address of the object associated with the
>> symbol.  It also has a compile-time check to make sure the object
>> whose address is passed to the macro has been declared as weak, which
>> makes the check on `sym' meaningful.
>>
>> As it happens, the check for weak doesn't work in GCC in all cases,
>> because __builtin_constant_p not always folds at parse time when
>> optimizing.  This is because optimizations that happen later in the
>> compilation process, like inlining, may make a previously non-constant
>> expression a constant.  This results in errors like the following when
>> building the selftests with GCC:
>>
>>    bpf_helpers.h:190:24: error: expression in static assertion is not constant
>>    190 |         _Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");       \
>>        |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
>>
>> Fortunately recent versions of GCC support a __builtin_has_attribute
>> that can be used to directly check for the __weak__ attribute.  This
>> patch changes bpf_helpers.h to use that builtin when building with a
>> recent enough GCC, and to omit the check if GCC is too old to support
>> the builtin.
>>
>> The macro used for GCC becomes:
>>
>>    #define bpf_ksym_exists(sym) ({									\
>> 	_Static_assert(__builtin_has_attribute (*sym, __weak__), #sym " should be marked as __weak");	\
>> 	!!sym;												\
>>    })
>>
>> Note that since bpf_ksym_exists is designed to get the address of the
>> object associated with symbol SYM, we pass *sym to
>> __builtin_has_attribute instead of sym.  When an expression is passed
>> to __builtin_has_attribute then it is the type of the passed
>> expression that is checked for the specified attribute.  The
>> expression itself is not evaluated.  This accommodates well with the
>> existing usages of the macro:
>>
>> - For function objects:
>>
>>    struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym __weak;
>>    [...]
>>    bpf_ksym_exists(bpf_task_acquire)
>>
>> - For variable objects:
>>
>>    extern const struct rq runqueues __ksym __weak; /* typed */
>>    [...]
>>    bpf_ksym_exists(&runqueues)
>>
>> Note also that BPF support was added in GCC 10 and support for
>> __builtin_has_attribute in GCC 9.
>
> It would be great if you can share details with asm code and
> BTF so we can understand better. I am not 100% sure about
> whether __builtin_has_attribute builtin can help to do
> run-time ksym resolution with libbpf.

Hi Yonghong.

I am a bit confused.  Is the _Static_assert supposed to contribute
anything to the generated code?

This is what GCC generates for pass_handler:

-----
pass_handler:
.LFB1:
	r2 = 0
	r1 = runqueues ll
	call	153
	if r0 == 0 goto .L2
	r1 = runqueues ll
	if r1 == 0 goto .L2
	r2 = out__existing_typed ll
	r0 = *(u32 *) (r0+2920)
	*(u32 *) (r2+0) = r0
.L2:
	r6 = out__non_existent_typed ll
	r1 = bpf_link_fops2 ll
	r3 = out__existing_typeless ll
	r4 = bpf_prog_active ll
	r5 = out__non_existent_typeless ll
	r9 = bpf_link_fops1 ll
	*(u64 *) (r3+0) = r4
	*(u64 *) (r5+0) = r9
	*(u64 *) (r6+0) = r1
	if r1 == 0 goto .L3
	r2 = 0
	call	153
	*(u64 *) (r6+0) = r0
.L3:
	r1 = bpf_task_acquire ll
	if r1 == 0 goto .L20
.L4:
	r1 = bpf_testmod_test_mod_kfunc ll
	if r1 == 0 goto .L21
.L5:
	r1 = invalid_kfunc ll
	if r1 == 0 goto .L6
	call	invalid_kfunc
.L6:
	r0 = 0
	exit
.L21:
	call	bpf_testmod_test_mod_kfunc
	goto .L5
.L20:
	call	bpf_task_acquire
	goto .L4
.LFE1:
	.size	pass_handler, .-pass_handler
-----

And the .ksyms datasec:

-----
[7693] DATASEC '.ksyms' size=0 vlen=7
	type_id=7690 offset=0 size=0 (FUNC 'invalid_kfunc')
	type_id=7691 offset=0 size=0 (FUNC 'bpf_testmod_test_mod_kfunc')
	type_id=7692 offset=0 size=0 (FUNC 'bpf_task_acquire')
	type_id=7530 offset=0 size=4 (VAR 'bpf_link_fops2')
	type_id=7550 offset=0 size=1 (VAR 'bpf_link_fops1')
	type_id=7475 offset=0 size=1 (VAR 'bpf_prog_active')
	type_id=7535 offset=0 size=3456 (VAR 'runqueues')
-----

Is the entry for runqueues en the datasec enough for libbpf to patch the
ksym value in the corresponding `r1 = runqueues ll' instructions?

>
> The following is what clang does:
>
> For example, for progs/test_ksyms_weak.c, we have
>  43         if (rq && bpf_ksym_exists(&runqueues))
>  44                 out__existing_typed = rq->cpu;
> ...
>  56         if (!bpf_ksym_exists(bpf_task_acquire))
>  57                 /* dead code won't be seen by the verifier */
>  58                 bpf_task_acquire(0);
>
> The asm code:
>
>         .loc    0 42 20 prologue_end            # progs/test_ksyms_weak.c:42:20
> .Ltmp0:
>         r6 = runqueues ll
>         r1 = runqueues ll
>         w2 = 0
>         call 153
> .Ltmp1:
> .Ltmp2:
>         #DEBUG_VALUE: pass_handler:rq <- $r0
>         .loc    0 43 9                          # progs/test_ksyms_weak.c:43:9
> .Ltmp3:
>         if r0 == 0 goto LBB0_3
> .Ltmp4:
> .Ltmp5:
> # %bb.1:                                # %entry
>         #DEBUG_VALUE: pass_handler:rq <- $r0
>         if r6 == 0 goto LBB0_3
> ...
> LBB0_5:                                 # %if.end4
>         .loc    0 56 6 is_stmt 1                # progs/test_ksyms_weak.c:56:6
> .Ltmp25:
>         r1 = bpf_task_acquire ll
>         if r1 != 0 goto LBB0_7
> # %bb.6:                                # %if.then9
>
> Here, 'runqueues' and 'bpf_task_acquire' will be changed by libbpf
> based on the *current* kernel state. The BTF datasec encodes such ksym
> information like below which will be used by libbpf:
>
>         .long   13079                           # BTF_KIND_DATASEC(id = 395)
>         .long   251658247                       # 0xf000007
>         .long   0
>         .long   377
>         .long   bpf_task_acquire
>         .long   0
>         .long   379
>         .long   bpf_testmod_test_mod_kfunc
>         .long   0
>         .long   381
>         .long   invalid_kfunc
>         .long   0
>         .long   387
>         .long   runqueues
>         .long   3264
>         .long   388
>         .long   bpf_prog_active
>         .long   1
>         .long   389
>         .long   bpf_link_fops1
>         .long   1
>         .long   391
>         .long   bpf_link_fops2
>         .long   4
>
> What gcc generates for the above example? It would be great
> if this can be put in the commit message.
>
>>
>> Locally tested in bpf-next master branch.
>> No regressions.
>>
>> Signed-of-by: Jose E. Marchesi <jose.marchesi@oracle.com>
>> Cc: Alexei Starovoitov <alexei.starovoitov@gmail.com>
>> Cc: david.faust@oracle.com
>> Cc: cupertino.miranda@oracle.com
>> ---
>>   tools/lib/bpf/bpf_helpers.h | 9 +++++++++
>>   1 file changed, 9 insertions(+)
>>
>> diff --git a/tools/lib/bpf/bpf_helpers.h b/tools/lib/bpf/bpf_helpers.h
>> index 62e1c0cc4a59..a720636a87d9 100644
>> --- a/tools/lib/bpf/bpf_helpers.h
>> +++ b/tools/lib/bpf/bpf_helpers.h
>> @@ -186,10 +186,19 @@ enum libbpf_tristate {
>>   #define __kptr __attribute__((btf_type_tag("kptr")))
>>   #define __percpu_kptr __attribute__((btf_type_tag("percpu_kptr")))
>>   +#if defined (__clang__)
>>   #define bpf_ksym_exists(sym) ({									\
>>   	_Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");	\
>>   	!!sym;											\
>>   })
>> +#elif __GNUC__ > 8
>
> | +#define bpf_ksym_exists(sym) ({									\
>
>> +	_Static_assert(__builtin_has_attribute (*sym, __weak__), #sym " should be marked as __weak");	\
>> +	!!sym;												\
>> +})
>> +#else
>> +#define bpf_ksym_exists(sym) !!sym
>> +#endif
>>     #define __arg_ctx __attribute__((btf_decl_tag("arg:ctx")))
>>   #define __arg_nonnull __attribute((btf_decl_tag("arg:nonnull")))
Jose E. Marchesi May 2, 2024, 6:23 p.m. UTC | #3
>         .long   runqueues
>         .long   3264
>         .long   388

Just to be sure, there seems to be a little discrepancy.  The size of
runqueues (struct rq) in the latest bpf-next is 3456 instead of 3264,
when compiled by both clang 18 and GCC.  This is correctly reflected in
the BTF generated by both compilers.

Is the size of struct rq different in your testing machine?
Yonghong Song May 3, 2024, 4:56 a.m. UTC | #4
On 5/2/24 10:44 AM, Jose E. Marchesi wrote:
>> On 4/28/24 4:25 AM, Jose E. Marchesi wrote:
>>> The macro bpf_ksym_exists is defined in bpf_helpers.h as:
>>>
>>>     #define bpf_ksym_exists(sym) ({								\
>>>     	_Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");	\
>>>     	!!sym;											\
>>>     })
>>>
>>> The purpose of the macro is to determine whether a given symbol has
>>> been defined, given the address of the object associated with the
>>> symbol.  It also has a compile-time check to make sure the object
>>> whose address is passed to the macro has been declared as weak, which
>>> makes the check on `sym' meaningful.
>>>
>>> As it happens, the check for weak doesn't work in GCC in all cases,
>>> because __builtin_constant_p not always folds at parse time when
>>> optimizing.  This is because optimizations that happen later in the
>>> compilation process, like inlining, may make a previously non-constant
>>> expression a constant.  This results in errors like the following when
>>> building the selftests with GCC:
>>>
>>>     bpf_helpers.h:190:24: error: expression in static assertion is not constant
>>>     190 |         _Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");       \
>>>         |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
>>>
>>> Fortunately recent versions of GCC support a __builtin_has_attribute
>>> that can be used to directly check for the __weak__ attribute.  This
>>> patch changes bpf_helpers.h to use that builtin when building with a
>>> recent enough GCC, and to omit the check if GCC is too old to support
>>> the builtin.
>>>
>>> The macro used for GCC becomes:
>>>
>>>     #define bpf_ksym_exists(sym) ({									\
>>> 	_Static_assert(__builtin_has_attribute (*sym, __weak__), #sym " should be marked as __weak");	\
>>> 	!!sym;												\
>>>     })
>>>
>>> Note that since bpf_ksym_exists is designed to get the address of the
>>> object associated with symbol SYM, we pass *sym to
>>> __builtin_has_attribute instead of sym.  When an expression is passed
>>> to __builtin_has_attribute then it is the type of the passed
>>> expression that is checked for the specified attribute.  The
>>> expression itself is not evaluated.  This accommodates well with the
>>> existing usages of the macro:
>>>
>>> - For function objects:
>>>
>>>     struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym __weak;
>>>     [...]
>>>     bpf_ksym_exists(bpf_task_acquire)
>>>
>>> - For variable objects:
>>>
>>>     extern const struct rq runqueues __ksym __weak; /* typed */
>>>     [...]
>>>     bpf_ksym_exists(&runqueues)
>>>
>>> Note also that BPF support was added in GCC 10 and support for
>>> __builtin_has_attribute in GCC 9.
>> It would be great if you can share details with asm code and
>> BTF so we can understand better. I am not 100% sure about
>> whether __builtin_has_attribute builtin can help to do
>> run-time ksym resolution with libbpf.
> Hi Yonghong.
>
> I am a bit confused.  Is the _Static_assert supposed to contribute
> anything to the generated code?

No it is not. It is used to check whether __weak attribute is added to
the symbol or not.

>
> This is what GCC generates for pass_handler:
>
> -----
> pass_handler:
> .LFB1:
> 	r2 = 0
> 	r1 = runqueues ll
> 	call	153
> 	if r0 == 0 goto .L2
> 	r1 = runqueues ll
> 	if r1 == 0 goto .L2
> 	r2 = out__existing_typed ll
> 	r0 = *(u32 *) (r0+2920)
> 	*(u32 *) (r2+0) = r0
> .L2:
> 	r6 = out__non_existent_typed ll
> 	r1 = bpf_link_fops2 ll
> 	r3 = out__existing_typeless ll
> 	r4 = bpf_prog_active ll
> 	r5 = out__non_existent_typeless ll
> 	r9 = bpf_link_fops1 ll
> 	*(u64 *) (r3+0) = r4
> 	*(u64 *) (r5+0) = r9
> 	*(u64 *) (r6+0) = r1
> 	if r1 == 0 goto .L3
> 	r2 = 0
> 	call	153
> 	*(u64 *) (r6+0) = r0
> .L3:
> 	r1 = bpf_task_acquire ll
> 	if r1 == 0 goto .L20
> .L4:
> 	r1 = bpf_testmod_test_mod_kfunc ll
> 	if r1 == 0 goto .L21
> .L5:
> 	r1 = invalid_kfunc ll
> 	if r1 == 0 goto .L6
> 	call	invalid_kfunc
> .L6:
> 	r0 = 0
> 	exit
> .L21:
> 	call	bpf_testmod_test_mod_kfunc
> 	goto .L5
> .L20:
> 	call	bpf_task_acquire
> 	goto .L4
> .LFE1:
> 	.size	pass_handler, .-pass_handler
> -----
>
> And the .ksyms datasec:
>
> -----
> [7693] DATASEC '.ksyms' size=0 vlen=7
> 	type_id=7690 offset=0 size=0 (FUNC 'invalid_kfunc')
> 	type_id=7691 offset=0 size=0 (FUNC 'bpf_testmod_test_mod_kfunc')
> 	type_id=7692 offset=0 size=0 (FUNC 'bpf_task_acquire')
> 	type_id=7530 offset=0 size=4 (VAR 'bpf_link_fops2')
> 	type_id=7550 offset=0 size=1 (VAR 'bpf_link_fops1')
> 	type_id=7475 offset=0 size=1 (VAR 'bpf_prog_active')
> 	type_id=7535 offset=0 size=3456 (VAR 'runqueues')
> -----
>
> Is the entry for runqueues en the datasec enough for libbpf to patch the
> ksym value in the corresponding `r1 = runqueues ll' instructions

It should be okay. libbpf will patch `r1 = runqueues ll` with
`r1 = <btf_obj_fd/btf_id>` and the kernel will translate it to
`r1 = <kernel addr of runqueues>`.

Based on your above output, the patch looks good to me.

Acked-by: Yonghong Song <yonghong.song@linux.dev>

>
>> The following is what clang does:
>>
>> For example, for progs/test_ksyms_weak.c, we have
>>   43         if (rq && bpf_ksym_exists(&runqueues))
>>   44                 out__existing_typed = rq->cpu;
>> ...
>>   56         if (!bpf_ksym_exists(bpf_task_acquire))
>>   57                 /* dead code won't be seen by the verifier */
>>   58                 bpf_task_acquire(0);
>>
>> The asm code:
>>
>>          .loc    0 42 20 prologue_end            # progs/test_ksyms_weak.c:42:20
>> .Ltmp0:
>>          r6 = runqueues ll
>>          r1 = runqueues ll
>>          w2 = 0
>>          call 153
>> .Ltmp1:
>> .Ltmp2:
>>          #DEBUG_VALUE: pass_handler:rq <- $r0
>>          .loc    0 43 9                          # progs/test_ksyms_weak.c:43:9
>> .Ltmp3:
>>          if r0 == 0 goto LBB0_3
>> .Ltmp4:
>> .Ltmp5:
>> # %bb.1:                                # %entry
>>          #DEBUG_VALUE: pass_handler:rq <- $r0
>>          if r6 == 0 goto LBB0_3
>> ...
>> LBB0_5:                                 # %if.end4
>>          .loc    0 56 6 is_stmt 1                # progs/test_ksyms_weak.c:56:6
>> .Ltmp25:
>>          r1 = bpf_task_acquire ll
>>          if r1 != 0 goto LBB0_7
>> # %bb.6:                                # %if.then9
>>
>> Here, 'runqueues' and 'bpf_task_acquire' will be changed by libbpf
>> based on the *current* kernel state. The BTF datasec encodes such ksym
>> information like below which will be used by libbpf:
>>
>>          .long   13079                           # BTF_KIND_DATASEC(id = 395)
>>          .long   251658247                       # 0xf000007
>>          .long   0
>>          .long   377
>>          .long   bpf_task_acquire
>>          .long   0
>>          .long   379
>>          .long   bpf_testmod_test_mod_kfunc
>>          .long   0
>>          .long   381
>>          .long   invalid_kfunc
>>          .long   0
>>          .long   387
>>          .long   runqueues
>>          .long   3264
>>          .long   388
>>          .long   bpf_prog_active
>>          .long   1
>>          .long   389
>>          .long   bpf_link_fops1
>>          .long   1
>>          .long   391
>>          .long   bpf_link_fops2
>>          .long   4
>>
>> What gcc generates for the above example? It would be great
>> if this can be put in the commit message.
>>
>>> Locally tested in bpf-next master branch.
>>> No regressions.
>>>
>>> Signed-of-by: Jose E. Marchesi <jose.marchesi@oracle.com>
>>> Cc: Alexei Starovoitov <alexei.starovoitov@gmail.com>
>>> Cc: david.faust@oracle.com
>>> Cc: cupertino.miranda@oracle.com
>>> ---
>>>    tools/lib/bpf/bpf_helpers.h | 9 +++++++++
>>>    1 file changed, 9 insertions(+)
>>>
>>> diff --git a/tools/lib/bpf/bpf_helpers.h b/tools/lib/bpf/bpf_helpers.h
>>> index 62e1c0cc4a59..a720636a87d9 100644
>>> --- a/tools/lib/bpf/bpf_helpers.h
>>> +++ b/tools/lib/bpf/bpf_helpers.h
>>> @@ -186,10 +186,19 @@ enum libbpf_tristate {
>>>    #define __kptr __attribute__((btf_type_tag("kptr")))
>>>    #define __percpu_kptr __attribute__((btf_type_tag("percpu_kptr")))
>>>    +#if defined (__clang__)
>>>    #define bpf_ksym_exists(sym) ({									\
>>>    	_Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");	\
>>>    	!!sym;											\
>>>    })
>>> +#elif __GNUC__ > 8
>> | +#define bpf_ksym_exists(sym) ({									\
>>
>>> +	_Static_assert(__builtin_has_attribute (*sym, __weak__), #sym " should be marked as __weak");	\
>>> +	!!sym;												\
>>> +})
>>> +#else
>>> +#define bpf_ksym_exists(sym) !!sym
>>> +#endif
>>>      #define __arg_ctx __attribute__((btf_decl_tag("arg:ctx")))
>>>    #define __arg_nonnull __attribute((btf_decl_tag("arg:nonnull")))
Yonghong Song May 3, 2024, 4:58 a.m. UTC | #5
On 5/2/24 11:23 AM, Jose E. Marchesi wrote:
>>          .long   runqueues
>>          .long   3264
>>          .long   388
> Just to be sure, there seems to be a little discrepancy.  The size of
> runqueues (struct rq) in the latest bpf-next is 3456 instead of 3264,
> when compiled by both clang 18 and GCC.  This is correctly reflected in
> the BTF generated by both compilers.
>
> Is the size of struct rq different in your testing machine?

Probably due to different configrations. Anyway, libbpf/kernel will do proper
rewrite to reference to the proper kernel runqueus.
Andrii Nakryiko May 3, 2024, 5:52 a.m. UTC | #6
On Sun, Apr 28, 2024 at 4:26 AM Jose E. Marchesi
<jose.marchesi@oracle.com> wrote:
>
> The macro bpf_ksym_exists is defined in bpf_helpers.h as:
>
>   #define bpf_ksym_exists(sym) ({                                                               \
>         _Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");       \
>         !!sym;                                                                                  \
>   })
>
> The purpose of the macro is to determine whether a given symbol has
> been defined, given the address of the object associated with the
> symbol.  It also has a compile-time check to make sure the object
> whose address is passed to the macro has been declared as weak, which
> makes the check on `sym' meaningful.
>
> As it happens, the check for weak doesn't work in GCC in all cases,
> because __builtin_constant_p not always folds at parse time when
> optimizing.  This is because optimizations that happen later in the
> compilation process, like inlining, may make a previously non-constant
> expression a constant.  This results in errors like the following when
> building the selftests with GCC:
>
>   bpf_helpers.h:190:24: error: expression in static assertion is not constant
>   190 |         _Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");       \
>       |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> Fortunately recent versions of GCC support a __builtin_has_attribute
> that can be used to directly check for the __weak__ attribute.  This
> patch changes bpf_helpers.h to use that builtin when building with a
> recent enough GCC, and to omit the check if GCC is too old to support
> the builtin.
>
> The macro used for GCC becomes:
>
>   #define bpf_ksym_exists(sym) ({                                                                       \
>         _Static_assert(__builtin_has_attribute (*sym, __weak__), #sym " should be marked as __weak");   \
>         !!sym;                                                                                          \
>   })
>
> Note that since bpf_ksym_exists is designed to get the address of the
> object associated with symbol SYM, we pass *sym to
> __builtin_has_attribute instead of sym.  When an expression is passed
> to __builtin_has_attribute then it is the type of the passed
> expression that is checked for the specified attribute.  The
> expression itself is not evaluated.  This accommodates well with the
> existing usages of the macro:
>
> - For function objects:
>
>   struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym __weak;
>   [...]
>   bpf_ksym_exists(bpf_task_acquire)
>
> - For variable objects:
>
>   extern const struct rq runqueues __ksym __weak; /* typed */
>   [...]
>   bpf_ksym_exists(&runqueues)
>
> Note also that BPF support was added in GCC 10 and support for
> __builtin_has_attribute in GCC 9.
>
> Locally tested in bpf-next master branch.
> No regressions.
>
> Signed-of-by: Jose E. Marchesi <jose.marchesi@oracle.com>
> Cc: Alexei Starovoitov <alexei.starovoitov@gmail.com>
> Cc: david.faust@oracle.com
> Cc: cupertino.miranda@oracle.com
> ---
>  tools/lib/bpf/bpf_helpers.h | 9 +++++++++
>  1 file changed, 9 insertions(+)
>
> diff --git a/tools/lib/bpf/bpf_helpers.h b/tools/lib/bpf/bpf_helpers.h
> index 62e1c0cc4a59..a720636a87d9 100644
> --- a/tools/lib/bpf/bpf_helpers.h
> +++ b/tools/lib/bpf/bpf_helpers.h
> @@ -186,10 +186,19 @@ enum libbpf_tristate {
>  #define __kptr __attribute__((btf_type_tag("kptr")))
>  #define __percpu_kptr __attribute__((btf_type_tag("percpu_kptr")))
>
> +#if defined (__clang__)
>  #define bpf_ksym_exists(sym) ({                                                                        \
>         _Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");       \
>         !!sym;                                                                                  \
>  })
> +#elif __GNUC__ > 8
> +#define bpf_ksym_exists(sym) ({                                                                        \
> +       _Static_assert(__builtin_has_attribute (*sym, __weak__), #sym " should be marked as __weak");   \
> +       !!sym;                                                                                          \
> +})

I wrapped _Static_assert() to keep it under 100 characters (and fix
one unaligned '\' while at it). Also, the patch prefix should be
"libbpf: " as this is purely a libbpf header. Applied to bpf-next,
thanks.

> +#else
> +#define bpf_ksym_exists(sym) !!sym
> +#endif
>
>  #define __arg_ctx __attribute__((btf_decl_tag("arg:ctx")))
>  #define __arg_nonnull __attribute((btf_decl_tag("arg:nonnull")))
> --
> 2.30.2
>
>
patchwork-bot+netdevbpf@kernel.org May 3, 2024, 6 a.m. UTC | #7
Hello:

This patch was applied to bpf/bpf-next.git (master)
by Andrii Nakryiko <andrii@kernel.org>:

On Sun, 28 Apr 2024 13:25:59 +0200 you wrote:
> The macro bpf_ksym_exists is defined in bpf_helpers.h as:
> 
>   #define bpf_ksym_exists(sym) ({								\
>   	_Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");	\
>   	!!sym;											\
>   })
> 
> [...]

Here is the summary with links:
  - [bpf-next] bpf: fix bpf_ksym_exists in GCC
    https://git.kernel.org/bpf/bpf-next/c/cf9bea94f6b2

You are awesome, thank you!
Jose E. Marchesi May 3, 2024, 7:50 a.m. UTC | #8
> On Sun, Apr 28, 2024 at 4:26 AM Jose E. Marchesi
> <jose.marchesi@oracle.com> wrote:
>>
>> The macro bpf_ksym_exists is defined in bpf_helpers.h as:
>>
>>   #define bpf_ksym_exists(sym) ({                                                               \
>>         _Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");       \
>>         !!sym;                                                                                  \
>>   })
>>
>> The purpose of the macro is to determine whether a given symbol has
>> been defined, given the address of the object associated with the
>> symbol.  It also has a compile-time check to make sure the object
>> whose address is passed to the macro has been declared as weak, which
>> makes the check on `sym' meaningful.
>>
>> As it happens, the check for weak doesn't work in GCC in all cases,
>> because __builtin_constant_p not always folds at parse time when
>> optimizing.  This is because optimizations that happen later in the
>> compilation process, like inlining, may make a previously non-constant
>> expression a constant.  This results in errors like the following when
>> building the selftests with GCC:
>>
>>   bpf_helpers.h:190:24: error: expression in static assertion is not constant
>>   190 |         _Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");       \
>>       |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
>>
>> Fortunately recent versions of GCC support a __builtin_has_attribute
>> that can be used to directly check for the __weak__ attribute.  This
>> patch changes bpf_helpers.h to use that builtin when building with a
>> recent enough GCC, and to omit the check if GCC is too old to support
>> the builtin.
>>
>> The macro used for GCC becomes:
>>
>>   #define bpf_ksym_exists(sym) ({                                                                       \
>>         _Static_assert(__builtin_has_attribute (*sym, __weak__), #sym " should be marked as __weak");   \
>>         !!sym;                                                                                          \
>>   })
>>
>> Note that since bpf_ksym_exists is designed to get the address of the
>> object associated with symbol SYM, we pass *sym to
>> __builtin_has_attribute instead of sym.  When an expression is passed
>> to __builtin_has_attribute then it is the type of the passed
>> expression that is checked for the specified attribute.  The
>> expression itself is not evaluated.  This accommodates well with the
>> existing usages of the macro:
>>
>> - For function objects:
>>
>>   struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym __weak;
>>   [...]
>>   bpf_ksym_exists(bpf_task_acquire)
>>
>> - For variable objects:
>>
>>   extern const struct rq runqueues __ksym __weak; /* typed */
>>   [...]
>>   bpf_ksym_exists(&runqueues)
>>
>> Note also that BPF support was added in GCC 10 and support for
>> __builtin_has_attribute in GCC 9.
>>
>> Locally tested in bpf-next master branch.
>> No regressions.
>>
>> Signed-of-by: Jose E. Marchesi <jose.marchesi@oracle.com>
>> Cc: Alexei Starovoitov <alexei.starovoitov@gmail.com>
>> Cc: david.faust@oracle.com
>> Cc: cupertino.miranda@oracle.com
>> ---
>>  tools/lib/bpf/bpf_helpers.h | 9 +++++++++
>>  1 file changed, 9 insertions(+)
>>
>> diff --git a/tools/lib/bpf/bpf_helpers.h b/tools/lib/bpf/bpf_helpers.h
>> index 62e1c0cc4a59..a720636a87d9 100644
>> --- a/tools/lib/bpf/bpf_helpers.h
>> +++ b/tools/lib/bpf/bpf_helpers.h
>> @@ -186,10 +186,19 @@ enum libbpf_tristate {
>>  #define __kptr __attribute__((btf_type_tag("kptr")))
>>  #define __percpu_kptr __attribute__((btf_type_tag("percpu_kptr")))
>>
>> +#if defined (__clang__)
>>  #define bpf_ksym_exists(sym) ({                                                                        \
>>         _Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");       \
>>         !!sym;                                                                                  \
>>  })
>> +#elif __GNUC__ > 8
>> +#define bpf_ksym_exists(sym) ({                                                                        \
>> +       _Static_assert(__builtin_has_attribute (*sym, __weak__), #sym " should be marked as __weak");   \
>> +       !!sym;                                                                                          \
>> +})
>
> I wrapped _Static_assert() to keep it under 100 characters (and fix
> one unaligned '\' while at it). Also, the patch prefix should be
> "libbpf: " as this is purely a libbpf header. Applied to bpf-next,
> thanks.

Thank you.
Sorry for the wrong prefix and for the style fixes.

>
>> +#else
>> +#define bpf_ksym_exists(sym) !!sym
>> +#endif
>>
>>  #define __arg_ctx __attribute__((btf_decl_tag("arg:ctx")))
>>  #define __arg_nonnull __attribute((btf_decl_tag("arg:nonnull")))
>> --
>> 2.30.2
>>
>>
diff mbox series

Patch

diff --git a/tools/lib/bpf/bpf_helpers.h b/tools/lib/bpf/bpf_helpers.h
index 62e1c0cc4a59..a720636a87d9 100644
--- a/tools/lib/bpf/bpf_helpers.h
+++ b/tools/lib/bpf/bpf_helpers.h
@@ -186,10 +186,19 @@  enum libbpf_tristate {
 #define __kptr __attribute__((btf_type_tag("kptr")))
 #define __percpu_kptr __attribute__((btf_type_tag("percpu_kptr")))
 
+#if defined (__clang__)
 #define bpf_ksym_exists(sym) ({									\
 	_Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak");	\
 	!!sym;											\
 })
+#elif __GNUC__ > 8
+#define bpf_ksym_exists(sym) ({									\
+	_Static_assert(__builtin_has_attribute (*sym, __weak__), #sym " should be marked as __weak");	\
+	!!sym;												\
+})
+#else
+#define bpf_ksym_exists(sym) !!sym
+#endif
 
 #define __arg_ctx __attribute__((btf_decl_tag("arg:ctx")))
 #define __arg_nonnull __attribute((btf_decl_tag("arg:nonnull")))