libbpf: Prepare light skeleton for the kernel.

Prepare light skeleton to be used in the kernel module and in the user space.
The look and feel of lskel.h is mostly the same with the difference that for
user space the skel->rodata is the same pointer before and after skel_load
operation, while in the kernel the skel->rodata after skel_open and the
skel->rodata after skel_load are different pointers.
Typical usage of skeleton remains the same for kernel and user space:
skel = my_bpf__open();
skel->rodata->my_global_var = init_val;
err = my_bpf__load(skel);
err = my_bpf__attach(skel);
// access skel->rodata->my_global_var;
// access skel->bss->another_var;

Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Yonghong Song <yhs@fb.com>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20220209232001.27490-3-alexei.starovoitov@gmail.com
This commit is contained in:
Alexei Starovoitov 2022-02-09 15:19:58 -08:00 committed by Daniel Borkmann
parent b1d18a7574
commit 6fe65f1b4d
2 changed files with 179 additions and 21 deletions

View File

@ -1043,18 +1043,27 @@ void bpf_gen__map_update_elem(struct bpf_gen *gen, int map_idx, void *pvalue,
value = add_data(gen, pvalue, value_size); value = add_data(gen, pvalue, value_size);
key = add_data(gen, &zero, sizeof(zero)); key = add_data(gen, &zero, sizeof(zero));
/* if (map_desc[map_idx].initial_value) /* if (map_desc[map_idx].initial_value) {
* copy_from_user(value, initial_value, value_size); * if (ctx->flags & BPF_SKEL_KERNEL)
* bpf_probe_read_kernel(value, value_size, initial_value);
* else
* bpf_copy_from_user(value, value_size, initial_value);
* }
*/ */
emit(gen, BPF_LDX_MEM(BPF_DW, BPF_REG_3, BPF_REG_6, emit(gen, BPF_LDX_MEM(BPF_DW, BPF_REG_3, BPF_REG_6,
sizeof(struct bpf_loader_ctx) + sizeof(struct bpf_loader_ctx) +
sizeof(struct bpf_map_desc) * map_idx + sizeof(struct bpf_map_desc) * map_idx +
offsetof(struct bpf_map_desc, initial_value))); offsetof(struct bpf_map_desc, initial_value)));
emit(gen, BPF_JMP_IMM(BPF_JEQ, BPF_REG_3, 0, 4)); emit(gen, BPF_JMP_IMM(BPF_JEQ, BPF_REG_3, 0, 8));
emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_1, BPF_PSEUDO_MAP_IDX_VALUE, emit2(gen, BPF_LD_IMM64_RAW_FULL(BPF_REG_1, BPF_PSEUDO_MAP_IDX_VALUE,
0, 0, 0, value)); 0, 0, 0, value));
emit(gen, BPF_MOV64_IMM(BPF_REG_2, value_size)); emit(gen, BPF_MOV64_IMM(BPF_REG_2, value_size));
emit(gen, BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_6,
offsetof(struct bpf_loader_ctx, flags)));
emit(gen, BPF_JMP_IMM(BPF_JSET, BPF_REG_0, BPF_SKEL_KERNEL, 2));
emit(gen, BPF_EMIT_CALL(BPF_FUNC_copy_from_user)); emit(gen, BPF_EMIT_CALL(BPF_FUNC_copy_from_user));
emit(gen, BPF_JMP_IMM(BPF_JA, 0, 0, 1));
emit(gen, BPF_EMIT_CALL(BPF_FUNC_probe_read_kernel));
map_update_attr = add_data(gen, &attr, attr_size); map_update_attr = add_data(gen, &attr, attr_size);
move_blob2blob(gen, attr_field(map_update_attr, map_fd), 4, move_blob2blob(gen, attr_field(map_update_attr, map_fd), 4,

View File

@ -3,9 +3,19 @@
#ifndef __SKEL_INTERNAL_H #ifndef __SKEL_INTERNAL_H
#define __SKEL_INTERNAL_H #define __SKEL_INTERNAL_H
#ifdef __KERNEL__
#include <linux/fdtable.h>
#include <linux/mm.h>
#include <linux/mman.h>
#include <linux/slab.h>
#include <linux/bpf.h>
#else
#include <unistd.h> #include <unistd.h>
#include <sys/syscall.h> #include <sys/syscall.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <stdlib.h>
#include "bpf.h"
#endif
#ifndef __NR_bpf #ifndef __NR_bpf
# if defined(__mips__) && defined(_ABIO32) # if defined(__mips__) && defined(_ABIO32)
@ -25,24 +35,23 @@
* requested during loader program generation. * requested during loader program generation.
*/ */
struct bpf_map_desc { struct bpf_map_desc {
union { /* output of the loader prog */
/* input for the loader prog */ int map_fd;
struct { /* input for the loader prog */
__aligned_u64 initial_value; __u32 max_entries;
__u32 max_entries; __aligned_u64 initial_value;
};
/* output of the loader prog */
struct {
int map_fd;
};
};
}; };
struct bpf_prog_desc { struct bpf_prog_desc {
int prog_fd; int prog_fd;
}; };
enum {
BPF_SKEL_KERNEL = (1ULL << 0),
};
struct bpf_loader_ctx { struct bpf_loader_ctx {
size_t sz; __u32 sz;
__u32 flags;
__u32 log_level; __u32 log_level;
__u32 log_size; __u32 log_size;
__u64 log_buf; __u64 log_buf;
@ -57,12 +66,144 @@ struct bpf_load_and_run_opts {
const char *errstr; const char *errstr;
}; };
long bpf_sys_bpf(__u32 cmd, void *attr, __u32 attr_size);
static inline int skel_sys_bpf(enum bpf_cmd cmd, union bpf_attr *attr, static inline int skel_sys_bpf(enum bpf_cmd cmd, union bpf_attr *attr,
unsigned int size) unsigned int size)
{ {
#ifdef __KERNEL__
return bpf_sys_bpf(cmd, attr, size);
#else
return syscall(__NR_bpf, cmd, attr, size); return syscall(__NR_bpf, cmd, attr, size);
#endif
} }
#ifdef __KERNEL__
static inline int close(int fd)
{
return close_fd(fd);
}
static inline void *skel_alloc(size_t size)
{
struct bpf_loader_ctx *ctx = kzalloc(size, GFP_KERNEL);
if (!ctx)
return NULL;
ctx->flags |= BPF_SKEL_KERNEL;
return ctx;
}
static inline void skel_free(const void *p)
{
kfree(p);
}
/* skel->bss/rodata maps are populated the following way:
*
* For kernel use:
* skel_prep_map_data() allocates kernel memory that kernel module can directly access.
* Generated lskel stores the pointer in skel->rodata and in skel->maps.rodata.initial_value.
* The loader program will perform probe_read_kernel() from maps.rodata.initial_value.
* skel_finalize_map_data() sets skel->rodata to point to actual value in a bpf map and
* does maps.rodata.initial_value = ~0ULL to signal skel_free_map_data() that kvfree
* is not nessary.
*
* For user space:
* skel_prep_map_data() mmaps anon memory into skel->rodata that can be accessed directly.
* Generated lskel stores the pointer in skel->rodata and in skel->maps.rodata.initial_value.
* The loader program will perform copy_from_user() from maps.rodata.initial_value.
* skel_finalize_map_data() remaps bpf array map value from the kernel memory into
* skel->rodata address.
*
* The "bpftool gen skeleton -L" command generates lskel.h that is suitable for
* both kernel and user space. The generated loader program does
* either bpf_probe_read_kernel() or bpf_copy_from_user() from initial_value
* depending on bpf_loader_ctx->flags.
*/
static inline void skel_free_map_data(void *p, __u64 addr, size_t sz)
{
if (addr != ~0ULL)
kvfree(p);
/* When addr == ~0ULL the 'p' points to
* ((struct bpf_array *)map)->value. See skel_finalize_map_data.
*/
}
static inline void *skel_prep_map_data(const void *val, size_t mmap_sz, size_t val_sz)
{
void *addr;
addr = kvmalloc(val_sz, GFP_KERNEL);
if (!addr)
return NULL;
memcpy(addr, val, val_sz);
return addr;
}
static inline void *skel_finalize_map_data(__u64 *init_val, size_t mmap_sz, int flags, int fd)
{
struct bpf_map *map;
void *addr = NULL;
kvfree((void *) (long) *init_val);
*init_val = ~0ULL;
/* At this point bpf_load_and_run() finished without error and
* 'fd' is a valid bpf map FD. All sanity checks below should succeed.
*/
map = bpf_map_get(fd);
if (IS_ERR(map))
return NULL;
if (map->map_type != BPF_MAP_TYPE_ARRAY)
goto out;
addr = ((struct bpf_array *)map)->value;
/* the addr stays valid, since FD is not closed */
out:
bpf_map_put(map);
return addr;
}
#else
static inline void *skel_alloc(size_t size)
{
return calloc(1, size);
}
static inline void skel_free(void *p)
{
free(p);
}
static inline void skel_free_map_data(void *p, __u64 addr, size_t sz)
{
munmap(p, sz);
}
static inline void *skel_prep_map_data(const void *val, size_t mmap_sz, size_t val_sz)
{
void *addr;
addr = mmap(NULL, mmap_sz, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (addr == (void *) -1)
return NULL;
memcpy(addr, val, val_sz);
return addr;
}
static inline void *skel_finalize_map_data(__u64 *init_val, size_t mmap_sz, int flags, int fd)
{
void *addr;
addr = mmap((void *) (long) *init_val, mmap_sz, flags, MAP_SHARED | MAP_FIXED, fd, 0);
if (addr == (void *) -1)
return NULL;
return addr;
}
#endif
static inline int skel_closenz(int fd) static inline int skel_closenz(int fd)
{ {
if (fd > 0) if (fd > 0)
@ -136,22 +277,28 @@ static inline int skel_link_create(int prog_fd, int target_fd,
return skel_sys_bpf(BPF_LINK_CREATE, &attr, attr_sz); return skel_sys_bpf(BPF_LINK_CREATE, &attr, attr_sz);
} }
#ifdef __KERNEL__
#define set_err
#else
#define set_err err = -errno
#endif
static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts) static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts)
{ {
int map_fd = -1, prog_fd = -1, key = 0, err; int map_fd = -1, prog_fd = -1, key = 0, err;
union bpf_attr attr; union bpf_attr attr;
map_fd = skel_map_create(BPF_MAP_TYPE_ARRAY, "__loader.map", 4, opts->data_sz, 1); err = map_fd = skel_map_create(BPF_MAP_TYPE_ARRAY, "__loader.map", 4, opts->data_sz, 1);
if (map_fd < 0) { if (map_fd < 0) {
opts->errstr = "failed to create loader map"; opts->errstr = "failed to create loader map";
err = -errno; set_err;
goto out; goto out;
} }
err = skel_map_update_elem(map_fd, &key, opts->data, 0); err = skel_map_update_elem(map_fd, &key, opts->data, 0);
if (err < 0) { if (err < 0) {
opts->errstr = "failed to update loader map"; opts->errstr = "failed to update loader map";
err = -errno; set_err;
goto out; goto out;
} }
@ -166,10 +313,10 @@ static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts)
attr.log_size = opts->ctx->log_size; attr.log_size = opts->ctx->log_size;
attr.log_buf = opts->ctx->log_buf; attr.log_buf = opts->ctx->log_buf;
attr.prog_flags = BPF_F_SLEEPABLE; attr.prog_flags = BPF_F_SLEEPABLE;
prog_fd = skel_sys_bpf(BPF_PROG_LOAD, &attr, sizeof(attr)); err = prog_fd = skel_sys_bpf(BPF_PROG_LOAD, &attr, sizeof(attr));
if (prog_fd < 0) { if (prog_fd < 0) {
opts->errstr = "failed to load loader prog"; opts->errstr = "failed to load loader prog";
err = -errno; set_err;
goto out; goto out;
} }
@ -181,10 +328,12 @@ static inline int bpf_load_and_run(struct bpf_load_and_run_opts *opts)
if (err < 0 || (int)attr.test.retval < 0) { if (err < 0 || (int)attr.test.retval < 0) {
opts->errstr = "failed to execute loader prog"; opts->errstr = "failed to execute loader prog";
if (err < 0) { if (err < 0) {
err = -errno; set_err;
} else { } else {
err = (int)attr.test.retval; err = (int)attr.test.retval;
#ifndef __KERNEL__
errno = -err; errno = -err;
#endif
} }
goto out; goto out;
} }