Andrii Nakryiko says: ==================== bpf-next 2021-07-30 We've added 64 non-merge commits during the last 15 day(s) which contain a total of 83 files changed, 5027 insertions(+), 1808 deletions(-). The main changes are: 1) BTF-guided binary data dumping libbpf API, from Alan. 2) Internal factoring out of libbpf CO-RE relocation logic, from Alexei. 3) Ambient BPF run context and cgroup storage cleanup, from Andrii. 4) Few small API additions for libbpf 1.0 effort, from Evgeniy and Hengqi. 5) bpf_program__attach_kprobe_opts() fixes in libbpf, from Jiri. 6) bpf_{get,set}sockopt() support in BPF iterators, from Martin. 7) BPF map pinning improvements in libbpf, from Martynas. 8) Improved module BTF support in libbpf and bpftool, from Quentin. 9) Bpftool cleanups and documentation improvements, from Quentin. 10) Libbpf improvements for supporting CO-RE on old kernels, from Shuyi. 11) Increased maximum cgroup storage size, from Stanislav. 12) Small fixes and improvements to BPF tests and samples, from various folks. * https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next: (64 commits) tools: bpftool: Complete metrics list in "bpftool prog profile" doc tools: bpftool: Document and add bash completion for -L, -B options selftests/bpf: Update bpftool's consistency script for checking options tools: bpftool: Update and synchronise option list in doc and help msg tools: bpftool: Complete and synchronise attach or map types selftests/bpf: Check consistency between bpftool source, doc, completion tools: bpftool: Slightly ease bash completion updates unix_bpf: Fix a potential deadlock in unix_dgram_bpf_recvmsg() libbpf: Add btf__load_vmlinux_btf/btf__load_module_btf tools: bpftool: Support dumping split BTF by id libbpf: Add split BTF support for btf__load_from_kernel_by_id() tools: Replace btf__get_from_id() with btf__load_from_kernel_by_id() tools: Free BTF objects at various locations libbpf: Rename btf__get_from_id() as btf__load_from_kernel_by_id() libbpf: Rename btf__load() as btf__load_into_kernel() libbpf: Return non-null error on failures in libbpf_find_prog_btf_id() bpf: Emit better log message if bpf_iter ctx arg btf_id == 0 tools/resolve_btfids: Emit warnings and patch zero id for missing symbols bpf: Increase supported cgroup storage value size libbpf: Fix race when pinning maps in parallel ... ==================== Link: https://lore.kernel.org/r/20210730225606.1897330-1-andrii@kernel.org Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
d39e8b92c3
@ -320,13 +320,6 @@ Examples for low-level BPF:
|
||||
ret #-1
|
||||
drop: ret #0
|
||||
|
||||
**(Accelerated) VLAN w/ id 10**::
|
||||
|
||||
ld vlan_tci
|
||||
jneq #10, drop
|
||||
ret #-1
|
||||
drop: ret #0
|
||||
|
||||
**icmp random packet sampling, 1 in 4**::
|
||||
|
||||
ldh [12]
|
||||
@ -358,6 +351,22 @@ Examples for low-level BPF:
|
||||
bad: ret #0 /* SECCOMP_RET_KILL_THREAD */
|
||||
good: ret #0x7fff0000 /* SECCOMP_RET_ALLOW */
|
||||
|
||||
Examples for low-level BPF extension:
|
||||
|
||||
**Packet for interface index 13**::
|
||||
|
||||
ld ifidx
|
||||
jneq #13, drop
|
||||
ret #-1
|
||||
drop: ret #0
|
||||
|
||||
**(Accelerated) VLAN w/ id 10**::
|
||||
|
||||
ld vlan_tci
|
||||
jneq #10, drop
|
||||
ret #-1
|
||||
drop: ret #0
|
||||
|
||||
The above example code can be placed into a file (here called "foo"), and
|
||||
then be passed to the bpf_asm tool for generating opcodes, output that xt_bpf
|
||||
and cls_bpf understands and can directly be loaded with. Example with above
|
||||
|
@ -27,19 +27,6 @@ struct task_struct;
|
||||
extern struct static_key_false cgroup_bpf_enabled_key[MAX_BPF_ATTACH_TYPE];
|
||||
#define cgroup_bpf_enabled(type) static_branch_unlikely(&cgroup_bpf_enabled_key[type])
|
||||
|
||||
#define BPF_CGROUP_STORAGE_NEST_MAX 8
|
||||
|
||||
struct bpf_cgroup_storage_info {
|
||||
struct task_struct *task;
|
||||
struct bpf_cgroup_storage *storage[MAX_BPF_CGROUP_STORAGE_TYPE];
|
||||
};
|
||||
|
||||
/* For each cpu, permit maximum BPF_CGROUP_STORAGE_NEST_MAX number of tasks
|
||||
* to use bpf cgroup storage simultaneously.
|
||||
*/
|
||||
DECLARE_PER_CPU(struct bpf_cgroup_storage_info,
|
||||
bpf_cgroup_storage_info[BPF_CGROUP_STORAGE_NEST_MAX]);
|
||||
|
||||
#define for_each_cgroup_storage_type(stype) \
|
||||
for (stype = 0; stype < MAX_BPF_CGROUP_STORAGE_TYPE; stype++)
|
||||
|
||||
@ -172,44 +159,6 @@ static inline enum bpf_cgroup_storage_type cgroup_storage_type(
|
||||
return BPF_CGROUP_STORAGE_SHARED;
|
||||
}
|
||||
|
||||
static inline int bpf_cgroup_storage_set(struct bpf_cgroup_storage
|
||||
*storage[MAX_BPF_CGROUP_STORAGE_TYPE])
|
||||
{
|
||||
enum bpf_cgroup_storage_type stype;
|
||||
int i, err = 0;
|
||||
|
||||
preempt_disable();
|
||||
for (i = 0; i < BPF_CGROUP_STORAGE_NEST_MAX; i++) {
|
||||
if (unlikely(this_cpu_read(bpf_cgroup_storage_info[i].task) != NULL))
|
||||
continue;
|
||||
|
||||
this_cpu_write(bpf_cgroup_storage_info[i].task, current);
|
||||
for_each_cgroup_storage_type(stype)
|
||||
this_cpu_write(bpf_cgroup_storage_info[i].storage[stype],
|
||||
storage[stype]);
|
||||
goto out;
|
||||
}
|
||||
err = -EBUSY;
|
||||
WARN_ON_ONCE(1);
|
||||
|
||||
out:
|
||||
preempt_enable();
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline void bpf_cgroup_storage_unset(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < BPF_CGROUP_STORAGE_NEST_MAX; i++) {
|
||||
if (unlikely(this_cpu_read(bpf_cgroup_storage_info[i].task) != current))
|
||||
continue;
|
||||
|
||||
this_cpu_write(bpf_cgroup_storage_info[i].task, NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
struct bpf_cgroup_storage *
|
||||
cgroup_storage_lookup(struct bpf_cgroup_storage_map *map,
|
||||
void *key, bool locked);
|
||||
@ -487,9 +436,6 @@ static inline int cgroup_bpf_prog_query(const union bpf_attr *attr,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static inline int bpf_cgroup_storage_set(
|
||||
struct bpf_cgroup_storage *storage[MAX_BPF_CGROUP_STORAGE_TYPE]) { return 0; }
|
||||
static inline void bpf_cgroup_storage_unset(void) {}
|
||||
static inline int bpf_cgroup_storage_assign(struct bpf_prog_aux *aux,
|
||||
struct bpf_map *map) { return 0; }
|
||||
static inline struct bpf_cgroup_storage *bpf_cgroup_storage_alloc(
|
||||
|
@ -1142,38 +1142,40 @@ int bpf_prog_array_copy(struct bpf_prog_array *old_array,
|
||||
struct bpf_prog *include_prog,
|
||||
struct bpf_prog_array **new_array);
|
||||
|
||||
struct bpf_run_ctx {};
|
||||
|
||||
struct bpf_cg_run_ctx {
|
||||
struct bpf_run_ctx run_ctx;
|
||||
struct bpf_prog_array_item *prog_item;
|
||||
};
|
||||
|
||||
/* BPF program asks to bypass CAP_NET_BIND_SERVICE in bind. */
|
||||
#define BPF_RET_BIND_NO_CAP_NET_BIND_SERVICE (1 << 0)
|
||||
/* BPF program asks to set CN on the packet. */
|
||||
#define BPF_RET_SET_CN (1 << 0)
|
||||
|
||||
/* For BPF_PROG_RUN_ARRAY_FLAGS and __BPF_PROG_RUN_ARRAY,
|
||||
* if bpf_cgroup_storage_set() failed, the rest of programs
|
||||
* will not execute. This should be a really rare scenario
|
||||
* as it requires BPF_CGROUP_STORAGE_NEST_MAX number of
|
||||
* preemptions all between bpf_cgroup_storage_set() and
|
||||
* bpf_cgroup_storage_unset() on the same cpu.
|
||||
*/
|
||||
#define BPF_PROG_RUN_ARRAY_FLAGS(array, ctx, func, ret_flags) \
|
||||
({ \
|
||||
struct bpf_prog_array_item *_item; \
|
||||
struct bpf_prog *_prog; \
|
||||
struct bpf_prog_array *_array; \
|
||||
struct bpf_run_ctx *old_run_ctx; \
|
||||
struct bpf_cg_run_ctx run_ctx; \
|
||||
u32 _ret = 1; \
|
||||
u32 func_ret; \
|
||||
migrate_disable(); \
|
||||
rcu_read_lock(); \
|
||||
_array = rcu_dereference(array); \
|
||||
_item = &_array->items[0]; \
|
||||
old_run_ctx = bpf_set_run_ctx(&run_ctx.run_ctx); \
|
||||
while ((_prog = READ_ONCE(_item->prog))) { \
|
||||
if (unlikely(bpf_cgroup_storage_set(_item->cgroup_storage))) \
|
||||
break; \
|
||||
run_ctx.prog_item = _item; \
|
||||
func_ret = func(_prog, ctx); \
|
||||
_ret &= (func_ret & 1); \
|
||||
*(ret_flags) |= (func_ret >> 1); \
|
||||
bpf_cgroup_storage_unset(); \
|
||||
*(ret_flags) |= (func_ret >> 1); \
|
||||
_item++; \
|
||||
} \
|
||||
bpf_reset_run_ctx(old_run_ctx); \
|
||||
rcu_read_unlock(); \
|
||||
migrate_enable(); \
|
||||
_ret; \
|
||||
@ -1184,6 +1186,8 @@ int bpf_prog_array_copy(struct bpf_prog_array *old_array,
|
||||
struct bpf_prog_array_item *_item; \
|
||||
struct bpf_prog *_prog; \
|
||||
struct bpf_prog_array *_array; \
|
||||
struct bpf_run_ctx *old_run_ctx; \
|
||||
struct bpf_cg_run_ctx run_ctx; \
|
||||
u32 _ret = 1; \
|
||||
migrate_disable(); \
|
||||
rcu_read_lock(); \
|
||||
@ -1191,17 +1195,13 @@ int bpf_prog_array_copy(struct bpf_prog_array *old_array,
|
||||
if (unlikely(check_non_null && !_array))\
|
||||
goto _out; \
|
||||
_item = &_array->items[0]; \
|
||||
while ((_prog = READ_ONCE(_item->prog))) { \
|
||||
if (!set_cg_storage) { \
|
||||
_ret &= func(_prog, ctx); \
|
||||
} else { \
|
||||
if (unlikely(bpf_cgroup_storage_set(_item->cgroup_storage))) \
|
||||
break; \
|
||||
_ret &= func(_prog, ctx); \
|
||||
bpf_cgroup_storage_unset(); \
|
||||
} \
|
||||
old_run_ctx = bpf_set_run_ctx(&run_ctx.run_ctx);\
|
||||
while ((_prog = READ_ONCE(_item->prog))) { \
|
||||
run_ctx.prog_item = _item; \
|
||||
_ret &= func(_prog, ctx); \
|
||||
_item++; \
|
||||
} \
|
||||
bpf_reset_run_ctx(old_run_ctx); \
|
||||
_out: \
|
||||
rcu_read_unlock(); \
|
||||
migrate_enable(); \
|
||||
@ -1284,6 +1284,20 @@ static inline void bpf_enable_instrumentation(void)
|
||||
migrate_enable();
|
||||
}
|
||||
|
||||
static inline struct bpf_run_ctx *bpf_set_run_ctx(struct bpf_run_ctx *new_ctx)
|
||||
{
|
||||
struct bpf_run_ctx *old_ctx;
|
||||
|
||||
old_ctx = current->bpf_ctx;
|
||||
current->bpf_ctx = new_ctx;
|
||||
return old_ctx;
|
||||
}
|
||||
|
||||
static inline void bpf_reset_run_ctx(struct bpf_run_ctx *old_ctx)
|
||||
{
|
||||
current->bpf_ctx = old_ctx;
|
||||
}
|
||||
|
||||
extern const struct file_operations bpf_map_fops;
|
||||
extern const struct file_operations bpf_prog_fops;
|
||||
extern const struct file_operations bpf_iter_fops;
|
||||
@ -1428,6 +1442,9 @@ typedef void (*bpf_iter_show_fdinfo_t) (const struct bpf_iter_aux_info *aux,
|
||||
struct seq_file *seq);
|
||||
typedef int (*bpf_iter_fill_link_info_t)(const struct bpf_iter_aux_info *aux,
|
||||
struct bpf_link_info *info);
|
||||
typedef const struct bpf_func_proto *
|
||||
(*bpf_iter_get_func_proto_t)(enum bpf_func_id func_id,
|
||||
const struct bpf_prog *prog);
|
||||
|
||||
enum bpf_iter_feature {
|
||||
BPF_ITER_RESCHED = BIT(0),
|
||||
@ -1440,6 +1457,7 @@ struct bpf_iter_reg {
|
||||
bpf_iter_detach_target_t detach_target;
|
||||
bpf_iter_show_fdinfo_t show_fdinfo;
|
||||
bpf_iter_fill_link_info_t fill_link_info;
|
||||
bpf_iter_get_func_proto_t get_func_proto;
|
||||
u32 ctx_arg_info_size;
|
||||
u32 feature;
|
||||
struct bpf_ctx_arg_aux ctx_arg_info[BPF_ITER_CTX_ARG_MAX];
|
||||
@ -1462,6 +1480,8 @@ struct bpf_iter__bpf_map_elem {
|
||||
int bpf_iter_reg_target(const struct bpf_iter_reg *reg_info);
|
||||
void bpf_iter_unreg_target(const struct bpf_iter_reg *reg_info);
|
||||
bool bpf_iter_prog_supported(struct bpf_prog *prog);
|
||||
const struct bpf_func_proto *
|
||||
bpf_iter_get_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog);
|
||||
int bpf_iter_link_attach(const union bpf_attr *attr, bpfptr_t uattr, struct bpf_prog *prog);
|
||||
int bpf_iter_new_fd(struct bpf_link *link);
|
||||
bool bpf_link_is_iter(struct bpf_link *link);
|
||||
@ -2036,6 +2056,8 @@ extern const struct bpf_func_proto bpf_task_storage_get_proto;
|
||||
extern const struct bpf_func_proto bpf_task_storage_delete_proto;
|
||||
extern const struct bpf_func_proto bpf_for_each_map_elem_proto;
|
||||
extern const struct bpf_func_proto bpf_btf_find_by_name_kind_proto;
|
||||
extern const struct bpf_func_proto bpf_sk_setsockopt_proto;
|
||||
extern const struct bpf_func_proto bpf_sk_getsockopt_proto;
|
||||
|
||||
const struct bpf_func_proto *bpf_tracing_func_proto(
|
||||
enum bpf_func_id func_id, const struct bpf_prog *prog);
|
||||
|
@ -42,6 +42,7 @@ struct backing_dev_info;
|
||||
struct bio_list;
|
||||
struct blk_plug;
|
||||
struct bpf_local_storage;
|
||||
struct bpf_run_ctx;
|
||||
struct capture_control;
|
||||
struct cfs_rq;
|
||||
struct fs_struct;
|
||||
@ -1379,6 +1380,8 @@ struct task_struct {
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
/* Used by BPF task local storage */
|
||||
struct bpf_local_storage __rcu *bpf_storage;
|
||||
/* Used for BPF run context */
|
||||
struct bpf_run_ctx *bpf_ctx;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GCC_PLUGIN_STACKLEAK
|
||||
|
@ -160,6 +160,12 @@ struct inet_hashinfo {
|
||||
____cacheline_aligned_in_smp;
|
||||
};
|
||||
|
||||
#define inet_lhash2_for_each_icsk_continue(__icsk) \
|
||||
hlist_for_each_entry_continue(__icsk, icsk_listen_portaddr_node)
|
||||
|
||||
#define inet_lhash2_for_each_icsk(__icsk, list) \
|
||||
hlist_for_each_entry(__icsk, list, icsk_listen_portaddr_node)
|
||||
|
||||
#define inet_lhash2_for_each_icsk_rcu(__icsk, list) \
|
||||
hlist_for_each_entry_rcu(__icsk, list, icsk_listen_portaddr_node)
|
||||
|
||||
|
@ -1958,7 +1958,6 @@ struct tcp_iter_state {
|
||||
struct seq_net_private p;
|
||||
enum tcp_seq_states state;
|
||||
struct sock *syn_wait_sk;
|
||||
struct tcp_seq_afinfo *bpf_seq_afinfo;
|
||||
int bucket, offset, sbucket, num;
|
||||
loff_t last_pos;
|
||||
};
|
||||
|
@ -360,6 +360,28 @@ bool bpf_iter_prog_supported(struct bpf_prog *prog)
|
||||
return supported;
|
||||
}
|
||||
|
||||
const struct bpf_func_proto *
|
||||
bpf_iter_get_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
|
||||
{
|
||||
const struct bpf_iter_target_info *tinfo;
|
||||
const struct bpf_func_proto *fn = NULL;
|
||||
|
||||
mutex_lock(&targets_mutex);
|
||||
list_for_each_entry(tinfo, &targets, list) {
|
||||
if (tinfo->btf_id == prog->aux->attach_btf_id) {
|
||||
const struct bpf_iter_reg *reg_info;
|
||||
|
||||
reg_info = tinfo->reg_info;
|
||||
if (reg_info->get_func_proto)
|
||||
fn = reg_info->get_func_proto(func_id, prog);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&targets_mutex);
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
static void bpf_iter_link_release(struct bpf_link *link)
|
||||
{
|
||||
struct bpf_iter_link *iter_link =
|
||||
|
@ -4825,6 +4825,11 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
|
||||
const struct bpf_ctx_arg_aux *ctx_arg_info = &prog->aux->ctx_arg_info[i];
|
||||
|
||||
if (ctx_arg_info->offset == off) {
|
||||
if (!ctx_arg_info->btf_id) {
|
||||
bpf_log(log,"invalid btf_id for context argument offset %u\n", off);
|
||||
return false;
|
||||
}
|
||||
|
||||
info->reg_type = ctx_arg_info->reg_type;
|
||||
info->btf = btf_vmlinux;
|
||||
info->btf_id = ctx_arg_info->btf_id;
|
||||
|
@ -393,8 +393,6 @@ const struct bpf_func_proto bpf_get_current_ancestor_cgroup_id_proto = {
|
||||
};
|
||||
|
||||
#ifdef CONFIG_CGROUP_BPF
|
||||
DECLARE_PER_CPU(struct bpf_cgroup_storage_info,
|
||||
bpf_cgroup_storage_info[BPF_CGROUP_STORAGE_NEST_MAX]);
|
||||
|
||||
BPF_CALL_2(bpf_get_local_storage, struct bpf_map *, map, u64, flags)
|
||||
{
|
||||
@ -403,17 +401,13 @@ BPF_CALL_2(bpf_get_local_storage, struct bpf_map *, map, u64, flags)
|
||||
* verifier checks that its value is correct.
|
||||
*/
|
||||
enum bpf_cgroup_storage_type stype = cgroup_storage_type(map);
|
||||
struct bpf_cgroup_storage *storage = NULL;
|
||||
struct bpf_cgroup_storage *storage;
|
||||
struct bpf_cg_run_ctx *ctx;
|
||||
void *ptr;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < BPF_CGROUP_STORAGE_NEST_MAX; i++) {
|
||||
if (unlikely(this_cpu_read(bpf_cgroup_storage_info[i].task) != current))
|
||||
continue;
|
||||
|
||||
storage = this_cpu_read(bpf_cgroup_storage_info[i].storage[stype]);
|
||||
break;
|
||||
}
|
||||
/* get current cgroup storage from BPF run context */
|
||||
ctx = container_of(current->bpf_ctx, struct bpf_cg_run_ctx, run_ctx);
|
||||
storage = ctx->prog_item->cgroup_storage[stype];
|
||||
|
||||
if (stype == BPF_CGROUP_STORAGE_SHARED)
|
||||
ptr = &READ_ONCE(storage->buf)->data[0];
|
||||
|
@ -1,6 +1,7 @@
|
||||
//SPDX-License-Identifier: GPL-2.0
|
||||
#include <linux/bpf-cgroup.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/bpf_local_storage.h>
|
||||
#include <linux/btf.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/filter.h>
|
||||
@ -11,9 +12,6 @@
|
||||
|
||||
#ifdef CONFIG_CGROUP_BPF
|
||||
|
||||
DEFINE_PER_CPU(struct bpf_cgroup_storage_info,
|
||||
bpf_cgroup_storage_info[BPF_CGROUP_STORAGE_NEST_MAX]);
|
||||
|
||||
#include "../cgroup/cgroup-internal.h"
|
||||
|
||||
#define LOCAL_STORAGE_CREATE_FLAG_MASK \
|
||||
@ -286,9 +284,17 @@ enoent:
|
||||
|
||||
static struct bpf_map *cgroup_storage_map_alloc(union bpf_attr *attr)
|
||||
{
|
||||
__u32 max_value_size = BPF_LOCAL_STORAGE_MAX_VALUE_SIZE;
|
||||
int numa_node = bpf_map_attr_numa_node(attr);
|
||||
struct bpf_cgroup_storage_map *map;
|
||||
|
||||
/* percpu is bound by PCPU_MIN_UNIT_SIZE, non-percu
|
||||
* is the same as other local storages.
|
||||
*/
|
||||
if (attr->map_type == BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE)
|
||||
max_value_size = min_t(__u32, max_value_size,
|
||||
PCPU_MIN_UNIT_SIZE);
|
||||
|
||||
if (attr->key_size != sizeof(struct bpf_cgroup_storage_key) &&
|
||||
attr->key_size != sizeof(__u64))
|
||||
return ERR_PTR(-EINVAL);
|
||||
@ -296,7 +302,7 @@ static struct bpf_map *cgroup_storage_map_alloc(union bpf_attr *attr)
|
||||
if (attr->value_size == 0)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (attr->value_size > PAGE_SIZE)
|
||||
if (attr->value_size > max_value_size)
|
||||
return ERR_PTR(-E2BIG);
|
||||
|
||||
if (attr->map_flags & ~LOCAL_STORAGE_CREATE_FLAG_MASK ||
|
||||
@ -409,7 +415,7 @@ static int cgroup_storage_check_btf(const struct bpf_map *map,
|
||||
static void cgroup_storage_seq_show_elem(struct bpf_map *map, void *key,
|
||||
struct seq_file *m)
|
||||
{
|
||||
enum bpf_cgroup_storage_type stype = cgroup_storage_type(map);
|
||||
enum bpf_cgroup_storage_type stype;
|
||||
struct bpf_cgroup_storage *storage;
|
||||
int cpu;
|
||||
|
||||
|
@ -2083,6 +2083,7 @@ static __latent_entropy struct task_struct *copy_process(
|
||||
#endif
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
RCU_INIT_POINTER(p->bpf_storage, NULL);
|
||||
p->bpf_ctx = NULL;
|
||||
#endif
|
||||
|
||||
/* Perform scheduler related setup. Assign this task to a CPU. */
|
||||
|
@ -965,7 +965,7 @@ BPF_CALL_1(bpf_get_func_ip_kprobe, struct pt_regs *, regs)
|
||||
{
|
||||
struct kprobe *kp = kprobe_running();
|
||||
|
||||
return kp ? (u64) kp->addr : 0;
|
||||
return kp ? (uintptr_t)kp->addr : 0;
|
||||
}
|
||||
|
||||
static const struct bpf_func_proto bpf_get_func_ip_proto_kprobe = {
|
||||
@ -1461,6 +1461,8 @@ raw_tp_prog_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
|
||||
const struct bpf_func_proto *
|
||||
tracing_prog_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
|
||||
{
|
||||
const struct bpf_func_proto *fn;
|
||||
|
||||
switch (func_id) {
|
||||
#ifdef CONFIG_NET
|
||||
case BPF_FUNC_skb_output:
|
||||
@ -1501,7 +1503,10 @@ tracing_prog_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
|
||||
case BPF_FUNC_d_path:
|
||||
return &bpf_d_path_proto;
|
||||
default:
|
||||
return raw_tp_prog_func_proto(func_id, prog);
|
||||
fn = raw_tp_prog_func_proto(func_id, prog);
|
||||
if (!fn && prog->expected_attach_type == BPF_TRACE_ITER)
|
||||
fn = bpf_iter_get_func_proto(func_id, prog);
|
||||
return fn;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4286,8 +4286,8 @@ static struct bpf_test tests[] = {
|
||||
.u.insns_int = {
|
||||
BPF_LD_IMM64(R0, 0),
|
||||
BPF_LD_IMM64(R1, 0xffffffffffffffffLL),
|
||||
BPF_STX_MEM(BPF_W, R10, R1, -40),
|
||||
BPF_LDX_MEM(BPF_W, R0, R10, -40),
|
||||
BPF_STX_MEM(BPF_DW, R10, R1, -40),
|
||||
BPF_LDX_MEM(BPF_DW, R0, R10, -40),
|
||||
BPF_EXIT_INSN(),
|
||||
},
|
||||
INTERNAL,
|
||||
@ -6659,7 +6659,14 @@ static int run_one(const struct bpf_prog *fp, struct bpf_test *test)
|
||||
u64 duration;
|
||||
u32 ret;
|
||||
|
||||
if (test->test[i].data_size == 0 &&
|
||||
/*
|
||||
* NOTE: Several sub-tests may be present, in which case
|
||||
* a zero {data_size, result} tuple indicates the end of
|
||||
* the sub-test array. The first test is always run,
|
||||
* even if both data_size and result happen to be zero.
|
||||
*/
|
||||
if (i > 0 &&
|
||||
test->test[i].data_size == 0 &&
|
||||
test->test[i].result == 0)
|
||||
break;
|
||||
|
||||
|
@ -88,17 +88,19 @@ reset:
|
||||
static int bpf_test_run(struct bpf_prog *prog, void *ctx, u32 repeat,
|
||||
u32 *retval, u32 *time, bool xdp)
|
||||
{
|
||||
struct bpf_cgroup_storage *storage[MAX_BPF_CGROUP_STORAGE_TYPE] = { NULL };
|
||||
struct bpf_prog_array_item item = {.prog = prog};
|
||||
struct bpf_run_ctx *old_ctx;
|
||||
struct bpf_cg_run_ctx run_ctx;
|
||||
struct bpf_test_timer t = { NO_MIGRATE };
|
||||
enum bpf_cgroup_storage_type stype;
|
||||
int ret;
|
||||
|
||||
for_each_cgroup_storage_type(stype) {
|
||||
storage[stype] = bpf_cgroup_storage_alloc(prog, stype);
|
||||
if (IS_ERR(storage[stype])) {
|
||||
storage[stype] = NULL;
|
||||
item.cgroup_storage[stype] = bpf_cgroup_storage_alloc(prog, stype);
|
||||
if (IS_ERR(item.cgroup_storage[stype])) {
|
||||
item.cgroup_storage[stype] = NULL;
|
||||
for_each_cgroup_storage_type(stype)
|
||||
bpf_cgroup_storage_free(storage[stype]);
|
||||
bpf_cgroup_storage_free(item.cgroup_storage[stype]);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
@ -107,22 +109,19 @@ static int bpf_test_run(struct bpf_prog *prog, void *ctx, u32 repeat,
|
||||
repeat = 1;
|
||||
|
||||
bpf_test_timer_enter(&t);
|
||||
old_ctx = bpf_set_run_ctx(&run_ctx.run_ctx);
|
||||
do {
|
||||
ret = bpf_cgroup_storage_set(storage);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
run_ctx.prog_item = &item;
|
||||
if (xdp)
|
||||
*retval = bpf_prog_run_xdp(prog, ctx);
|
||||
else
|
||||
*retval = BPF_PROG_RUN(prog, ctx);
|
||||
|
||||
bpf_cgroup_storage_unset();
|
||||
} while (bpf_test_timer_continue(&t, repeat, &ret, time));
|
||||
bpf_reset_run_ctx(old_ctx);
|
||||
bpf_test_timer_leave(&t);
|
||||
|
||||
for_each_cgroup_storage_type(stype)
|
||||
bpf_cgroup_storage_free(storage[stype]);
|
||||
bpf_cgroup_storage_free(item.cgroup_storage[stype]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -5016,6 +5016,40 @@ err_clear:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
BPF_CALL_5(bpf_sk_setsockopt, struct sock *, sk, int, level,
|
||||
int, optname, char *, optval, int, optlen)
|
||||
{
|
||||
return _bpf_setsockopt(sk, level, optname, optval, optlen);
|
||||
}
|
||||
|
||||
const struct bpf_func_proto bpf_sk_setsockopt_proto = {
|
||||
.func = bpf_sk_setsockopt,
|
||||
.gpl_only = false,
|
||||
.ret_type = RET_INTEGER,
|
||||
.arg1_type = ARG_PTR_TO_BTF_ID_SOCK_COMMON,
|
||||
.arg2_type = ARG_ANYTHING,
|
||||
.arg3_type = ARG_ANYTHING,
|
||||
.arg4_type = ARG_PTR_TO_MEM,
|
||||
.arg5_type = ARG_CONST_SIZE,
|
||||
};
|
||||
|
||||
BPF_CALL_5(bpf_sk_getsockopt, struct sock *, sk, int, level,
|
||||
int, optname, char *, optval, int, optlen)
|
||||
{
|
||||
return _bpf_getsockopt(sk, level, optname, optval, optlen);
|
||||
}
|
||||
|
||||
const struct bpf_func_proto bpf_sk_getsockopt_proto = {
|
||||
.func = bpf_sk_getsockopt,
|
||||
.gpl_only = false,
|
||||
.ret_type = RET_INTEGER,
|
||||
.arg1_type = ARG_PTR_TO_BTF_ID_SOCK_COMMON,
|
||||
.arg2_type = ARG_ANYTHING,
|
||||
.arg3_type = ARG_ANYTHING,
|
||||
.arg4_type = ARG_PTR_TO_UNINIT_MEM,
|
||||
.arg5_type = ARG_CONST_SIZE,
|
||||
};
|
||||
|
||||
BPF_CALL_5(bpf_sock_addr_setsockopt, struct bpf_sock_addr_kern *, ctx,
|
||||
int, level, int, optname, char *, optval, int, optlen)
|
||||
{
|
||||
|
@ -2277,51 +2277,72 @@ EXPORT_SYMBOL(tcp_v4_destroy_sock);
|
||||
#ifdef CONFIG_PROC_FS
|
||||
/* Proc filesystem TCP sock list dumping. */
|
||||
|
||||
/*
|
||||
* Get next listener socket follow cur. If cur is NULL, get first socket
|
||||
* starting from bucket given in st->bucket; when st->bucket is zero the
|
||||
* very first socket in the hash table is returned.
|
||||
static unsigned short seq_file_family(const struct seq_file *seq);
|
||||
|
||||
static bool seq_sk_match(struct seq_file *seq, const struct sock *sk)
|
||||
{
|
||||
unsigned short family = seq_file_family(seq);
|
||||
|
||||
/* AF_UNSPEC is used as a match all */
|
||||
return ((family == AF_UNSPEC || family == sk->sk_family) &&
|
||||
net_eq(sock_net(sk), seq_file_net(seq)));
|
||||
}
|
||||
|
||||
/* Find a non empty bucket (starting from st->bucket)
|
||||
* and return the first sk from it.
|
||||
*/
|
||||
static void *listening_get_first(struct seq_file *seq)
|
||||
{
|
||||
struct tcp_iter_state *st = seq->private;
|
||||
|
||||
st->offset = 0;
|
||||
for (; st->bucket <= tcp_hashinfo.lhash2_mask; st->bucket++) {
|
||||
struct inet_listen_hashbucket *ilb2;
|
||||
struct inet_connection_sock *icsk;
|
||||
struct sock *sk;
|
||||
|
||||
ilb2 = &tcp_hashinfo.lhash2[st->bucket];
|
||||
if (hlist_empty(&ilb2->head))
|
||||
continue;
|
||||
|
||||
spin_lock(&ilb2->lock);
|
||||
inet_lhash2_for_each_icsk(icsk, &ilb2->head) {
|
||||
sk = (struct sock *)icsk;
|
||||
if (seq_sk_match(seq, sk))
|
||||
return sk;
|
||||
}
|
||||
spin_unlock(&ilb2->lock);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Find the next sk of "cur" within the same bucket (i.e. st->bucket).
|
||||
* If "cur" is the last one in the st->bucket,
|
||||
* call listening_get_first() to return the first sk of the next
|
||||
* non empty bucket.
|
||||
*/
|
||||
static void *listening_get_next(struct seq_file *seq, void *cur)
|
||||
{
|
||||
struct tcp_seq_afinfo *afinfo;
|
||||
struct tcp_iter_state *st = seq->private;
|
||||
struct net *net = seq_file_net(seq);
|
||||
struct inet_listen_hashbucket *ilb;
|
||||
struct hlist_nulls_node *node;
|
||||
struct inet_listen_hashbucket *ilb2;
|
||||
struct inet_connection_sock *icsk;
|
||||
struct sock *sk = cur;
|
||||
|
||||
if (st->bpf_seq_afinfo)
|
||||
afinfo = st->bpf_seq_afinfo;
|
||||
else
|
||||
afinfo = PDE_DATA(file_inode(seq->file));
|
||||
|
||||
if (!sk) {
|
||||
get_head:
|
||||
ilb = &tcp_hashinfo.listening_hash[st->bucket];
|
||||
spin_lock(&ilb->lock);
|
||||
sk = sk_nulls_head(&ilb->nulls_head);
|
||||
st->offset = 0;
|
||||
goto get_sk;
|
||||
}
|
||||
ilb = &tcp_hashinfo.listening_hash[st->bucket];
|
||||
++st->num;
|
||||
++st->offset;
|
||||
|
||||
sk = sk_nulls_next(sk);
|
||||
get_sk:
|
||||
sk_nulls_for_each_from(sk, node) {
|
||||
if (!net_eq(sock_net(sk), net))
|
||||
continue;
|
||||
if (afinfo->family == AF_UNSPEC ||
|
||||
sk->sk_family == afinfo->family)
|
||||
icsk = inet_csk(sk);
|
||||
inet_lhash2_for_each_icsk_continue(icsk) {
|
||||
sk = (struct sock *)icsk;
|
||||
if (seq_sk_match(seq, sk))
|
||||
return sk;
|
||||
}
|
||||
spin_unlock(&ilb->lock);
|
||||
st->offset = 0;
|
||||
if (++st->bucket < INET_LHTABLE_SIZE)
|
||||
goto get_head;
|
||||
return NULL;
|
||||
|
||||
ilb2 = &tcp_hashinfo.lhash2[st->bucket];
|
||||
spin_unlock(&ilb2->lock);
|
||||
++st->bucket;
|
||||
return listening_get_first(seq);
|
||||
}
|
||||
|
||||
static void *listening_get_idx(struct seq_file *seq, loff_t *pos)
|
||||
@ -2331,7 +2352,7 @@ static void *listening_get_idx(struct seq_file *seq, loff_t *pos)
|
||||
|
||||
st->bucket = 0;
|
||||
st->offset = 0;
|
||||
rc = listening_get_next(seq, NULL);
|
||||
rc = listening_get_first(seq);
|
||||
|
||||
while (rc && *pos) {
|
||||
rc = listening_get_next(seq, rc);
|
||||
@ -2351,15 +2372,7 @@ static inline bool empty_bucket(const struct tcp_iter_state *st)
|
||||
*/
|
||||
static void *established_get_first(struct seq_file *seq)
|
||||
{
|
||||
struct tcp_seq_afinfo *afinfo;
|
||||
struct tcp_iter_state *st = seq->private;
|
||||
struct net *net = seq_file_net(seq);
|
||||
void *rc = NULL;
|
||||
|
||||
if (st->bpf_seq_afinfo)
|
||||
afinfo = st->bpf_seq_afinfo;
|
||||
else
|
||||
afinfo = PDE_DATA(file_inode(seq->file));
|
||||
|
||||
st->offset = 0;
|
||||
for (; st->bucket <= tcp_hashinfo.ehash_mask; ++st->bucket) {
|
||||
@ -2373,32 +2386,20 @@ static void *established_get_first(struct seq_file *seq)
|
||||
|
||||
spin_lock_bh(lock);
|
||||
sk_nulls_for_each(sk, node, &tcp_hashinfo.ehash[st->bucket].chain) {
|
||||
if ((afinfo->family != AF_UNSPEC &&
|
||||
sk->sk_family != afinfo->family) ||
|
||||
!net_eq(sock_net(sk), net)) {
|
||||
continue;
|
||||
}
|
||||
rc = sk;
|
||||
goto out;
|
||||
if (seq_sk_match(seq, sk))
|
||||
return sk;
|
||||
}
|
||||
spin_unlock_bh(lock);
|
||||
}
|
||||
out:
|
||||
return rc;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *established_get_next(struct seq_file *seq, void *cur)
|
||||
{
|
||||
struct tcp_seq_afinfo *afinfo;
|
||||
struct sock *sk = cur;
|
||||
struct hlist_nulls_node *node;
|
||||
struct tcp_iter_state *st = seq->private;
|
||||
struct net *net = seq_file_net(seq);
|
||||
|
||||
if (st->bpf_seq_afinfo)
|
||||
afinfo = st->bpf_seq_afinfo;
|
||||
else
|
||||
afinfo = PDE_DATA(file_inode(seq->file));
|
||||
|
||||
++st->num;
|
||||
++st->offset;
|
||||
@ -2406,9 +2407,7 @@ static void *established_get_next(struct seq_file *seq, void *cur)
|
||||
sk = sk_nulls_next(sk);
|
||||
|
||||
sk_nulls_for_each_from(sk, node) {
|
||||
if ((afinfo->family == AF_UNSPEC ||
|
||||
sk->sk_family == afinfo->family) &&
|
||||
net_eq(sock_net(sk), net))
|
||||
if (seq_sk_match(seq, sk))
|
||||
return sk;
|
||||
}
|
||||
|
||||
@ -2451,17 +2450,18 @@ static void *tcp_get_idx(struct seq_file *seq, loff_t pos)
|
||||
static void *tcp_seek_last_pos(struct seq_file *seq)
|
||||
{
|
||||
struct tcp_iter_state *st = seq->private;
|
||||
int bucket = st->bucket;
|
||||
int offset = st->offset;
|
||||
int orig_num = st->num;
|
||||
void *rc = NULL;
|
||||
|
||||
switch (st->state) {
|
||||
case TCP_SEQ_STATE_LISTENING:
|
||||
if (st->bucket >= INET_LHTABLE_SIZE)
|
||||
if (st->bucket > tcp_hashinfo.lhash2_mask)
|
||||
break;
|
||||
st->state = TCP_SEQ_STATE_LISTENING;
|
||||
rc = listening_get_next(seq, NULL);
|
||||
while (offset-- && rc)
|
||||
rc = listening_get_first(seq);
|
||||
while (offset-- && rc && bucket == st->bucket)
|
||||
rc = listening_get_next(seq, rc);
|
||||
if (rc)
|
||||
break;
|
||||
@ -2472,7 +2472,7 @@ static void *tcp_seek_last_pos(struct seq_file *seq)
|
||||
if (st->bucket > tcp_hashinfo.ehash_mask)
|
||||
break;
|
||||
rc = established_get_first(seq);
|
||||
while (offset-- && rc)
|
||||
while (offset-- && rc && bucket == st->bucket)
|
||||
rc = established_get_next(seq, rc);
|
||||
}
|
||||
|
||||
@ -2542,7 +2542,7 @@ void tcp_seq_stop(struct seq_file *seq, void *v)
|
||||
switch (st->state) {
|
||||
case TCP_SEQ_STATE_LISTENING:
|
||||
if (v != SEQ_START_TOKEN)
|
||||
spin_unlock(&tcp_hashinfo.listening_hash[st->bucket].lock);
|
||||
spin_unlock(&tcp_hashinfo.lhash2[st->bucket].lock);
|
||||
break;
|
||||
case TCP_SEQ_STATE_ESTABLISHED:
|
||||
if (v)
|
||||
@ -2687,6 +2687,15 @@ out:
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
struct bpf_tcp_iter_state {
|
||||
struct tcp_iter_state state;
|
||||
unsigned int cur_sk;
|
||||
unsigned int end_sk;
|
||||
unsigned int max_sk;
|
||||
struct sock **batch;
|
||||
bool st_bucket_done;
|
||||
};
|
||||
|
||||
struct bpf_iter__tcp {
|
||||
__bpf_md_ptr(struct bpf_iter_meta *, meta);
|
||||
__bpf_md_ptr(struct sock_common *, sk_common);
|
||||
@ -2705,16 +2714,204 @@ static int tcp_prog_seq_show(struct bpf_prog *prog, struct bpf_iter_meta *meta,
|
||||
return bpf_iter_run_prog(prog, &ctx);
|
||||
}
|
||||
|
||||
static void bpf_iter_tcp_put_batch(struct bpf_tcp_iter_state *iter)
|
||||
{
|
||||
while (iter->cur_sk < iter->end_sk)
|
||||
sock_put(iter->batch[iter->cur_sk++]);
|
||||
}
|
||||
|
||||
static int bpf_iter_tcp_realloc_batch(struct bpf_tcp_iter_state *iter,
|
||||
unsigned int new_batch_sz)
|
||||
{
|
||||
struct sock **new_batch;
|
||||
|
||||
new_batch = kvmalloc(sizeof(*new_batch) * new_batch_sz,
|
||||
GFP_USER | __GFP_NOWARN);
|
||||
if (!new_batch)
|
||||
return -ENOMEM;
|
||||
|
||||
bpf_iter_tcp_put_batch(iter);
|
||||
kvfree(iter->batch);
|
||||
iter->batch = new_batch;
|
||||
iter->max_sk = new_batch_sz;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int bpf_iter_tcp_listening_batch(struct seq_file *seq,
|
||||
struct sock *start_sk)
|
||||
{
|
||||
struct bpf_tcp_iter_state *iter = seq->private;
|
||||
struct tcp_iter_state *st = &iter->state;
|
||||
struct inet_connection_sock *icsk;
|
||||
unsigned int expected = 1;
|
||||
struct sock *sk;
|
||||
|
||||
sock_hold(start_sk);
|
||||
iter->batch[iter->end_sk++] = start_sk;
|
||||
|
||||
icsk = inet_csk(start_sk);
|
||||
inet_lhash2_for_each_icsk_continue(icsk) {
|
||||
sk = (struct sock *)icsk;
|
||||
if (seq_sk_match(seq, sk)) {
|
||||
if (iter->end_sk < iter->max_sk) {
|
||||
sock_hold(sk);
|
||||
iter->batch[iter->end_sk++] = sk;
|
||||
}
|
||||
expected++;
|
||||
}
|
||||
}
|
||||
spin_unlock(&tcp_hashinfo.lhash2[st->bucket].lock);
|
||||
|
||||
return expected;
|
||||
}
|
||||
|
||||
static unsigned int bpf_iter_tcp_established_batch(struct seq_file *seq,
|
||||
struct sock *start_sk)
|
||||
{
|
||||
struct bpf_tcp_iter_state *iter = seq->private;
|
||||
struct tcp_iter_state *st = &iter->state;
|
||||
struct hlist_nulls_node *node;
|
||||
unsigned int expected = 1;
|
||||
struct sock *sk;
|
||||
|
||||
sock_hold(start_sk);
|
||||
iter->batch[iter->end_sk++] = start_sk;
|
||||
|
||||
sk = sk_nulls_next(start_sk);
|
||||
sk_nulls_for_each_from(sk, node) {
|
||||
if (seq_sk_match(seq, sk)) {
|
||||
if (iter->end_sk < iter->max_sk) {
|
||||
sock_hold(sk);
|
||||
iter->batch[iter->end_sk++] = sk;
|
||||
}
|
||||
expected++;
|
||||
}
|
||||
}
|
||||
spin_unlock_bh(inet_ehash_lockp(&tcp_hashinfo, st->bucket));
|
||||
|
||||
return expected;
|
||||
}
|
||||
|
||||
static struct sock *bpf_iter_tcp_batch(struct seq_file *seq)
|
||||
{
|
||||
struct bpf_tcp_iter_state *iter = seq->private;
|
||||
struct tcp_iter_state *st = &iter->state;
|
||||
unsigned int expected;
|
||||
bool resized = false;
|
||||
struct sock *sk;
|
||||
|
||||
/* The st->bucket is done. Directly advance to the next
|
||||
* bucket instead of having the tcp_seek_last_pos() to skip
|
||||
* one by one in the current bucket and eventually find out
|
||||
* it has to advance to the next bucket.
|
||||
*/
|
||||
if (iter->st_bucket_done) {
|
||||
st->offset = 0;
|
||||
st->bucket++;
|
||||
if (st->state == TCP_SEQ_STATE_LISTENING &&
|
||||
st->bucket > tcp_hashinfo.lhash2_mask) {
|
||||
st->state = TCP_SEQ_STATE_ESTABLISHED;
|
||||
st->bucket = 0;
|
||||
}
|
||||
}
|
||||
|
||||
again:
|
||||
/* Get a new batch */
|
||||
iter->cur_sk = 0;
|
||||
iter->end_sk = 0;
|
||||
iter->st_bucket_done = false;
|
||||
|
||||
sk = tcp_seek_last_pos(seq);
|
||||
if (!sk)
|
||||
return NULL; /* Done */
|
||||
|
||||
if (st->state == TCP_SEQ_STATE_LISTENING)
|
||||
expected = bpf_iter_tcp_listening_batch(seq, sk);
|
||||
else
|
||||
expected = bpf_iter_tcp_established_batch(seq, sk);
|
||||
|
||||
if (iter->end_sk == expected) {
|
||||
iter->st_bucket_done = true;
|
||||
return sk;
|
||||
}
|
||||
|
||||
if (!resized && !bpf_iter_tcp_realloc_batch(iter, expected * 3 / 2)) {
|
||||
resized = true;
|
||||
goto again;
|
||||
}
|
||||
|
||||
return sk;
|
||||
}
|
||||
|
||||
static void *bpf_iter_tcp_seq_start(struct seq_file *seq, loff_t *pos)
|
||||
{
|
||||
/* bpf iter does not support lseek, so it always
|
||||
* continue from where it was stop()-ped.
|
||||
*/
|
||||
if (*pos)
|
||||
return bpf_iter_tcp_batch(seq);
|
||||
|
||||
return SEQ_START_TOKEN;
|
||||
}
|
||||
|
||||
static void *bpf_iter_tcp_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
||||
{
|
||||
struct bpf_tcp_iter_state *iter = seq->private;
|
||||
struct tcp_iter_state *st = &iter->state;
|
||||
struct sock *sk;
|
||||
|
||||
/* Whenever seq_next() is called, the iter->cur_sk is
|
||||
* done with seq_show(), so advance to the next sk in
|
||||
* the batch.
|
||||
*/
|
||||
if (iter->cur_sk < iter->end_sk) {
|
||||
/* Keeping st->num consistent in tcp_iter_state.
|
||||
* bpf_iter_tcp does not use st->num.
|
||||
* meta.seq_num is used instead.
|
||||
*/
|
||||
st->num++;
|
||||
/* Move st->offset to the next sk in the bucket such that
|
||||
* the future start() will resume at st->offset in
|
||||
* st->bucket. See tcp_seek_last_pos().
|
||||
*/
|
||||
st->offset++;
|
||||
sock_put(iter->batch[iter->cur_sk++]);
|
||||
}
|
||||
|
||||
if (iter->cur_sk < iter->end_sk)
|
||||
sk = iter->batch[iter->cur_sk];
|
||||
else
|
||||
sk = bpf_iter_tcp_batch(seq);
|
||||
|
||||
++*pos;
|
||||
/* Keeping st->last_pos consistent in tcp_iter_state.
|
||||
* bpf iter does not do lseek, so st->last_pos always equals to *pos.
|
||||
*/
|
||||
st->last_pos = *pos;
|
||||
return sk;
|
||||
}
|
||||
|
||||
static int bpf_iter_tcp_seq_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct bpf_iter_meta meta;
|
||||
struct bpf_prog *prog;
|
||||
struct sock *sk = v;
|
||||
bool slow;
|
||||
uid_t uid;
|
||||
int ret;
|
||||
|
||||
if (v == SEQ_START_TOKEN)
|
||||
return 0;
|
||||
|
||||
if (sk_fullsock(sk))
|
||||
slow = lock_sock_fast(sk);
|
||||
|
||||
if (unlikely(sk_unhashed(sk))) {
|
||||
ret = SEQ_SKIP;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
if (sk->sk_state == TCP_TIME_WAIT) {
|
||||
uid = 0;
|
||||
} else if (sk->sk_state == TCP_NEW_SYN_RECV) {
|
||||
@ -2728,11 +2925,18 @@ static int bpf_iter_tcp_seq_show(struct seq_file *seq, void *v)
|
||||
|
||||
meta.seq = seq;
|
||||
prog = bpf_iter_get_info(&meta, false);
|
||||
return tcp_prog_seq_show(prog, &meta, v, uid);
|
||||
ret = tcp_prog_seq_show(prog, &meta, v, uid);
|
||||
|
||||
unlock:
|
||||
if (sk_fullsock(sk))
|
||||
unlock_sock_fast(sk, slow);
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
static void bpf_iter_tcp_seq_stop(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct bpf_tcp_iter_state *iter = seq->private;
|
||||
struct bpf_iter_meta meta;
|
||||
struct bpf_prog *prog;
|
||||
|
||||
@ -2743,16 +2947,33 @@ static void bpf_iter_tcp_seq_stop(struct seq_file *seq, void *v)
|
||||
(void)tcp_prog_seq_show(prog, &meta, v, 0);
|
||||
}
|
||||
|
||||
tcp_seq_stop(seq, v);
|
||||
if (iter->cur_sk < iter->end_sk) {
|
||||
bpf_iter_tcp_put_batch(iter);
|
||||
iter->st_bucket_done = false;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct seq_operations bpf_iter_tcp_seq_ops = {
|
||||
.show = bpf_iter_tcp_seq_show,
|
||||
.start = tcp_seq_start,
|
||||
.next = tcp_seq_next,
|
||||
.start = bpf_iter_tcp_seq_start,
|
||||
.next = bpf_iter_tcp_seq_next,
|
||||
.stop = bpf_iter_tcp_seq_stop,
|
||||
};
|
||||
#endif
|
||||
static unsigned short seq_file_family(const struct seq_file *seq)
|
||||
{
|
||||
const struct tcp_seq_afinfo *afinfo;
|
||||
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
/* Iterated from bpf_iter. Let the bpf prog to filter instead. */
|
||||
if (seq->op == &bpf_iter_tcp_seq_ops)
|
||||
return AF_UNSPEC;
|
||||
#endif
|
||||
|
||||
/* Iterated from proc fs */
|
||||
afinfo = PDE_DATA(file_inode(seq->file));
|
||||
return afinfo->family;
|
||||
}
|
||||
|
||||
static const struct seq_operations tcp4_seq_ops = {
|
||||
.show = tcp4_seq_show,
|
||||
@ -3002,39 +3223,55 @@ static struct pernet_operations __net_initdata tcp_sk_ops = {
|
||||
DEFINE_BPF_ITER_FUNC(tcp, struct bpf_iter_meta *meta,
|
||||
struct sock_common *sk_common, uid_t uid)
|
||||
|
||||
#define INIT_BATCH_SZ 16
|
||||
|
||||
static int bpf_iter_init_tcp(void *priv_data, struct bpf_iter_aux_info *aux)
|
||||
{
|
||||
struct tcp_iter_state *st = priv_data;
|
||||
struct tcp_seq_afinfo *afinfo;
|
||||
int ret;
|
||||
struct bpf_tcp_iter_state *iter = priv_data;
|
||||
int err;
|
||||
|
||||
afinfo = kmalloc(sizeof(*afinfo), GFP_USER | __GFP_NOWARN);
|
||||
if (!afinfo)
|
||||
return -ENOMEM;
|
||||
err = bpf_iter_init_seq_net(priv_data, aux);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
afinfo->family = AF_UNSPEC;
|
||||
st->bpf_seq_afinfo = afinfo;
|
||||
ret = bpf_iter_init_seq_net(priv_data, aux);
|
||||
if (ret)
|
||||
kfree(afinfo);
|
||||
return ret;
|
||||
err = bpf_iter_tcp_realloc_batch(iter, INIT_BATCH_SZ);
|
||||
if (err) {
|
||||
bpf_iter_fini_seq_net(priv_data);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bpf_iter_fini_tcp(void *priv_data)
|
||||
{
|
||||
struct tcp_iter_state *st = priv_data;
|
||||
struct bpf_tcp_iter_state *iter = priv_data;
|
||||
|
||||
kfree(st->bpf_seq_afinfo);
|
||||
bpf_iter_fini_seq_net(priv_data);
|
||||
kvfree(iter->batch);
|
||||
}
|
||||
|
||||
static const struct bpf_iter_seq_info tcp_seq_info = {
|
||||
.seq_ops = &bpf_iter_tcp_seq_ops,
|
||||
.init_seq_private = bpf_iter_init_tcp,
|
||||
.fini_seq_private = bpf_iter_fini_tcp,
|
||||
.seq_priv_size = sizeof(struct tcp_iter_state),
|
||||
.seq_priv_size = sizeof(struct bpf_tcp_iter_state),
|
||||
};
|
||||
|
||||
static const struct bpf_func_proto *
|
||||
bpf_iter_tcp_get_func_proto(enum bpf_func_id func_id,
|
||||
const struct bpf_prog *prog)
|
||||
{
|
||||
switch (func_id) {
|
||||
case BPF_FUNC_setsockopt:
|
||||
return &bpf_sk_setsockopt_proto;
|
||||
case BPF_FUNC_getsockopt:
|
||||
return &bpf_sk_getsockopt_proto;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static struct bpf_iter_reg tcp_reg_info = {
|
||||
.target = "tcp",
|
||||
.ctx_arg_info_size = 1,
|
||||
@ -3042,6 +3279,7 @@ static struct bpf_iter_reg tcp_reg_info = {
|
||||
{ offsetof(struct bpf_iter__tcp, sk_common),
|
||||
PTR_TO_BTF_ID_OR_NULL },
|
||||
},
|
||||
.get_func_proto = bpf_iter_tcp_get_func_proto,
|
||||
.seq_info = &tcp_seq_info,
|
||||
};
|
||||
|
||||
|
@ -44,7 +44,7 @@ static int unix_dgram_bpf_recvmsg(struct sock *sk, struct msghdr *msg,
|
||||
{
|
||||
struct unix_sock *u = unix_sk(sk);
|
||||
struct sk_psock *psock;
|
||||
int copied, ret;
|
||||
int copied;
|
||||
|
||||
psock = sk_psock_get(sk);
|
||||
if (unlikely(!psock))
|
||||
@ -53,8 +53,9 @@ static int unix_dgram_bpf_recvmsg(struct sock *sk, struct msghdr *msg,
|
||||
mutex_lock(&u->iolock);
|
||||
if (!skb_queue_empty(&sk->sk_receive_queue) &&
|
||||
sk_psock_queue_empty(psock)) {
|
||||
ret = __unix_dgram_recvmsg(sk, msg, len, flags);
|
||||
goto out;
|
||||
mutex_unlock(&u->iolock);
|
||||
sk_psock_put(sk, psock);
|
||||
return __unix_dgram_recvmsg(sk, msg, len, flags);
|
||||
}
|
||||
|
||||
msg_bytes_ready:
|
||||
@ -68,16 +69,15 @@ msg_bytes_ready:
|
||||
if (data) {
|
||||
if (!sk_psock_queue_empty(psock))
|
||||
goto msg_bytes_ready;
|
||||
ret = __unix_dgram_recvmsg(sk, msg, len, flags);
|
||||
goto out;
|
||||
mutex_unlock(&u->iolock);
|
||||
sk_psock_put(sk, psock);
|
||||
return __unix_dgram_recvmsg(sk, msg, len, flags);
|
||||
}
|
||||
copied = -EAGAIN;
|
||||
}
|
||||
ret = copied;
|
||||
out:
|
||||
mutex_unlock(&u->iolock);
|
||||
sk_psock_put(sk, psock);
|
||||
return ret;
|
||||
return copied;
|
||||
}
|
||||
|
||||
static struct proto *unix_prot_saved __read_mostly;
|
||||
|
2
samples/bpf/.gitignore
vendored
2
samples/bpf/.gitignore
vendored
@ -45,11 +45,13 @@ xdp_monitor
|
||||
xdp_redirect
|
||||
xdp_redirect_cpu
|
||||
xdp_redirect_map
|
||||
xdp_redirect_map_multi
|
||||
xdp_router_ipv4
|
||||
xdp_rxq_info
|
||||
xdp_sample_pkts
|
||||
xdp_tx_iptunnel
|
||||
xdpsock
|
||||
xdpsock_ctrl_proc
|
||||
xsk_fwd
|
||||
testfile.img
|
||||
hbm_out.log
|
||||
|
@ -1,5 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
rm -r tmpmnt
|
||||
rm -f testfile.img
|
||||
dd if=/dev/zero of=testfile.img bs=1M seek=1000 count=1
|
||||
DEVICE=$(losetup --show -f testfile.img)
|
||||
|
@ -14,6 +14,11 @@ int main(int argc, char **argv)
|
||||
int ret = 0;
|
||||
FILE *f;
|
||||
|
||||
if (!argv[1]) {
|
||||
fprintf(stderr, "ERROR: Run with the btrfs device argument!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
|
||||
obj = bpf_object__open_file(filename, NULL);
|
||||
if (libbpf_get_error(obj)) {
|
||||
|
@ -12,7 +12,8 @@ SYNOPSIS
|
||||
|
||||
**bpftool** [*OPTIONS*] **btf** *COMMAND*
|
||||
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] }
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | {**-d** | **--debug** } |
|
||||
{ **-B** | **--base-btf** } }
|
||||
|
||||
*COMMANDS* := { **dump** | **help** }
|
||||
|
||||
@ -73,6 +74,20 @@ OPTIONS
|
||||
=======
|
||||
.. include:: common_options.rst
|
||||
|
||||
-B, --base-btf *FILE*
|
||||
Pass a base BTF object. Base BTF objects are typically used
|
||||
with BTF objects for kernel modules. To avoid duplicating
|
||||
all kernel symbols required by modules, BTF objects for
|
||||
modules are "split", they are built incrementally on top of
|
||||
the kernel (vmlinux) BTF object. So the base BTF reference
|
||||
should usually point to the kernel BTF.
|
||||
|
||||
When the main BTF object to process (for example, the
|
||||
module BTF to dump) is passed as a *FILE*, bpftool attempts
|
||||
to autodetect the path for the base object, and passing
|
||||
this option is optional. When the main BTF object is passed
|
||||
through other handles, this option becomes necessary.
|
||||
|
||||
EXAMPLES
|
||||
========
|
||||
**# bpftool btf dump id 1226**
|
||||
@ -217,3 +232,34 @@ All the standard ways to specify map or program are supported:
|
||||
**# bpftool btf dump prog tag b88e0a09b1d9759d**
|
||||
|
||||
**# bpftool btf dump prog pinned /sys/fs/bpf/prog_name**
|
||||
|
||||
|
|
||||
| **# bpftool btf dump file /sys/kernel/btf/i2c_smbus**
|
||||
| (or)
|
||||
| **# I2C_SMBUS_ID=$(bpftool btf show -p | jq '.[] | select(.name=="i2c_smbus").id')**
|
||||
| **# bpftool btf dump id ${I2C_SMBUS_ID} -B /sys/kernel/btf/vmlinux**
|
||||
|
||||
::
|
||||
|
||||
[104848] STRUCT 'i2c_smbus_alert' size=40 vlen=2
|
||||
'alert' type_id=393 bits_offset=0
|
||||
'ara' type_id=56050 bits_offset=256
|
||||
[104849] STRUCT 'alert_data' size=12 vlen=3
|
||||
'addr' type_id=16 bits_offset=0
|
||||
'type' type_id=56053 bits_offset=32
|
||||
'data' type_id=7 bits_offset=64
|
||||
[104850] PTR '(anon)' type_id=104848
|
||||
[104851] PTR '(anon)' type_id=104849
|
||||
[104852] FUNC 'i2c_register_spd' type_id=84745 linkage=static
|
||||
[104853] FUNC 'smbalert_driver_init' type_id=1213 linkage=static
|
||||
[104854] FUNC_PROTO '(anon)' ret_type_id=18 vlen=1
|
||||
'ara' type_id=56050
|
||||
[104855] FUNC 'i2c_handle_smbus_alert' type_id=104854 linkage=static
|
||||
[104856] FUNC 'smbalert_remove' type_id=104854 linkage=static
|
||||
[104857] FUNC_PROTO '(anon)' ret_type_id=18 vlen=2
|
||||
'ara' type_id=56050
|
||||
'id' type_id=56056
|
||||
[104858] FUNC 'smbalert_probe' type_id=104857 linkage=static
|
||||
[104859] FUNC 'smbalert_work' type_id=9695 linkage=static
|
||||
[104860] FUNC 'smbus_alert' type_id=71367 linkage=static
|
||||
[104861] FUNC 'smbus_do_alert' type_id=84827 linkage=static
|
||||
|
@ -12,7 +12,8 @@ SYNOPSIS
|
||||
|
||||
**bpftool** [*OPTIONS*] **cgroup** *COMMAND*
|
||||
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-f** | **--bpffs** } }
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-d** | **--debug** } |
|
||||
{ **-f** | **--bpffs** } }
|
||||
|
||||
*COMMANDS* :=
|
||||
{ **show** | **list** | **tree** | **attach** | **detach** | **help** }
|
||||
|
@ -12,7 +12,7 @@ SYNOPSIS
|
||||
|
||||
**bpftool** [*OPTIONS*] **feature** *COMMAND*
|
||||
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] }
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-d** | **--debug** } }
|
||||
|
||||
*COMMANDS* := { **probe** | **help** }
|
||||
|
||||
|
@ -12,7 +12,8 @@ SYNOPSIS
|
||||
|
||||
**bpftool** [*OPTIONS*] **gen** *COMMAND*
|
||||
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] }
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-d** | **--debug** } |
|
||||
{ **-L** | **--use-loader** } }
|
||||
|
||||
*COMMAND* := { **object** | **skeleton** | **help** }
|
||||
|
||||
@ -152,6 +153,12 @@ OPTIONS
|
||||
=======
|
||||
.. include:: common_options.rst
|
||||
|
||||
-L, --use-loader
|
||||
For skeletons, generate a "light" skeleton (also known as "loader"
|
||||
skeleton). A light skeleton contains a loader eBPF program. It does
|
||||
not use the majority of the libbpf infrastructure, and does not need
|
||||
libelf.
|
||||
|
||||
EXAMPLES
|
||||
========
|
||||
**$ cat example1.bpf.c**
|
||||
|
@ -12,6 +12,8 @@ SYNOPSIS
|
||||
|
||||
**bpftool** [*OPTIONS*] **iter** *COMMAND*
|
||||
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-d** | **--debug** } }
|
||||
|
||||
*COMMANDS* := { **pin** | **help** }
|
||||
|
||||
ITER COMMANDS
|
||||
|
@ -12,7 +12,8 @@ SYNOPSIS
|
||||
|
||||
**bpftool** [*OPTIONS*] **link** *COMMAND*
|
||||
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-f** | **--bpffs** } }
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-d** | **--debug** } |
|
||||
{ **-f** | **--bpffs** } | { **-n** | **--nomount** } }
|
||||
|
||||
*COMMANDS* := { **show** | **list** | **pin** | **help** }
|
||||
|
||||
|
@ -12,7 +12,8 @@ SYNOPSIS
|
||||
|
||||
**bpftool** [*OPTIONS*] **map** *COMMAND*
|
||||
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-f** | **--bpffs** } }
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-d** | **--debug** } |
|
||||
{ **-f** | **--bpffs** } | { **-n** | **--nomount** } }
|
||||
|
||||
*COMMANDS* :=
|
||||
{ **show** | **list** | **create** | **dump** | **update** | **lookup** | **getnext**
|
||||
|
@ -12,7 +12,7 @@ SYNOPSIS
|
||||
|
||||
**bpftool** [*OPTIONS*] **net** *COMMAND*
|
||||
|
||||
*OPTIONS* := { [{ **-j** | **--json** }] [{ **-p** | **--pretty** }] }
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-d** | **--debug** } }
|
||||
|
||||
*COMMANDS* :=
|
||||
{ **show** | **list** | **attach** | **detach** | **help** }
|
||||
|
@ -12,7 +12,7 @@ SYNOPSIS
|
||||
|
||||
**bpftool** [*OPTIONS*] **perf** *COMMAND*
|
||||
|
||||
*OPTIONS* := { [{ **-j** | **--json** }] [{ **-p** | **--pretty** }] }
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-d** | **--debug** } }
|
||||
|
||||
*COMMANDS* :=
|
||||
{ **show** | **list** | **help** }
|
||||
|
@ -12,7 +12,9 @@ SYNOPSIS
|
||||
|
||||
**bpftool** [*OPTIONS*] **prog** *COMMAND*
|
||||
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-f** | **--bpffs** } }
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-d** | **--debug** } |
|
||||
{ **-f** | **--bpffs** } | { **-m** | **--mapcompat** } | { **-n** | **--nomount** } |
|
||||
{ **-L** | **--use-loader** } }
|
||||
|
||||
*COMMANDS* :=
|
||||
{ **show** | **list** | **dump xlated** | **dump jited** | **pin** | **load**
|
||||
@ -48,10 +50,11 @@ PROG COMMANDS
|
||||
| **struct_ops** | **fentry** | **fexit** | **freplace** | **sk_lookup**
|
||||
| }
|
||||
| *ATTACH_TYPE* := {
|
||||
| **msg_verdict** | **stream_verdict** | **stream_parser** | **flow_dissector**
|
||||
| **msg_verdict** | **skb_verdict** | **stream_verdict** | **stream_parser** | **flow_dissector**
|
||||
| }
|
||||
| *METRICs* := {
|
||||
| **cycles** | **instructions** | **l1d_loads** | **llc_misses**
|
||||
| **cycles** | **instructions** | **l1d_loads** | **llc_misses** |
|
||||
| **itlb_misses** | **dtlb_misses**
|
||||
| }
|
||||
|
||||
|
||||
@ -223,6 +226,20 @@ OPTIONS
|
||||
Do not automatically attempt to mount any virtual file system
|
||||
(such as tracefs or BPF virtual file system) when necessary.
|
||||
|
||||
-L, --use-loader
|
||||
Load program as a "loader" program. This is useful to debug
|
||||
the generation of such programs. When this option is in
|
||||
use, bpftool attempts to load the programs from the object
|
||||
file into the kernel, but does not pin them (therefore, the
|
||||
*PATH* must not be provided).
|
||||
|
||||
When combined with the **-d**\ \|\ **--debug** option,
|
||||
additional debug messages are generated, and the execution
|
||||
of the loader program will use the **bpf_trace_printk**\ ()
|
||||
helper to log each step of loading BTF, creating the maps,
|
||||
and loading the programs (see **bpftool prog tracelog** as
|
||||
a way to dump those messages).
|
||||
|
||||
EXAMPLES
|
||||
========
|
||||
**# bpftool prog show**
|
||||
@ -326,3 +343,16 @@ EXAMPLES
|
||||
40176203 cycles (83.05%)
|
||||
42518139 instructions # 1.06 insns per cycle (83.39%)
|
||||
123 llc_misses # 2.89 LLC misses per million insns (83.15%)
|
||||
|
||||
|
|
||||
| Output below is for the trace logs.
|
||||
| Run in separate terminals:
|
||||
| **# bpftool prog tracelog**
|
||||
| **# bpftool prog load -L -d file.o**
|
||||
|
||||
::
|
||||
|
||||
bpftool-620059 [004] d... 2634685.517903: bpf_trace_printk: btf_load size 665 r=5
|
||||
bpftool-620059 [004] d... 2634685.517912: bpf_trace_printk: map_create sample_map idx 0 type 2 value_size 4 value_btf_id 0 r=6
|
||||
bpftool-620059 [004] d... 2634685.517997: bpf_trace_printk: prog_load sample insn_cnt 13 r=7
|
||||
bpftool-620059 [004] d... 2634685.517999: bpf_trace_printk: close(5) = 0
|
||||
|
@ -12,7 +12,7 @@ SYNOPSIS
|
||||
|
||||
**bpftool** [*OPTIONS*] **struct_ops** *COMMAND*
|
||||
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] }
|
||||
*OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-d** | **--debug** } }
|
||||
|
||||
*COMMANDS* :=
|
||||
{ **show** | **list** | **dump** | **register** | **unregister** | **help** }
|
||||
|
@ -18,15 +18,15 @@ SYNOPSIS
|
||||
|
||||
*OBJECT* := { **map** | **program** | **cgroup** | **perf** | **net** | **feature** }
|
||||
|
||||
*OPTIONS* := { { **-V** | **--version** } | { **-h** | **--help** }
|
||||
| { **-j** | **--json** } [{ **-p** | **--pretty** }] }
|
||||
*OPTIONS* := { { **-V** | **--version** } |
|
||||
{ **-j** | **--json** } [{ **-p** | **--pretty** }] | { **-d** | **--debug** } }
|
||||
|
||||
*MAP-COMMANDS* :=
|
||||
{ **show** | **list** | **create** | **dump** | **update** | **lookup** | **getnext**
|
||||
| **delete** | **pin** | **event_pipe** | **help** }
|
||||
{ **show** | **list** | **create** | **dump** | **update** | **lookup** | **getnext** |
|
||||
**delete** | **pin** | **event_pipe** | **help** }
|
||||
|
||||
*PROG-COMMANDS* := { **show** | **list** | **dump jited** | **dump xlated** | **pin**
|
||||
| **load** | **attach** | **detach** | **help** }
|
||||
*PROG-COMMANDS* := { **show** | **list** | **dump jited** | **dump xlated** | **pin** |
|
||||
**load** | **attach** | **detach** | **help** }
|
||||
|
||||
*CGROUP-COMMANDS* := { **show** | **list** | **attach** | **detach** | **help** }
|
||||
|
||||
|
@ -260,7 +260,8 @@ _bpftool()
|
||||
|
||||
# Deal with options
|
||||
if [[ ${words[cword]} == -* ]]; then
|
||||
local c='--version --json --pretty --bpffs --mapcompat --debug'
|
||||
local c='--version --json --pretty --bpffs --mapcompat --debug \
|
||||
--use-loader --base-btf'
|
||||
COMPREPLY=( $( compgen -W "$c" -- "$cur" ) )
|
||||
return 0
|
||||
fi
|
||||
@ -278,7 +279,7 @@ _bpftool()
|
||||
_sysfs_get_netdevs
|
||||
return 0
|
||||
;;
|
||||
file|pinned)
|
||||
file|pinned|-B|--base-btf)
|
||||
_filedir
|
||||
return 0
|
||||
;;
|
||||
@ -291,7 +292,8 @@ _bpftool()
|
||||
# Remove all options so completions don't have to deal with them.
|
||||
local i
|
||||
for (( i=1; i < ${#words[@]}; )); do
|
||||
if [[ ${words[i]::1} == - ]]; then
|
||||
if [[ ${words[i]::1} == - ]] &&
|
||||
[[ ${words[i]} != "-B" ]] && [[ ${words[i]} != "--base-btf" ]]; then
|
||||
words=( "${words[@]:0:i}" "${words[@]:i+1}" )
|
||||
[[ $i -le $cword ]] && cword=$(( cword - 1 ))
|
||||
else
|
||||
@ -343,7 +345,8 @@ _bpftool()
|
||||
|
||||
local PROG_TYPE='id pinned tag name'
|
||||
local MAP_TYPE='id pinned name'
|
||||
local METRIC_TYPE='cycles instructions l1d_loads llc_misses'
|
||||
local METRIC_TYPE='cycles instructions l1d_loads llc_misses \
|
||||
itlb_misses dtlb_misses'
|
||||
case $command in
|
||||
show|list)
|
||||
[[ $prev != "$command" ]] && return 0
|
||||
@ -404,8 +407,10 @@ _bpftool()
|
||||
return 0
|
||||
;;
|
||||
5)
|
||||
COMPREPLY=( $( compgen -W 'msg_verdict stream_verdict \
|
||||
stream_parser flow_dissector' -- "$cur" ) )
|
||||
local BPFTOOL_PROG_ATTACH_TYPES='msg_verdict \
|
||||
skb_verdict stream_verdict stream_parser \
|
||||
flow_dissector'
|
||||
COMPREPLY=( $( compgen -W "$BPFTOOL_PROG_ATTACH_TYPES" -- "$cur" ) )
|
||||
return 0
|
||||
;;
|
||||
6)
|
||||
@ -464,7 +469,7 @@ _bpftool()
|
||||
|
||||
case $prev in
|
||||
type)
|
||||
COMPREPLY=( $( compgen -W "socket kprobe \
|
||||
local BPFTOOL_PROG_LOAD_TYPES='socket kprobe \
|
||||
kretprobe classifier flow_dissector \
|
||||
action tracepoint raw_tracepoint \
|
||||
xdp perf_event cgroup/skb cgroup/sock \
|
||||
@ -479,8 +484,8 @@ _bpftool()
|
||||
cgroup/post_bind4 cgroup/post_bind6 \
|
||||
cgroup/sysctl cgroup/getsockopt \
|
||||
cgroup/setsockopt cgroup/sock_release struct_ops \
|
||||
fentry fexit freplace sk_lookup" -- \
|
||||
"$cur" ) )
|
||||
fentry fexit freplace sk_lookup'
|
||||
COMPREPLY=( $( compgen -W "$BPFTOOL_PROG_LOAD_TYPES" -- "$cur" ) )
|
||||
return 0
|
||||
;;
|
||||
id)
|
||||
@ -698,15 +703,15 @@ _bpftool()
|
||||
return 0
|
||||
;;
|
||||
type)
|
||||
COMPREPLY=( $( compgen -W 'hash array prog_array \
|
||||
perf_event_array percpu_hash percpu_array \
|
||||
stack_trace cgroup_array lru_hash \
|
||||
local BPFTOOL_MAP_CREATE_TYPES='hash array \
|
||||
prog_array perf_event_array percpu_hash \
|
||||
percpu_array stack_trace cgroup_array lru_hash \
|
||||
lru_percpu_hash lpm_trie array_of_maps \
|
||||
hash_of_maps devmap devmap_hash sockmap cpumap \
|
||||
xskmap sockhash cgroup_storage reuseport_sockarray \
|
||||
percpu_cgroup_storage queue stack sk_storage \
|
||||
struct_ops inode_storage task_storage' -- \
|
||||
"$cur" ) )
|
||||
struct_ops inode_storage task_storage ringbuf'
|
||||
COMPREPLY=( $( compgen -W "$BPFTOOL_MAP_CREATE_TYPES" -- "$cur" ) )
|
||||
return 0
|
||||
;;
|
||||
key|value|flags|entries)
|
||||
@ -1017,34 +1022,37 @@ _bpftool()
|
||||
return 0
|
||||
;;
|
||||
attach|detach)
|
||||
local ATTACH_TYPES='ingress egress sock_create sock_ops \
|
||||
device bind4 bind6 post_bind4 post_bind6 connect4 connect6 \
|
||||
local BPFTOOL_CGROUP_ATTACH_TYPES='ingress egress \
|
||||
sock_create sock_ops device \
|
||||
bind4 bind6 post_bind4 post_bind6 connect4 connect6 \
|
||||
getpeername4 getpeername6 getsockname4 getsockname6 \
|
||||
sendmsg4 sendmsg6 recvmsg4 recvmsg6 sysctl getsockopt \
|
||||
setsockopt sock_release'
|
||||
local ATTACH_FLAGS='multi override'
|
||||
local PROG_TYPE='id pinned tag name'
|
||||
case $prev in
|
||||
$command)
|
||||
_filedir
|
||||
return 0
|
||||
;;
|
||||
ingress|egress|sock_create|sock_ops|device|bind4|bind6|\
|
||||
post_bind4|post_bind6|connect4|connect6|getpeername4|\
|
||||
getpeername6|getsockname4|getsockname6|sendmsg4|sendmsg6|\
|
||||
recvmsg4|recvmsg6|sysctl|getsockopt|setsockopt|sock_release)
|
||||
# Check for $prev = $command first
|
||||
if [ $prev = $command ]; then
|
||||
_filedir
|
||||
return 0
|
||||
# Then check for attach type. This is done outside of the
|
||||
# "case $prev in" to avoid writing the whole list of attach
|
||||
# types again as pattern to match (where we cannot reuse
|
||||
# our variable).
|
||||
elif [[ $BPFTOOL_CGROUP_ATTACH_TYPES =~ $prev ]]; then
|
||||
COMPREPLY=( $( compgen -W "$PROG_TYPE" -- \
|
||||
"$cur" ) )
|
||||
return 0
|
||||
;;
|
||||
fi
|
||||
# case/esac for the other cases
|
||||
case $prev in
|
||||
id)
|
||||
_bpftool_get_prog_ids
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
if ! _bpftool_search_list "$ATTACH_TYPES"; then
|
||||
COMPREPLY=( $( compgen -W "$ATTACH_TYPES" -- \
|
||||
"$cur" ) )
|
||||
if ! _bpftool_search_list "$BPFTOOL_CGROUP_ATTACH_TYPES"; then
|
||||
COMPREPLY=( $( compgen -W \
|
||||
"$BPFTOOL_CGROUP_ATTACH_TYPES" -- "$cur" ) )
|
||||
elif [[ "$command" == "attach" ]]; then
|
||||
# We have an attach type on the command line,
|
||||
# but it is not the previous word, or
|
||||
|
@ -580,16 +580,12 @@ static int do_dump(int argc, char **argv)
|
||||
}
|
||||
|
||||
if (!btf) {
|
||||
err = btf__get_from_id(btf_id, &btf);
|
||||
btf = btf__load_from_kernel_by_id_split(btf_id, base_btf);
|
||||
err = libbpf_get_error(btf);
|
||||
if (err) {
|
||||
p_err("get btf by id (%u): %s", btf_id, strerror(err));
|
||||
goto done;
|
||||
}
|
||||
if (!btf) {
|
||||
err = -ENOENT;
|
||||
p_err("can't find btf with ID (%u)", btf_id);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (dump_c) {
|
||||
@ -985,7 +981,8 @@ static int do_help(int argc, char **argv)
|
||||
" FORMAT := { raw | c }\n"
|
||||
" " HELP_SPEC_MAP "\n"
|
||||
" " HELP_SPEC_PROGRAM "\n"
|
||||
" " HELP_SPEC_OPTIONS "\n"
|
||||
" " HELP_SPEC_OPTIONS " |\n"
|
||||
" {-B|--base-btf} }\n"
|
||||
"",
|
||||
bin_name, "btf");
|
||||
|
||||
|
@ -64,8 +64,10 @@ static int dump_prog_id_as_func_ptr(const struct btf_dumper *d,
|
||||
}
|
||||
info = &prog_info->info;
|
||||
|
||||
if (!info->btf_id || !info->nr_func_info ||
|
||||
btf__get_from_id(info->btf_id, &prog_btf))
|
||||
if (!info->btf_id || !info->nr_func_info)
|
||||
goto print;
|
||||
prog_btf = btf__load_from_kernel_by_id(info->btf_id);
|
||||
if (libbpf_get_error(prog_btf))
|
||||
goto print;
|
||||
finfo = u64_to_ptr(info->func_info);
|
||||
func_type = btf__type_by_id(prog_btf, finfo->type_id);
|
||||
|
@ -501,7 +501,8 @@ static int do_help(int argc, char **argv)
|
||||
HELP_SPEC_ATTACH_TYPES "\n"
|
||||
" " HELP_SPEC_ATTACH_FLAGS "\n"
|
||||
" " HELP_SPEC_PROGRAM "\n"
|
||||
" " HELP_SPEC_OPTIONS "\n"
|
||||
" " HELP_SPEC_OPTIONS " |\n"
|
||||
" {-f|--bpffs} }\n"
|
||||
"",
|
||||
bin_name, argv[-2]);
|
||||
|
||||
|
@ -67,6 +67,12 @@ const char * const attach_type_name[__MAX_BPF_ATTACH_TYPE] = {
|
||||
[BPF_MODIFY_RETURN] = "mod_ret",
|
||||
[BPF_LSM_MAC] = "lsm_mac",
|
||||
[BPF_SK_LOOKUP] = "sk_lookup",
|
||||
[BPF_TRACE_ITER] = "trace_iter",
|
||||
[BPF_XDP_DEVMAP] = "xdp_devmap",
|
||||
[BPF_XDP_CPUMAP] = "xdp_cpumap",
|
||||
[BPF_XDP] = "xdp",
|
||||
[BPF_SK_REUSEPORT_SELECT] = "sk_skb_reuseport_select",
|
||||
[BPF_SK_REUSEPORT_SELECT_OR_MIGRATE] = "sk_skb_reuseport_select_or_migrate",
|
||||
};
|
||||
|
||||
void p_err(const char *fmt, ...)
|
||||
|
@ -1005,6 +1005,7 @@ static int do_help(int argc, char **argv)
|
||||
" %1$s %2$s help\n"
|
||||
"\n"
|
||||
" COMPONENT := { kernel | dev NAME }\n"
|
||||
" " HELP_SPEC_OPTIONS " }\n"
|
||||
"",
|
||||
bin_name, argv[-2]);
|
||||
|
||||
|
@ -1026,7 +1026,8 @@ static int do_help(int argc, char **argv)
|
||||
" %1$s %2$s skeleton FILE [name OBJECT_NAME]\n"
|
||||
" %1$s %2$s help\n"
|
||||
"\n"
|
||||
" " HELP_SPEC_OPTIONS "\n"
|
||||
" " HELP_SPEC_OPTIONS " |\n"
|
||||
" {-L|--use-loader} }\n"
|
||||
"",
|
||||
bin_name, "gen");
|
||||
|
||||
|
@ -97,7 +97,9 @@ static int do_help(int argc, char **argv)
|
||||
fprintf(stderr,
|
||||
"Usage: %1$s %2$s pin OBJ PATH [map MAP]\n"
|
||||
" %1$s %2$s help\n"
|
||||
"\n"
|
||||
" " HELP_SPEC_MAP "\n"
|
||||
" " HELP_SPEC_OPTIONS " }\n"
|
||||
"",
|
||||
bin_name, "iter");
|
||||
|
||||
|
@ -401,7 +401,8 @@ static int do_help(int argc, char **argv)
|
||||
" %1$s %2$s help\n"
|
||||
"\n"
|
||||
" " HELP_SPEC_LINK "\n"
|
||||
" " HELP_SPEC_OPTIONS "\n"
|
||||
" " HELP_SPEC_OPTIONS " |\n"
|
||||
" {-f|--bpffs} | {-n|--nomount} }\n"
|
||||
"",
|
||||
bin_name, argv[-2]);
|
||||
|
||||
|
@ -64,7 +64,8 @@ static int do_help(int argc, char **argv)
|
||||
" %s version\n"
|
||||
"\n"
|
||||
" OBJECT := { prog | map | link | cgroup | perf | net | feature | btf | gen | struct_ops | iter }\n"
|
||||
" " HELP_SPEC_OPTIONS "\n"
|
||||
" " HELP_SPEC_OPTIONS " |\n"
|
||||
" {-V|--version} }\n"
|
||||
"",
|
||||
bin_name, bin_name, bin_name);
|
||||
|
||||
|
@ -57,8 +57,7 @@ static inline void *u64_to_ptr(__u64 ptr)
|
||||
#define HELP_SPEC_PROGRAM \
|
||||
"PROG := { id PROG_ID | pinned FILE | tag PROG_TAG | name PROG_NAME }"
|
||||
#define HELP_SPEC_OPTIONS \
|
||||
"OPTIONS := { {-j|--json} [{-p|--pretty}] | {-f|--bpffs} |\n" \
|
||||
"\t {-m|--mapcompat} | {-n|--nomount} }"
|
||||
"OPTIONS := { {-j|--json} [{-p|--pretty}] | {-d|--debug}"
|
||||
#define HELP_SPEC_MAP \
|
||||
"MAP := { id MAP_ID | pinned FILE | name MAP_NAME }"
|
||||
#define HELP_SPEC_LINK \
|
||||
|
@ -807,10 +807,11 @@ static struct btf *get_map_kv_btf(const struct bpf_map_info *info)
|
||||
} else if (info->btf_value_type_id) {
|
||||
int err;
|
||||
|
||||
err = btf__get_from_id(info->btf_id, &btf);
|
||||
if (err || !btf) {
|
||||
btf = btf__load_from_kernel_by_id(info->btf_id);
|
||||
err = libbpf_get_error(btf);
|
||||
if (err) {
|
||||
p_err("failed to get btf");
|
||||
btf = err ? ERR_PTR(err) : ERR_PTR(-ESRCH);
|
||||
btf = ERR_PTR(err);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1039,11 +1040,10 @@ static void print_key_value(struct bpf_map_info *info, void *key,
|
||||
void *value)
|
||||
{
|
||||
json_writer_t *btf_wtr;
|
||||
struct btf *btf = NULL;
|
||||
int err;
|
||||
struct btf *btf;
|
||||
|
||||
err = btf__get_from_id(info->btf_id, &btf);
|
||||
if (err) {
|
||||
btf = btf__load_from_kernel_by_id(info->btf_id);
|
||||
if (libbpf_get_error(btf)) {
|
||||
p_err("failed to get btf");
|
||||
return;
|
||||
}
|
||||
@ -1466,8 +1466,9 @@ static int do_help(int argc, char **argv)
|
||||
" devmap | devmap_hash | sockmap | cpumap | xskmap | sockhash |\n"
|
||||
" cgroup_storage | reuseport_sockarray | percpu_cgroup_storage |\n"
|
||||
" queue | stack | sk_storage | struct_ops | ringbuf | inode_storage |\n"
|
||||
" task_storage }\n"
|
||||
" " HELP_SPEC_OPTIONS "\n"
|
||||
" task_storage }\n"
|
||||
" " HELP_SPEC_OPTIONS " |\n"
|
||||
" {-f|--bpffs} | {-n|--nomount} }\n"
|
||||
"",
|
||||
bin_name, argv[-2]);
|
||||
|
||||
|
@ -729,6 +729,7 @@ static int do_help(int argc, char **argv)
|
||||
"\n"
|
||||
" " HELP_SPEC_PROGRAM "\n"
|
||||
" ATTACH_TYPE := { xdp | xdpgeneric | xdpdrv | xdpoffload }\n"
|
||||
" " HELP_SPEC_OPTIONS " }\n"
|
||||
"\n"
|
||||
"Note: Only xdp and tc attachments are supported now.\n"
|
||||
" For progs attached to cgroups, use \"bpftool cgroup\"\n"
|
||||
|
@ -231,7 +231,10 @@ static int do_show(int argc, char **argv)
|
||||
static int do_help(int argc, char **argv)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Usage: %1$s %2$s { show | list | help }\n"
|
||||
"Usage: %1$s %2$s { show | list }\n"
|
||||
" %1$s %2$s help }\n"
|
||||
"\n"
|
||||
" " HELP_SPEC_OPTIONS " }\n"
|
||||
"",
|
||||
bin_name, argv[-2]);
|
||||
|
||||
|
@ -249,10 +249,10 @@ static void show_prog_metadata(int fd, __u32 num_maps)
|
||||
struct bpf_map_info map_info;
|
||||
struct btf_var_secinfo *vsi;
|
||||
bool printed_header = false;
|
||||
struct btf *btf = NULL;
|
||||
unsigned int i, vlen;
|
||||
void *value = NULL;
|
||||
const char *name;
|
||||
struct btf *btf;
|
||||
int err;
|
||||
|
||||
if (!num_maps)
|
||||
@ -263,8 +263,8 @@ static void show_prog_metadata(int fd, __u32 num_maps)
|
||||
if (!value)
|
||||
return;
|
||||
|
||||
err = btf__get_from_id(map_info.btf_id, &btf);
|
||||
if (err || !btf)
|
||||
btf = btf__load_from_kernel_by_id(map_info.btf_id);
|
||||
if (libbpf_get_error(btf))
|
||||
goto out_free;
|
||||
|
||||
t_datasec = btf__type_by_id(btf, map_info.btf_value_type_id);
|
||||
@ -646,9 +646,12 @@ prog_dump(struct bpf_prog_info *info, enum dump_mode mode,
|
||||
member_len = info->xlated_prog_len;
|
||||
}
|
||||
|
||||
if (info->btf_id && btf__get_from_id(info->btf_id, &btf)) {
|
||||
p_err("failed to get btf");
|
||||
return -1;
|
||||
if (info->btf_id) {
|
||||
btf = btf__load_from_kernel_by_id(info->btf_id);
|
||||
if (libbpf_get_error(btf)) {
|
||||
p_err("failed to get btf");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
func_info = u64_to_ptr(info->func_info);
|
||||
@ -781,6 +784,8 @@ prog_dump(struct bpf_prog_info *info, enum dump_mode mode,
|
||||
kernel_syms_destroy(&dd);
|
||||
}
|
||||
|
||||
btf__free(btf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -2002,8 +2007,8 @@ static char *profile_target_name(int tgt_fd)
|
||||
struct bpf_prog_info_linear *info_linear;
|
||||
struct bpf_func_info *func_info;
|
||||
const struct btf_type *t;
|
||||
struct btf *btf = NULL;
|
||||
char *name = NULL;
|
||||
struct btf *btf;
|
||||
|
||||
info_linear = bpf_program__get_prog_info_linear(
|
||||
tgt_fd, 1UL << BPF_PROG_INFO_FUNC_INFO);
|
||||
@ -2012,12 +2017,17 @@ static char *profile_target_name(int tgt_fd)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (info_linear->info.btf_id == 0 ||
|
||||
btf__get_from_id(info_linear->info.btf_id, &btf)) {
|
||||
if (info_linear->info.btf_id == 0) {
|
||||
p_err("prog FD %d doesn't have valid btf", tgt_fd);
|
||||
goto out;
|
||||
}
|
||||
|
||||
btf = btf__load_from_kernel_by_id(info_linear->info.btf_id);
|
||||
if (libbpf_get_error(btf)) {
|
||||
p_err("failed to load btf for prog FD %d", tgt_fd);
|
||||
goto out;
|
||||
}
|
||||
|
||||
func_info = u64_to_ptr(info_linear->info.func_info);
|
||||
t = btf__type_by_id(btf, func_info[0].type_id);
|
||||
if (!t) {
|
||||
@ -2027,6 +2037,7 @@ static char *profile_target_name(int tgt_fd)
|
||||
}
|
||||
name = strdup(btf__name_by_offset(btf, t->name_off));
|
||||
out:
|
||||
btf__free(btf);
|
||||
free(info_linear);
|
||||
return name;
|
||||
}
|
||||
@ -2245,10 +2256,12 @@ static int do_help(int argc, char **argv)
|
||||
" cgroup/sendmsg6 | cgroup/recvmsg4 | cgroup/recvmsg6 |\n"
|
||||
" cgroup/getsockopt | cgroup/setsockopt | cgroup/sock_release |\n"
|
||||
" struct_ops | fentry | fexit | freplace | sk_lookup }\n"
|
||||
" ATTACH_TYPE := { msg_verdict | stream_verdict | stream_parser |\n"
|
||||
" flow_dissector }\n"
|
||||
" ATTACH_TYPE := { msg_verdict | skb_verdict | stream_verdict |\n"
|
||||
" stream_parser | flow_dissector }\n"
|
||||
" METRIC := { cycles | instructions | l1d_loads | llc_misses | itlb_misses | dtlb_misses }\n"
|
||||
" " HELP_SPEC_OPTIONS "\n"
|
||||
" " HELP_SPEC_OPTIONS " |\n"
|
||||
" {-f|--bpffs} | {-m|--mapcompat} | {-n|--nomount} |\n"
|
||||
" {-L|--use-loader} }\n"
|
||||
"",
|
||||
bin_name, argv[-2]);
|
||||
|
||||
|
@ -572,8 +572,8 @@ static int do_help(int argc, char **argv)
|
||||
" %1$s %2$s unregister STRUCT_OPS_MAP\n"
|
||||
" %1$s %2$s help\n"
|
||||
"\n"
|
||||
" OPTIONS := { {-j|--json} [{-p|--pretty}] }\n"
|
||||
" STRUCT_OPS_MAP := [ id STRUCT_OPS_MAP_ID | name STRUCT_OPS_MAP_NAME ]\n"
|
||||
" " HELP_SPEC_OPTIONS " }\n"
|
||||
"",
|
||||
bin_name, argv[-2]);
|
||||
|
||||
|
@ -291,7 +291,7 @@ static int compressed_section_fix(Elf *elf, Elf_Scn *scn, GElf_Shdr *sh)
|
||||
sh->sh_addralign = expected;
|
||||
|
||||
if (gelf_update_shdr(scn, sh) == 0) {
|
||||
printf("FAILED cannot update section header: %s\n",
|
||||
pr_err("FAILED cannot update section header: %s\n",
|
||||
elf_errmsg(-1));
|
||||
return -1;
|
||||
}
|
||||
@ -317,6 +317,7 @@ static int elf_collect(struct object *obj)
|
||||
|
||||
elf = elf_begin(fd, ELF_C_RDWR_MMAP, NULL);
|
||||
if (!elf) {
|
||||
close(fd);
|
||||
pr_err("FAILED cannot create ELF descriptor: %s\n",
|
||||
elf_errmsg(-1));
|
||||
return -1;
|
||||
@ -484,7 +485,7 @@ static int symbols_resolve(struct object *obj)
|
||||
err = libbpf_get_error(btf);
|
||||
if (err) {
|
||||
pr_err("FAILED: load BTF from %s: %s\n",
|
||||
obj->path, strerror(-err));
|
||||
obj->btf ?: obj->path, strerror(-err));
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -555,8 +556,7 @@ static int id_patch(struct object *obj, struct btf_id *id)
|
||||
int i;
|
||||
|
||||
if (!id->id) {
|
||||
pr_err("FAILED unresolved symbol %s\n", id->name);
|
||||
return -EINVAL;
|
||||
pr_err("WARN: resolve_btfids: unresolved symbol %s\n", id->name);
|
||||
}
|
||||
|
||||
for (i = 0; i < id->addr_cnt; i++) {
|
||||
@ -734,8 +734,9 @@ int main(int argc, const char **argv)
|
||||
|
||||
err = 0;
|
||||
out:
|
||||
if (obj.efile.elf)
|
||||
if (obj.efile.elf) {
|
||||
elf_end(obj.efile.elf);
|
||||
close(obj.efile.fd);
|
||||
close(obj.efile.fd);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
libbpf-y := libbpf.o bpf.o nlattr.o btf.o libbpf_errno.o str_error.o \
|
||||
netlink.o bpf_prog_linfo.o libbpf_probes.o xsk.o hashmap.o \
|
||||
btf_dump.o ringbuf.o strset.o linker.o gen_loader.o
|
||||
btf_dump.o ringbuf.o strset.o linker.o gen_loader.o relo_core.o
|
||||
|
@ -1180,7 +1180,7 @@ int btf__finalize_data(struct bpf_object *obj, struct btf *btf)
|
||||
|
||||
static void *btf_get_raw_data(const struct btf *btf, __u32 *size, bool swap_endian);
|
||||
|
||||
int btf__load(struct btf *btf)
|
||||
int btf__load_into_kernel(struct btf *btf)
|
||||
{
|
||||
__u32 log_buf_size = 0, raw_size;
|
||||
char *log_buf = NULL;
|
||||
@ -1228,6 +1228,7 @@ done:
|
||||
free(log_buf);
|
||||
return libbpf_err(err);
|
||||
}
|
||||
int btf__load(struct btf *) __attribute__((alias("btf__load_into_kernel")));
|
||||
|
||||
int btf__fd(const struct btf *btf)
|
||||
{
|
||||
@ -1382,21 +1383,35 @@ exit_free:
|
||||
return btf;
|
||||
}
|
||||
|
||||
struct btf *btf__load_from_kernel_by_id_split(__u32 id, struct btf *base_btf)
|
||||
{
|
||||
struct btf *btf;
|
||||
int btf_fd;
|
||||
|
||||
btf_fd = bpf_btf_get_fd_by_id(id);
|
||||
if (btf_fd < 0)
|
||||
return libbpf_err_ptr(-errno);
|
||||
|
||||
btf = btf_get_from_fd(btf_fd, base_btf);
|
||||
close(btf_fd);
|
||||
|
||||
return libbpf_ptr(btf);
|
||||
}
|
||||
|
||||
struct btf *btf__load_from_kernel_by_id(__u32 id)
|
||||
{
|
||||
return btf__load_from_kernel_by_id_split(id, NULL);
|
||||
}
|
||||
|
||||
int btf__get_from_id(__u32 id, struct btf **btf)
|
||||
{
|
||||
struct btf *res;
|
||||
int err, btf_fd;
|
||||
int err;
|
||||
|
||||
*btf = NULL;
|
||||
btf_fd = bpf_btf_get_fd_by_id(id);
|
||||
if (btf_fd < 0)
|
||||
return libbpf_err(-errno);
|
||||
|
||||
res = btf_get_from_fd(btf_fd, NULL);
|
||||
res = btf__load_from_kernel_by_id(id);
|
||||
err = libbpf_get_error(res);
|
||||
|
||||
close(btf_fd);
|
||||
|
||||
if (err)
|
||||
return libbpf_err(err);
|
||||
|
||||
@ -4021,7 +4036,7 @@ static void btf_dedup_merge_hypot_map(struct btf_dedup *d)
|
||||
*/
|
||||
if (d->hypot_adjust_canon)
|
||||
continue;
|
||||
|
||||
|
||||
if (t_kind == BTF_KIND_FWD && c_kind != BTF_KIND_FWD)
|
||||
d->map[t_id] = c_id;
|
||||
|
||||
@ -4394,7 +4409,7 @@ static int btf_dedup_remap_types(struct btf_dedup *d)
|
||||
* Probe few well-known locations for vmlinux kernel image and try to load BTF
|
||||
* data out of it to use for target BTF.
|
||||
*/
|
||||
struct btf *libbpf_find_kernel_btf(void)
|
||||
struct btf *btf__load_vmlinux_btf(void)
|
||||
{
|
||||
struct {
|
||||
const char *path_fmt;
|
||||
@ -4440,6 +4455,16 @@ struct btf *libbpf_find_kernel_btf(void)
|
||||
return libbpf_err_ptr(-ESRCH);
|
||||
}
|
||||
|
||||
struct btf *libbpf_find_kernel_btf(void) __attribute__((alias("btf__load_vmlinux_btf")));
|
||||
|
||||
struct btf *btf__load_module_btf(const char *module_name, struct btf *vmlinux_btf)
|
||||
{
|
||||
char path[80];
|
||||
|
||||
snprintf(path, sizeof(path), "/sys/kernel/btf/%s", module_name);
|
||||
return btf__parse_split(path, vmlinux_btf);
|
||||
}
|
||||
|
||||
int btf_type_visit_type_ids(struct btf_type *t, type_id_visit_fn visit, void *ctx)
|
||||
{
|
||||
int i, n, err;
|
||||
|
@ -44,8 +44,17 @@ LIBBPF_API struct btf *btf__parse_elf_split(const char *path, struct btf *base_b
|
||||
LIBBPF_API struct btf *btf__parse_raw(const char *path);
|
||||
LIBBPF_API struct btf *btf__parse_raw_split(const char *path, struct btf *base_btf);
|
||||
|
||||
LIBBPF_API struct btf *btf__load_vmlinux_btf(void);
|
||||
LIBBPF_API struct btf *btf__load_module_btf(const char *module_name, struct btf *vmlinux_btf);
|
||||
LIBBPF_API struct btf *libbpf_find_kernel_btf(void);
|
||||
|
||||
LIBBPF_API struct btf *btf__load_from_kernel_by_id(__u32 id);
|
||||
LIBBPF_API struct btf *btf__load_from_kernel_by_id_split(__u32 id, struct btf *base_btf);
|
||||
LIBBPF_API int btf__get_from_id(__u32 id, struct btf **btf);
|
||||
|
||||
LIBBPF_API int btf__finalize_data(struct bpf_object *obj, struct btf *btf);
|
||||
LIBBPF_API int btf__load(struct btf *btf);
|
||||
LIBBPF_API int btf__load_into_kernel(struct btf *btf);
|
||||
LIBBPF_API __s32 btf__find_by_name(const struct btf *btf,
|
||||
const char *type_name);
|
||||
LIBBPF_API __s32 btf__find_by_name_kind(const struct btf *btf,
|
||||
@ -66,7 +75,6 @@ LIBBPF_API void btf__set_fd(struct btf *btf, int fd);
|
||||
LIBBPF_API const void *btf__get_raw_data(const struct btf *btf, __u32 *size);
|
||||
LIBBPF_API const char *btf__name_by_offset(const struct btf *btf, __u32 offset);
|
||||
LIBBPF_API const char *btf__str_by_offset(const struct btf *btf, __u32 offset);
|
||||
LIBBPF_API int btf__get_from_id(__u32 id, struct btf **btf);
|
||||
LIBBPF_API int btf__get_map_kv_tids(const struct btf *btf, const char *map_name,
|
||||
__u32 expected_key_size,
|
||||
__u32 expected_value_size,
|
||||
@ -89,8 +97,6 @@ int btf_ext__reloc_line_info(const struct btf *btf,
|
||||
LIBBPF_API __u32 btf_ext__func_info_rec_size(const struct btf_ext *btf_ext);
|
||||
LIBBPF_API __u32 btf_ext__line_info_rec_size(const struct btf_ext *btf_ext);
|
||||
|
||||
LIBBPF_API struct btf *libbpf_find_kernel_btf(void);
|
||||
|
||||
LIBBPF_API int btf__find_str(struct btf *btf, const char *s);
|
||||
LIBBPF_API int btf__add_str(struct btf *btf, const char *s);
|
||||
LIBBPF_API int btf__add_type(struct btf *btf, const struct btf *src_btf,
|
||||
@ -184,6 +190,25 @@ LIBBPF_API int
|
||||
btf_dump__emit_type_decl(struct btf_dump *d, __u32 id,
|
||||
const struct btf_dump_emit_type_decl_opts *opts);
|
||||
|
||||
|
||||
struct btf_dump_type_data_opts {
|
||||
/* size of this struct, for forward/backward compatibility */
|
||||
size_t sz;
|
||||
const char *indent_str;
|
||||
int indent_level;
|
||||
/* below match "show" flags for bpf_show_snprintf() */
|
||||
bool compact; /* no newlines/indentation */
|
||||
bool skip_names; /* skip member/type names */
|
||||
bool emit_zeroes; /* show 0-valued fields */
|
||||
size_t :0;
|
||||
};
|
||||
#define btf_dump_type_data_opts__last_field emit_zeroes
|
||||
|
||||
LIBBPF_API int
|
||||
btf_dump__dump_type_data(struct btf_dump *d, __u32 id,
|
||||
const void *data, size_t data_sz,
|
||||
const struct btf_dump_type_data_opts *opts);
|
||||
|
||||
/*
|
||||
* A set of helpers for easier BTF types handling
|
||||
*/
|
||||
|
@ -10,6 +10,8 @@
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <endian.h>
|
||||
#include <errno.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/btf.h>
|
||||
@ -53,6 +55,26 @@ struct btf_dump_type_aux_state {
|
||||
__u8 referenced: 1;
|
||||
};
|
||||
|
||||
/* indent string length; one indent string is added for each indent level */
|
||||
#define BTF_DATA_INDENT_STR_LEN 32
|
||||
|
||||
/*
|
||||
* Common internal data for BTF type data dump operations.
|
||||
*/
|
||||
struct btf_dump_data {
|
||||
const void *data_end; /* end of valid data to show */
|
||||
bool compact;
|
||||
bool skip_names;
|
||||
bool emit_zeroes;
|
||||
__u8 indent_lvl; /* base indent level */
|
||||
char indent_str[BTF_DATA_INDENT_STR_LEN];
|
||||
/* below are used during iteration */
|
||||
int depth;
|
||||
bool is_array_member;
|
||||
bool is_array_terminated;
|
||||
bool is_array_char;
|
||||
};
|
||||
|
||||
struct btf_dump {
|
||||
const struct btf *btf;
|
||||
const struct btf_ext *btf_ext;
|
||||
@ -60,6 +82,7 @@ struct btf_dump {
|
||||
struct btf_dump_opts opts;
|
||||
int ptr_sz;
|
||||
bool strip_mods;
|
||||
bool skip_anon_defs;
|
||||
int last_id;
|
||||
|
||||
/* per-type auxiliary state */
|
||||
@ -89,6 +112,10 @@ struct btf_dump {
|
||||
* name occurrences
|
||||
*/
|
||||
struct hashmap *ident_names;
|
||||
/*
|
||||
* data for typed display; allocated if needed.
|
||||
*/
|
||||
struct btf_dump_data *typed_dump;
|
||||
};
|
||||
|
||||
static size_t str_hash_fn(const void *key, void *ctx)
|
||||
@ -765,11 +792,11 @@ static void btf_dump_emit_type(struct btf_dump *d, __u32 id, __u32 cont_id)
|
||||
break;
|
||||
case BTF_KIND_FUNC_PROTO: {
|
||||
const struct btf_param *p = btf_params(t);
|
||||
__u16 vlen = btf_vlen(t);
|
||||
__u16 n = btf_vlen(t);
|
||||
int i;
|
||||
|
||||
btf_dump_emit_type(d, t->type, cont_id);
|
||||
for (i = 0; i < vlen; i++, p++)
|
||||
for (i = 0; i < n; i++, p++)
|
||||
btf_dump_emit_type(d, p->type, cont_id);
|
||||
|
||||
break;
|
||||
@ -852,8 +879,9 @@ static void btf_dump_emit_bit_padding(const struct btf_dump *d,
|
||||
static void btf_dump_emit_struct_fwd(struct btf_dump *d, __u32 id,
|
||||
const struct btf_type *t)
|
||||
{
|
||||
btf_dump_printf(d, "%s %s",
|
||||
btf_dump_printf(d, "%s%s%s",
|
||||
btf_is_struct(t) ? "struct" : "union",
|
||||
t->name_off ? " " : "",
|
||||
btf_dump_type_name(d, id));
|
||||
}
|
||||
|
||||
@ -1259,7 +1287,7 @@ static void btf_dump_emit_type_chain(struct btf_dump *d,
|
||||
case BTF_KIND_UNION:
|
||||
btf_dump_emit_mods(d, decls);
|
||||
/* inline anonymous struct/union */
|
||||
if (t->name_off == 0)
|
||||
if (t->name_off == 0 && !d->skip_anon_defs)
|
||||
btf_dump_emit_struct_def(d, id, t, lvl);
|
||||
else
|
||||
btf_dump_emit_struct_fwd(d, id, t);
|
||||
@ -1267,7 +1295,7 @@ static void btf_dump_emit_type_chain(struct btf_dump *d,
|
||||
case BTF_KIND_ENUM:
|
||||
btf_dump_emit_mods(d, decls);
|
||||
/* inline anonymous enum */
|
||||
if (t->name_off == 0)
|
||||
if (t->name_off == 0 && !d->skip_anon_defs)
|
||||
btf_dump_emit_enum_def(d, id, t, lvl);
|
||||
else
|
||||
btf_dump_emit_enum_fwd(d, id, t);
|
||||
@ -1392,6 +1420,39 @@ static void btf_dump_emit_type_chain(struct btf_dump *d,
|
||||
btf_dump_emit_name(d, fname, last_was_ptr);
|
||||
}
|
||||
|
||||
/* show type name as (type_name) */
|
||||
static void btf_dump_emit_type_cast(struct btf_dump *d, __u32 id,
|
||||
bool top_level)
|
||||
{
|
||||
const struct btf_type *t;
|
||||
|
||||
/* for array members, we don't bother emitting type name for each
|
||||
* member to avoid the redundancy of
|
||||
* .name = (char[4])[(char)'f',(char)'o',(char)'o',]
|
||||
*/
|
||||
if (d->typed_dump->is_array_member)
|
||||
return;
|
||||
|
||||
/* avoid type name specification for variable/section; it will be done
|
||||
* for the associated variable value(s).
|
||||
*/
|
||||
t = btf__type_by_id(d->btf, id);
|
||||
if (btf_is_var(t) || btf_is_datasec(t))
|
||||
return;
|
||||
|
||||
if (top_level)
|
||||
btf_dump_printf(d, "(");
|
||||
|
||||
d->skip_anon_defs = true;
|
||||
d->strip_mods = true;
|
||||
btf_dump_emit_type_decl(d, id, "", 0);
|
||||
d->strip_mods = false;
|
||||
d->skip_anon_defs = false;
|
||||
|
||||
if (top_level)
|
||||
btf_dump_printf(d, ")");
|
||||
}
|
||||
|
||||
/* return number of duplicates (occurrences) of a given name */
|
||||
static size_t btf_dump_name_dups(struct btf_dump *d, struct hashmap *name_map,
|
||||
const char *orig_name)
|
||||
@ -1442,3 +1503,803 @@ static const char *btf_dump_ident_name(struct btf_dump *d, __u32 id)
|
||||
{
|
||||
return btf_dump_resolve_name(d, id, d->ident_names);
|
||||
}
|
||||
|
||||
static int btf_dump_dump_type_data(struct btf_dump *d,
|
||||
const char *fname,
|
||||
const struct btf_type *t,
|
||||
__u32 id,
|
||||
const void *data,
|
||||
__u8 bits_offset,
|
||||
__u8 bit_sz);
|
||||
|
||||
static const char *btf_dump_data_newline(struct btf_dump *d)
|
||||
{
|
||||
return d->typed_dump->compact || d->typed_dump->depth == 0 ? "" : "\n";
|
||||
}
|
||||
|
||||
static const char *btf_dump_data_delim(struct btf_dump *d)
|
||||
{
|
||||
return d->typed_dump->depth == 0 ? "" : ",";
|
||||
}
|
||||
|
||||
static void btf_dump_data_pfx(struct btf_dump *d)
|
||||
{
|
||||
int i, lvl = d->typed_dump->indent_lvl + d->typed_dump->depth;
|
||||
|
||||
if (d->typed_dump->compact)
|
||||
return;
|
||||
|
||||
for (i = 0; i < lvl; i++)
|
||||
btf_dump_printf(d, "%s", d->typed_dump->indent_str);
|
||||
}
|
||||
|
||||
/* A macro is used here as btf_type_value[s]() appends format specifiers
|
||||
* to the format specifier passed in; these do the work of appending
|
||||
* delimiters etc while the caller simply has to specify the type values
|
||||
* in the format specifier + value(s).
|
||||
*/
|
||||
#define btf_dump_type_values(d, fmt, ...) \
|
||||
btf_dump_printf(d, fmt "%s%s", \
|
||||
##__VA_ARGS__, \
|
||||
btf_dump_data_delim(d), \
|
||||
btf_dump_data_newline(d))
|
||||
|
||||
static int btf_dump_unsupported_data(struct btf_dump *d,
|
||||
const struct btf_type *t,
|
||||
__u32 id)
|
||||
{
|
||||
btf_dump_printf(d, "<unsupported kind:%u>", btf_kind(t));
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
static int btf_dump_get_bitfield_value(struct btf_dump *d,
|
||||
const struct btf_type *t,
|
||||
const void *data,
|
||||
__u8 bits_offset,
|
||||
__u8 bit_sz,
|
||||
__u64 *value)
|
||||
{
|
||||
__u16 left_shift_bits, right_shift_bits;
|
||||
__u8 nr_copy_bits, nr_copy_bytes;
|
||||
const __u8 *bytes = data;
|
||||
int sz = t->size;
|
||||
__u64 num = 0;
|
||||
int i;
|
||||
|
||||
/* Maximum supported bitfield size is 64 bits */
|
||||
if (sz > 8) {
|
||||
pr_warn("unexpected bitfield size %d\n", sz);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Bitfield value retrieval is done in two steps; first relevant bytes are
|
||||
* stored in num, then we left/right shift num to eliminate irrelevant bits.
|
||||
*/
|
||||
nr_copy_bits = bit_sz + bits_offset;
|
||||
nr_copy_bytes = t->size;
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
for (i = nr_copy_bytes - 1; i >= 0; i--)
|
||||
num = num * 256 + bytes[i];
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
for (i = 0; i < nr_copy_bytes; i++)
|
||||
num = num * 256 + bytes[i];
|
||||
#else
|
||||
# error "Unrecognized __BYTE_ORDER__"
|
||||
#endif
|
||||
left_shift_bits = 64 - nr_copy_bits;
|
||||
right_shift_bits = 64 - bit_sz;
|
||||
|
||||
*value = (num << left_shift_bits) >> right_shift_bits;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btf_dump_bitfield_check_zero(struct btf_dump *d,
|
||||
const struct btf_type *t,
|
||||
const void *data,
|
||||
__u8 bits_offset,
|
||||
__u8 bit_sz)
|
||||
{
|
||||
__u64 check_num;
|
||||
int err;
|
||||
|
||||
err = btf_dump_get_bitfield_value(d, t, data, bits_offset, bit_sz, &check_num);
|
||||
if (err)
|
||||
return err;
|
||||
if (check_num == 0)
|
||||
return -ENODATA;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btf_dump_bitfield_data(struct btf_dump *d,
|
||||
const struct btf_type *t,
|
||||
const void *data,
|
||||
__u8 bits_offset,
|
||||
__u8 bit_sz)
|
||||
{
|
||||
__u64 print_num;
|
||||
int err;
|
||||
|
||||
err = btf_dump_get_bitfield_value(d, t, data, bits_offset, bit_sz, &print_num);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
btf_dump_type_values(d, "0x%llx", (unsigned long long)print_num);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ints, floats and ptrs */
|
||||
static int btf_dump_base_type_check_zero(struct btf_dump *d,
|
||||
const struct btf_type *t,
|
||||
__u32 id,
|
||||
const void *data)
|
||||
{
|
||||
static __u8 bytecmp[16] = {};
|
||||
int nr_bytes;
|
||||
|
||||
/* For pointer types, pointer size is not defined on a per-type basis.
|
||||
* On dump creation however, we store the pointer size.
|
||||
*/
|
||||
if (btf_kind(t) == BTF_KIND_PTR)
|
||||
nr_bytes = d->ptr_sz;
|
||||
else
|
||||
nr_bytes = t->size;
|
||||
|
||||
if (nr_bytes < 1 || nr_bytes > 16) {
|
||||
pr_warn("unexpected size %d for id [%u]\n", nr_bytes, id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (memcmp(data, bytecmp, nr_bytes) == 0)
|
||||
return -ENODATA;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool ptr_is_aligned(const void *data, int data_sz)
|
||||
{
|
||||
return ((uintptr_t)data) % data_sz == 0;
|
||||
}
|
||||
|
||||
static int btf_dump_int_data(struct btf_dump *d,
|
||||
const struct btf_type *t,
|
||||
__u32 type_id,
|
||||
const void *data,
|
||||
__u8 bits_offset)
|
||||
{
|
||||
__u8 encoding = btf_int_encoding(t);
|
||||
bool sign = encoding & BTF_INT_SIGNED;
|
||||
int sz = t->size;
|
||||
|
||||
if (sz == 0) {
|
||||
pr_warn("unexpected size %d for id [%u]\n", sz, type_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* handle packed int data - accesses of integers not aligned on
|
||||
* int boundaries can cause problems on some platforms.
|
||||
*/
|
||||
if (!ptr_is_aligned(data, sz))
|
||||
return btf_dump_bitfield_data(d, t, data, 0, 0);
|
||||
|
||||
switch (sz) {
|
||||
case 16: {
|
||||
const __u64 *ints = data;
|
||||
__u64 lsi, msi;
|
||||
|
||||
/* avoid use of __int128 as some 32-bit platforms do not
|
||||
* support it.
|
||||
*/
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
lsi = ints[0];
|
||||
msi = ints[1];
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
lsi = ints[1];
|
||||
msi = ints[0];
|
||||
#else
|
||||
# error "Unrecognized __BYTE_ORDER__"
|
||||
#endif
|
||||
if (msi == 0)
|
||||
btf_dump_type_values(d, "0x%llx", (unsigned long long)lsi);
|
||||
else
|
||||
btf_dump_type_values(d, "0x%llx%016llx", (unsigned long long)msi,
|
||||
(unsigned long long)lsi);
|
||||
break;
|
||||
}
|
||||
case 8:
|
||||
if (sign)
|
||||
btf_dump_type_values(d, "%lld", *(long long *)data);
|
||||
else
|
||||
btf_dump_type_values(d, "%llu", *(unsigned long long *)data);
|
||||
break;
|
||||
case 4:
|
||||
if (sign)
|
||||
btf_dump_type_values(d, "%d", *(__s32 *)data);
|
||||
else
|
||||
btf_dump_type_values(d, "%u", *(__u32 *)data);
|
||||
break;
|
||||
case 2:
|
||||
if (sign)
|
||||
btf_dump_type_values(d, "%d", *(__s16 *)data);
|
||||
else
|
||||
btf_dump_type_values(d, "%u", *(__u16 *)data);
|
||||
break;
|
||||
case 1:
|
||||
if (d->typed_dump->is_array_char) {
|
||||
/* check for null terminator */
|
||||
if (d->typed_dump->is_array_terminated)
|
||||
break;
|
||||
if (*(char *)data == '\0') {
|
||||
d->typed_dump->is_array_terminated = true;
|
||||
break;
|
||||
}
|
||||
if (isprint(*(char *)data)) {
|
||||
btf_dump_type_values(d, "'%c'", *(char *)data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sign)
|
||||
btf_dump_type_values(d, "%d", *(__s8 *)data);
|
||||
else
|
||||
btf_dump_type_values(d, "%u", *(__u8 *)data);
|
||||
break;
|
||||
default:
|
||||
pr_warn("unexpected sz %d for id [%u]\n", sz, type_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
union float_data {
|
||||
long double ld;
|
||||
double d;
|
||||
float f;
|
||||
};
|
||||
|
||||
static int btf_dump_float_data(struct btf_dump *d,
|
||||
const struct btf_type *t,
|
||||
__u32 type_id,
|
||||
const void *data)
|
||||
{
|
||||
const union float_data *flp = data;
|
||||
union float_data fl;
|
||||
int sz = t->size;
|
||||
|
||||
/* handle unaligned data; copy to local union */
|
||||
if (!ptr_is_aligned(data, sz)) {
|
||||
memcpy(&fl, data, sz);
|
||||
flp = &fl;
|
||||
}
|
||||
|
||||
switch (sz) {
|
||||
case 16:
|
||||
btf_dump_type_values(d, "%Lf", flp->ld);
|
||||
break;
|
||||
case 8:
|
||||
btf_dump_type_values(d, "%lf", flp->d);
|
||||
break;
|
||||
case 4:
|
||||
btf_dump_type_values(d, "%f", flp->f);
|
||||
break;
|
||||
default:
|
||||
pr_warn("unexpected size %d for id [%u]\n", sz, type_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btf_dump_var_data(struct btf_dump *d,
|
||||
const struct btf_type *v,
|
||||
__u32 id,
|
||||
const void *data)
|
||||
{
|
||||
enum btf_func_linkage linkage = btf_var(v)->linkage;
|
||||
const struct btf_type *t;
|
||||
const char *l;
|
||||
__u32 type_id;
|
||||
|
||||
switch (linkage) {
|
||||
case BTF_FUNC_STATIC:
|
||||
l = "static ";
|
||||
break;
|
||||
case BTF_FUNC_EXTERN:
|
||||
l = "extern ";
|
||||
break;
|
||||
case BTF_FUNC_GLOBAL:
|
||||
default:
|
||||
l = "";
|
||||
break;
|
||||
}
|
||||
|
||||
/* format of output here is [linkage] [type] [varname] = (type)value,
|
||||
* for example "static int cpu_profile_flip = (int)1"
|
||||
*/
|
||||
btf_dump_printf(d, "%s", l);
|
||||
type_id = v->type;
|
||||
t = btf__type_by_id(d->btf, type_id);
|
||||
btf_dump_emit_type_cast(d, type_id, false);
|
||||
btf_dump_printf(d, " %s = ", btf_name_of(d, v->name_off));
|
||||
return btf_dump_dump_type_data(d, NULL, t, type_id, data, 0, 0);
|
||||
}
|
||||
|
||||
static int btf_dump_array_data(struct btf_dump *d,
|
||||
const struct btf_type *t,
|
||||
__u32 id,
|
||||
const void *data)
|
||||
{
|
||||
const struct btf_array *array = btf_array(t);
|
||||
const struct btf_type *elem_type;
|
||||
__u32 i, elem_size = 0, elem_type_id;
|
||||
bool is_array_member;
|
||||
|
||||
elem_type_id = array->type;
|
||||
elem_type = skip_mods_and_typedefs(d->btf, elem_type_id, NULL);
|
||||
elem_size = btf__resolve_size(d->btf, elem_type_id);
|
||||
if (elem_size <= 0) {
|
||||
pr_warn("unexpected elem size %d for array type [%u]\n", elem_size, id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (btf_is_int(elem_type)) {
|
||||
/*
|
||||
* BTF_INT_CHAR encoding never seems to be set for
|
||||
* char arrays, so if size is 1 and element is
|
||||
* printable as a char, we'll do that.
|
||||
*/
|
||||
if (elem_size == 1)
|
||||
d->typed_dump->is_array_char = true;
|
||||
}
|
||||
|
||||
/* note that we increment depth before calling btf_dump_print() below;
|
||||
* this is intentional. btf_dump_data_newline() will not print a
|
||||
* newline for depth 0 (since this leaves us with trailing newlines
|
||||
* at the end of typed display), so depth is incremented first.
|
||||
* For similar reasons, we decrement depth before showing the closing
|
||||
* parenthesis.
|
||||
*/
|
||||
d->typed_dump->depth++;
|
||||
btf_dump_printf(d, "[%s", btf_dump_data_newline(d));
|
||||
|
||||
/* may be a multidimensional array, so store current "is array member"
|
||||
* status so we can restore it correctly later.
|
||||
*/
|
||||
is_array_member = d->typed_dump->is_array_member;
|
||||
d->typed_dump->is_array_member = true;
|
||||
for (i = 0; i < array->nelems; i++, data += elem_size) {
|
||||
if (d->typed_dump->is_array_terminated)
|
||||
break;
|
||||
btf_dump_dump_type_data(d, NULL, elem_type, elem_type_id, data, 0, 0);
|
||||
}
|
||||
d->typed_dump->is_array_member = is_array_member;
|
||||
d->typed_dump->depth--;
|
||||
btf_dump_data_pfx(d);
|
||||
btf_dump_type_values(d, "]");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btf_dump_struct_data(struct btf_dump *d,
|
||||
const struct btf_type *t,
|
||||
__u32 id,
|
||||
const void *data)
|
||||
{
|
||||
const struct btf_member *m = btf_members(t);
|
||||
__u16 n = btf_vlen(t);
|
||||
int i, err;
|
||||
|
||||
/* note that we increment depth before calling btf_dump_print() below;
|
||||
* this is intentional. btf_dump_data_newline() will not print a
|
||||
* newline for depth 0 (since this leaves us with trailing newlines
|
||||
* at the end of typed display), so depth is incremented first.
|
||||
* For similar reasons, we decrement depth before showing the closing
|
||||
* parenthesis.
|
||||
*/
|
||||
d->typed_dump->depth++;
|
||||
btf_dump_printf(d, "{%s", btf_dump_data_newline(d));
|
||||
|
||||
for (i = 0; i < n; i++, m++) {
|
||||
const struct btf_type *mtype;
|
||||
const char *mname;
|
||||
__u32 moffset;
|
||||
__u8 bit_sz;
|
||||
|
||||
mtype = btf__type_by_id(d->btf, m->type);
|
||||
mname = btf_name_of(d, m->name_off);
|
||||
moffset = btf_member_bit_offset(t, i);
|
||||
|
||||
bit_sz = btf_member_bitfield_size(t, i);
|
||||
err = btf_dump_dump_type_data(d, mname, mtype, m->type, data + moffset / 8,
|
||||
moffset % 8, bit_sz);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
d->typed_dump->depth--;
|
||||
btf_dump_data_pfx(d);
|
||||
btf_dump_type_values(d, "}");
|
||||
return err;
|
||||
}
|
||||
|
||||
union ptr_data {
|
||||
unsigned int p;
|
||||
unsigned long long lp;
|
||||
};
|
||||
|
||||
static int btf_dump_ptr_data(struct btf_dump *d,
|
||||
const struct btf_type *t,
|
||||
__u32 id,
|
||||
const void *data)
|
||||
{
|
||||
if (ptr_is_aligned(data, d->ptr_sz) && d->ptr_sz == sizeof(void *)) {
|
||||
btf_dump_type_values(d, "%p", *(void **)data);
|
||||
} else {
|
||||
union ptr_data pt;
|
||||
|
||||
memcpy(&pt, data, d->ptr_sz);
|
||||
if (d->ptr_sz == 4)
|
||||
btf_dump_type_values(d, "0x%x", pt.p);
|
||||
else
|
||||
btf_dump_type_values(d, "0x%llx", pt.lp);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btf_dump_get_enum_value(struct btf_dump *d,
|
||||
const struct btf_type *t,
|
||||
const void *data,
|
||||
__u32 id,
|
||||
__s64 *value)
|
||||
{
|
||||
int sz = t->size;
|
||||
|
||||
/* handle unaligned enum value */
|
||||
if (!ptr_is_aligned(data, sz)) {
|
||||
__u64 val;
|
||||
int err;
|
||||
|
||||
err = btf_dump_get_bitfield_value(d, t, data, 0, 0, &val);
|
||||
if (err)
|
||||
return err;
|
||||
*value = (__s64)val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (t->size) {
|
||||
case 8:
|
||||
*value = *(__s64 *)data;
|
||||
return 0;
|
||||
case 4:
|
||||
*value = *(__s32 *)data;
|
||||
return 0;
|
||||
case 2:
|
||||
*value = *(__s16 *)data;
|
||||
return 0;
|
||||
case 1:
|
||||
*value = *(__s8 *)data;
|
||||
return 0;
|
||||
default:
|
||||
pr_warn("unexpected size %d for enum, id:[%u]\n", t->size, id);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int btf_dump_enum_data(struct btf_dump *d,
|
||||
const struct btf_type *t,
|
||||
__u32 id,
|
||||
const void *data)
|
||||
{
|
||||
const struct btf_enum *e;
|
||||
__s64 value;
|
||||
int i, err;
|
||||
|
||||
err = btf_dump_get_enum_value(d, t, data, id, &value);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
for (i = 0, e = btf_enum(t); i < btf_vlen(t); i++, e++) {
|
||||
if (value != e->val)
|
||||
continue;
|
||||
btf_dump_type_values(d, "%s", btf_name_of(d, e->name_off));
|
||||
return 0;
|
||||
}
|
||||
|
||||
btf_dump_type_values(d, "%d", value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btf_dump_datasec_data(struct btf_dump *d,
|
||||
const struct btf_type *t,
|
||||
__u32 id,
|
||||
const void *data)
|
||||
{
|
||||
const struct btf_var_secinfo *vsi;
|
||||
const struct btf_type *var;
|
||||
__u32 i;
|
||||
int err;
|
||||
|
||||
btf_dump_type_values(d, "SEC(\"%s\") ", btf_name_of(d, t->name_off));
|
||||
|
||||
for (i = 0, vsi = btf_var_secinfos(t); i < btf_vlen(t); i++, vsi++) {
|
||||
var = btf__type_by_id(d->btf, vsi->type);
|
||||
err = btf_dump_dump_type_data(d, NULL, var, vsi->type, data + vsi->offset, 0, 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
btf_dump_printf(d, ";");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* return size of type, or if base type overflows, return -E2BIG. */
|
||||
static int btf_dump_type_data_check_overflow(struct btf_dump *d,
|
||||
const struct btf_type *t,
|
||||
__u32 id,
|
||||
const void *data,
|
||||
__u8 bits_offset)
|
||||
{
|
||||
__s64 size = btf__resolve_size(d->btf, id);
|
||||
|
||||
if (size < 0 || size >= INT_MAX) {
|
||||
pr_warn("unexpected size [%zu] for id [%u]\n",
|
||||
(size_t)size, id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Only do overflow checking for base types; we do not want to
|
||||
* avoid showing part of a struct, union or array, even if we
|
||||
* do not have enough data to show the full object. By
|
||||
* restricting overflow checking to base types we can ensure
|
||||
* that partial display succeeds, while avoiding overflowing
|
||||
* and using bogus data for display.
|
||||
*/
|
||||
t = skip_mods_and_typedefs(d->btf, id, NULL);
|
||||
if (!t) {
|
||||
pr_warn("unexpected error skipping mods/typedefs for id [%u]\n",
|
||||
id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (btf_kind(t)) {
|
||||
case BTF_KIND_INT:
|
||||
case BTF_KIND_FLOAT:
|
||||
case BTF_KIND_PTR:
|
||||
case BTF_KIND_ENUM:
|
||||
if (data + bits_offset / 8 + size > d->typed_dump->data_end)
|
||||
return -E2BIG;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return (int)size;
|
||||
}
|
||||
|
||||
static int btf_dump_type_data_check_zero(struct btf_dump *d,
|
||||
const struct btf_type *t,
|
||||
__u32 id,
|
||||
const void *data,
|
||||
__u8 bits_offset,
|
||||
__u8 bit_sz)
|
||||
{
|
||||
__s64 value;
|
||||
int i, err;
|
||||
|
||||
/* toplevel exceptions; we show zero values if
|
||||
* - we ask for them (emit_zeros)
|
||||
* - if we are at top-level so we see "struct empty { }"
|
||||
* - or if we are an array member and the array is non-empty and
|
||||
* not a char array; we don't want to be in a situation where we
|
||||
* have an integer array 0, 1, 0, 1 and only show non-zero values.
|
||||
* If the array contains zeroes only, or is a char array starting
|
||||
* with a '\0', the array-level check_zero() will prevent showing it;
|
||||
* we are concerned with determining zero value at the array member
|
||||
* level here.
|
||||
*/
|
||||
if (d->typed_dump->emit_zeroes || d->typed_dump->depth == 0 ||
|
||||
(d->typed_dump->is_array_member &&
|
||||
!d->typed_dump->is_array_char))
|
||||
return 0;
|
||||
|
||||
t = skip_mods_and_typedefs(d->btf, id, NULL);
|
||||
|
||||
switch (btf_kind(t)) {
|
||||
case BTF_KIND_INT:
|
||||
if (bit_sz)
|
||||
return btf_dump_bitfield_check_zero(d, t, data, bits_offset, bit_sz);
|
||||
return btf_dump_base_type_check_zero(d, t, id, data);
|
||||
case BTF_KIND_FLOAT:
|
||||
case BTF_KIND_PTR:
|
||||
return btf_dump_base_type_check_zero(d, t, id, data);
|
||||
case BTF_KIND_ARRAY: {
|
||||
const struct btf_array *array = btf_array(t);
|
||||
const struct btf_type *elem_type;
|
||||
__u32 elem_type_id, elem_size;
|
||||
bool ischar;
|
||||
|
||||
elem_type_id = array->type;
|
||||
elem_size = btf__resolve_size(d->btf, elem_type_id);
|
||||
elem_type = skip_mods_and_typedefs(d->btf, elem_type_id, NULL);
|
||||
|
||||
ischar = btf_is_int(elem_type) && elem_size == 1;
|
||||
|
||||
/* check all elements; if _any_ element is nonzero, all
|
||||
* of array is displayed. We make an exception however
|
||||
* for char arrays where the first element is 0; these
|
||||
* are considered zeroed also, even if later elements are
|
||||
* non-zero because the string is terminated.
|
||||
*/
|
||||
for (i = 0; i < array->nelems; i++) {
|
||||
if (i == 0 && ischar && *(char *)data == 0)
|
||||
return -ENODATA;
|
||||
err = btf_dump_type_data_check_zero(d, elem_type,
|
||||
elem_type_id,
|
||||
data +
|
||||
(i * elem_size),
|
||||
bits_offset, 0);
|
||||
if (err != -ENODATA)
|
||||
return err;
|
||||
}
|
||||
return -ENODATA;
|
||||
}
|
||||
case BTF_KIND_STRUCT:
|
||||
case BTF_KIND_UNION: {
|
||||
const struct btf_member *m = btf_members(t);
|
||||
__u16 n = btf_vlen(t);
|
||||
|
||||
/* if any struct/union member is non-zero, the struct/union
|
||||
* is considered non-zero and dumped.
|
||||
*/
|
||||
for (i = 0; i < n; i++, m++) {
|
||||
const struct btf_type *mtype;
|
||||
__u32 moffset;
|
||||
|
||||
mtype = btf__type_by_id(d->btf, m->type);
|
||||
moffset = btf_member_bit_offset(t, i);
|
||||
|
||||
/* btf_int_bits() does not store member bitfield size;
|
||||
* bitfield size needs to be stored here so int display
|
||||
* of member can retrieve it.
|
||||
*/
|
||||
bit_sz = btf_member_bitfield_size(t, i);
|
||||
err = btf_dump_type_data_check_zero(d, mtype, m->type, data + moffset / 8,
|
||||
moffset % 8, bit_sz);
|
||||
if (err != ENODATA)
|
||||
return err;
|
||||
}
|
||||
return -ENODATA;
|
||||
}
|
||||
case BTF_KIND_ENUM:
|
||||
err = btf_dump_get_enum_value(d, t, data, id, &value);
|
||||
if (err)
|
||||
return err;
|
||||
if (value == 0)
|
||||
return -ENODATA;
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* returns size of data dumped, or error. */
|
||||
static int btf_dump_dump_type_data(struct btf_dump *d,
|
||||
const char *fname,
|
||||
const struct btf_type *t,
|
||||
__u32 id,
|
||||
const void *data,
|
||||
__u8 bits_offset,
|
||||
__u8 bit_sz)
|
||||
{
|
||||
int size, err;
|
||||
|
||||
size = btf_dump_type_data_check_overflow(d, t, id, data, bits_offset);
|
||||
if (size < 0)
|
||||
return size;
|
||||
err = btf_dump_type_data_check_zero(d, t, id, data, bits_offset, bit_sz);
|
||||
if (err) {
|
||||
/* zeroed data is expected and not an error, so simply skip
|
||||
* dumping such data. Record other errors however.
|
||||
*/
|
||||
if (err == -ENODATA)
|
||||
return size;
|
||||
return err;
|
||||
}
|
||||
btf_dump_data_pfx(d);
|
||||
|
||||
if (!d->typed_dump->skip_names) {
|
||||
if (fname && strlen(fname) > 0)
|
||||
btf_dump_printf(d, ".%s = ", fname);
|
||||
btf_dump_emit_type_cast(d, id, true);
|
||||
}
|
||||
|
||||
t = skip_mods_and_typedefs(d->btf, id, NULL);
|
||||
|
||||
switch (btf_kind(t)) {
|
||||
case BTF_KIND_UNKN:
|
||||
case BTF_KIND_FWD:
|
||||
case BTF_KIND_FUNC:
|
||||
case BTF_KIND_FUNC_PROTO:
|
||||
err = btf_dump_unsupported_data(d, t, id);
|
||||
break;
|
||||
case BTF_KIND_INT:
|
||||
if (bit_sz)
|
||||
err = btf_dump_bitfield_data(d, t, data, bits_offset, bit_sz);
|
||||
else
|
||||
err = btf_dump_int_data(d, t, id, data, bits_offset);
|
||||
break;
|
||||
case BTF_KIND_FLOAT:
|
||||
err = btf_dump_float_data(d, t, id, data);
|
||||
break;
|
||||
case BTF_KIND_PTR:
|
||||
err = btf_dump_ptr_data(d, t, id, data);
|
||||
break;
|
||||
case BTF_KIND_ARRAY:
|
||||
err = btf_dump_array_data(d, t, id, data);
|
||||
break;
|
||||
case BTF_KIND_STRUCT:
|
||||
case BTF_KIND_UNION:
|
||||
err = btf_dump_struct_data(d, t, id, data);
|
||||
break;
|
||||
case BTF_KIND_ENUM:
|
||||
/* handle bitfield and int enum values */
|
||||
if (bit_sz) {
|
||||
__u64 print_num;
|
||||
__s64 enum_val;
|
||||
|
||||
err = btf_dump_get_bitfield_value(d, t, data, bits_offset, bit_sz,
|
||||
&print_num);
|
||||
if (err)
|
||||
break;
|
||||
enum_val = (__s64)print_num;
|
||||
err = btf_dump_enum_data(d, t, id, &enum_val);
|
||||
} else
|
||||
err = btf_dump_enum_data(d, t, id, data);
|
||||
break;
|
||||
case BTF_KIND_VAR:
|
||||
err = btf_dump_var_data(d, t, id, data);
|
||||
break;
|
||||
case BTF_KIND_DATASEC:
|
||||
err = btf_dump_datasec_data(d, t, id, data);
|
||||
break;
|
||||
default:
|
||||
pr_warn("unexpected kind [%u] for id [%u]\n",
|
||||
BTF_INFO_KIND(t->info), id);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (err < 0)
|
||||
return err;
|
||||
return size;
|
||||
}
|
||||
|
||||
int btf_dump__dump_type_data(struct btf_dump *d, __u32 id,
|
||||
const void *data, size_t data_sz,
|
||||
const struct btf_dump_type_data_opts *opts)
|
||||
{
|
||||
struct btf_dump_data typed_dump = {};
|
||||
const struct btf_type *t;
|
||||
int ret;
|
||||
|
||||
if (!OPTS_VALID(opts, btf_dump_type_data_opts))
|
||||
return libbpf_err(-EINVAL);
|
||||
|
||||
t = btf__type_by_id(d->btf, id);
|
||||
if (!t)
|
||||
return libbpf_err(-ENOENT);
|
||||
|
||||
d->typed_dump = &typed_dump;
|
||||
d->typed_dump->data_end = data + data_sz;
|
||||
d->typed_dump->indent_lvl = OPTS_GET(opts, indent_level, 0);
|
||||
|
||||
/* default indent string is a tab */
|
||||
if (!opts->indent_str)
|
||||
d->typed_dump->indent_str[0] = '\t';
|
||||
else
|
||||
strncat(d->typed_dump->indent_str, opts->indent_str,
|
||||
sizeof(d->typed_dump->indent_str) - 1);
|
||||
|
||||
d->typed_dump->compact = OPTS_GET(opts, compact, false);
|
||||
d->typed_dump->skip_names = OPTS_GET(opts, skip_names, false);
|
||||
d->typed_dump->emit_zeroes = OPTS_GET(opts, emit_zeroes, false);
|
||||
|
||||
ret = btf_dump_dump_type_data(d, NULL, t, id, data, 0, 0);
|
||||
|
||||
d->typed_dump = NULL;
|
||||
|
||||
return libbpf_err(ret);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -94,8 +94,26 @@ struct bpf_object_open_opts {
|
||||
* system Kconfig for CONFIG_xxx externs.
|
||||
*/
|
||||
const char *kconfig;
|
||||
/* Path to the custom BTF to be used for BPF CO-RE relocations.
|
||||
* This custom BTF completely replaces the use of vmlinux BTF
|
||||
* for the purpose of CO-RE relocations.
|
||||
* NOTE: any other BPF feature (e.g., fentry/fexit programs,
|
||||
* struct_ops, etc) will need actual kernel BTF at /sys/kernel/btf/vmlinux.
|
||||
*/
|
||||
const char *btf_custom_path;
|
||||
};
|
||||
#define bpf_object_open_opts__last_field kconfig
|
||||
#define bpf_object_open_opts__last_field btf_custom_path
|
||||
|
||||
struct bpf_kprobe_opts {
|
||||
/* size of this struct, for forward/backward compatiblity */
|
||||
size_t sz;
|
||||
/* function's offset to install kprobe to */
|
||||
unsigned long offset;
|
||||
/* kprobe is return probe */
|
||||
bool retprobe;
|
||||
size_t :0;
|
||||
};
|
||||
#define bpf_kprobe_opts__last_field retprobe
|
||||
|
||||
LIBBPF_API struct bpf_object *bpf_object__open(const char *path);
|
||||
LIBBPF_API struct bpf_object *
|
||||
@ -243,6 +261,10 @@ LIBBPF_API struct bpf_link *
|
||||
bpf_program__attach_kprobe(struct bpf_program *prog, bool retprobe,
|
||||
const char *func_name);
|
||||
LIBBPF_API struct bpf_link *
|
||||
bpf_program__attach_kprobe_opts(struct bpf_program *prog,
|
||||
const char *func_name,
|
||||
struct bpf_kprobe_opts *opts);
|
||||
LIBBPF_API struct bpf_link *
|
||||
bpf_program__attach_uprobe(struct bpf_program *prog, bool retprobe,
|
||||
pid_t pid, const char *binary_path,
|
||||
size_t func_offset);
|
||||
@ -477,6 +499,7 @@ LIBBPF_API bool bpf_map__is_offload_neutral(const struct bpf_map *map);
|
||||
LIBBPF_API bool bpf_map__is_internal(const struct bpf_map *map);
|
||||
LIBBPF_API int bpf_map__set_pin_path(struct bpf_map *map, const char *path);
|
||||
LIBBPF_API const char *bpf_map__get_pin_path(const struct bpf_map *map);
|
||||
LIBBPF_API const char *bpf_map__pin_path(const struct bpf_map *map);
|
||||
LIBBPF_API bool bpf_map__is_pinned(const struct bpf_map *map);
|
||||
LIBBPF_API int bpf_map__pin(struct bpf_map *map, const char *path);
|
||||
LIBBPF_API int bpf_map__unpin(struct bpf_map *map, const char *path);
|
||||
|
@ -371,7 +371,15 @@ LIBBPF_0.4.0 {
|
||||
LIBBPF_0.5.0 {
|
||||
global:
|
||||
bpf_map__initial_value;
|
||||
bpf_map__pin_path;
|
||||
bpf_map_lookup_and_delete_elem_flags;
|
||||
bpf_program__attach_kprobe_opts;
|
||||
bpf_object__gen_loader;
|
||||
btf__load_from_kernel_by_id;
|
||||
btf__load_from_kernel_by_id_split;
|
||||
btf__load_into_kernel;
|
||||
btf__load_module_btf;
|
||||
btf__load_vmlinux_btf;
|
||||
btf_dump__dump_type_data;
|
||||
libbpf_set_strict_mode;
|
||||
} LIBBPF_0.4.0;
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <errno.h>
|
||||
#include <linux/err.h>
|
||||
#include "libbpf_legacy.h"
|
||||
#include "relo_core.h"
|
||||
|
||||
/* make sure libbpf doesn't use kernel-only integer typedefs */
|
||||
#pragma GCC poison u8 u16 u32 u64 s8 s16 s32 s64
|
||||
@ -366,76 +367,6 @@ struct bpf_line_info_min {
|
||||
__u32 line_col;
|
||||
};
|
||||
|
||||
/* bpf_core_relo_kind encodes which aspect of captured field/type/enum value
|
||||
* has to be adjusted by relocations.
|
||||
*/
|
||||
enum bpf_core_relo_kind {
|
||||
BPF_FIELD_BYTE_OFFSET = 0, /* field byte offset */
|
||||
BPF_FIELD_BYTE_SIZE = 1, /* field size in bytes */
|
||||
BPF_FIELD_EXISTS = 2, /* field existence in target kernel */
|
||||
BPF_FIELD_SIGNED = 3, /* field signedness (0 - unsigned, 1 - signed) */
|
||||
BPF_FIELD_LSHIFT_U64 = 4, /* bitfield-specific left bitshift */
|
||||
BPF_FIELD_RSHIFT_U64 = 5, /* bitfield-specific right bitshift */
|
||||
BPF_TYPE_ID_LOCAL = 6, /* type ID in local BPF object */
|
||||
BPF_TYPE_ID_TARGET = 7, /* type ID in target kernel */
|
||||
BPF_TYPE_EXISTS = 8, /* type existence in target kernel */
|
||||
BPF_TYPE_SIZE = 9, /* type size in bytes */
|
||||
BPF_ENUMVAL_EXISTS = 10, /* enum value existence in target kernel */
|
||||
BPF_ENUMVAL_VALUE = 11, /* enum value integer value */
|
||||
};
|
||||
|
||||
/* The minimum bpf_core_relo checked by the loader
|
||||
*
|
||||
* CO-RE relocation captures the following data:
|
||||
* - insn_off - instruction offset (in bytes) within a BPF program that needs
|
||||
* its insn->imm field to be relocated with actual field info;
|
||||
* - type_id - BTF type ID of the "root" (containing) entity of a relocatable
|
||||
* type or field;
|
||||
* - access_str_off - offset into corresponding .BTF string section. String
|
||||
* interpretation depends on specific relocation kind:
|
||||
* - for field-based relocations, string encodes an accessed field using
|
||||
* a sequence of field and array indices, separated by colon (:). It's
|
||||
* conceptually very close to LLVM's getelementptr ([0]) instruction's
|
||||
* arguments for identifying offset to a field.
|
||||
* - for type-based relocations, strings is expected to be just "0";
|
||||
* - for enum value-based relocations, string contains an index of enum
|
||||
* value within its enum type;
|
||||
*
|
||||
* Example to provide a better feel.
|
||||
*
|
||||
* struct sample {
|
||||
* int a;
|
||||
* struct {
|
||||
* int b[10];
|
||||
* };
|
||||
* };
|
||||
*
|
||||
* struct sample *s = ...;
|
||||
* int x = &s->a; // encoded as "0:0" (a is field #0)
|
||||
* int y = &s->b[5]; // encoded as "0:1:0:5" (anon struct is field #1,
|
||||
* // b is field #0 inside anon struct, accessing elem #5)
|
||||
* int z = &s[10]->b; // encoded as "10:1" (ptr is used as an array)
|
||||
*
|
||||
* type_id for all relocs in this example will capture BTF type id of
|
||||
* `struct sample`.
|
||||
*
|
||||
* Such relocation is emitted when using __builtin_preserve_access_index()
|
||||
* Clang built-in, passing expression that captures field address, e.g.:
|
||||
*
|
||||
* bpf_probe_read(&dst, sizeof(dst),
|
||||
* __builtin_preserve_access_index(&src->a.b.c));
|
||||
*
|
||||
* In this case Clang will emit field relocation recording necessary data to
|
||||
* be able to find offset of embedded `a.b.c` field within `src` struct.
|
||||
*
|
||||
* [0] https://llvm.org/docs/LangRef.html#getelementptr-instruction
|
||||
*/
|
||||
struct bpf_core_relo {
|
||||
__u32 insn_off;
|
||||
__u32 type_id;
|
||||
__u32 access_str_off;
|
||||
enum bpf_core_relo_kind kind;
|
||||
};
|
||||
|
||||
typedef int (*type_id_visit_fn)(__u32 *type_id, void *ctx);
|
||||
typedef int (*str_off_visit_fn)(__u32 *str_off, void *ctx);
|
||||
@ -494,4 +425,14 @@ static inline void *libbpf_ptr(void *ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline bool str_is_empty(const char *s)
|
||||
{
|
||||
return !s || !s[0];
|
||||
}
|
||||
|
||||
static inline bool is_ldimm64_insn(struct bpf_insn *insn)
|
||||
{
|
||||
return insn->code == (BPF_LD | BPF_IMM | BPF_DW);
|
||||
}
|
||||
|
||||
#endif /* __LIBBPF_LIBBPF_INTERNAL_H */
|
||||
|
1295
tools/lib/bpf/relo_core.c
Normal file
1295
tools/lib/bpf/relo_core.c
Normal file
File diff suppressed because it is too large
Load Diff
100
tools/lib/bpf/relo_core.h
Normal file
100
tools/lib/bpf/relo_core.h
Normal file
@ -0,0 +1,100 @@
|
||||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
/* Copyright (c) 2019 Facebook */
|
||||
|
||||
#ifndef __RELO_CORE_H
|
||||
#define __RELO_CORE_H
|
||||
|
||||
/* bpf_core_relo_kind encodes which aspect of captured field/type/enum value
|
||||
* has to be adjusted by relocations.
|
||||
*/
|
||||
enum bpf_core_relo_kind {
|
||||
BPF_FIELD_BYTE_OFFSET = 0, /* field byte offset */
|
||||
BPF_FIELD_BYTE_SIZE = 1, /* field size in bytes */
|
||||
BPF_FIELD_EXISTS = 2, /* field existence in target kernel */
|
||||
BPF_FIELD_SIGNED = 3, /* field signedness (0 - unsigned, 1 - signed) */
|
||||
BPF_FIELD_LSHIFT_U64 = 4, /* bitfield-specific left bitshift */
|
||||
BPF_FIELD_RSHIFT_U64 = 5, /* bitfield-specific right bitshift */
|
||||
BPF_TYPE_ID_LOCAL = 6, /* type ID in local BPF object */
|
||||
BPF_TYPE_ID_TARGET = 7, /* type ID in target kernel */
|
||||
BPF_TYPE_EXISTS = 8, /* type existence in target kernel */
|
||||
BPF_TYPE_SIZE = 9, /* type size in bytes */
|
||||
BPF_ENUMVAL_EXISTS = 10, /* enum value existence in target kernel */
|
||||
BPF_ENUMVAL_VALUE = 11, /* enum value integer value */
|
||||
};
|
||||
|
||||
/* The minimum bpf_core_relo checked by the loader
|
||||
*
|
||||
* CO-RE relocation captures the following data:
|
||||
* - insn_off - instruction offset (in bytes) within a BPF program that needs
|
||||
* its insn->imm field to be relocated with actual field info;
|
||||
* - type_id - BTF type ID of the "root" (containing) entity of a relocatable
|
||||
* type or field;
|
||||
* - access_str_off - offset into corresponding .BTF string section. String
|
||||
* interpretation depends on specific relocation kind:
|
||||
* - for field-based relocations, string encodes an accessed field using
|
||||
* a sequence of field and array indices, separated by colon (:). It's
|
||||
* conceptually very close to LLVM's getelementptr ([0]) instruction's
|
||||
* arguments for identifying offset to a field.
|
||||
* - for type-based relocations, strings is expected to be just "0";
|
||||
* - for enum value-based relocations, string contains an index of enum
|
||||
* value within its enum type;
|
||||
*
|
||||
* Example to provide a better feel.
|
||||
*
|
||||
* struct sample {
|
||||
* int a;
|
||||
* struct {
|
||||
* int b[10];
|
||||
* };
|
||||
* };
|
||||
*
|
||||
* struct sample *s = ...;
|
||||
* int x = &s->a; // encoded as "0:0" (a is field #0)
|
||||
* int y = &s->b[5]; // encoded as "0:1:0:5" (anon struct is field #1,
|
||||
* // b is field #0 inside anon struct, accessing elem #5)
|
||||
* int z = &s[10]->b; // encoded as "10:1" (ptr is used as an array)
|
||||
*
|
||||
* type_id for all relocs in this example will capture BTF type id of
|
||||
* `struct sample`.
|
||||
*
|
||||
* Such relocation is emitted when using __builtin_preserve_access_index()
|
||||
* Clang built-in, passing expression that captures field address, e.g.:
|
||||
*
|
||||
* bpf_probe_read(&dst, sizeof(dst),
|
||||
* __builtin_preserve_access_index(&src->a.b.c));
|
||||
*
|
||||
* In this case Clang will emit field relocation recording necessary data to
|
||||
* be able to find offset of embedded `a.b.c` field within `src` struct.
|
||||
*
|
||||
* [0] https://llvm.org/docs/LangRef.html#getelementptr-instruction
|
||||
*/
|
||||
struct bpf_core_relo {
|
||||
__u32 insn_off;
|
||||
__u32 type_id;
|
||||
__u32 access_str_off;
|
||||
enum bpf_core_relo_kind kind;
|
||||
};
|
||||
|
||||
struct bpf_core_cand {
|
||||
const struct btf *btf;
|
||||
const struct btf_type *t;
|
||||
const char *name;
|
||||
__u32 id;
|
||||
};
|
||||
|
||||
/* dynamically sized list of type IDs and its associated struct btf */
|
||||
struct bpf_core_cand_list {
|
||||
struct bpf_core_cand *cands;
|
||||
int len;
|
||||
};
|
||||
|
||||
int bpf_core_apply_relo_insn(const char *prog_name,
|
||||
struct bpf_insn *insn, int insn_idx,
|
||||
const struct bpf_core_relo *relo, int relo_idx,
|
||||
const struct btf *local_btf,
|
||||
struct bpf_core_cand_list *cands);
|
||||
int bpf_core_types_are_compat(const struct btf *local_btf, __u32 local_id,
|
||||
const struct btf *targ_btf, __u32 targ_id);
|
||||
|
||||
size_t bpf_core_essential_name_len(const char *name);
|
||||
#endif
|
@ -223,10 +223,10 @@ static int perf_event__synthesize_one_bpf_prog(struct perf_session *session,
|
||||
free(info_linear);
|
||||
return -1;
|
||||
}
|
||||
if (btf__get_from_id(info->btf_id, &btf)) {
|
||||
btf = btf__load_from_kernel_by_id(info->btf_id);
|
||||
if (libbpf_get_error(btf)) {
|
||||
pr_debug("%s: failed to get BTF of id %u, aborting\n", __func__, info->btf_id);
|
||||
err = -1;
|
||||
btf = NULL;
|
||||
goto out;
|
||||
}
|
||||
perf_env__fetch_btf(env, info->btf_id, btf);
|
||||
@ -296,7 +296,7 @@ static int perf_event__synthesize_one_bpf_prog(struct perf_session *session,
|
||||
|
||||
out:
|
||||
free(info_linear);
|
||||
free(btf);
|
||||
btf__free(btf);
|
||||
return err ? -1 : 0;
|
||||
}
|
||||
|
||||
@ -478,7 +478,8 @@ static void perf_env__add_bpf_info(struct perf_env *env, u32 id)
|
||||
if (btf_id == 0)
|
||||
goto out;
|
||||
|
||||
if (btf__get_from_id(btf_id, &btf)) {
|
||||
btf = btf__load_from_kernel_by_id(btf_id);
|
||||
if (libbpf_get_error(btf)) {
|
||||
pr_debug("%s: failed to get BTF of id %u, aborting\n",
|
||||
__func__, btf_id);
|
||||
goto out;
|
||||
@ -486,7 +487,7 @@ static void perf_env__add_bpf_info(struct perf_env *env, u32 id)
|
||||
perf_env__fetch_btf(env, btf_id, btf);
|
||||
|
||||
out:
|
||||
free(btf);
|
||||
btf__free(btf);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
|
@ -64,8 +64,8 @@ static char *bpf_target_prog_name(int tgt_fd)
|
||||
struct bpf_prog_info_linear *info_linear;
|
||||
struct bpf_func_info *func_info;
|
||||
const struct btf_type *t;
|
||||
struct btf *btf = NULL;
|
||||
char *name = NULL;
|
||||
struct btf *btf;
|
||||
|
||||
info_linear = bpf_program__get_prog_info_linear(
|
||||
tgt_fd, 1UL << BPF_PROG_INFO_FUNC_INFO);
|
||||
@ -74,12 +74,17 @@ static char *bpf_target_prog_name(int tgt_fd)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (info_linear->info.btf_id == 0 ||
|
||||
btf__get_from_id(info_linear->info.btf_id, &btf)) {
|
||||
if (info_linear->info.btf_id == 0) {
|
||||
pr_debug("prog FD %d doesn't have valid btf\n", tgt_fd);
|
||||
goto out;
|
||||
}
|
||||
|
||||
btf = btf__load_from_kernel_by_id(info_linear->info.btf_id);
|
||||
if (libbpf_get_error(btf)) {
|
||||
pr_debug("failed to load btf for prog FD %d\n", tgt_fd);
|
||||
goto out;
|
||||
}
|
||||
|
||||
func_info = u64_to_ptr(info_linear->info.func_info);
|
||||
t = btf__type_by_id(btf, func_info[0].type_id);
|
||||
if (!t) {
|
||||
@ -89,6 +94,7 @@ static char *bpf_target_prog_name(int tgt_fd)
|
||||
}
|
||||
name = strdup(btf__name_by_offset(btf, t->name_off));
|
||||
out:
|
||||
btf__free(btf);
|
||||
free(info_linear);
|
||||
return name;
|
||||
}
|
||||
|
@ -19,6 +19,13 @@ the CI. It builds the kernel (without overwriting your existing Kconfig), recomp
|
||||
bpf selftests, runs them (by default ``tools/testing/selftests/bpf/test_progs``) and
|
||||
saves the resulting output (by default in ``~/.bpf_selftests``).
|
||||
|
||||
Script dependencies:
|
||||
- clang (preferably built from sources, https://github.com/llvm/llvm-project);
|
||||
- pahole (preferably built from sources, https://git.kernel.org/pub/scm/devel/pahole/pahole.git/);
|
||||
- qemu;
|
||||
- docutils (for ``rst2man``);
|
||||
- libcap-devel.
|
||||
|
||||
For more information on about using the script, run:
|
||||
|
||||
.. code-block:: console
|
||||
|
@ -6,19 +6,39 @@
|
||||
|
||||
#define MAX_PERCPU_PACKETS 32
|
||||
|
||||
struct percpu_net_cnt {
|
||||
__u64 packets;
|
||||
__u64 bytes;
|
||||
/* sizeof(struct bpf_local_storage_elem):
|
||||
*
|
||||
* It really is about 128 bytes on x86_64, but allocate more to account for
|
||||
* possible layout changes, different architectures, etc.
|
||||
* The kernel will wrap up to PAGE_SIZE internally anyway.
|
||||
*/
|
||||
#define SIZEOF_BPF_LOCAL_STORAGE_ELEM 256
|
||||
|
||||
__u64 prev_ts;
|
||||
/* Try to estimate kernel's BPF_LOCAL_STORAGE_MAX_VALUE_SIZE: */
|
||||
#define BPF_LOCAL_STORAGE_MAX_VALUE_SIZE (0xFFFF - \
|
||||
SIZEOF_BPF_LOCAL_STORAGE_ELEM)
|
||||
|
||||
__u64 prev_packets;
|
||||
__u64 prev_bytes;
|
||||
#define PCPU_MIN_UNIT_SIZE 32768
|
||||
|
||||
union percpu_net_cnt {
|
||||
struct {
|
||||
__u64 packets;
|
||||
__u64 bytes;
|
||||
|
||||
__u64 prev_ts;
|
||||
|
||||
__u64 prev_packets;
|
||||
__u64 prev_bytes;
|
||||
};
|
||||
__u8 data[PCPU_MIN_UNIT_SIZE];
|
||||
};
|
||||
|
||||
struct net_cnt {
|
||||
__u64 packets;
|
||||
__u64 bytes;
|
||||
union net_cnt {
|
||||
struct {
|
||||
__u64 packets;
|
||||
__u64 bytes;
|
||||
};
|
||||
__u8 data[BPF_LOCAL_STORAGE_MAX_VALUE_SIZE];
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -66,17 +66,13 @@ int settimeo(int fd, int timeout_ms)
|
||||
|
||||
#define save_errno_close(fd) ({ int __save = errno; close(fd); errno = __save; })
|
||||
|
||||
int start_server(int family, int type, const char *addr_str, __u16 port,
|
||||
int timeout_ms)
|
||||
static int __start_server(int type, const struct sockaddr *addr,
|
||||
socklen_t addrlen, int timeout_ms, bool reuseport)
|
||||
{
|
||||
struct sockaddr_storage addr = {};
|
||||
socklen_t len;
|
||||
int on = 1;
|
||||
int fd;
|
||||
|
||||
if (make_sockaddr(family, addr_str, port, &addr, &len))
|
||||
return -1;
|
||||
|
||||
fd = socket(family, type, 0);
|
||||
fd = socket(addr->sa_family, type, 0);
|
||||
if (fd < 0) {
|
||||
log_err("Failed to create server socket");
|
||||
return -1;
|
||||
@ -85,7 +81,13 @@ int start_server(int family, int type, const char *addr_str, __u16 port,
|
||||
if (settimeo(fd, timeout_ms))
|
||||
goto error_close;
|
||||
|
||||
if (bind(fd, (const struct sockaddr *)&addr, len) < 0) {
|
||||
if (reuseport &&
|
||||
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on))) {
|
||||
log_err("Failed to set SO_REUSEPORT");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (bind(fd, addr, addrlen) < 0) {
|
||||
log_err("Failed to bind socket");
|
||||
goto error_close;
|
||||
}
|
||||
@ -104,6 +106,69 @@ error_close:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int start_server(int family, int type, const char *addr_str, __u16 port,
|
||||
int timeout_ms)
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addrlen;
|
||||
|
||||
if (make_sockaddr(family, addr_str, port, &addr, &addrlen))
|
||||
return -1;
|
||||
|
||||
return __start_server(type, (struct sockaddr *)&addr,
|
||||
addrlen, timeout_ms, false);
|
||||
}
|
||||
|
||||
int *start_reuseport_server(int family, int type, const char *addr_str,
|
||||
__u16 port, int timeout_ms, unsigned int nr_listens)
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
unsigned int nr_fds = 0;
|
||||
socklen_t addrlen;
|
||||
int *fds;
|
||||
|
||||
if (!nr_listens)
|
||||
return NULL;
|
||||
|
||||
if (make_sockaddr(family, addr_str, port, &addr, &addrlen))
|
||||
return NULL;
|
||||
|
||||
fds = malloc(sizeof(*fds) * nr_listens);
|
||||
if (!fds)
|
||||
return NULL;
|
||||
|
||||
fds[0] = __start_server(type, (struct sockaddr *)&addr, addrlen,
|
||||
timeout_ms, true);
|
||||
if (fds[0] == -1)
|
||||
goto close_fds;
|
||||
nr_fds = 1;
|
||||
|
||||
if (getsockname(fds[0], (struct sockaddr *)&addr, &addrlen))
|
||||
goto close_fds;
|
||||
|
||||
for (; nr_fds < nr_listens; nr_fds++) {
|
||||
fds[nr_fds] = __start_server(type, (struct sockaddr *)&addr,
|
||||
addrlen, timeout_ms, true);
|
||||
if (fds[nr_fds] == -1)
|
||||
goto close_fds;
|
||||
}
|
||||
|
||||
return fds;
|
||||
|
||||
close_fds:
|
||||
free_fds(fds, nr_fds);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void free_fds(int *fds, unsigned int nr_close_fds)
|
||||
{
|
||||
if (fds) {
|
||||
while (nr_close_fds)
|
||||
close(fds[--nr_close_fds]);
|
||||
free(fds);
|
||||
}
|
||||
}
|
||||
|
||||
int fastopen_connect(int server_fd, const char *data, unsigned int data_len,
|
||||
int timeout_ms)
|
||||
{
|
||||
@ -217,6 +282,7 @@ int make_sockaddr(int family, const char *addr_str, __u16 port,
|
||||
if (family == AF_INET) {
|
||||
struct sockaddr_in *sin = (void *)addr;
|
||||
|
||||
memset(addr, 0, sizeof(*sin));
|
||||
sin->sin_family = AF_INET;
|
||||
sin->sin_port = htons(port);
|
||||
if (addr_str &&
|
||||
@ -230,6 +296,7 @@ int make_sockaddr(int family, const char *addr_str, __u16 port,
|
||||
} else if (family == AF_INET6) {
|
||||
struct sockaddr_in6 *sin6 = (void *)addr;
|
||||
|
||||
memset(addr, 0, sizeof(*sin6));
|
||||
sin6->sin6_family = AF_INET6;
|
||||
sin6->sin6_port = htons(port);
|
||||
if (addr_str &&
|
||||
|
@ -36,6 +36,10 @@ extern struct ipv6_packet pkt_v6;
|
||||
int settimeo(int fd, int timeout_ms);
|
||||
int start_server(int family, int type, const char *addr, __u16 port,
|
||||
int timeout_ms);
|
||||
int *start_reuseport_server(int family, int type, const char *addr_str,
|
||||
__u16 port, int timeout_ms,
|
||||
unsigned int nr_listens);
|
||||
void free_fds(int *fds, unsigned int nr_close_fds);
|
||||
int connect_to_fd(int server_fd, int timeout_ms);
|
||||
int connect_fd_to_fd(int client_fd, int server_fd, int timeout_ms);
|
||||
int fastopen_connect(int server_fd, const char *data, unsigned int data_len,
|
||||
|
226
tools/testing/selftests/bpf/prog_tests/bpf_iter_setsockopt.c
Normal file
226
tools/testing/selftests/bpf/prog_tests/bpf_iter_setsockopt.c
Normal file
@ -0,0 +1,226 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2021 Facebook */
|
||||
#define _GNU_SOURCE
|
||||
#include <sched.h>
|
||||
#include <test_progs.h>
|
||||
#include "network_helpers.h"
|
||||
#include "bpf_dctcp.skel.h"
|
||||
#include "bpf_cubic.skel.h"
|
||||
#include "bpf_iter_setsockopt.skel.h"
|
||||
|
||||
static int create_netns(void)
|
||||
{
|
||||
if (!ASSERT_OK(unshare(CLONE_NEWNET), "create netns"))
|
||||
return -1;
|
||||
|
||||
if (!ASSERT_OK(system("ip link set dev lo up"), "bring up lo"))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int set_bpf_cubic(int *fds, unsigned int nr_fds)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < nr_fds; i++) {
|
||||
if (setsockopt(fds[i], SOL_TCP, TCP_CONGESTION, "bpf_cubic",
|
||||
sizeof("bpf_cubic")))
|
||||
return i;
|
||||
}
|
||||
|
||||
return nr_fds;
|
||||
}
|
||||
|
||||
static unsigned int check_bpf_dctcp(int *fds, unsigned int nr_fds)
|
||||
{
|
||||
char tcp_cc[16];
|
||||
socklen_t optlen = sizeof(tcp_cc);
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < nr_fds; i++) {
|
||||
if (getsockopt(fds[i], SOL_TCP, TCP_CONGESTION,
|
||||
tcp_cc, &optlen) ||
|
||||
strcmp(tcp_cc, "bpf_dctcp"))
|
||||
return i;
|
||||
}
|
||||
|
||||
return nr_fds;
|
||||
}
|
||||
|
||||
static int *make_established(int listen_fd, unsigned int nr_est,
|
||||
int **paccepted_fds)
|
||||
{
|
||||
int *est_fds, *accepted_fds;
|
||||
unsigned int i;
|
||||
|
||||
est_fds = malloc(sizeof(*est_fds) * nr_est);
|
||||
if (!est_fds)
|
||||
return NULL;
|
||||
|
||||
accepted_fds = malloc(sizeof(*accepted_fds) * nr_est);
|
||||
if (!accepted_fds) {
|
||||
free(est_fds);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < nr_est; i++) {
|
||||
est_fds[i] = connect_to_fd(listen_fd, 0);
|
||||
if (est_fds[i] == -1)
|
||||
break;
|
||||
if (set_bpf_cubic(&est_fds[i], 1) != 1) {
|
||||
close(est_fds[i]);
|
||||
break;
|
||||
}
|
||||
|
||||
accepted_fds[i] = accept(listen_fd, NULL, 0);
|
||||
if (accepted_fds[i] == -1) {
|
||||
close(est_fds[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ASSERT_EQ(i, nr_est, "create established fds")) {
|
||||
free_fds(accepted_fds, i);
|
||||
free_fds(est_fds, i);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*paccepted_fds = accepted_fds;
|
||||
return est_fds;
|
||||
}
|
||||
|
||||
static unsigned short get_local_port(int fd)
|
||||
{
|
||||
struct sockaddr_in6 addr;
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
|
||||
if (!getsockname(fd, &addr, &addrlen))
|
||||
return ntohs(addr.sin6_port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void do_bpf_iter_setsockopt(struct bpf_iter_setsockopt *iter_skel,
|
||||
bool random_retry)
|
||||
{
|
||||
int *reuse_listen_fds = NULL, *accepted_fds = NULL, *est_fds = NULL;
|
||||
unsigned int nr_reuse_listens = 256, nr_est = 256;
|
||||
int err, iter_fd = -1, listen_fd = -1;
|
||||
char buf;
|
||||
|
||||
/* Prepare non-reuseport listen_fd */
|
||||
listen_fd = start_server(AF_INET6, SOCK_STREAM, "::1", 0, 0);
|
||||
if (!ASSERT_GE(listen_fd, 0, "start_server"))
|
||||
return;
|
||||
if (!ASSERT_EQ(set_bpf_cubic(&listen_fd, 1), 1,
|
||||
"set listen_fd to cubic"))
|
||||
goto done;
|
||||
iter_skel->bss->listen_hport = get_local_port(listen_fd);
|
||||
if (!ASSERT_NEQ(iter_skel->bss->listen_hport, 0,
|
||||
"get_local_port(listen_fd)"))
|
||||
goto done;
|
||||
|
||||
/* Connect to non-reuseport listen_fd */
|
||||
est_fds = make_established(listen_fd, nr_est, &accepted_fds);
|
||||
if (!ASSERT_OK_PTR(est_fds, "create established"))
|
||||
goto done;
|
||||
|
||||
/* Prepare reuseport listen fds */
|
||||
reuse_listen_fds = start_reuseport_server(AF_INET6, SOCK_STREAM,
|
||||
"::1", 0, 0,
|
||||
nr_reuse_listens);
|
||||
if (!ASSERT_OK_PTR(reuse_listen_fds, "start_reuseport_server"))
|
||||
goto done;
|
||||
if (!ASSERT_EQ(set_bpf_cubic(reuse_listen_fds, nr_reuse_listens),
|
||||
nr_reuse_listens, "set reuse_listen_fds to cubic"))
|
||||
goto done;
|
||||
iter_skel->bss->reuse_listen_hport = get_local_port(reuse_listen_fds[0]);
|
||||
if (!ASSERT_NEQ(iter_skel->bss->reuse_listen_hport, 0,
|
||||
"get_local_port(reuse_listen_fds[0])"))
|
||||
goto done;
|
||||
|
||||
/* Run bpf tcp iter to switch from bpf_cubic to bpf_dctcp */
|
||||
iter_skel->bss->random_retry = random_retry;
|
||||
iter_fd = bpf_iter_create(bpf_link__fd(iter_skel->links.change_tcp_cc));
|
||||
if (!ASSERT_GE(iter_fd, 0, "create iter_fd"))
|
||||
goto done;
|
||||
|
||||
while ((err = read(iter_fd, &buf, sizeof(buf))) == -1 &&
|
||||
errno == EAGAIN)
|
||||
;
|
||||
if (!ASSERT_OK(err, "read iter error"))
|
||||
goto done;
|
||||
|
||||
/* Check reuseport listen fds for dctcp */
|
||||
ASSERT_EQ(check_bpf_dctcp(reuse_listen_fds, nr_reuse_listens),
|
||||
nr_reuse_listens,
|
||||
"check reuse_listen_fds dctcp");
|
||||
|
||||
/* Check non reuseport listen fd for dctcp */
|
||||
ASSERT_EQ(check_bpf_dctcp(&listen_fd, 1), 1,
|
||||
"check listen_fd dctcp");
|
||||
|
||||
/* Check established fds for dctcp */
|
||||
ASSERT_EQ(check_bpf_dctcp(est_fds, nr_est), nr_est,
|
||||
"check est_fds dctcp");
|
||||
|
||||
/* Check accepted fds for dctcp */
|
||||
ASSERT_EQ(check_bpf_dctcp(accepted_fds, nr_est), nr_est,
|
||||
"check accepted_fds dctcp");
|
||||
|
||||
done:
|
||||
if (iter_fd != -1)
|
||||
close(iter_fd);
|
||||
if (listen_fd != -1)
|
||||
close(listen_fd);
|
||||
free_fds(reuse_listen_fds, nr_reuse_listens);
|
||||
free_fds(accepted_fds, nr_est);
|
||||
free_fds(est_fds, nr_est);
|
||||
}
|
||||
|
||||
void test_bpf_iter_setsockopt(void)
|
||||
{
|
||||
struct bpf_iter_setsockopt *iter_skel = NULL;
|
||||
struct bpf_cubic *cubic_skel = NULL;
|
||||
struct bpf_dctcp *dctcp_skel = NULL;
|
||||
struct bpf_link *cubic_link = NULL;
|
||||
struct bpf_link *dctcp_link = NULL;
|
||||
|
||||
if (create_netns())
|
||||
return;
|
||||
|
||||
/* Load iter_skel */
|
||||
iter_skel = bpf_iter_setsockopt__open_and_load();
|
||||
if (!ASSERT_OK_PTR(iter_skel, "iter_skel"))
|
||||
return;
|
||||
iter_skel->links.change_tcp_cc = bpf_program__attach_iter(iter_skel->progs.change_tcp_cc, NULL);
|
||||
if (!ASSERT_OK_PTR(iter_skel->links.change_tcp_cc, "attach iter"))
|
||||
goto done;
|
||||
|
||||
/* Load bpf_cubic */
|
||||
cubic_skel = bpf_cubic__open_and_load();
|
||||
if (!ASSERT_OK_PTR(cubic_skel, "cubic_skel"))
|
||||
goto done;
|
||||
cubic_link = bpf_map__attach_struct_ops(cubic_skel->maps.cubic);
|
||||
if (!ASSERT_OK_PTR(cubic_link, "cubic_link"))
|
||||
goto done;
|
||||
|
||||
/* Load bpf_dctcp */
|
||||
dctcp_skel = bpf_dctcp__open_and_load();
|
||||
if (!ASSERT_OK_PTR(dctcp_skel, "dctcp_skel"))
|
||||
goto done;
|
||||
dctcp_link = bpf_map__attach_struct_ops(dctcp_skel->maps.dctcp);
|
||||
if (!ASSERT_OK_PTR(dctcp_link, "dctcp_link"))
|
||||
goto done;
|
||||
|
||||
do_bpf_iter_setsockopt(iter_skel, true);
|
||||
do_bpf_iter_setsockopt(iter_skel, false);
|
||||
|
||||
done:
|
||||
bpf_link__destroy(cubic_link);
|
||||
bpf_link__destroy(dctcp_link);
|
||||
bpf_cubic__destroy(cubic_skel);
|
||||
bpf_dctcp__destroy(dctcp_skel);
|
||||
bpf_iter_setsockopt__destroy(iter_skel);
|
||||
}
|
@ -4350,7 +4350,8 @@ static void do_test_file(unsigned int test_num)
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = btf__get_from_id(info.btf_id, &btf);
|
||||
btf = btf__load_from_kernel_by_id(info.btf_id);
|
||||
err = libbpf_get_error(btf);
|
||||
if (CHECK(err, "cannot get btf from kernel, err: %d", err))
|
||||
goto done;
|
||||
|
||||
@ -4386,6 +4387,7 @@ skip:
|
||||
fprintf(stderr, "OK");
|
||||
|
||||
done:
|
||||
btf__free(btf);
|
||||
free(func_info);
|
||||
bpf_object__close(obj);
|
||||
}
|
||||
|
@ -232,7 +232,593 @@ err_out:
|
||||
btf__free(btf);
|
||||
}
|
||||
|
||||
#define STRSIZE 4096
|
||||
|
||||
static void btf_dump_snprintf(void *ctx, const char *fmt, va_list args)
|
||||
{
|
||||
char *s = ctx, new[STRSIZE];
|
||||
|
||||
vsnprintf(new, STRSIZE, fmt, args);
|
||||
if (strlen(s) < STRSIZE)
|
||||
strncat(s, new, STRSIZE - strlen(s) - 1);
|
||||
}
|
||||
|
||||
static int btf_dump_data(struct btf *btf, struct btf_dump *d,
|
||||
char *name, char *prefix, __u64 flags, void *ptr,
|
||||
size_t ptr_sz, char *str, const char *expected_val)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(btf_dump_type_data_opts, opts);
|
||||
size_t type_sz;
|
||||
__s32 type_id;
|
||||
int ret = 0;
|
||||
|
||||
if (flags & BTF_F_COMPACT)
|
||||
opts.compact = true;
|
||||
if (flags & BTF_F_NONAME)
|
||||
opts.skip_names = true;
|
||||
if (flags & BTF_F_ZERO)
|
||||
opts.emit_zeroes = true;
|
||||
if (prefix) {
|
||||
ASSERT_STRNEQ(name, prefix, strlen(prefix),
|
||||
"verify prefix match");
|
||||
name += strlen(prefix) + 1;
|
||||
}
|
||||
type_id = btf__find_by_name(btf, name);
|
||||
if (!ASSERT_GE(type_id, 0, "find type id"))
|
||||
return -ENOENT;
|
||||
type_sz = btf__resolve_size(btf, type_id);
|
||||
str[0] = '\0';
|
||||
ret = btf_dump__dump_type_data(d, type_id, ptr, ptr_sz, &opts);
|
||||
if (type_sz <= ptr_sz) {
|
||||
if (!ASSERT_EQ(ret, type_sz, "failed/unexpected type_sz"))
|
||||
return -EINVAL;
|
||||
} else {
|
||||
if (!ASSERT_EQ(ret, -E2BIG, "failed to return -E2BIG"))
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!ASSERT_STREQ(str, expected_val, "ensure expected/actual match"))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define TEST_BTF_DUMP_DATA(_b, _d, _prefix, _str, _type, _flags, \
|
||||
_expected, ...) \
|
||||
do { \
|
||||
char __ptrtype[64] = #_type; \
|
||||
char *_ptrtype = (char *)__ptrtype; \
|
||||
_type _ptrdata = __VA_ARGS__; \
|
||||
void *_ptr = &_ptrdata; \
|
||||
\
|
||||
(void) btf_dump_data(_b, _d, _ptrtype, _prefix, _flags, \
|
||||
_ptr, sizeof(_type), _str, \
|
||||
_expected); \
|
||||
} while (0)
|
||||
|
||||
/* Use where expected data string matches its stringified declaration */
|
||||
#define TEST_BTF_DUMP_DATA_C(_b, _d, _prefix, _str, _type, _flags, \
|
||||
...) \
|
||||
TEST_BTF_DUMP_DATA(_b, _d, _prefix, _str, _type, _flags, \
|
||||
"(" #_type ")" #__VA_ARGS__, __VA_ARGS__)
|
||||
|
||||
/* overflow test; pass typesize < expected type size, ensure E2BIG returned */
|
||||
#define TEST_BTF_DUMP_DATA_OVER(_b, _d, _prefix, _str, _type, _type_sz, \
|
||||
_expected, ...) \
|
||||
do { \
|
||||
char __ptrtype[64] = #_type; \
|
||||
char *_ptrtype = (char *)__ptrtype; \
|
||||
_type _ptrdata = __VA_ARGS__; \
|
||||
void *_ptr = &_ptrdata; \
|
||||
\
|
||||
(void) btf_dump_data(_b, _d, _ptrtype, _prefix, 0, \
|
||||
_ptr, _type_sz, _str, _expected); \
|
||||
} while (0)
|
||||
|
||||
#define TEST_BTF_DUMP_VAR(_b, _d, _prefix, _str, _var, _type, _flags, \
|
||||
_expected, ...) \
|
||||
do { \
|
||||
_type _ptrdata = __VA_ARGS__; \
|
||||
void *_ptr = &_ptrdata; \
|
||||
\
|
||||
(void) btf_dump_data(_b, _d, _var, _prefix, _flags, \
|
||||
_ptr, sizeof(_type), _str, \
|
||||
_expected); \
|
||||
} while (0)
|
||||
|
||||
static void test_btf_dump_int_data(struct btf *btf, struct btf_dump *d,
|
||||
char *str)
|
||||
{
|
||||
#ifdef __SIZEOF_INT128__
|
||||
__int128 i = 0xffffffffffffffff;
|
||||
|
||||
/* this dance is required because we cannot directly initialize
|
||||
* a 128-bit value to anything larger than a 64-bit value.
|
||||
*/
|
||||
i = (i << 64) | (i - 1);
|
||||
#endif
|
||||
/* simple int */
|
||||
TEST_BTF_DUMP_DATA_C(btf, d, NULL, str, int, BTF_F_COMPACT, 1234);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, int, BTF_F_COMPACT | BTF_F_NONAME,
|
||||
"1234", 1234);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, int, 0, "(int)1234", 1234);
|
||||
|
||||
/* zero value should be printed at toplevel */
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, int, BTF_F_COMPACT, "(int)0", 0);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, int, BTF_F_COMPACT | BTF_F_NONAME,
|
||||
"0", 0);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, int, BTF_F_COMPACT | BTF_F_ZERO,
|
||||
"(int)0", 0);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, int,
|
||||
BTF_F_COMPACT | BTF_F_NONAME | BTF_F_ZERO,
|
||||
"0", 0);
|
||||
TEST_BTF_DUMP_DATA_C(btf, d, NULL, str, int, BTF_F_COMPACT, -4567);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, int, BTF_F_COMPACT | BTF_F_NONAME,
|
||||
"-4567", -4567);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, int, 0, "(int)-4567", -4567);
|
||||
|
||||
TEST_BTF_DUMP_DATA_OVER(btf, d, NULL, str, int, sizeof(int)-1, "", 1);
|
||||
|
||||
#ifdef __SIZEOF_INT128__
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, __int128, BTF_F_COMPACT,
|
||||
"(__int128)0xffffffffffffffff",
|
||||
0xffffffffffffffff);
|
||||
ASSERT_OK(btf_dump_data(btf, d, "__int128", NULL, 0, &i, 16, str,
|
||||
"(__int128)0xfffffffffffffffffffffffffffffffe"),
|
||||
"dump __int128");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void test_btf_dump_float_data(struct btf *btf, struct btf_dump *d,
|
||||
char *str)
|
||||
{
|
||||
float t1 = 1.234567;
|
||||
float t2 = -1.234567;
|
||||
float t3 = 0.0;
|
||||
double t4 = 5.678912;
|
||||
double t5 = -5.678912;
|
||||
double t6 = 0.0;
|
||||
long double t7 = 9.876543;
|
||||
long double t8 = -9.876543;
|
||||
long double t9 = 0.0;
|
||||
|
||||
/* since the kernel does not likely have any float types in its BTF, we
|
||||
* will need to add some of various sizes.
|
||||
*/
|
||||
|
||||
ASSERT_GT(btf__add_float(btf, "test_float", 4), 0, "add float");
|
||||
ASSERT_OK(btf_dump_data(btf, d, "test_float", NULL, 0, &t1, 4, str,
|
||||
"(test_float)1.234567"), "dump float");
|
||||
ASSERT_OK(btf_dump_data(btf, d, "test_float", NULL, 0, &t2, 4, str,
|
||||
"(test_float)-1.234567"), "dump float");
|
||||
ASSERT_OK(btf_dump_data(btf, d, "test_float", NULL, 0, &t3, 4, str,
|
||||
"(test_float)0.000000"), "dump float");
|
||||
|
||||
ASSERT_GT(btf__add_float(btf, "test_double", 8), 0, "add_double");
|
||||
ASSERT_OK(btf_dump_data(btf, d, "test_double", NULL, 0, &t4, 8, str,
|
||||
"(test_double)5.678912"), "dump double");
|
||||
ASSERT_OK(btf_dump_data(btf, d, "test_double", NULL, 0, &t5, 8, str,
|
||||
"(test_double)-5.678912"), "dump double");
|
||||
ASSERT_OK(btf_dump_data(btf, d, "test_double", NULL, 0, &t6, 8, str,
|
||||
"(test_double)0.000000"), "dump double");
|
||||
|
||||
ASSERT_GT(btf__add_float(btf, "test_long_double", 16), 0, "add long double");
|
||||
ASSERT_OK(btf_dump_data(btf, d, "test_long_double", NULL, 0, &t7, 16,
|
||||
str, "(test_long_double)9.876543"),
|
||||
"dump long_double");
|
||||
ASSERT_OK(btf_dump_data(btf, d, "test_long_double", NULL, 0, &t8, 16,
|
||||
str, "(test_long_double)-9.876543"),
|
||||
"dump long_double");
|
||||
ASSERT_OK(btf_dump_data(btf, d, "test_long_double", NULL, 0, &t9, 16,
|
||||
str, "(test_long_double)0.000000"),
|
||||
"dump long_double");
|
||||
}
|
||||
|
||||
static void test_btf_dump_char_data(struct btf *btf, struct btf_dump *d,
|
||||
char *str)
|
||||
{
|
||||
/* simple char */
|
||||
TEST_BTF_DUMP_DATA_C(btf, d, NULL, str, char, BTF_F_COMPACT, 100);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, char, BTF_F_COMPACT | BTF_F_NONAME,
|
||||
"100", 100);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, char, 0, "(char)100", 100);
|
||||
/* zero value should be printed at toplevel */
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, char, BTF_F_COMPACT,
|
||||
"(char)0", 0);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, char, BTF_F_COMPACT | BTF_F_NONAME,
|
||||
"0", 0);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, char, BTF_F_COMPACT | BTF_F_ZERO,
|
||||
"(char)0", 0);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, char, BTF_F_COMPACT | BTF_F_NONAME | BTF_F_ZERO,
|
||||
"0", 0);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, char, 0, "(char)0", 0);
|
||||
|
||||
TEST_BTF_DUMP_DATA_OVER(btf, d, NULL, str, char, sizeof(char)-1, "", 100);
|
||||
}
|
||||
|
||||
static void test_btf_dump_typedef_data(struct btf *btf, struct btf_dump *d,
|
||||
char *str)
|
||||
{
|
||||
/* simple typedef */
|
||||
TEST_BTF_DUMP_DATA_C(btf, d, NULL, str, uint64_t, BTF_F_COMPACT, 100);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, u64, BTF_F_COMPACT | BTF_F_NONAME,
|
||||
"1", 1);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, u64, 0, "(u64)1", 1);
|
||||
/* zero value should be printed at toplevel */
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, u64, BTF_F_COMPACT, "(u64)0", 0);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, u64, BTF_F_COMPACT | BTF_F_NONAME,
|
||||
"0", 0);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, u64, BTF_F_COMPACT | BTF_F_ZERO,
|
||||
"(u64)0", 0);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, u64,
|
||||
BTF_F_COMPACT | BTF_F_NONAME | BTF_F_ZERO,
|
||||
"0", 0);
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, u64, 0, "(u64)0", 0);
|
||||
|
||||
/* typedef struct */
|
||||
TEST_BTF_DUMP_DATA_C(btf, d, NULL, str, atomic_t, BTF_F_COMPACT,
|
||||
{.counter = (int)1,});
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, atomic_t, BTF_F_COMPACT | BTF_F_NONAME,
|
||||
"{1,}", { .counter = 1 });
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, atomic_t, 0,
|
||||
"(atomic_t){\n"
|
||||
" .counter = (int)1,\n"
|
||||
"}",
|
||||
{.counter = 1,});
|
||||
/* typedef with 0 value should be printed at toplevel */
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, atomic_t, BTF_F_COMPACT, "(atomic_t){}",
|
||||
{.counter = 0,});
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, atomic_t, BTF_F_COMPACT | BTF_F_NONAME,
|
||||
"{}", {.counter = 0,});
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, atomic_t, 0,
|
||||
"(atomic_t){\n"
|
||||
"}",
|
||||
{.counter = 0,});
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, atomic_t, BTF_F_COMPACT | BTF_F_ZERO,
|
||||
"(atomic_t){.counter = (int)0,}",
|
||||
{.counter = 0,});
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, atomic_t,
|
||||
BTF_F_COMPACT | BTF_F_NONAME | BTF_F_ZERO,
|
||||
"{0,}", {.counter = 0,});
|
||||
TEST_BTF_DUMP_DATA(btf, d, NULL, str, atomic_t, BTF_F_ZERO,
|
||||
"(atomic_t){\n"
|
||||
" .counter = (int)0,\n"
|
||||
"}",
|
||||
{ .counter = 0,});
|
||||
|
||||
/* overflow should show type but not value since it overflows */
|
||||
TEST_BTF_DUMP_DATA_OVER(btf, d, NULL, str, atomic_t, sizeof(atomic_t)-1,
|
||||
"(atomic_t){\n", { .counter = 1});
|
||||
}
|
||||
|
||||
static void test_btf_dump_enum_data(struct btf *btf, struct btf_dump *d,
|
||||
char *str)
|
||||
{
|
||||
/* enum where enum value does (and does not) exist */
|
||||
TEST_BTF_DUMP_DATA_C(btf, d, "enum", str, enum bpf_cmd, BTF_F_COMPACT,
|
||||
BPF_MAP_CREATE);
|
||||
TEST_BTF_DUMP_DATA(btf, d, "enum", str, enum bpf_cmd, BTF_F_COMPACT,
|
||||
"(enum bpf_cmd)BPF_MAP_CREATE", 0);
|
||||
TEST_BTF_DUMP_DATA(btf, d, "enum", str, enum bpf_cmd,
|
||||
BTF_F_COMPACT | BTF_F_NONAME,
|
||||
"BPF_MAP_CREATE",
|
||||
BPF_MAP_CREATE);
|
||||
TEST_BTF_DUMP_DATA(btf, d, "enum", str, enum bpf_cmd, 0,
|
||||
"(enum bpf_cmd)BPF_MAP_CREATE",
|
||||
BPF_MAP_CREATE);
|
||||
TEST_BTF_DUMP_DATA(btf, d, "enum", str, enum bpf_cmd,
|
||||
BTF_F_COMPACT | BTF_F_NONAME | BTF_F_ZERO,
|
||||
"BPF_MAP_CREATE", 0);
|
||||
TEST_BTF_DUMP_DATA(btf, d, "enum", str, enum bpf_cmd,
|
||||
BTF_F_COMPACT | BTF_F_ZERO,
|
||||
"(enum bpf_cmd)BPF_MAP_CREATE",
|
||||
BPF_MAP_CREATE);
|
||||
TEST_BTF_DUMP_DATA(btf, d, "enum", str, enum bpf_cmd,
|
||||
BTF_F_COMPACT | BTF_F_NONAME | BTF_F_ZERO,
|
||||
"BPF_MAP_CREATE", BPF_MAP_CREATE);
|
||||
TEST_BTF_DUMP_DATA_C(btf, d, "enum", str, enum bpf_cmd, BTF_F_COMPACT, 2000);
|
||||
TEST_BTF_DUMP_DATA(btf, d, "enum", str, enum bpf_cmd,
|
||||
BTF_F_COMPACT | BTF_F_NONAME,
|
||||
"2000", 2000);
|
||||
TEST_BTF_DUMP_DATA(btf, d, "enum", str, enum bpf_cmd, 0,
|
||||
"(enum bpf_cmd)2000", 2000);
|
||||
|
||||
TEST_BTF_DUMP_DATA_OVER(btf, d, "enum", str, enum bpf_cmd,
|
||||
sizeof(enum bpf_cmd) - 1, "", BPF_MAP_CREATE);
|
||||
}
|
||||
|
||||
static void test_btf_dump_struct_data(struct btf *btf, struct btf_dump *d,
|
||||
char *str)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(btf_dump_type_data_opts, opts);
|
||||
char zero_data[512] = { };
|
||||
char type_data[512];
|
||||
void *fops = type_data;
|
||||
void *skb = type_data;
|
||||
size_t type_sz;
|
||||
__s32 type_id;
|
||||
char *cmpstr;
|
||||
int ret;
|
||||
|
||||
memset(type_data, 255, sizeof(type_data));
|
||||
|
||||
/* simple struct */
|
||||
TEST_BTF_DUMP_DATA_C(btf, d, "struct", str, struct btf_enum, BTF_F_COMPACT,
|
||||
{.name_off = (__u32)3,.val = (__s32)-1,});
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct btf_enum,
|
||||
BTF_F_COMPACT | BTF_F_NONAME,
|
||||
"{3,-1,}",
|
||||
{ .name_off = 3, .val = -1,});
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct btf_enum, 0,
|
||||
"(struct btf_enum){\n"
|
||||
" .name_off = (__u32)3,\n"
|
||||
" .val = (__s32)-1,\n"
|
||||
"}",
|
||||
{ .name_off = 3, .val = -1,});
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct btf_enum,
|
||||
BTF_F_COMPACT | BTF_F_NONAME,
|
||||
"{-1,}",
|
||||
{ .name_off = 0, .val = -1,});
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct btf_enum,
|
||||
BTF_F_COMPACT | BTF_F_NONAME | BTF_F_ZERO,
|
||||
"{0,-1,}",
|
||||
{ .name_off = 0, .val = -1,});
|
||||
/* empty struct should be printed */
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct btf_enum, BTF_F_COMPACT,
|
||||
"(struct btf_enum){}",
|
||||
{ .name_off = 0, .val = 0,});
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct btf_enum,
|
||||
BTF_F_COMPACT | BTF_F_NONAME,
|
||||
"{}",
|
||||
{ .name_off = 0, .val = 0,});
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct btf_enum, 0,
|
||||
"(struct btf_enum){\n"
|
||||
"}",
|
||||
{ .name_off = 0, .val = 0,});
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct btf_enum,
|
||||
BTF_F_COMPACT | BTF_F_ZERO,
|
||||
"(struct btf_enum){.name_off = (__u32)0,.val = (__s32)0,}",
|
||||
{ .name_off = 0, .val = 0,});
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct btf_enum,
|
||||
BTF_F_ZERO,
|
||||
"(struct btf_enum){\n"
|
||||
" .name_off = (__u32)0,\n"
|
||||
" .val = (__s32)0,\n"
|
||||
"}",
|
||||
{ .name_off = 0, .val = 0,});
|
||||
|
||||
/* struct with pointers */
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct list_head, BTF_F_COMPACT,
|
||||
"(struct list_head){.next = (struct list_head *)0x1,}",
|
||||
{ .next = (struct list_head *)1 });
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct list_head, 0,
|
||||
"(struct list_head){\n"
|
||||
" .next = (struct list_head *)0x1,\n"
|
||||
"}",
|
||||
{ .next = (struct list_head *)1 });
|
||||
/* NULL pointer should not be displayed */
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct list_head, BTF_F_COMPACT,
|
||||
"(struct list_head){}",
|
||||
{ .next = (struct list_head *)0 });
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct list_head, 0,
|
||||
"(struct list_head){\n"
|
||||
"}",
|
||||
{ .next = (struct list_head *)0 });
|
||||
|
||||
/* struct with function pointers */
|
||||
type_id = btf__find_by_name(btf, "file_operations");
|
||||
if (ASSERT_GT(type_id, 0, "find type id")) {
|
||||
type_sz = btf__resolve_size(btf, type_id);
|
||||
str[0] = '\0';
|
||||
|
||||
ret = btf_dump__dump_type_data(d, type_id, fops, type_sz, &opts);
|
||||
ASSERT_EQ(ret, type_sz,
|
||||
"unexpected return value dumping file_operations");
|
||||
cmpstr =
|
||||
"(struct file_operations){\n"
|
||||
" .owner = (struct module *)0xffffffffffffffff,\n"
|
||||
" .llseek = (loff_t (*)(struct file *, loff_t, int))0xffffffffffffffff,";
|
||||
|
||||
ASSERT_STRNEQ(str, cmpstr, strlen(cmpstr), "file_operations");
|
||||
}
|
||||
|
||||
/* struct with char array */
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct bpf_prog_info, BTF_F_COMPACT,
|
||||
"(struct bpf_prog_info){.name = (char[16])['f','o','o',],}",
|
||||
{ .name = "foo",});
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct bpf_prog_info,
|
||||
BTF_F_COMPACT | BTF_F_NONAME,
|
||||
"{['f','o','o',],}",
|
||||
{.name = "foo",});
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct bpf_prog_info, 0,
|
||||
"(struct bpf_prog_info){\n"
|
||||
" .name = (char[16])[\n"
|
||||
" 'f',\n"
|
||||
" 'o',\n"
|
||||
" 'o',\n"
|
||||
" ],\n"
|
||||
"}",
|
||||
{.name = "foo",});
|
||||
/* leading null char means do not display string */
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct bpf_prog_info, BTF_F_COMPACT,
|
||||
"(struct bpf_prog_info){}",
|
||||
{.name = {'\0', 'f', 'o', 'o'}});
|
||||
/* handle non-printable characters */
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct bpf_prog_info, BTF_F_COMPACT,
|
||||
"(struct bpf_prog_info){.name = (char[16])[1,2,3,],}",
|
||||
{ .name = {1, 2, 3, 0}});
|
||||
|
||||
/* struct with non-char array */
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct __sk_buff, BTF_F_COMPACT,
|
||||
"(struct __sk_buff){.cb = (__u32[5])[1,2,3,4,5,],}",
|
||||
{ .cb = {1, 2, 3, 4, 5,},});
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct __sk_buff,
|
||||
BTF_F_COMPACT | BTF_F_NONAME,
|
||||
"{[1,2,3,4,5,],}",
|
||||
{ .cb = { 1, 2, 3, 4, 5},});
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct __sk_buff, 0,
|
||||
"(struct __sk_buff){\n"
|
||||
" .cb = (__u32[5])[\n"
|
||||
" 1,\n"
|
||||
" 2,\n"
|
||||
" 3,\n"
|
||||
" 4,\n"
|
||||
" 5,\n"
|
||||
" ],\n"
|
||||
"}",
|
||||
{ .cb = { 1, 2, 3, 4, 5},});
|
||||
/* For non-char, arrays, show non-zero values only */
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct __sk_buff, BTF_F_COMPACT,
|
||||
"(struct __sk_buff){.cb = (__u32[5])[0,0,1,0,0,],}",
|
||||
{ .cb = { 0, 0, 1, 0, 0},});
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct __sk_buff, 0,
|
||||
"(struct __sk_buff){\n"
|
||||
" .cb = (__u32[5])[\n"
|
||||
" 0,\n"
|
||||
" 0,\n"
|
||||
" 1,\n"
|
||||
" 0,\n"
|
||||
" 0,\n"
|
||||
" ],\n"
|
||||
"}",
|
||||
{ .cb = { 0, 0, 1, 0, 0},});
|
||||
|
||||
/* struct with bitfields */
|
||||
TEST_BTF_DUMP_DATA_C(btf, d, "struct", str, struct bpf_insn, BTF_F_COMPACT,
|
||||
{.code = (__u8)1,.dst_reg = (__u8)0x2,.src_reg = (__u8)0x3,.off = (__s16)4,.imm = (__s32)5,});
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct bpf_insn,
|
||||
BTF_F_COMPACT | BTF_F_NONAME,
|
||||
"{1,0x2,0x3,4,5,}",
|
||||
{ .code = 1, .dst_reg = 0x2, .src_reg = 0x3, .off = 4,
|
||||
.imm = 5,});
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct bpf_insn, 0,
|
||||
"(struct bpf_insn){\n"
|
||||
" .code = (__u8)1,\n"
|
||||
" .dst_reg = (__u8)0x2,\n"
|
||||
" .src_reg = (__u8)0x3,\n"
|
||||
" .off = (__s16)4,\n"
|
||||
" .imm = (__s32)5,\n"
|
||||
"}",
|
||||
{.code = 1, .dst_reg = 2, .src_reg = 3, .off = 4, .imm = 5});
|
||||
|
||||
/* zeroed bitfields should not be displayed */
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct bpf_insn, BTF_F_COMPACT,
|
||||
"(struct bpf_insn){.dst_reg = (__u8)0x1,}",
|
||||
{ .code = 0, .dst_reg = 1});
|
||||
|
||||
/* struct with enum bitfield */
|
||||
type_id = btf__find_by_name(btf, "fs_context");
|
||||
if (ASSERT_GT(type_id, 0, "find fs_context")) {
|
||||
type_sz = btf__resolve_size(btf, type_id);
|
||||
str[0] = '\0';
|
||||
|
||||
opts.emit_zeroes = true;
|
||||
ret = btf_dump__dump_type_data(d, type_id, zero_data, type_sz, &opts);
|
||||
ASSERT_EQ(ret, type_sz,
|
||||
"unexpected return value dumping fs_context");
|
||||
|
||||
ASSERT_NEQ(strstr(str, "FS_CONTEXT_FOR_MOUNT"), NULL,
|
||||
"bitfield value not present");
|
||||
}
|
||||
|
||||
/* struct with nested anon union */
|
||||
TEST_BTF_DUMP_DATA(btf, d, "struct", str, struct bpf_sock_ops, BTF_F_COMPACT,
|
||||
"(struct bpf_sock_ops){.op = (__u32)1,(union){.args = (__u32[4])[1,2,3,4,],.reply = (__u32)1,.replylong = (__u32[4])[1,2,3,4,],},}",
|
||||
{ .op = 1, .args = { 1, 2, 3, 4}});
|
||||
|
||||
/* union with nested struct */
|
||||
TEST_BTF_DUMP_DATA(btf, d, "union", str, union bpf_iter_link_info, BTF_F_COMPACT,
|
||||
"(union bpf_iter_link_info){.map = (struct){.map_fd = (__u32)1,},}",
|
||||
{ .map = { .map_fd = 1 }});
|
||||
|
||||
/* struct skb with nested structs/unions; because type output is so
|
||||
* complex, we don't do a string comparison, just verify we return
|
||||
* the type size as the amount of data displayed.
|
||||
*/
|
||||
type_id = btf__find_by_name(btf, "sk_buff");
|
||||
if (ASSERT_GT(type_id, 0, "find struct sk_buff")) {
|
||||
type_sz = btf__resolve_size(btf, type_id);
|
||||
str[0] = '\0';
|
||||
|
||||
ret = btf_dump__dump_type_data(d, type_id, skb, type_sz, &opts);
|
||||
ASSERT_EQ(ret, type_sz,
|
||||
"unexpected return value dumping sk_buff");
|
||||
}
|
||||
|
||||
/* overflow bpf_sock_ops struct with final element nonzero/zero.
|
||||
* Regardless of the value of the final field, we don't have all the
|
||||
* data we need to display it, so we should trigger an overflow.
|
||||
* In other words oveflow checking should trump "is field zero?"
|
||||
* checks because if we've overflowed, it shouldn't matter what the
|
||||
* field is - we can't trust its value so shouldn't display it.
|
||||
*/
|
||||
TEST_BTF_DUMP_DATA_OVER(btf, d, "struct", str, struct bpf_sock_ops,
|
||||
sizeof(struct bpf_sock_ops) - 1,
|
||||
"(struct bpf_sock_ops){\n\t.op = (__u32)1,\n",
|
||||
{ .op = 1, .skb_tcp_flags = 2});
|
||||
TEST_BTF_DUMP_DATA_OVER(btf, d, "struct", str, struct bpf_sock_ops,
|
||||
sizeof(struct bpf_sock_ops) - 1,
|
||||
"(struct bpf_sock_ops){\n\t.op = (__u32)1,\n",
|
||||
{ .op = 1, .skb_tcp_flags = 0});
|
||||
}
|
||||
|
||||
static void test_btf_dump_var_data(struct btf *btf, struct btf_dump *d,
|
||||
char *str)
|
||||
{
|
||||
TEST_BTF_DUMP_VAR(btf, d, NULL, str, "cpu_number", int, BTF_F_COMPACT,
|
||||
"int cpu_number = (int)100", 100);
|
||||
TEST_BTF_DUMP_VAR(btf, d, NULL, str, "cpu_profile_flip", int, BTF_F_COMPACT,
|
||||
"static int cpu_profile_flip = (int)2", 2);
|
||||
}
|
||||
|
||||
static void test_btf_datasec(struct btf *btf, struct btf_dump *d, char *str,
|
||||
const char *name, const char *expected_val,
|
||||
void *data, size_t data_sz)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(btf_dump_type_data_opts, opts);
|
||||
int ret = 0, cmp;
|
||||
size_t secsize;
|
||||
__s32 type_id;
|
||||
|
||||
opts.compact = true;
|
||||
|
||||
type_id = btf__find_by_name(btf, name);
|
||||
if (!ASSERT_GT(type_id, 0, "find type id"))
|
||||
return;
|
||||
|
||||
secsize = btf__resolve_size(btf, type_id);
|
||||
ASSERT_EQ(secsize, 0, "verify section size");
|
||||
|
||||
str[0] = '\0';
|
||||
ret = btf_dump__dump_type_data(d, type_id, data, data_sz, &opts);
|
||||
ASSERT_EQ(ret, 0, "unexpected return value");
|
||||
|
||||
cmp = strcmp(str, expected_val);
|
||||
ASSERT_EQ(cmp, 0, "ensure expected/actual match");
|
||||
}
|
||||
|
||||
static void test_btf_dump_datasec_data(char *str)
|
||||
{
|
||||
struct btf *btf = btf__parse("xdping_kern.o", NULL);
|
||||
struct btf_dump_opts opts = { .ctx = str };
|
||||
char license[4] = "GPL";
|
||||
struct btf_dump *d;
|
||||
|
||||
if (!ASSERT_OK_PTR(btf, "xdping_kern.o BTF not found"))
|
||||
return;
|
||||
|
||||
d = btf_dump__new(btf, NULL, &opts, btf_dump_snprintf);
|
||||
if (!ASSERT_OK_PTR(d, "could not create BTF dump"))
|
||||
return;
|
||||
|
||||
test_btf_datasec(btf, d, str, "license",
|
||||
"SEC(\"license\") char[4] _license = (char[4])['G','P','L',];",
|
||||
license, sizeof(license));
|
||||
}
|
||||
|
||||
void test_btf_dump() {
|
||||
char str[STRSIZE];
|
||||
struct btf_dump_opts opts = { .ctx = str };
|
||||
struct btf_dump *d;
|
||||
struct btf *btf;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(btf_dump_test_cases); i++) {
|
||||
@ -245,4 +831,33 @@ void test_btf_dump() {
|
||||
}
|
||||
if (test__start_subtest("btf_dump: incremental"))
|
||||
test_btf_dump_incremental();
|
||||
|
||||
btf = libbpf_find_kernel_btf();
|
||||
if (!ASSERT_OK_PTR(btf, "no kernel BTF found"))
|
||||
return;
|
||||
|
||||
d = btf_dump__new(btf, NULL, &opts, btf_dump_snprintf);
|
||||
if (!ASSERT_OK_PTR(d, "could not create BTF dump"))
|
||||
return;
|
||||
|
||||
/* Verify type display for various types. */
|
||||
if (test__start_subtest("btf_dump: int_data"))
|
||||
test_btf_dump_int_data(btf, d, str);
|
||||
if (test__start_subtest("btf_dump: float_data"))
|
||||
test_btf_dump_float_data(btf, d, str);
|
||||
if (test__start_subtest("btf_dump: char_data"))
|
||||
test_btf_dump_char_data(btf, d, str);
|
||||
if (test__start_subtest("btf_dump: typedef_data"))
|
||||
test_btf_dump_typedef_data(btf, d, str);
|
||||
if (test__start_subtest("btf_dump: enum_data"))
|
||||
test_btf_dump_enum_data(btf, d, str);
|
||||
if (test__start_subtest("btf_dump: struct_data"))
|
||||
test_btf_dump_struct_data(btf, d, str);
|
||||
if (test__start_subtest("btf_dump: var_data"))
|
||||
test_btf_dump_var_data(btf, d, str);
|
||||
btf_dump__free(d);
|
||||
btf__free(btf);
|
||||
|
||||
if (test__start_subtest("btf_dump: datasec_data"))
|
||||
test_btf_dump_datasec_data(str);
|
||||
}
|
||||
|
@ -53,8 +53,8 @@ void test_core_autosize(void)
|
||||
char btf_file[] = "/tmp/core_autosize.btf.XXXXXX";
|
||||
int err, fd = -1, zero = 0;
|
||||
int char_id, short_id, int_id, long_long_id, void_ptr_id, id;
|
||||
DECLARE_LIBBPF_OPTS(bpf_object_open_opts, open_opts);
|
||||
struct test_core_autosize* skel = NULL;
|
||||
struct bpf_object_load_attr load_attr = {};
|
||||
struct bpf_program *prog;
|
||||
struct bpf_map *bss_map;
|
||||
struct btf *btf = NULL;
|
||||
@ -125,9 +125,10 @@ void test_core_autosize(void)
|
||||
fd = -1;
|
||||
|
||||
/* open and load BPF program with custom BTF as the kernel BTF */
|
||||
skel = test_core_autosize__open();
|
||||
open_opts.btf_custom_path = btf_file;
|
||||
skel = test_core_autosize__open_opts(&open_opts);
|
||||
if (!ASSERT_OK_PTR(skel, "skel_open"))
|
||||
return;
|
||||
goto cleanup;
|
||||
|
||||
/* disable handle_signed() for now */
|
||||
prog = bpf_object__find_program_by_name(skel->obj, "handle_signed");
|
||||
@ -135,9 +136,7 @@ void test_core_autosize(void)
|
||||
goto cleanup;
|
||||
bpf_program__set_autoload(prog, false);
|
||||
|
||||
load_attr.obj = skel->obj;
|
||||
load_attr.target_btf_path = btf_file;
|
||||
err = bpf_object__load_xattr(&load_attr);
|
||||
err = bpf_object__load(skel->obj);
|
||||
if (!ASSERT_OK(err, "prog_load"))
|
||||
goto cleanup;
|
||||
|
||||
@ -204,14 +203,13 @@ void test_core_autosize(void)
|
||||
skel = NULL;
|
||||
|
||||
/* now re-load with handle_signed() enabled, it should fail loading */
|
||||
skel = test_core_autosize__open();
|
||||
open_opts.btf_custom_path = btf_file;
|
||||
skel = test_core_autosize__open_opts(&open_opts);
|
||||
if (!ASSERT_OK_PTR(skel, "skel_open"))
|
||||
return;
|
||||
goto cleanup;
|
||||
|
||||
load_attr.obj = skel->obj;
|
||||
load_attr.target_btf_path = btf_file;
|
||||
err = bpf_object__load_xattr(&load_attr);
|
||||
if (!ASSERT_ERR(err, "bad_prog_load"))
|
||||
err = test_core_autosize__load(skel);
|
||||
if (!ASSERT_ERR(err, "skel_load"))
|
||||
goto cleanup;
|
||||
|
||||
cleanup:
|
||||
|
@ -816,7 +816,7 @@ static size_t roundup_page(size_t sz)
|
||||
void test_core_reloc(void)
|
||||
{
|
||||
const size_t mmap_sz = roundup_page(sizeof(struct data));
|
||||
struct bpf_object_load_attr load_attr = {};
|
||||
DECLARE_LIBBPF_OPTS(bpf_object_open_opts, open_opts);
|
||||
struct core_reloc_test_case *test_case;
|
||||
const char *tp_name, *probe_name;
|
||||
int err, i, equal;
|
||||
@ -846,9 +846,16 @@ void test_core_reloc(void)
|
||||
continue;
|
||||
}
|
||||
|
||||
obj = bpf_object__open_file(test_case->bpf_obj_file, NULL);
|
||||
if (test_case->btf_src_file) {
|
||||
err = access(test_case->btf_src_file, R_OK);
|
||||
if (!ASSERT_OK(err, "btf_src_file"))
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
open_opts.btf_custom_path = test_case->btf_src_file;
|
||||
obj = bpf_object__open_file(test_case->bpf_obj_file, &open_opts);
|
||||
if (!ASSERT_OK_PTR(obj, "obj_open"))
|
||||
continue;
|
||||
goto cleanup;
|
||||
|
||||
probe_name = "raw_tracepoint/sys_enter";
|
||||
tp_name = "sys_enter";
|
||||
@ -862,17 +869,7 @@ void test_core_reloc(void)
|
||||
"prog '%s' not found\n", probe_name))
|
||||
goto cleanup;
|
||||
|
||||
|
||||
if (test_case->btf_src_file) {
|
||||
err = access(test_case->btf_src_file, R_OK);
|
||||
if (!ASSERT_OK(err, "btf_src_file"))
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
load_attr.obj = obj;
|
||||
load_attr.log_level = 0;
|
||||
load_attr.target_btf_path = test_case->btf_src_file;
|
||||
err = bpf_object__load_xattr(&load_attr);
|
||||
err = bpf_object__load(obj);
|
||||
if (err) {
|
||||
if (!test_case->fails)
|
||||
ASSERT_OK(err, "obj_load");
|
||||
|
@ -17,6 +17,7 @@ void test_get_func_ip_test(void)
|
||||
*/
|
||||
#ifndef __x86_64__
|
||||
bpf_program__set_autoload(skel->progs.test6, false);
|
||||
bpf_program__set_autoload(skel->progs.test7, false);
|
||||
#endif
|
||||
|
||||
err = get_func_ip_test__load(skel);
|
||||
@ -46,6 +47,7 @@ void test_get_func_ip_test(void)
|
||||
ASSERT_EQ(skel->bss->test5_result, 1, "test5_result");
|
||||
#ifdef __x86_64__
|
||||
ASSERT_EQ(skel->bss->test6_result, 1, "test6_result");
|
||||
ASSERT_EQ(skel->bss->test7_result, 1, "test7_result");
|
||||
#endif
|
||||
|
||||
cleanup:
|
||||
|
@ -125,6 +125,10 @@ void test_pinning(void)
|
||||
if (CHECK(err, "pin maps", "err %d errno %d\n", err, errno))
|
||||
goto out;
|
||||
|
||||
/* get pinning path */
|
||||
if (!ASSERT_STREQ(bpf_map__pin_path(map), pinpath, "get pin path"))
|
||||
goto out;
|
||||
|
||||
/* set pinning path of other map and re-pin all */
|
||||
map = bpf_object__find_map_by_name(obj, "nopinmap");
|
||||
if (CHECK(!map, "find map", "NULL map"))
|
||||
@ -134,6 +138,11 @@ void test_pinning(void)
|
||||
if (CHECK(err, "set pin path", "err %d errno %d\n", err, errno))
|
||||
goto out;
|
||||
|
||||
/* get pinning path after set */
|
||||
if (!ASSERT_STREQ(bpf_map__pin_path(map), custpinpath,
|
||||
"get pin path after set"))
|
||||
goto out;
|
||||
|
||||
/* should only pin the one unpinned map */
|
||||
err = bpf_object__pin_maps(obj, NULL);
|
||||
if (CHECK(err, "pin maps", "err %d errno %d\n", err, errno))
|
||||
|
@ -13,15 +13,16 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <linux/if.h>
|
||||
#include <linux/if_tun.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/sysctl.h>
|
||||
#include <linux/if_tun.h>
|
||||
#include <linux/if.h>
|
||||
#include <sched.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "test_progs.h"
|
||||
#include "network_helpers.h"
|
||||
@ -389,11 +390,21 @@ done:
|
||||
close(client_fd);
|
||||
}
|
||||
|
||||
static char *ping_command(int family)
|
||||
{
|
||||
if (family == AF_INET6) {
|
||||
/* On some systems 'ping' doesn't support IPv6, so use ping6 if it is present. */
|
||||
if (!system("which ping6 >/dev/null 2>&1"))
|
||||
return "ping6";
|
||||
else
|
||||
return "ping -6";
|
||||
}
|
||||
return "ping";
|
||||
}
|
||||
|
||||
static int test_ping(int family, const char *addr)
|
||||
{
|
||||
const char *ping = family == AF_INET6 ? "ping6" : "ping";
|
||||
|
||||
SYS("ip netns exec " NS_SRC " %s " PING_ARGS " %s > /dev/null", ping, addr);
|
||||
SYS("ip netns exec " NS_SRC " %s " PING_ARGS " %s > /dev/null", ping_command(family), addr);
|
||||
return 0;
|
||||
fail:
|
||||
return -1;
|
||||
|
72
tools/testing/selftests/bpf/progs/bpf_iter_setsockopt.c
Normal file
72
tools/testing/selftests/bpf/progs/bpf_iter_setsockopt.c
Normal file
@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2021 Facebook */
|
||||
#include "bpf_iter.h"
|
||||
#include "bpf_tracing_net.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
|
||||
#define bpf_tcp_sk(skc) ({ \
|
||||
struct sock_common *_skc = skc; \
|
||||
sk = NULL; \
|
||||
tp = NULL; \
|
||||
if (_skc) { \
|
||||
tp = bpf_skc_to_tcp_sock(_skc); \
|
||||
sk = (struct sock *)tp; \
|
||||
} \
|
||||
tp; \
|
||||
})
|
||||
|
||||
unsigned short reuse_listen_hport = 0;
|
||||
unsigned short listen_hport = 0;
|
||||
char cubic_cc[TCP_CA_NAME_MAX] = "bpf_cubic";
|
||||
char dctcp_cc[TCP_CA_NAME_MAX] = "bpf_dctcp";
|
||||
bool random_retry = false;
|
||||
|
||||
static bool tcp_cc_eq(const char *a, const char *b)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < TCP_CA_NAME_MAX; i++) {
|
||||
if (a[i] != b[i])
|
||||
return false;
|
||||
if (!a[i])
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SEC("iter/tcp")
|
||||
int change_tcp_cc(struct bpf_iter__tcp *ctx)
|
||||
{
|
||||
char cur_cc[TCP_CA_NAME_MAX];
|
||||
struct tcp_sock *tp;
|
||||
struct sock *sk;
|
||||
int ret;
|
||||
|
||||
if (!bpf_tcp_sk(ctx->sk_common))
|
||||
return 0;
|
||||
|
||||
if (sk->sk_family != AF_INET6 ||
|
||||
(sk->sk_state != TCP_LISTEN &&
|
||||
sk->sk_state != TCP_ESTABLISHED) ||
|
||||
(sk->sk_num != reuse_listen_hport &&
|
||||
sk->sk_num != listen_hport &&
|
||||
bpf_ntohs(sk->sk_dport) != listen_hport))
|
||||
return 0;
|
||||
|
||||
if (bpf_getsockopt(tp, SOL_TCP, TCP_CONGESTION,
|
||||
cur_cc, sizeof(cur_cc)))
|
||||
return 0;
|
||||
|
||||
if (!tcp_cc_eq(cur_cc, cubic_cc))
|
||||
return 0;
|
||||
|
||||
if (random_retry && bpf_get_prandom_u32() % 4 == 1)
|
||||
return 1;
|
||||
|
||||
bpf_setsockopt(tp, SOL_TCP, TCP_CONGESTION, dctcp_cc, sizeof(dctcp_cc));
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
@ -5,6 +5,10 @@
|
||||
#define AF_INET 2
|
||||
#define AF_INET6 10
|
||||
|
||||
#define SOL_TCP 6
|
||||
#define TCP_CONGESTION 13
|
||||
#define TCP_CA_NAME_MAX 16
|
||||
|
||||
#define ICSK_TIME_RETRANS 1
|
||||
#define ICSK_TIME_PROBE0 3
|
||||
#define ICSK_TIME_LOSS_PROBE 5
|
||||
@ -32,6 +36,8 @@
|
||||
#define ir_v6_rmt_addr req.__req_common.skc_v6_daddr
|
||||
#define ir_v6_loc_addr req.__req_common.skc_v6_rcv_saddr
|
||||
|
||||
#define sk_num __sk_common.skc_num
|
||||
#define sk_dport __sk_common.skc_dport
|
||||
#define sk_family __sk_common.skc_family
|
||||
#define sk_rmem_alloc sk_backlog.rmem_alloc
|
||||
#define sk_refcnt __sk_common.skc_refcnt
|
||||
|
@ -11,6 +11,7 @@ extern const void bpf_fentry_test3 __ksym;
|
||||
extern const void bpf_fentry_test4 __ksym;
|
||||
extern const void bpf_modify_return_test __ksym;
|
||||
extern const void bpf_fentry_test6 __ksym;
|
||||
extern const void bpf_fentry_test7 __ksym;
|
||||
|
||||
__u64 test1_result = 0;
|
||||
SEC("fentry/bpf_fentry_test1")
|
||||
@ -71,3 +72,13 @@ int test6(struct pt_regs *ctx)
|
||||
test6_result = (const void *) addr == &bpf_fentry_test6 + 5;
|
||||
return 0;
|
||||
}
|
||||
|
||||
__u64 test7_result = 0;
|
||||
SEC("kprobe/bpf_fentry_test7+5")
|
||||
int test7(struct pt_regs *ctx)
|
||||
{
|
||||
__u64 addr = bpf_get_func_ip(ctx);
|
||||
|
||||
test7_result = (const void *) addr == &bpf_fentry_test7 + 5;
|
||||
return 0;
|
||||
}
|
||||
|
@ -13,21 +13,21 @@
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE);
|
||||
__type(key, struct bpf_cgroup_storage_key);
|
||||
__type(value, struct percpu_net_cnt);
|
||||
__type(value, union percpu_net_cnt);
|
||||
} percpu_netcnt SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_CGROUP_STORAGE);
|
||||
__type(key, struct bpf_cgroup_storage_key);
|
||||
__type(value, struct net_cnt);
|
||||
__type(value, union net_cnt);
|
||||
} netcnt SEC(".maps");
|
||||
|
||||
SEC("cgroup/skb")
|
||||
int bpf_nextcnt(struct __sk_buff *skb)
|
||||
{
|
||||
struct percpu_net_cnt *percpu_cnt;
|
||||
union percpu_net_cnt *percpu_cnt;
|
||||
char fmt[] = "%d %llu %llu\n";
|
||||
struct net_cnt *cnt;
|
||||
union net_cnt *cnt;
|
||||
__u64 ts, dt;
|
||||
int ret;
|
||||
|
||||
|
26
tools/testing/selftests/bpf/progs/test_map_in_map_invalid.c
Normal file
26
tools/testing/selftests/bpf/progs/test_map_in_map_invalid.c
Normal file
@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2021 Isovalent, Inc. */
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
struct inner {
|
||||
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||
__type(key, __u32);
|
||||
__type(value, int);
|
||||
__uint(max_entries, 4);
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
|
||||
__uint(max_entries, 0); /* This will make map creation to fail */
|
||||
__uint(key_size, sizeof(__u32));
|
||||
__array(values, struct inner);
|
||||
} mim SEC(".maps");
|
||||
|
||||
SEC("xdp")
|
||||
int xdp_noop0(struct xdp_md *ctx)
|
||||
{
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
586
tools/testing/selftests/bpf/test_bpftool_synctypes.py
Executable file
586
tools/testing/selftests/bpf/test_bpftool_synctypes.py
Executable file
@ -0,0 +1,586 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
#
|
||||
# Copyright (C) 2021 Isovalent, Inc.
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import os, sys
|
||||
|
||||
LINUX_ROOT = os.path.abspath(os.path.join(__file__,
|
||||
os.pardir, os.pardir, os.pardir, os.pardir, os.pardir))
|
||||
BPFTOOL_DIR = os.path.join(LINUX_ROOT, 'tools/bpf/bpftool')
|
||||
retval = 0
|
||||
|
||||
class BlockParser(object):
|
||||
"""
|
||||
A parser for extracting set of values from blocks such as enums.
|
||||
@reader: a pointer to the open file to parse
|
||||
"""
|
||||
def __init__(self, reader):
|
||||
self.reader = reader
|
||||
|
||||
def search_block(self, start_marker):
|
||||
"""
|
||||
Search for a given structure in a file.
|
||||
@start_marker: regex marking the beginning of a structure to parse
|
||||
"""
|
||||
offset = self.reader.tell()
|
||||
array_start = re.search(start_marker, self.reader.read())
|
||||
if array_start is None:
|
||||
raise Exception('Failed to find start of block')
|
||||
self.reader.seek(offset + array_start.start())
|
||||
|
||||
def parse(self, pattern, end_marker):
|
||||
"""
|
||||
Parse a block and return a set of values. Values to extract must be
|
||||
on separate lines in the file.
|
||||
@pattern: pattern used to identify the values to extract
|
||||
@end_marker: regex marking the end of the block to parse
|
||||
"""
|
||||
entries = set()
|
||||
while True:
|
||||
line = self.reader.readline()
|
||||
if not line or re.match(end_marker, line):
|
||||
break
|
||||
capture = pattern.search(line)
|
||||
if capture and pattern.groups >= 1:
|
||||
entries.add(capture.group(1))
|
||||
return entries
|
||||
|
||||
class ArrayParser(BlockParser):
|
||||
"""
|
||||
A parser for extracting dicionaries of values from some BPF-related arrays.
|
||||
@reader: a pointer to the open file to parse
|
||||
@array_name: name of the array to parse
|
||||
"""
|
||||
end_marker = re.compile('^};')
|
||||
|
||||
def __init__(self, reader, array_name):
|
||||
self.array_name = array_name
|
||||
self.start_marker = re.compile(f'(static )?const char \* const {self.array_name}\[.*\] = {{\n')
|
||||
super().__init__(reader)
|
||||
|
||||
def search_block(self):
|
||||
"""
|
||||
Search for the given array in a file.
|
||||
"""
|
||||
super().search_block(self.start_marker);
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
Parse a block and return data as a dictionary. Items to extract must be
|
||||
on separate lines in the file.
|
||||
"""
|
||||
pattern = re.compile('\[(BPF_\w*)\]\s*= "(.*)",?$')
|
||||
entries = {}
|
||||
while True:
|
||||
line = self.reader.readline()
|
||||
if line == '' or re.match(self.end_marker, line):
|
||||
break
|
||||
capture = pattern.search(line)
|
||||
if capture:
|
||||
entries[capture.group(1)] = capture.group(2)
|
||||
return entries
|
||||
|
||||
class InlineListParser(BlockParser):
|
||||
"""
|
||||
A parser for extracting set of values from inline lists.
|
||||
"""
|
||||
def parse(self, pattern, end_marker):
|
||||
"""
|
||||
Parse a block and return a set of values. Multiple values to extract
|
||||
can be on a same line in the file.
|
||||
@pattern: pattern used to identify the values to extract
|
||||
@end_marker: regex marking the end of the block to parse
|
||||
"""
|
||||
entries = set()
|
||||
while True:
|
||||
line = self.reader.readline()
|
||||
if not line:
|
||||
break
|
||||
entries.update(pattern.findall(line))
|
||||
if re.search(end_marker, line):
|
||||
break
|
||||
return entries
|
||||
|
||||
class FileExtractor(object):
|
||||
"""
|
||||
A generic reader for extracting data from a given file. This class contains
|
||||
several helper methods that wrap arround parser objects to extract values
|
||||
from different structures.
|
||||
This class does not offer a way to set a filename, which is expected to be
|
||||
defined in children classes.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.reader = open(self.filename, 'r')
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the file used by the parser.
|
||||
"""
|
||||
self.reader.close()
|
||||
|
||||
def reset_read(self):
|
||||
"""
|
||||
Reset the file position indicator for this parser. This is useful when
|
||||
parsing several structures in the file without respecting the order in
|
||||
which those structures appear in the file.
|
||||
"""
|
||||
self.reader.seek(0)
|
||||
|
||||
def get_types_from_array(self, array_name):
|
||||
"""
|
||||
Search for and parse an array associating names to BPF_* enum members,
|
||||
for example:
|
||||
|
||||
const char * const prog_type_name[] = {
|
||||
[BPF_PROG_TYPE_UNSPEC] = "unspec",
|
||||
[BPF_PROG_TYPE_SOCKET_FILTER] = "socket_filter",
|
||||
[BPF_PROG_TYPE_KPROBE] = "kprobe",
|
||||
};
|
||||
|
||||
Return a dictionary with the enum member names as keys and the
|
||||
associated names as values, for example:
|
||||
|
||||
{'BPF_PROG_TYPE_UNSPEC': 'unspec',
|
||||
'BPF_PROG_TYPE_SOCKET_FILTER': 'socket_filter',
|
||||
'BPF_PROG_TYPE_KPROBE': 'kprobe'}
|
||||
|
||||
@array_name: name of the array to parse
|
||||
"""
|
||||
array_parser = ArrayParser(self.reader, array_name)
|
||||
array_parser.search_block()
|
||||
return array_parser.parse()
|
||||
|
||||
def get_enum(self, enum_name):
|
||||
"""
|
||||
Search for and parse an enum containing BPF_* members, for example:
|
||||
|
||||
enum bpf_prog_type {
|
||||
BPF_PROG_TYPE_UNSPEC,
|
||||
BPF_PROG_TYPE_SOCKET_FILTER,
|
||||
BPF_PROG_TYPE_KPROBE,
|
||||
};
|
||||
|
||||
Return a set containing all member names, for example:
|
||||
|
||||
{'BPF_PROG_TYPE_UNSPEC',
|
||||
'BPF_PROG_TYPE_SOCKET_FILTER',
|
||||
'BPF_PROG_TYPE_KPROBE'}
|
||||
|
||||
@enum_name: name of the enum to parse
|
||||
"""
|
||||
start_marker = re.compile(f'enum {enum_name} {{\n')
|
||||
pattern = re.compile('^\s*(BPF_\w+),?$')
|
||||
end_marker = re.compile('^};')
|
||||
parser = BlockParser(self.reader)
|
||||
parser.search_block(start_marker)
|
||||
return parser.parse(pattern, end_marker)
|
||||
|
||||
def __get_description_list(self, start_marker, pattern, end_marker):
|
||||
parser = InlineListParser(self.reader)
|
||||
parser.search_block(start_marker)
|
||||
return parser.parse(pattern, end_marker)
|
||||
|
||||
def get_rst_list(self, block_name):
|
||||
"""
|
||||
Search for and parse a list of type names from RST documentation, for
|
||||
example:
|
||||
|
||||
| *TYPE* := {
|
||||
| **socket** | **kprobe** |
|
||||
| **kretprobe**
|
||||
| }
|
||||
|
||||
Return a set containing all type names, for example:
|
||||
|
||||
{'socket', 'kprobe', 'kretprobe'}
|
||||
|
||||
@block_name: name of the blog to parse, 'TYPE' in the example
|
||||
"""
|
||||
start_marker = re.compile(f'\*{block_name}\* := {{')
|
||||
pattern = re.compile('\*\*([\w/-]+)\*\*')
|
||||
end_marker = re.compile('}\n')
|
||||
return self.__get_description_list(start_marker, pattern, end_marker)
|
||||
|
||||
def get_help_list(self, block_name):
|
||||
"""
|
||||
Search for and parse a list of type names from a help message in
|
||||
bpftool, for example:
|
||||
|
||||
" TYPE := { socket | kprobe |\\n"
|
||||
" kretprobe }\\n"
|
||||
|
||||
Return a set containing all type names, for example:
|
||||
|
||||
{'socket', 'kprobe', 'kretprobe'}
|
||||
|
||||
@block_name: name of the blog to parse, 'TYPE' in the example
|
||||
"""
|
||||
start_marker = re.compile(f'"\s*{block_name} := {{')
|
||||
pattern = re.compile('([\w/]+) [|}]')
|
||||
end_marker = re.compile('}')
|
||||
return self.__get_description_list(start_marker, pattern, end_marker)
|
||||
|
||||
def get_help_list_macro(self, macro):
|
||||
"""
|
||||
Search for and parse a list of values from a help message starting with
|
||||
a macro in bpftool, for example:
|
||||
|
||||
" " HELP_SPEC_OPTIONS " |\\n"
|
||||
" {-f|--bpffs} | {-m|--mapcompat} | {-n|--nomount} }\\n"
|
||||
|
||||
Return a set containing all item names, for example:
|
||||
|
||||
{'-f', '--bpffs', '-m', '--mapcompat', '-n', '--nomount'}
|
||||
|
||||
@macro: macro starting the block, 'HELP_SPEC_OPTIONS' in the example
|
||||
"""
|
||||
start_marker = re.compile(f'"\s*{macro}\s*" [|}}]')
|
||||
pattern = re.compile('([\w-]+) ?(?:\||}[ }\]])')
|
||||
end_marker = re.compile('}\\\\n')
|
||||
return self.__get_description_list(start_marker, pattern, end_marker)
|
||||
|
||||
def default_options(self):
|
||||
"""
|
||||
Return the default options contained in HELP_SPEC_OPTIONS
|
||||
"""
|
||||
return { '-j', '--json', '-p', '--pretty', '-d', '--debug' }
|
||||
|
||||
def get_bashcomp_list(self, block_name):
|
||||
"""
|
||||
Search for and parse a list of type names from a variable in bash
|
||||
completion file, for example:
|
||||
|
||||
local BPFTOOL_PROG_LOAD_TYPES='socket kprobe \\
|
||||
kretprobe'
|
||||
|
||||
Return a set containing all type names, for example:
|
||||
|
||||
{'socket', 'kprobe', 'kretprobe'}
|
||||
|
||||
@block_name: name of the blog to parse, 'TYPE' in the example
|
||||
"""
|
||||
start_marker = re.compile(f'local {block_name}=\'')
|
||||
pattern = re.compile('(?:.*=\')?([\w/]+)')
|
||||
end_marker = re.compile('\'$')
|
||||
return self.__get_description_list(start_marker, pattern, end_marker)
|
||||
|
||||
class SourceFileExtractor(FileExtractor):
|
||||
"""
|
||||
An abstract extractor for a source file with usage message.
|
||||
This class does not offer a way to set a filename, which is expected to be
|
||||
defined in children classes.
|
||||
"""
|
||||
def get_options(self):
|
||||
return self.default_options().union(self.get_help_list_macro('HELP_SPEC_OPTIONS'))
|
||||
|
||||
class ProgFileExtractor(SourceFileExtractor):
|
||||
"""
|
||||
An extractor for bpftool's prog.c.
|
||||
"""
|
||||
filename = os.path.join(BPFTOOL_DIR, 'prog.c')
|
||||
|
||||
def get_prog_types(self):
|
||||
return self.get_types_from_array('prog_type_name')
|
||||
|
||||
def get_attach_types(self):
|
||||
return self.get_types_from_array('attach_type_strings')
|
||||
|
||||
def get_prog_attach_help(self):
|
||||
return self.get_help_list('ATTACH_TYPE')
|
||||
|
||||
class MapFileExtractor(SourceFileExtractor):
|
||||
"""
|
||||
An extractor for bpftool's map.c.
|
||||
"""
|
||||
filename = os.path.join(BPFTOOL_DIR, 'map.c')
|
||||
|
||||
def get_map_types(self):
|
||||
return self.get_types_from_array('map_type_name')
|
||||
|
||||
def get_map_help(self):
|
||||
return self.get_help_list('TYPE')
|
||||
|
||||
class CgroupFileExtractor(SourceFileExtractor):
|
||||
"""
|
||||
An extractor for bpftool's cgroup.c.
|
||||
"""
|
||||
filename = os.path.join(BPFTOOL_DIR, 'cgroup.c')
|
||||
|
||||
def get_prog_attach_help(self):
|
||||
return self.get_help_list('ATTACH_TYPE')
|
||||
|
||||
class CommonFileExtractor(SourceFileExtractor):
|
||||
"""
|
||||
An extractor for bpftool's common.c.
|
||||
"""
|
||||
filename = os.path.join(BPFTOOL_DIR, 'common.c')
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.attach_types = {}
|
||||
|
||||
def get_attach_types(self):
|
||||
if not self.attach_types:
|
||||
self.attach_types = self.get_types_from_array('attach_type_name')
|
||||
return self.attach_types
|
||||
|
||||
def get_cgroup_attach_types(self):
|
||||
if not self.attach_types:
|
||||
self.get_attach_types()
|
||||
cgroup_types = {}
|
||||
for (key, value) in self.attach_types.items():
|
||||
if key.find('BPF_CGROUP') != -1:
|
||||
cgroup_types[key] = value
|
||||
return cgroup_types
|
||||
|
||||
class GenericSourceExtractor(SourceFileExtractor):
|
||||
"""
|
||||
An extractor for generic source code files.
|
||||
"""
|
||||
filename = ""
|
||||
|
||||
def __init__(self, filename):
|
||||
self.filename = os.path.join(BPFTOOL_DIR, filename)
|
||||
super().__init__()
|
||||
|
||||
class BpfHeaderExtractor(FileExtractor):
|
||||
"""
|
||||
An extractor for the UAPI BPF header.
|
||||
"""
|
||||
filename = os.path.join(LINUX_ROOT, 'tools/include/uapi/linux/bpf.h')
|
||||
|
||||
def get_prog_types(self):
|
||||
return self.get_enum('bpf_prog_type')
|
||||
|
||||
def get_map_types(self):
|
||||
return self.get_enum('bpf_map_type')
|
||||
|
||||
def get_attach_types(self):
|
||||
return self.get_enum('bpf_attach_type')
|
||||
|
||||
class ManPageExtractor(FileExtractor):
|
||||
"""
|
||||
An abstract extractor for an RST documentation page.
|
||||
This class does not offer a way to set a filename, which is expected to be
|
||||
defined in children classes.
|
||||
"""
|
||||
def get_options(self):
|
||||
return self.get_rst_list('OPTIONS')
|
||||
|
||||
class ManProgExtractor(ManPageExtractor):
|
||||
"""
|
||||
An extractor for bpftool-prog.rst.
|
||||
"""
|
||||
filename = os.path.join(BPFTOOL_DIR, 'Documentation/bpftool-prog.rst')
|
||||
|
||||
def get_attach_types(self):
|
||||
return self.get_rst_list('ATTACH_TYPE')
|
||||
|
||||
class ManMapExtractor(ManPageExtractor):
|
||||
"""
|
||||
An extractor for bpftool-map.rst.
|
||||
"""
|
||||
filename = os.path.join(BPFTOOL_DIR, 'Documentation/bpftool-map.rst')
|
||||
|
||||
def get_map_types(self):
|
||||
return self.get_rst_list('TYPE')
|
||||
|
||||
class ManCgroupExtractor(ManPageExtractor):
|
||||
"""
|
||||
An extractor for bpftool-cgroup.rst.
|
||||
"""
|
||||
filename = os.path.join(BPFTOOL_DIR, 'Documentation/bpftool-cgroup.rst')
|
||||
|
||||
def get_attach_types(self):
|
||||
return self.get_rst_list('ATTACH_TYPE')
|
||||
|
||||
class ManGenericExtractor(ManPageExtractor):
|
||||
"""
|
||||
An extractor for generic RST documentation pages.
|
||||
"""
|
||||
filename = ""
|
||||
|
||||
def __init__(self, filename):
|
||||
self.filename = os.path.join(BPFTOOL_DIR, filename)
|
||||
super().__init__()
|
||||
|
||||
class BashcompExtractor(FileExtractor):
|
||||
"""
|
||||
An extractor for bpftool's bash completion file.
|
||||
"""
|
||||
filename = os.path.join(BPFTOOL_DIR, 'bash-completion/bpftool')
|
||||
|
||||
def get_prog_attach_types(self):
|
||||
return self.get_bashcomp_list('BPFTOOL_PROG_ATTACH_TYPES')
|
||||
|
||||
def get_map_types(self):
|
||||
return self.get_bashcomp_list('BPFTOOL_MAP_CREATE_TYPES')
|
||||
|
||||
def get_cgroup_attach_types(self):
|
||||
return self.get_bashcomp_list('BPFTOOL_CGROUP_ATTACH_TYPES')
|
||||
|
||||
def verify(first_set, second_set, message):
|
||||
"""
|
||||
Print all values that differ between two sets.
|
||||
@first_set: one set to compare
|
||||
@second_set: another set to compare
|
||||
@message: message to print for values belonging to only one of the sets
|
||||
"""
|
||||
global retval
|
||||
diff = first_set.symmetric_difference(second_set)
|
||||
if diff:
|
||||
print(message, diff)
|
||||
retval = 1
|
||||
|
||||
def main():
|
||||
# No arguments supported at this time, but print usage for -h|--help
|
||||
argParser = argparse.ArgumentParser(description="""
|
||||
Verify that bpftool's code, help messages, documentation and bash
|
||||
completion are all in sync on program types, map types, attach types, and
|
||||
options. Also check that bpftool is in sync with the UAPI BPF header.
|
||||
""")
|
||||
args = argParser.parse_args()
|
||||
|
||||
# Map types (enum)
|
||||
|
||||
bpf_info = BpfHeaderExtractor()
|
||||
ref = bpf_info.get_map_types()
|
||||
|
||||
map_info = MapFileExtractor()
|
||||
source_map_items = map_info.get_map_types()
|
||||
map_types_enum = set(source_map_items.keys())
|
||||
|
||||
verify(ref, map_types_enum,
|
||||
f'Comparing BPF header (enum bpf_map_type) and {MapFileExtractor.filename} (map_type_name):')
|
||||
|
||||
# Map types (names)
|
||||
|
||||
source_map_types = set(source_map_items.values())
|
||||
source_map_types.discard('unspec')
|
||||
|
||||
help_map_types = map_info.get_map_help()
|
||||
help_map_options = map_info.get_options()
|
||||
map_info.close()
|
||||
|
||||
man_map_info = ManMapExtractor()
|
||||
man_map_options = man_map_info.get_options()
|
||||
man_map_types = man_map_info.get_map_types()
|
||||
man_map_info.close()
|
||||
|
||||
bashcomp_info = BashcompExtractor()
|
||||
bashcomp_map_types = bashcomp_info.get_map_types()
|
||||
|
||||
verify(source_map_types, help_map_types,
|
||||
f'Comparing {MapFileExtractor.filename} (map_type_name) and {MapFileExtractor.filename} (do_help() TYPE):')
|
||||
verify(source_map_types, man_map_types,
|
||||
f'Comparing {MapFileExtractor.filename} (map_type_name) and {ManMapExtractor.filename} (TYPE):')
|
||||
verify(help_map_options, man_map_options,
|
||||
f'Comparing {MapFileExtractor.filename} (do_help() OPTIONS) and {ManMapExtractor.filename} (OPTIONS):')
|
||||
verify(source_map_types, bashcomp_map_types,
|
||||
f'Comparing {MapFileExtractor.filename} (map_type_name) and {BashcompExtractor.filename} (BPFTOOL_MAP_CREATE_TYPES):')
|
||||
|
||||
# Program types (enum)
|
||||
|
||||
ref = bpf_info.get_prog_types()
|
||||
|
||||
prog_info = ProgFileExtractor()
|
||||
prog_types = set(prog_info.get_prog_types().keys())
|
||||
|
||||
verify(ref, prog_types,
|
||||
f'Comparing BPF header (enum bpf_prog_type) and {ProgFileExtractor.filename} (prog_type_name):')
|
||||
|
||||
# Attach types (enum)
|
||||
|
||||
ref = bpf_info.get_attach_types()
|
||||
bpf_info.close()
|
||||
|
||||
common_info = CommonFileExtractor()
|
||||
attach_types = common_info.get_attach_types()
|
||||
|
||||
verify(ref, attach_types,
|
||||
f'Comparing BPF header (enum bpf_attach_type) and {CommonFileExtractor.filename} (attach_type_name):')
|
||||
|
||||
# Attach types (names)
|
||||
|
||||
source_prog_attach_types = set(prog_info.get_attach_types().values())
|
||||
|
||||
help_prog_attach_types = prog_info.get_prog_attach_help()
|
||||
help_prog_options = prog_info.get_options()
|
||||
prog_info.close()
|
||||
|
||||
man_prog_info = ManProgExtractor()
|
||||
man_prog_options = man_prog_info.get_options()
|
||||
man_prog_attach_types = man_prog_info.get_attach_types()
|
||||
man_prog_info.close()
|
||||
|
||||
bashcomp_info.reset_read() # We stopped at map types, rewind
|
||||
bashcomp_prog_attach_types = bashcomp_info.get_prog_attach_types()
|
||||
|
||||
verify(source_prog_attach_types, help_prog_attach_types,
|
||||
f'Comparing {ProgFileExtractor.filename} (attach_type_strings) and {ProgFileExtractor.filename} (do_help() ATTACH_TYPE):')
|
||||
verify(source_prog_attach_types, man_prog_attach_types,
|
||||
f'Comparing {ProgFileExtractor.filename} (attach_type_strings) and {ManProgExtractor.filename} (ATTACH_TYPE):')
|
||||
verify(help_prog_options, man_prog_options,
|
||||
f'Comparing {ProgFileExtractor.filename} (do_help() OPTIONS) and {ManProgExtractor.filename} (OPTIONS):')
|
||||
verify(source_prog_attach_types, bashcomp_prog_attach_types,
|
||||
f'Comparing {ProgFileExtractor.filename} (attach_type_strings) and {BashcompExtractor.filename} (BPFTOOL_PROG_ATTACH_TYPES):')
|
||||
|
||||
# Cgroup attach types
|
||||
|
||||
source_cgroup_attach_types = set(common_info.get_cgroup_attach_types().values())
|
||||
common_info.close()
|
||||
|
||||
cgroup_info = CgroupFileExtractor()
|
||||
help_cgroup_attach_types = cgroup_info.get_prog_attach_help()
|
||||
help_cgroup_options = cgroup_info.get_options()
|
||||
cgroup_info.close()
|
||||
|
||||
man_cgroup_info = ManCgroupExtractor()
|
||||
man_cgroup_options = man_cgroup_info.get_options()
|
||||
man_cgroup_attach_types = man_cgroup_info.get_attach_types()
|
||||
man_cgroup_info.close()
|
||||
|
||||
bashcomp_cgroup_attach_types = bashcomp_info.get_cgroup_attach_types()
|
||||
bashcomp_info.close()
|
||||
|
||||
verify(source_cgroup_attach_types, help_cgroup_attach_types,
|
||||
f'Comparing {CommonFileExtractor.filename} (attach_type_strings) and {CgroupFileExtractor.filename} (do_help() ATTACH_TYPE):')
|
||||
verify(source_cgroup_attach_types, man_cgroup_attach_types,
|
||||
f'Comparing {CommonFileExtractor.filename} (attach_type_strings) and {ManCgroupExtractor.filename} (ATTACH_TYPE):')
|
||||
verify(help_cgroup_options, man_cgroup_options,
|
||||
f'Comparing {CgroupFileExtractor.filename} (do_help() OPTIONS) and {ManCgroupExtractor.filename} (OPTIONS):')
|
||||
verify(source_cgroup_attach_types, bashcomp_cgroup_attach_types,
|
||||
f'Comparing {CommonFileExtractor.filename} (attach_type_strings) and {BashcompExtractor.filename} (BPFTOOL_CGROUP_ATTACH_TYPES):')
|
||||
|
||||
# Options for remaining commands
|
||||
|
||||
for cmd in [ 'btf', 'feature', 'gen', 'iter', 'link', 'net', 'perf', 'struct_ops', ]:
|
||||
source_info = GenericSourceExtractor(cmd + '.c')
|
||||
help_cmd_options = source_info.get_options()
|
||||
source_info.close()
|
||||
|
||||
man_cmd_info = ManGenericExtractor(os.path.join('Documentation', 'bpftool-' + cmd + '.rst'))
|
||||
man_cmd_options = man_cmd_info.get_options()
|
||||
man_cmd_info.close()
|
||||
|
||||
verify(help_cmd_options, man_cmd_options,
|
||||
f'Comparing {source_info.filename} (do_help() OPTIONS) and {man_cmd_info.filename} (OPTIONS):')
|
||||
|
||||
source_main_info = GenericSourceExtractor('main.c')
|
||||
help_main_options = source_main_info.get_options()
|
||||
source_main_info.close()
|
||||
|
||||
man_main_info = ManGenericExtractor(os.path.join('Documentation', 'bpftool.rst'))
|
||||
man_main_options = man_main_info.get_options()
|
||||
man_main_info.close()
|
||||
|
||||
verify(help_main_options, man_main_options,
|
||||
f'Comparing {source_main_info.filename} (do_help() OPTIONS) and {man_main_info.filename} (OPTIONS):')
|
||||
|
||||
sys.exit(retval)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -764,8 +764,8 @@ static void test_sockmap(unsigned int tasks, void *data)
|
||||
udp = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
i = 0;
|
||||
err = bpf_map_update_elem(fd, &i, &udp, BPF_ANY);
|
||||
if (!err) {
|
||||
printf("Failed socket SOCK_DGRAM allowed '%i:%i'\n",
|
||||
if (err) {
|
||||
printf("Failed socket update SOCK_DGRAM '%i:%i'\n",
|
||||
i, udp);
|
||||
goto out_sockmap;
|
||||
}
|
||||
@ -1153,12 +1153,17 @@ out_sockmap:
|
||||
}
|
||||
|
||||
#define MAPINMAP_PROG "./test_map_in_map.o"
|
||||
#define MAPINMAP_INVALID_PROG "./test_map_in_map_invalid.o"
|
||||
static void test_map_in_map(void)
|
||||
{
|
||||
struct bpf_object *obj;
|
||||
struct bpf_map *map;
|
||||
int mim_fd, fd, err;
|
||||
int pos = 0;
|
||||
struct bpf_map_info info = {};
|
||||
__u32 len = sizeof(info);
|
||||
__u32 id = 0;
|
||||
libbpf_print_fn_t old_print_fn;
|
||||
|
||||
obj = bpf_object__open(MAPINMAP_PROG);
|
||||
|
||||
@ -1228,11 +1233,72 @@ static void test_map_in_map(void)
|
||||
}
|
||||
|
||||
close(fd);
|
||||
fd = -1;
|
||||
bpf_object__close(obj);
|
||||
|
||||
/* Test that failing bpf_object__create_map() destroys the inner map */
|
||||
obj = bpf_object__open(MAPINMAP_INVALID_PROG);
|
||||
err = libbpf_get_error(obj);
|
||||
if (err) {
|
||||
printf("Failed to load %s program: %d %d",
|
||||
MAPINMAP_INVALID_PROG, err, errno);
|
||||
goto out_map_in_map;
|
||||
}
|
||||
|
||||
map = bpf_object__find_map_by_name(obj, "mim");
|
||||
if (!map) {
|
||||
printf("Failed to load array of maps from test prog\n");
|
||||
goto out_map_in_map;
|
||||
}
|
||||
|
||||
old_print_fn = libbpf_set_print(NULL);
|
||||
|
||||
err = bpf_object__load(obj);
|
||||
if (!err) {
|
||||
printf("Loading obj supposed to fail\n");
|
||||
goto out_map_in_map;
|
||||
}
|
||||
|
||||
libbpf_set_print(old_print_fn);
|
||||
|
||||
/* Iterate over all maps to check whether the internal map
|
||||
* ("mim.internal") has been destroyed.
|
||||
*/
|
||||
while (true) {
|
||||
err = bpf_map_get_next_id(id, &id);
|
||||
if (err) {
|
||||
if (errno == ENOENT)
|
||||
break;
|
||||
printf("Failed to get next map: %d", errno);
|
||||
goto out_map_in_map;
|
||||
}
|
||||
|
||||
fd = bpf_map_get_fd_by_id(id);
|
||||
if (fd < 0) {
|
||||
if (errno == ENOENT)
|
||||
continue;
|
||||
printf("Failed to get map by id %u: %d", id, errno);
|
||||
goto out_map_in_map;
|
||||
}
|
||||
|
||||
err = bpf_obj_get_info_by_fd(fd, &info, &len);
|
||||
if (err) {
|
||||
printf("Failed to get map info by fd %d: %d", fd,
|
||||
errno);
|
||||
goto out_map_in_map;
|
||||
}
|
||||
|
||||
if (!strcmp(info.name, "mim.inner")) {
|
||||
printf("Inner map mim.inner was not destroyed\n");
|
||||
goto out_map_in_map;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
out_map_in_map:
|
||||
close(fd);
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
@ -33,14 +33,14 @@ static int bpf_find_map(const char *test, struct bpf_object *obj,
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct percpu_net_cnt *percpu_netcnt;
|
||||
union percpu_net_cnt *percpu_netcnt;
|
||||
struct bpf_cgroup_storage_key key;
|
||||
int map_fd, percpu_map_fd;
|
||||
int error = EXIT_FAILURE;
|
||||
struct net_cnt netcnt;
|
||||
struct bpf_object *obj;
|
||||
int prog_fd, cgroup_fd;
|
||||
unsigned long packets;
|
||||
union net_cnt netcnt;
|
||||
unsigned long bytes;
|
||||
int cpu, nproc;
|
||||
__u32 prog_cnt;
|
||||
|
@ -221,6 +221,18 @@ extern int test__join_cgroup(const char *path);
|
||||
___ok; \
|
||||
})
|
||||
|
||||
#define ASSERT_STRNEQ(actual, expected, len, name) ({ \
|
||||
static int duration = 0; \
|
||||
const char *___act = actual; \
|
||||
const char *___exp = expected; \
|
||||
int ___len = len; \
|
||||
bool ___ok = strncmp(___act, ___exp, ___len) == 0; \
|
||||
CHECK(!___ok, (name), \
|
||||
"unexpected %s: actual '%.*s' != expected '%.*s'\n", \
|
||||
(name), ___len, ___act, ___len, ___exp); \
|
||||
___ok; \
|
||||
})
|
||||
|
||||
#define ASSERT_OK(res, name) ({ \
|
||||
static int duration = 0; \
|
||||
long long ___res = (res); \
|
||||
|
@ -69,7 +69,7 @@ cleanup() {
|
||||
}
|
||||
|
||||
server_listen() {
|
||||
ip netns exec "${ns2}" nc "${netcat_opt}" -l -p "${port}" > "${outfile}" &
|
||||
ip netns exec "${ns2}" nc "${netcat_opt}" -l "${port}" > "${outfile}" &
|
||||
server_pid=$!
|
||||
sleep 0.2
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user