Merge branch 'libbpf: support custom .rodata.*/.data.* sections'

Andrii Nakryiko says:

====================

This patch set refactors internals of libbpf to enable support for multiple
custom .rodata.* and .data.* sections. Each such section is backed by its own
BPF_MAP_TYPE_ARRAY, memory-mappable just like .rodata/.data. This is not
extended to .bss because .bss is not a great name, it is generated by compiler
with name that reflects completely irrelevant historical implementation
details. Given that users have to annotate their variables with
SEC(".data.my_sec") explicitly, standardizing on .rodata. and .data. prefixes
makes more sense and keeps things simpler.

Additionally, this patch set makes it simpler to work with those special
internal maps by allowing to look them up by their full ELF section name.

Patch #1 is a preparatory patch that deprecates one libbpf API and moves
custom logic into libbpf.c, where it's used. This code is later refactored
with the rest of libbpf.c logic to support multiple data section maps.

See individual patches for all the details.

For new custom "dot maps", their full ELF section names are used as the names
that are sent into the kernel. Object name isn't prepended like for
.data/.rodata/.bss. The reason is that with longer custom names, there isn't
much space left for object name anyways. Also, if BTF is supported,
btf_value_type_id points to DATASEC BTF type, which contains full original ELF
name of the section, so tools like bpftool could use that to recover full
name. This patch set doesn't add this logic yet, this is left for follow up
patches.

One interesting possibility that is now open by these changes is that it's
possible to do:

    bpf_trace_printk("My fmt %s", sizeof("My fmt %s"), "blah");

and it will work as expected. I haven't updated libbpf-provided helpers in
bpf_helpers.h for snprintf, seq_printf, and printk, because using
`static const char ___fmt[] = fmt;` trick is still efficient and doesn't fill
out the buffer at runtime (no copying). But we might consider updating them in
the future, especially with the array check that Kumar proposed (see [0]).

  [0] https://lore.kernel.org/bpf/20211012041524.udytbr2xs5wid6x2@apollo.localdomain/

v1->v2:
  - don't prepend object name for new dot maps;
  - add __read_mostly example in selftests (Daniel).
====================

Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2021-10-21 17:10:11 -07:00
commit 29da17c488
14 changed files with 732 additions and 492 deletions

View File

@ -33,6 +33,11 @@ static void sanitize_identifier(char *name)
name[i] = '_';
}
static bool str_has_prefix(const char *str, const char *prefix)
{
return strncmp(str, prefix, strlen(prefix)) == 0;
}
static bool str_has_suffix(const char *str, const char *suffix)
{
size_t i, n1 = strlen(str), n2 = strlen(suffix);
@ -67,23 +72,47 @@ static void get_header_guard(char *guard, const char *obj_name)
guard[i] = toupper(guard[i]);
}
static const char *get_map_ident(const struct bpf_map *map)
static bool get_map_ident(const struct bpf_map *map, char *buf, size_t buf_sz)
{
static const char *sfxs[] = { ".data", ".rodata", ".bss", ".kconfig" };
const char *name = bpf_map__name(map);
int i, n;
if (!bpf_map__is_internal(map))
return name;
if (!bpf_map__is_internal(map)) {
snprintf(buf, buf_sz, "%s", name);
return true;
}
if (str_has_suffix(name, ".data"))
return "data";
else if (str_has_suffix(name, ".rodata"))
return "rodata";
else if (str_has_suffix(name, ".bss"))
return "bss";
else if (str_has_suffix(name, ".kconfig"))
return "kconfig";
else
return NULL;
for (i = 0, n = ARRAY_SIZE(sfxs); i < n; i++) {
const char *sfx = sfxs[i], *p;
p = strstr(name, sfx);
if (p) {
snprintf(buf, buf_sz, "%s", p + 1);
sanitize_identifier(buf);
return true;
}
}
return false;
}
static bool get_datasec_ident(const char *sec_name, char *buf, size_t buf_sz)
{
static const char *pfxs[] = { ".data", ".rodata", ".bss", ".kconfig" };
int i, n;
for (i = 0, n = ARRAY_SIZE(pfxs); i < n; i++) {
const char *pfx = pfxs[i];
if (str_has_prefix(sec_name, pfx)) {
snprintf(buf, buf_sz, "%s", sec_name + 1);
sanitize_identifier(buf);
return true;
}
}
return false;
}
static void codegen_btf_dump_printf(void *ctx, const char *fmt, va_list args)
@ -100,24 +129,14 @@ static int codegen_datasec_def(struct bpf_object *obj,
const char *sec_name = btf__name_by_offset(btf, sec->name_off);
const struct btf_var_secinfo *sec_var = btf_var_secinfos(sec);
int i, err, off = 0, pad_cnt = 0, vlen = btf_vlen(sec);
const char *sec_ident;
char var_ident[256];
char var_ident[256], sec_ident[256];
bool strip_mods = false;
if (strcmp(sec_name, ".data") == 0) {
sec_ident = "data";
strip_mods = true;
} else if (strcmp(sec_name, ".bss") == 0) {
sec_ident = "bss";
strip_mods = true;
} else if (strcmp(sec_name, ".rodata") == 0) {
sec_ident = "rodata";
strip_mods = true;
} else if (strcmp(sec_name, ".kconfig") == 0) {
sec_ident = "kconfig";
} else {
if (!get_datasec_ident(sec_name, sec_ident, sizeof(sec_ident)))
return 0;
}
if (strcmp(sec_name, ".kconfig") != 0)
strip_mods = true;
printf(" struct %s__%s {\n", obj_name, sec_ident);
for (i = 0; i < vlen; i++, sec_var++) {
@ -194,22 +213,61 @@ static int codegen_datasecs(struct bpf_object *obj, const char *obj_name)
struct btf *btf = bpf_object__btf(obj);
int n = btf__get_nr_types(btf);
struct btf_dump *d;
struct bpf_map *map;
const struct btf_type *sec;
char sec_ident[256], map_ident[256];
int i, err = 0;
d = btf_dump__new(btf, NULL, NULL, codegen_btf_dump_printf);
if (IS_ERR(d))
return PTR_ERR(d);
for (i = 1; i <= n; i++) {
const struct btf_type *t = btf__type_by_id(btf, i);
if (!btf_is_datasec(t))
bpf_object__for_each_map(map, obj) {
/* only generate definitions for memory-mapped internal maps */
if (!bpf_map__is_internal(map))
continue;
if (!(bpf_map__def(map)->map_flags & BPF_F_MMAPABLE))
continue;
err = codegen_datasec_def(obj, btf, d, t, obj_name);
if (err)
goto out;
if (!get_map_ident(map, map_ident, sizeof(map_ident)))
continue;
sec = NULL;
for (i = 1; i <= n; i++) {
const struct btf_type *t = btf__type_by_id(btf, i);
const char *name;
if (!btf_is_datasec(t))
continue;
name = btf__str_by_offset(btf, t->name_off);
if (!get_datasec_ident(name, sec_ident, sizeof(sec_ident)))
continue;
if (strcmp(sec_ident, map_ident) == 0) {
sec = t;
break;
}
}
/* In some cases (e.g., sections like .rodata.cst16 containing
* compiler allocated string constants only) there will be
* special internal maps with no corresponding DATASEC BTF
* type. In such case, generate empty structs for each such
* map. It will still be memory-mapped and its contents
* accessible from user-space through BPF skeleton.
*/
if (!sec) {
printf(" struct %s__%s {\n", obj_name, map_ident);
printf(" } *%s;\n", map_ident);
} else {
err = codegen_datasec_def(obj, btf, d, sec, obj_name);
if (err)
goto out;
}
}
out:
btf_dump__free(d);
return err;
@ -385,6 +443,7 @@ static void codegen_destroy(struct bpf_object *obj, const char *obj_name)
{
struct bpf_program *prog;
struct bpf_map *map;
char ident[256];
codegen("\
\n\
@ -405,10 +464,7 @@ static void codegen_destroy(struct bpf_object *obj, const char *obj_name)
}
bpf_object__for_each_map(map, obj) {
const char *ident;
ident = get_map_ident(map);
if (!ident)
if (!get_map_ident(map, ident, sizeof(ident)))
continue;
if (bpf_map__is_internal(map) &&
(bpf_map__def(map)->map_flags & BPF_F_MMAPABLE))
@ -432,6 +488,7 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
struct bpf_object_load_attr load_attr = {};
DECLARE_LIBBPF_OPTS(gen_loader_opts, opts);
struct bpf_map *map;
char ident[256];
int err = 0;
err = bpf_object__gen_loader(obj, &opts);
@ -477,12 +534,10 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
",
obj_name, opts.data_sz);
bpf_object__for_each_map(map, obj) {
const char *ident;
const void *mmap_data = NULL;
size_t mmap_size = 0;
ident = get_map_ident(map);
if (!ident)
if (!get_map_ident(map, ident, sizeof(ident)))
continue;
if (!bpf_map__is_internal(map) ||
@ -544,15 +599,15 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
return err; \n\
", obj_name);
bpf_object__for_each_map(map, obj) {
const char *ident, *mmap_flags;
const char *mmap_flags;
ident = get_map_ident(map);
if (!ident)
if (!get_map_ident(map, ident, sizeof(ident)))
continue;
if (!bpf_map__is_internal(map) ||
!(bpf_map__def(map)->map_flags & BPF_F_MMAPABLE))
continue;
if (bpf_map__def(map)->map_flags & BPF_F_RDONLY_PROG)
mmap_flags = "PROT_READ";
else
@ -602,7 +657,8 @@ static int do_skeleton(int argc, char **argv)
DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts);
char obj_name[MAX_OBJ_NAME_LEN] = "", *obj_data;
struct bpf_object *obj = NULL;
const char *file, *ident;
const char *file;
char ident[256];
struct bpf_program *prog;
int fd, err = -1;
struct bpf_map *map;
@ -673,8 +729,7 @@ static int do_skeleton(int argc, char **argv)
}
bpf_object__for_each_map(map, obj) {
ident = get_map_ident(map);
if (!ident) {
if (!get_map_ident(map, ident, sizeof(ident))) {
p_err("ignoring unrecognized internal map '%s'...",
bpf_map__name(map));
continue;
@ -727,8 +782,7 @@ static int do_skeleton(int argc, char **argv)
if (map_cnt) {
printf("\tstruct {\n");
bpf_object__for_each_map(map, obj) {
ident = get_map_ident(map);
if (!ident)
if (!get_map_ident(map, ident, sizeof(ident)))
continue;
if (use_loader)
printf("\t\tstruct bpf_map_desc %s;\n", ident);
@ -897,9 +951,7 @@ static int do_skeleton(int argc, char **argv)
);
i = 0;
bpf_object__for_each_map(map, obj) {
ident = get_map_ident(map);
if (!ident)
if (!get_map_ident(map, ident, sizeof(ident)))
continue;
codegen("\

View File

@ -1107,99 +1107,6 @@ struct btf *btf__parse_split(const char *path, struct btf *base_btf)
return libbpf_ptr(btf_parse(path, base_btf, NULL));
}
static int compare_vsi_off(const void *_a, const void *_b)
{
const struct btf_var_secinfo *a = _a;
const struct btf_var_secinfo *b = _b;
return a->offset - b->offset;
}
static int btf_fixup_datasec(struct bpf_object *obj, struct btf *btf,
struct btf_type *t)
{
__u32 size = 0, off = 0, i, vars = btf_vlen(t);
const char *name = btf__name_by_offset(btf, t->name_off);
const struct btf_type *t_var;
struct btf_var_secinfo *vsi;
const struct btf_var *var;
int ret;
if (!name) {
pr_debug("No name found in string section for DATASEC kind.\n");
return -ENOENT;
}
/* .extern datasec size and var offsets were set correctly during
* extern collection step, so just skip straight to sorting variables
*/
if (t->size)
goto sort_vars;
ret = bpf_object__section_size(obj, name, &size);
if (ret || !size || (t->size && t->size != size)) {
pr_debug("Invalid size for section %s: %u bytes\n", name, size);
return -ENOENT;
}
t->size = size;
for (i = 0, vsi = btf_var_secinfos(t); i < vars; i++, vsi++) {
t_var = btf__type_by_id(btf, vsi->type);
var = btf_var(t_var);
if (!btf_is_var(t_var)) {
pr_debug("Non-VAR type seen in section %s\n", name);
return -EINVAL;
}
if (var->linkage == BTF_VAR_STATIC)
continue;
name = btf__name_by_offset(btf, t_var->name_off);
if (!name) {
pr_debug("No name found in string section for VAR kind\n");
return -ENOENT;
}
ret = bpf_object__variable_offset(obj, name, &off);
if (ret) {
pr_debug("No offset found in symbol table for VAR %s\n",
name);
return -ENOENT;
}
vsi->offset = off;
}
sort_vars:
qsort(btf_var_secinfos(t), vars, sizeof(*vsi), compare_vsi_off);
return 0;
}
int btf__finalize_data(struct bpf_object *obj, struct btf *btf)
{
int err = 0;
__u32 i;
for (i = 1; i <= btf->nr_types; i++) {
struct btf_type *t = btf_type_by_id(btf, i);
/* Loader needs to fix up some of the things compiler
* couldn't get its hands on while emitting BTF. This
* is section size and global variable offset. We use
* the info from the ELF itself for this purpose.
*/
if (btf_is_datasec(t)) {
err = btf_fixup_datasec(obj, btf, t);
if (err)
break;
}
}
return libbpf_err(err);
}
static void *btf_get_raw_data(const struct btf *btf, __u32 *size, bool swap_endian);
int btf__load_into_kernel(struct btf *btf)

View File

@ -123,6 +123,7 @@ LIBBPF_API struct btf *btf__load_from_kernel_by_id_split(__u32 id, struct btf *b
LIBBPF_DEPRECATED_SINCE(0, 6, "use btf__load_from_kernel_by_id instead")
LIBBPF_API int btf__get_from_id(__u32 id, struct btf **btf);
LIBBPF_DEPRECATED_SINCE(0, 6, "intended for internal libbpf use only")
LIBBPF_API int btf__finalize_data(struct bpf_object *obj, struct btf *btf);
LIBBPF_DEPRECATED_SINCE(0, 6, "use btf__load_into_kernel instead")
LIBBPF_API int btf__load(struct btf *btf);

File diff suppressed because it is too large Load Diff

View File

@ -52,8 +52,8 @@
#endif
/* Older libelf all end up in this expression, for both 32 and 64 bit */
#ifndef GELF_ST_VISIBILITY
#define GELF_ST_VISIBILITY(o) ((o) & 0x03)
#ifndef ELF64_ST_VISIBILITY
#define ELF64_ST_VISIBILITY(o) ((o) & 0x03)
#endif
#define BTF_INFO_ENC(kind, kind_flag, vlen) \
@ -303,10 +303,6 @@ struct bpf_prog_load_params {
int libbpf__bpf_prog_load(const struct bpf_prog_load_params *load_attr);
int bpf_object__section_size(const struct bpf_object *obj, const char *name,
__u32 *size);
int bpf_object__variable_offset(const struct bpf_object *obj, const char *name,
__u32 *off);
struct btf *btf_get_from_fd(int btf_fd, struct btf *base_btf);
void btf_get_kernel_prefix_kind(enum bpf_attach_type attach_type,
const char **prefix, int *kind);

View File

@ -15,7 +15,6 @@
#include <linux/btf.h>
#include <elf.h>
#include <libelf.h>
#include <gelf.h>
#include <fcntl.h>
#include "libbpf.h"
#include "btf.h"

View File

@ -163,7 +163,7 @@ void test_core_autosize(void)
usleep(1);
bss_map = bpf_object__find_map_by_name(skel->obj, "test_cor.bss");
bss_map = bpf_object__find_map_by_name(skel->obj, ".bss");
if (!ASSERT_OK_PTR(bss_map, "bss_map_find"))
goto cleanup;

View File

@ -867,7 +867,7 @@ void test_core_reloc(void)
goto cleanup;
}
data_map = bpf_object__find_map_by_name(obj, "test_cor.bss");
data_map = bpf_object__find_map_by_name(obj, ".bss");
if (CHECK(!data_map, "find_data_map", "data map not found\n"))
goto cleanup;

View File

@ -103,11 +103,18 @@ static void test_global_data_struct(struct bpf_object *obj, __u32 duration)
static void test_global_data_rdonly(struct bpf_object *obj, __u32 duration)
{
int err = -ENOMEM, map_fd, zero = 0;
struct bpf_map *map;
struct bpf_map *map, *map2;
__u8 *buff;
map = bpf_object__find_map_by_name(obj, "test_glo.rodata");
if (CHECK_FAIL(!map || !bpf_map__is_internal(map)))
if (!ASSERT_OK_PTR(map, "map"))
return;
if (!ASSERT_TRUE(bpf_map__is_internal(map), "is_internal"))
return;
/* ensure we can lookup internal maps by their ELF names */
map2 = bpf_object__find_map_by_name(obj, ".rodata");
if (!ASSERT_EQ(map, map2, "same_maps"))
return;
map_fd = bpf_map__fd(map);

View File

@ -16,7 +16,7 @@ void test_global_data_init(void)
if (CHECK_FAIL(err))
return;
map = bpf_object__find_map_by_name(obj, "test_glo.rodata");
map = bpf_object__find_map_by_name(obj, ".rodata");
if (CHECK_FAIL(!map || !bpf_map__is_internal(map)))
goto out;

View File

@ -93,7 +93,7 @@ void serial_test_kfree_skb(void)
if (CHECK(!fexit, "find_prog", "prog eth_type_trans not found\n"))
goto close_prog;
global_data = bpf_object__find_map_by_name(obj2, "kfree_sk.bss");
global_data = bpf_object__find_map_by_name(obj2, ".bss");
if (CHECK(!global_data, "find global data", "not found\n"))
goto close_prog;

View File

@ -37,7 +37,7 @@ void test_rdonly_maps(void)
if (CHECK(err, "obj_load", "err %d errno %d\n", err, errno))
goto cleanup;
bss_map = bpf_object__find_map_by_name(obj, "test_rdo.bss");
bss_map = bpf_object__find_map_by_name(obj, ".bss");
if (CHECK(!bss_map, "find_bss_map", "failed\n"))
goto cleanup;

View File

@ -16,10 +16,13 @@ void test_skeleton(void)
struct test_skeleton* skel;
struct test_skeleton__bss *bss;
struct test_skeleton__data *data;
struct test_skeleton__data_dyn *data_dyn;
struct test_skeleton__rodata *rodata;
struct test_skeleton__rodata_dyn *rodata_dyn;
struct test_skeleton__kconfig *kcfg;
const void *elf_bytes;
size_t elf_bytes_sz = 0;
int i;
skel = test_skeleton__open();
if (CHECK(!skel, "skel_open", "failed to open skeleton\n"))
@ -30,7 +33,12 @@ void test_skeleton(void)
bss = skel->bss;
data = skel->data;
data_dyn = skel->data_dyn;
rodata = skel->rodata;
rodata_dyn = skel->rodata_dyn;
ASSERT_STREQ(bpf_map__name(skel->maps.rodata_dyn), ".rodata.dyn", "rodata_dyn_name");
ASSERT_STREQ(bpf_map__name(skel->maps.data_dyn), ".data.dyn", "data_dyn_name");
/* validate values are pre-initialized correctly */
CHECK(data->in1 != -1, "in1", "got %d != exp %d\n", data->in1, -1);
@ -46,6 +54,12 @@ void test_skeleton(void)
CHECK(rodata->in.in6 != 0, "in6", "got %d != exp %d\n", rodata->in.in6, 0);
CHECK(bss->out6 != 0, "out6", "got %d != exp %d\n", bss->out6, 0);
ASSERT_EQ(rodata_dyn->in_dynarr_sz, 0, "in_dynarr_sz");
for (i = 0; i < 4; i++)
ASSERT_EQ(rodata_dyn->in_dynarr[i], -(i + 1), "in_dynarr");
for (i = 0; i < 4; i++)
ASSERT_EQ(data_dyn->out_dynarr[i], i + 1, "out_dynarr");
/* validate we can pre-setup global variables, even in .bss */
data->in1 = 10;
data->in2 = 11;
@ -53,6 +67,10 @@ void test_skeleton(void)
bss->in4 = 13;
rodata->in.in6 = 14;
rodata_dyn->in_dynarr_sz = 4;
for (i = 0; i < 4; i++)
rodata_dyn->in_dynarr[i] = i + 10;
err = test_skeleton__load(skel);
if (CHECK(err, "skel_load", "failed to load skeleton: %d\n", err))
goto cleanup;
@ -64,6 +82,10 @@ void test_skeleton(void)
CHECK(bss->in4 != 13, "in4", "got %lld != exp %lld\n", bss->in4, 13LL);
CHECK(rodata->in.in6 != 14, "in6", "got %d != exp %d\n", rodata->in.in6, 14);
ASSERT_EQ(rodata_dyn->in_dynarr_sz, 4, "in_dynarr_sz");
for (i = 0; i < 4; i++)
ASSERT_EQ(rodata_dyn->in_dynarr[i], i + 10, "in_dynarr");
/* now set new values and attach to get them into outX variables */
data->in1 = 1;
data->in2 = 2;
@ -73,6 +95,8 @@ void test_skeleton(void)
bss->in5.b = 6;
kcfg = skel->kconfig;
skel->data_read_mostly->read_mostly_var = 123;
err = test_skeleton__attach(skel);
if (CHECK(err, "skel_attach", "skeleton attach failed: %d\n", err))
goto cleanup;
@ -93,6 +117,11 @@ void test_skeleton(void)
CHECK(bss->kern_ver != kcfg->LINUX_KERNEL_VERSION, "ext2",
"got %d != exp %d\n", bss->kern_ver, kcfg->LINUX_KERNEL_VERSION);
for (i = 0; i < 4; i++)
ASSERT_EQ(data_dyn->out_dynarr[i], i + 10, "out_dynarr");
ASSERT_EQ(skel->bss->out_mostly_var, 123, "out_mostly_var");
elf_bytes = test_skeleton__elf_bytes(&elf_bytes_sz);
ASSERT_OK_PTR(elf_bytes, "elf_bytes");
ASSERT_GE(elf_bytes_sz, 0, "elf_bytes_sz");

View File

@ -5,6 +5,8 @@
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#define __read_mostly SEC(".data.read_mostly")
struct s {
int a;
long long b;
@ -40,9 +42,20 @@ int kern_ver = 0;
struct s out5 = {};
const volatile int in_dynarr_sz SEC(".rodata.dyn");
const volatile int in_dynarr[4] SEC(".rodata.dyn") = { -1, -2, -3, -4 };
int out_dynarr[4] SEC(".data.dyn") = { 1, 2, 3, 4 };
int read_mostly_var __read_mostly;
int out_mostly_var;
SEC("raw_tp/sys_enter")
int handler(const void *ctx)
{
int i;
out1 = in1;
out2 = in2;
out3 = in3;
@ -53,6 +66,11 @@ int handler(const void *ctx)
bpf_syscall = CONFIG_BPF_SYSCALL;
kern_ver = LINUX_KERNEL_VERSION;
for (i = 0; i < in_dynarr_sz; i++)
out_dynarr[i] = in_dynarr[i];
out_mostly_var = read_mostly_var;
return 0;
}