Commit 55d00c37 authored by Andrii Nakryiko's avatar Andrii Nakryiko Committed by Alexei Starovoitov

libbpf: generalize virtual __kconfig externs and use it for USDT

Libbpf supports single virtual __kconfig extern currently: LINUX_KERNEL_VERSION.
LINUX_KERNEL_VERSION isn't coming from /proc/kconfig.gz and is intead
customly filled out by libbpf.

This patch generalizes this approach to support more such virtual
__kconfig externs. One such extern added in this patch is
LINUX_HAS_BPF_COOKIE which is used for BPF-side USDT supporting code in
usdt.bpf.h instead of using CO-RE-based enum detection approach for
detecting bpf_get_attach_cookie() BPF helper. This allows to remove
otherwise not needed CO-RE dependency and keeps user-space and BPF-side
parts of libbpf's USDT support strictly in sync in terms of their
feature detection.

We'll use similar approach for syscall wrapper detection for
BPF_KSYSCALL() BPF-side macro in follow up patch.

Generally, currently libbpf reserves CONFIG_ prefix for Kconfig values
and LINUX_ for virtual libbpf-backed externs. In the future we might
extend the set of prefixes that are supported. This can be done without
any breaking changes, as currently any __kconfig extern with
unrecognized name is rejected.

For LINUX_xxx externs we support the normal "weak rule": if libbpf
doesn't recognize given LINUX_xxx extern but such extern is marked as
__weak, it is not rejected and defaults to zero.  This follows
CONFIG_xxx handling logic and will allow BPF applications to
opportunistically use newer libbpf virtual externs without breaking on
older libbpf versions unnecessarily.
Tested-by: default avatarAlan Maguire <alan.maguire@oracle.com>
Reviewed-by: default avatarAlan Maguire <alan.maguire@oracle.com>
Signed-off-by: default avatarAndrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/r/20220714070755.3235561-2-andrii@kernel.orgSigned-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parent 9ff5efde
...@@ -1694,7 +1694,7 @@ static int set_kcfg_value_tri(struct extern_desc *ext, void *ext_val, ...@@ -1694,7 +1694,7 @@ static int set_kcfg_value_tri(struct extern_desc *ext, void *ext_val,
switch (ext->kcfg.type) { switch (ext->kcfg.type) {
case KCFG_BOOL: case KCFG_BOOL:
if (value == 'm') { if (value == 'm') {
pr_warn("extern (kcfg) %s=%c should be tristate or char\n", pr_warn("extern (kcfg) '%s': value '%c' implies tristate or char type\n",
ext->name, value); ext->name, value);
return -EINVAL; return -EINVAL;
} }
...@@ -1715,7 +1715,7 @@ static int set_kcfg_value_tri(struct extern_desc *ext, void *ext_val, ...@@ -1715,7 +1715,7 @@ static int set_kcfg_value_tri(struct extern_desc *ext, void *ext_val,
case KCFG_INT: case KCFG_INT:
case KCFG_CHAR_ARR: case KCFG_CHAR_ARR:
default: default:
pr_warn("extern (kcfg) %s=%c should be bool, tristate, or char\n", pr_warn("extern (kcfg) '%s': value '%c' implies bool, tristate, or char type\n",
ext->name, value); ext->name, value);
return -EINVAL; return -EINVAL;
} }
...@@ -1729,7 +1729,8 @@ static int set_kcfg_value_str(struct extern_desc *ext, char *ext_val, ...@@ -1729,7 +1729,8 @@ static int set_kcfg_value_str(struct extern_desc *ext, char *ext_val,
size_t len; size_t len;
if (ext->kcfg.type != KCFG_CHAR_ARR) { if (ext->kcfg.type != KCFG_CHAR_ARR) {
pr_warn("extern (kcfg) %s=%s should be char array\n", ext->name, value); pr_warn("extern (kcfg) '%s': value '%s' implies char array type\n",
ext->name, value);
return -EINVAL; return -EINVAL;
} }
...@@ -1743,7 +1744,7 @@ static int set_kcfg_value_str(struct extern_desc *ext, char *ext_val, ...@@ -1743,7 +1744,7 @@ static int set_kcfg_value_str(struct extern_desc *ext, char *ext_val,
/* strip quotes */ /* strip quotes */
len -= 2; len -= 2;
if (len >= ext->kcfg.sz) { if (len >= ext->kcfg.sz) {
pr_warn("extern (kcfg) '%s': long string config %s of (%zu bytes) truncated to %d bytes\n", pr_warn("extern (kcfg) '%s': long string '%s' of (%zu bytes) truncated to %d bytes\n",
ext->name, value, len, ext->kcfg.sz - 1); ext->name, value, len, ext->kcfg.sz - 1);
len = ext->kcfg.sz - 1; len = ext->kcfg.sz - 1;
} }
...@@ -1800,13 +1801,20 @@ static bool is_kcfg_value_in_range(const struct extern_desc *ext, __u64 v) ...@@ -1800,13 +1801,20 @@ static bool is_kcfg_value_in_range(const struct extern_desc *ext, __u64 v)
static int set_kcfg_value_num(struct extern_desc *ext, void *ext_val, static int set_kcfg_value_num(struct extern_desc *ext, void *ext_val,
__u64 value) __u64 value)
{ {
if (ext->kcfg.type != KCFG_INT && ext->kcfg.type != KCFG_CHAR) { if (ext->kcfg.type != KCFG_INT && ext->kcfg.type != KCFG_CHAR &&
pr_warn("extern (kcfg) %s=%llu should be integer\n", ext->kcfg.type != KCFG_BOOL) {
pr_warn("extern (kcfg) '%s': value '%llu' implies integer, char, or boolean type\n",
ext->name, (unsigned long long)value); ext->name, (unsigned long long)value);
return -EINVAL; return -EINVAL;
} }
if (ext->kcfg.type == KCFG_BOOL && value > 1) {
pr_warn("extern (kcfg) '%s': value '%llu' isn't boolean compatible\n",
ext->name, (unsigned long long)value);
return -EINVAL;
}
if (!is_kcfg_value_in_range(ext, value)) { if (!is_kcfg_value_in_range(ext, value)) {
pr_warn("extern (kcfg) %s=%llu value doesn't fit in %d bytes\n", pr_warn("extern (kcfg) '%s': value '%llu' doesn't fit in %d bytes\n",
ext->name, (unsigned long long)value, ext->kcfg.sz); ext->name, (unsigned long long)value, ext->kcfg.sz);
return -ERANGE; return -ERANGE;
} }
...@@ -1870,16 +1878,19 @@ static int bpf_object__process_kconfig_line(struct bpf_object *obj, ...@@ -1870,16 +1878,19 @@ static int bpf_object__process_kconfig_line(struct bpf_object *obj,
/* assume integer */ /* assume integer */
err = parse_u64(value, &num); err = parse_u64(value, &num);
if (err) { if (err) {
pr_warn("extern (kcfg) %s=%s should be integer\n", pr_warn("extern (kcfg) '%s': value '%s' isn't a valid integer\n", ext->name, value);
ext->name, value);
return err; return err;
} }
if (ext->kcfg.type != KCFG_INT && ext->kcfg.type != KCFG_CHAR) {
pr_warn("extern (kcfg) '%s': value '%s' implies integer type\n", ext->name, value);
return -EINVAL;
}
err = set_kcfg_value_num(ext, ext_val, num); err = set_kcfg_value_num(ext, ext_val, num);
break; break;
} }
if (err) if (err)
return err; return err;
pr_debug("extern (kcfg) %s=%s\n", ext->name, value); pr_debug("extern (kcfg) '%s': set to %s\n", ext->name, value);
return 0; return 0;
} }
...@@ -3687,7 +3698,7 @@ static int bpf_object__collect_externs(struct bpf_object *obj) ...@@ -3687,7 +3698,7 @@ static int bpf_object__collect_externs(struct bpf_object *obj)
ext->kcfg.type = find_kcfg_type(obj->btf, t->type, ext->kcfg.type = find_kcfg_type(obj->btf, t->type,
&ext->kcfg.is_signed); &ext->kcfg.is_signed);
if (ext->kcfg.type == KCFG_UNKNOWN) { if (ext->kcfg.type == KCFG_UNKNOWN) {
pr_warn("extern (kcfg) '%s' type is unsupported\n", ext_name); pr_warn("extern (kcfg) '%s': type is unsupported\n", ext_name);
return -ENOTSUP; return -ENOTSUP;
} }
} else if (strcmp(sec_name, KSYMS_SEC) == 0) { } else if (strcmp(sec_name, KSYMS_SEC) == 0) {
...@@ -7287,14 +7298,14 @@ static int kallsyms_cb(unsigned long long sym_addr, char sym_type, ...@@ -7287,14 +7298,14 @@ static int kallsyms_cb(unsigned long long sym_addr, char sym_type,
return 0; return 0;
if (ext->is_set && ext->ksym.addr != sym_addr) { if (ext->is_set && ext->ksym.addr != sym_addr) {
pr_warn("extern (ksym) '%s' resolution is ambiguous: 0x%llx or 0x%llx\n", pr_warn("extern (ksym) '%s': resolution is ambiguous: 0x%llx or 0x%llx\n",
sym_name, ext->ksym.addr, sym_addr); sym_name, ext->ksym.addr, sym_addr);
return -EINVAL; return -EINVAL;
} }
if (!ext->is_set) { if (!ext->is_set) {
ext->is_set = true; ext->is_set = true;
ext->ksym.addr = sym_addr; ext->ksym.addr = sym_addr;
pr_debug("extern (ksym) %s=0x%llx\n", sym_name, sym_addr); pr_debug("extern (ksym) '%s': set to 0x%llx\n", sym_name, sym_addr);
} }
return 0; return 0;
} }
...@@ -7498,28 +7509,50 @@ static int bpf_object__resolve_externs(struct bpf_object *obj, ...@@ -7498,28 +7509,50 @@ static int bpf_object__resolve_externs(struct bpf_object *obj,
for (i = 0; i < obj->nr_extern; i++) { for (i = 0; i < obj->nr_extern; i++) {
ext = &obj->externs[i]; ext = &obj->externs[i];
if (ext->type == EXT_KCFG && if (ext->type == EXT_KSYM) {
strcmp(ext->name, "LINUX_KERNEL_VERSION") == 0) { if (ext->ksym.type_id)
void *ext_val = kcfg_data + ext->kcfg.data_off; need_vmlinux_btf = true;
__u32 kver = get_kernel_version(); else
need_kallsyms = true;
continue;
} else if (ext->type == EXT_KCFG) {
void *ext_ptr = kcfg_data + ext->kcfg.data_off;
__u64 value = 0;
/* Kconfig externs need actual /proc/config.gz */
if (str_has_pfx(ext->name, "CONFIG_")) {
need_config = true;
continue;
}
if (!kver) { /* Virtual kcfg externs are customly handled by libbpf */
pr_warn("failed to get kernel version\n"); if (strcmp(ext->name, "LINUX_KERNEL_VERSION") == 0) {
value = get_kernel_version();
if (!value) {
pr_warn("extern (kcfg) '%s': failed to get kernel version\n", ext->name);
return -EINVAL;
}
} else if (strcmp(ext->name, "LINUX_HAS_BPF_COOKIE") == 0) {
value = kernel_supports(obj, FEAT_BPF_COOKIE);
} else if (!str_has_pfx(ext->name, "LINUX_") || !ext->is_weak) {
/* Currently libbpf supports only CONFIG_ and LINUX_ prefixed
* __kconfig externs, where LINUX_ ones are virtual and filled out
* customly by libbpf (their values don't come from Kconfig).
* If LINUX_xxx variable is not recognized by libbpf, but is marked
* __weak, it defaults to zero value, just like for CONFIG_xxx
* externs.
*/
pr_warn("extern (kcfg) '%s': unrecognized virtual extern\n", ext->name);
return -EINVAL; return -EINVAL;
} }
err = set_kcfg_value_num(ext, ext_val, kver);
err = set_kcfg_value_num(ext, ext_ptr, value);
if (err) if (err)
return err; return err;
pr_debug("extern (kcfg) %s=0x%x\n", ext->name, kver); pr_debug("extern (kcfg) '%s': set to 0x%llx\n",
} else if (ext->type == EXT_KCFG && str_has_pfx(ext->name, "CONFIG_")) { ext->name, (long long)value);
need_config = true;
} else if (ext->type == EXT_KSYM) {
if (ext->ksym.type_id)
need_vmlinux_btf = true;
else
need_kallsyms = true;
} else { } else {
pr_warn("unrecognized extern '%s'\n", ext->name); pr_warn("extern '%s': unrecognized extern kind\n", ext->name);
return -EINVAL; return -EINVAL;
} }
} }
...@@ -7555,10 +7588,10 @@ static int bpf_object__resolve_externs(struct bpf_object *obj, ...@@ -7555,10 +7588,10 @@ static int bpf_object__resolve_externs(struct bpf_object *obj,
ext = &obj->externs[i]; ext = &obj->externs[i];
if (!ext->is_set && !ext->is_weak) { if (!ext->is_set && !ext->is_weak) {
pr_warn("extern %s (strong) not resolved\n", ext->name); pr_warn("extern '%s' (strong): not resolved\n", ext->name);
return -ESRCH; return -ESRCH;
} else if (!ext->is_set) { } else if (!ext->is_set) {
pr_debug("extern %s (weak) not resolved, defaulting to zero\n", pr_debug("extern '%s' (weak): not resolved, defaulting to zero\n",
ext->name); ext->name);
} }
} }
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
#include <linux/errno.h> #include <linux/errno.h>
#include <bpf/bpf_helpers.h> #include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h> #include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
/* Below types and maps are internal implementation details of libbpf's USDT /* Below types and maps are internal implementation details of libbpf's USDT
* support and are subjects to change. Also, bpf_usdt_xxx() API helpers should * support and are subjects to change. Also, bpf_usdt_xxx() API helpers should
...@@ -30,14 +29,6 @@ ...@@ -30,14 +29,6 @@
#ifndef BPF_USDT_MAX_IP_CNT #ifndef BPF_USDT_MAX_IP_CNT
#define BPF_USDT_MAX_IP_CNT (4 * BPF_USDT_MAX_SPEC_CNT) #define BPF_USDT_MAX_IP_CNT (4 * BPF_USDT_MAX_SPEC_CNT)
#endif #endif
/* We use BPF CO-RE to detect support for BPF cookie from BPF side. This is
* the only dependency on CO-RE, so if it's undesirable, user can override
* BPF_USDT_HAS_BPF_COOKIE to specify whether to BPF cookie is supported or not.
*/
#ifndef BPF_USDT_HAS_BPF_COOKIE
#define BPF_USDT_HAS_BPF_COOKIE \
bpf_core_enum_value_exists(enum bpf_func_id___usdt, BPF_FUNC_get_attach_cookie___usdt)
#endif
enum __bpf_usdt_arg_type { enum __bpf_usdt_arg_type {
BPF_USDT_ARG_CONST, BPF_USDT_ARG_CONST,
...@@ -83,15 +74,12 @@ struct { ...@@ -83,15 +74,12 @@ struct {
__type(value, __u32); __type(value, __u32);
} __bpf_usdt_ip_to_spec_id SEC(".maps") __weak; } __bpf_usdt_ip_to_spec_id SEC(".maps") __weak;
/* don't rely on user's BPF code to have latest definition of bpf_func_id */ extern const _Bool LINUX_HAS_BPF_COOKIE __kconfig;
enum bpf_func_id___usdt {
BPF_FUNC_get_attach_cookie___usdt = 0xBAD, /* value doesn't matter */
};
static __always_inline static __always_inline
int __bpf_usdt_spec_id(struct pt_regs *ctx) int __bpf_usdt_spec_id(struct pt_regs *ctx)
{ {
if (!BPF_USDT_HAS_BPF_COOKIE) { if (!LINUX_HAS_BPF_COOKIE) {
long ip = PT_REGS_IP(ctx); long ip = PT_REGS_IP(ctx);
int *spec_id_ptr; int *spec_id_ptr;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment