Commit f6afdaf7 authored by Andrii Nakryiko's avatar Andrii Nakryiko
Browse files

Merge branch 'bpf-support-resilient-split-btf'

Alan Maguire says:

====================
bpf: support resilient split BTF

Split BPF Type Format (BTF) provides huge advantages in that kernel
modules only have to provide type information for types that they do not
share with the core kernel; for core kernel types, split BTF refers to
core kernel BTF type ids.  So for a STRUCT sk_buff, a module that
uses that structure (or a pointer to it) simply needs to refer to the
core kernel type id, saving the need to define the structure and its many
dependents.  This cuts down on duplication and makes BTF as compact
as possible.

However, there is a downside.  This scheme requires the references from
split BTF to base BTF to be valid not just at encoding time, but at use
time (when the module is loaded).  Even a small change in kernel types
can perturb the type ids in core kernel BTF, and - if the new reproducible
BTF option is not used - pahole's parallel processing of compilation units
can lead to different type ids for the same kernel if the BTF is
regenerated.

So we have a robustness problem for split BTF for cases where a module is
not always compiled at the same time as the kernel.  This problem is
particularly acute for distros which generally want module builders to be
able to compile a module for the lifetime of a Linux stable-based release,
and have it continue to be valid over the lifetime of that release, even
as changes in data structures (and hence BTF types) accrue.  Today it's not
possible to generate BTF for modules that works beyond the initial
kernel it is compiled against - kernel bugfixes etc invalidate the split
BTF references to vmlinux BTF, and BTF is no longer usable for the
module.

The goal of this series is to provide options to provide additional
context for cases like this.  That context comes in the form of
distilled base BTF; it stands in for the base BTF, and contains
information about the types referenced from split BTF, but not their
full descriptions.  The modified split BTF will refer to type ids in
this .BTF.base section, and when the kernel loads such modules it
will use that .BTF.base to map references from split BTF to the
equivalent current vmlinux base BTF types.  Once this relocation
process has succeeded, the module BTF available in /sys/kernel/btf
will look exactly as if it was built with the current vmlinux;
references to base types will be fixed up etc.

A module builder - using this series along with the pahole changes -
can then build a module with distilled base BTF via an out-of-tree
module build, i.e.

make -C . M=path/2/module

The module will have a .BTF section (the split BTF) and a
.BTF.base section.  The latter is small in size - distilled base
BTF does not need full struct/union/enum information for named
types for example.  For 2667 modules built with distilled base BTF,
the average size observed was 1556 bytes (stddev 1563).  The overall
size added to this 2667 modules was 5.3Mb.

Note that for the in-tree modules, this approach is not needed as
split and base BTF in the case of in-tree modules are always built
and re-built together.

The series first focuses on generating split BTF with distilled base
BTF; then relocation support is added to allow split BTF with
an associated distlled base to be relocated with a new base BTF.

Next Eduard's patch allows BTF ELF parsing to work with both
.BTF and .BTF.base sections; this ensures that bpftool will be
able to dump BTF for a module with a .BTF.base section for example,
or indeed dump relocated BTF where a module and a "-B vmlinux"
is supplied.

Then we add support to resolve_btfids to ignore base BTF - i.e.
to avoid relocation - if a .BTF.base section is found.  This ensures
the .BTF.ids section is populated with ids relative to the distilled
base (these will be relocated as part of module load).

Finally the series supports storage of .BTF.base data/size in modules
and supports sharing of relocation code with the kernel to allow
relocation of module BTF.  For the kernel, this relocation
process happens at module load time, and we relocate split BTF
references to point at types in the current vmlinux BTF.  As part of
this, .BTF.ids references need to be mapped also.

So concretely, what happens is

- we generate split BTF in the .BTF section of a module that refers to
  types in the .BTF.base section as base types; the latter are not full
  type descriptions but provide information about the base type.  So
  a STRUCT sk_buff would be represented as a FWD struct sk_buff in
  distilled base BTF for example.
- when the module is loaded, the split BTF is relocated with vmlinux
  BTF; in the case of the FWD struct sk_buff, we find the STRUCT sk_buff
  in vmlinux BTF and map all split BTF references to the distilled base
  FWD sk_buff, replacing them with references to the vmlinux BTF
  STRUCT sk_buff.

A previous approach to this problem [1] utilized standalone BTF for such
cases - where the BTF is not defined relative to base BTF so there is no
relocation required.  The problem with that approach is that from
the verifier perspective, some types are special, and having a custom
representation of a core kernel type that did not necessarily match the
current representation is not tenable.  So the approach taken here was
to preserve the split BTF model while minimizing the representation of
the context needed to relocate split and current vmlinux BTF.

To generate distilled .BTF.base sections the associated dwarves
patch (to be applied on the "next" branch there) is needed [3]
Without it, things will still work but modules will not be built
with a .BTF.base section.

Changes since v5[4]:

- Update search of distilled types to return the first occurrence
  of a string (or a string+size pair); this allows us to iterate
  over all matches in distilled base BTF (Andrii, patch 3)
- Update to use BTF field iterators (Andrii, patches 1, 3 and 8)
- Update tests to cover multiple match and associated error cases
  (Eduard, patch 4)
- Rename elf_sections_info to btf_elf_secs, remove use of
  libbpf_get_error(), reset btf->owns_base when relocation
  succeeds (Andrii, patch 5)

Changes since v4[5]:

- Moved embeddedness, duplicate name checks to relocation time
  and record struct/union size for all distilled struct/unions
  instead of using forwards.  This allows us to carry out
  type compatibility checks based on the base BTF we want to
  relocate with (Eduard, patches 1, 3)
- Moved to using qsort() instead of qsort_r() as support for
  qsort_r() appears to be missing in Android libc (Andrii, patch 3)
- Sorting/searching now incorporates size matching depending
  on BTF kind and embeddedness of struct/union (Eduard, Andrii,
  patch 3)
- Improved naming of various types during relocation to avoid
  confusion (Andrii, patch 3)
- Incorporated Eduard's patch (patch 5) which handles .BTF.base
  sections internally in btf_parse_elf().  This makes ELF parsing
  work with split BTF, split BTF with a distilled base, split
  BTF with a distilled base _and_ base BTF (by relocating) etc.
  Having this avoids the need for bpftool changes; it will work
  as-is with .BTF.base sections (Eduard, patch 4)
- Updated resolve_btfids to _not_ relocate BTF for modules
  where a .BTF.base section is present; in that one case we
  do not want to relocate BTF as the .BTF.ids section should
  reflect ids in .BTF.base which will later be relocated on
  module load (Eduard, Andrii, patch 5)

Changes since v3[6]:

- distill now checks for duplicate-named struct/unions and records
  them as a sized struct/union to help identify which of the
  multiple base BTF structs/unions it refers to (Eduard, patch 1)
- added test support for multiple name handling (Eduard, patch 2)
- simplified the string mapping when updating split BTF to use
  base BTF instead of distilled base.  Since the only string
  references split BTF can make to base BTF are the names of
  the base types, create a string map from distilled string
  offset -> base BTF string offset and update string offsets
  by visiting all strings in split BTF; this saves having to
  do costly searches of base BTF (Eduard, patch 7,10)
- fixed bpftool manpage and indentation issues (Quentin, patch 11)

Also explored Eduard's suggestion of doing an implicit fallback
to checking for .BTF.base section in btf__parse() when it is
called to get base BTF.  However while it is doable, it turned
out to be difficult operationally.  Since fallback is implicit
we do not know the source of the BTF - was it from .BTF or
.BTF.base? In bpftool, we want to try first standalone BTF,
then split, then split with distilled base.  Having a way
to explicitly request .BTF.base via btf__parse_opts() fits
that model better.

Changes since v2[7]:

- submitted patch to use --btf_features in Makefile.btf for pahole
  v1.26 and later separately (Andrii).  That has landed in bpf-next
  now.
- distilled base now encodes ENUM64 as fwd ENUM (size 8), eliminating
  the need for support for ENUM64 in btf__add_fwd (patch 1, Andrii)
- moved to distilling only named types, augmenting split BTF with
  associated reference types; this simplifies greatly the distilled
  base BTF and the mapping operation between distilled and base
  BTF when relocating (most of the series changes, Andrii)
- relocation now iterates over base BTF, looking for matches based
  on name in distilled BTF.  Distilled BTF is pre-sorted by name
  (Andrii, patch 8)
- removed most redundant compabitiliby checks aside from struct
  size for base types/embedded structs and kind compatibility
  (since we only match on name) (Andrii, patch 8)
- btf__parse_opts() now replaces btf_parse() internally in libbpf
  (Eduard, patch 3)

Changes since RFC [8]:

- updated terminology; we replace clunky "base reference" BTF with
  distilling base BTF into a .BTF.base section. Similarly BTF
  reconcilation becomes BTF relocation (Andrii, most patches)
- add distilled base BTF by default for out-of-tree modules
  (Alexei, patch 8)
- distill algorithm updated to record size of embedded struct/union
  by recording it as a 0-vlen STRUCT/UNION with size preserved
  (Andrii, patch 2)
- verify size match on relocation for such STRUCT/UNIONs (Andrii,
  patch 9)
- with embedded STRUCT/UNION recording size, we can have bpftool
  dump a header representation using .BTF.base + .BTF sections
  rather than special-casing and refusing to use "format c" for
  that case (patch 5)
- match enum with enum64 and vice versa (Andrii, patch 9)
- ensure that resolve_btfids works with BTF without .BTF.base
  section (patch 7)
- update tests to cover embedded types, arrays and function
  prototypes (patches 3, 12)

[1] https://lore.kernel.org/bpf/20231112124834.388735-14-alan.maguire@oracle.com/
[2] https://lore.kernel.org/bpf/20240501175035.2476830-1-alan.maguire@oracle.com/
[3] https://lore.kernel.org/bpf/20240517102714.4072080-1-alan.maguire@oracle.com/
[4] https://lore.kernel.org/bpf/20240528122408.3154936-1-alan.maguire@oracle.com/
[5] https://lore.kernel.org/bpf/20240517102246.4070184-1-alan.maguire@oracle.com/
[6] https://lore.kernel.org/bpf/20240510103052.850012-1-alan.maguire@oracle.com/
[7] https://lore.kernel.org/bpf/20240424154806.3417662-1-alan.maguire@oracle.com/
[8] https://lore.kernel.org/bpf/20240322102455.98558-1-alan.maguire@oracle.com/
====================

Link: https://lore.kernel.org/r/20240613095014.357981-1-alan.maguire@oracle.com


Signed-off-by: default avatarAndrii Nakryiko <andrii@kernel.org>
parents dedf56d7 6ba77385
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -409,6 +409,14 @@ static int elf_collect(struct object *obj)
			obj->efile.idlist       = data;
			obj->efile.idlist_shndx = idx;
			obj->efile.idlist_addr  = sh.sh_addr;
		} else if (!strcmp(name, BTF_BASE_ELF_SEC)) {
			/* If a .BTF.base section is found, do not resolve
			 * BTF ids relative to vmlinux; resolve relative
			 * to the .BTF.base section instead.  btf__parse_split()
			 * will take care of this once the base BTF it is
			 * passed is NULL.
			 */
			obj->base_btf_path = NULL;
		}

		if (compressed_section_fix(elf, scn, &sh))
+1 −1
Original line number Diff line number Diff line
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 hashmap.o \
	    btf_dump.o ringbuf.o strset.o linker.o gen_loader.o relo_core.o \
	    usdt.o zip.o elf.o features.o
	    usdt.o zip.o elf.o features.o btf_relocate.o
+439 −59
Original line number Diff line number Diff line
@@ -116,6 +116,9 @@ struct btf {
	/* whether strings are already deduplicated */
	bool strs_deduped;

	/* whether base_btf should be freed in btf_free for this instance */
	bool owns_base;

	/* BTF object FD, if loaded into kernel */
	int fd;

@@ -969,6 +972,8 @@ void btf__free(struct btf *btf)
	free(btf->raw_data);
	free(btf->raw_data_swapped);
	free(btf->type_offs);
	if (btf->owns_base)
		btf__free(btf->base_btf);
	free(btf);
}

@@ -1084,53 +1089,38 @@ struct btf *btf__new_split(const void *data, __u32 size, struct btf *base_btf)
	return libbpf_ptr(btf_new(data, size, base_btf));
}

static struct btf *btf_parse_elf(const char *path, struct btf *base_btf,
				 struct btf_ext **btf_ext)
struct btf_elf_secs {
	Elf_Data *btf_data;
	Elf_Data *btf_ext_data;
	Elf_Data *btf_base_data;
};

static int btf_find_elf_sections(Elf *elf, const char *path, struct btf_elf_secs *secs)
{
	Elf_Data *btf_data = NULL, *btf_ext_data = NULL;
	int err = 0, fd = -1, idx = 0;
	struct btf *btf = NULL;
	Elf_Scn *scn = NULL;
	Elf *elf = NULL;
	Elf_Data *data;
	GElf_Ehdr ehdr;
	size_t shstrndx;
	int idx = 0;

	if (elf_version(EV_CURRENT) == EV_NONE) {
		pr_warn("failed to init libelf for %s\n", path);
		return ERR_PTR(-LIBBPF_ERRNO__LIBELF);
	}

	fd = open(path, O_RDONLY | O_CLOEXEC);
	if (fd < 0) {
		err = -errno;
		pr_warn("failed to open %s: %s\n", path, strerror(errno));
		return ERR_PTR(err);
	}

	err = -LIBBPF_ERRNO__FORMAT;

	elf = elf_begin(fd, ELF_C_READ, NULL);
	if (!elf) {
		pr_warn("failed to open %s as ELF file\n", path);
		goto done;
	}
	if (!gelf_getehdr(elf, &ehdr)) {
		pr_warn("failed to get EHDR from %s\n", path);
		goto done;
		goto err;
	}

	if (elf_getshdrstrndx(elf, &shstrndx)) {
		pr_warn("failed to get section names section index for %s\n",
			path);
		goto done;
		goto err;
	}

	if (!elf_rawdata(elf_getscn(elf, shstrndx), NULL)) {
		pr_warn("failed to get e_shstrndx from %s\n", path);
		goto done;
		goto err;
	}

	while ((scn = elf_nextscn(elf, scn)) != NULL) {
		Elf_Data **field;
		GElf_Shdr sh;
		char *name;

@@ -1138,42 +1128,102 @@ static struct btf *btf_parse_elf(const char *path, struct btf *base_btf,
		if (gelf_getshdr(scn, &sh) != &sh) {
			pr_warn("failed to get section(%d) header from %s\n",
				idx, path);
			goto done;
			goto err;
		}
		name = elf_strptr(elf, shstrndx, sh.sh_name);
		if (!name) {
			pr_warn("failed to get section(%d) name from %s\n",
				idx, path);
			goto done;
		}
		if (strcmp(name, BTF_ELF_SEC) == 0) {
			btf_data = elf_getdata(scn, 0);
			if (!btf_data) {
				pr_warn("failed to get section(%d, %s) data from %s\n",
					idx, name, path);
				goto done;
			goto err;
		}

		if (strcmp(name, BTF_ELF_SEC) == 0)
			field = &secs->btf_data;
		else if (strcmp(name, BTF_EXT_ELF_SEC) == 0)
			field = &secs->btf_ext_data;
		else if (strcmp(name, BTF_BASE_ELF_SEC) == 0)
			field = &secs->btf_base_data;
		else
			continue;
		} else if (btf_ext && strcmp(name, BTF_EXT_ELF_SEC) == 0) {
			btf_ext_data = elf_getdata(scn, 0);
			if (!btf_ext_data) {

		data = elf_getdata(scn, 0);
		if (!data) {
			pr_warn("failed to get section(%d, %s) data from %s\n",
				idx, name, path);
				goto done;
			goto err;
		}
			continue;
		*field = data;
	}

	return 0;

err:
	return -LIBBPF_ERRNO__FORMAT;
}

static struct btf *btf_parse_elf(const char *path, struct btf *base_btf,
				 struct btf_ext **btf_ext)
{
	struct btf_elf_secs secs = {};
	struct btf *dist_base_btf = NULL;
	struct btf *btf = NULL;
	int err = 0, fd = -1;
	Elf *elf = NULL;

	if (elf_version(EV_CURRENT) == EV_NONE) {
		pr_warn("failed to init libelf for %s\n", path);
		return ERR_PTR(-LIBBPF_ERRNO__LIBELF);
	}

	fd = open(path, O_RDONLY | O_CLOEXEC);
	if (fd < 0) {
		err = -errno;
		pr_warn("failed to open %s: %s\n", path, strerror(errno));
		return ERR_PTR(err);
	}

	elf = elf_begin(fd, ELF_C_READ, NULL);
	if (!elf) {
		pr_warn("failed to open %s as ELF file\n", path);
		goto done;
	}

	if (!btf_data) {
	err = btf_find_elf_sections(elf, path, &secs);
	if (err)
		goto done;

	if (!secs.btf_data) {
		pr_warn("failed to find '%s' ELF section in %s\n", BTF_ELF_SEC, path);
		err = -ENODATA;
		goto done;
	}
	btf = btf_new(btf_data->d_buf, btf_data->d_size, base_btf);
	err = libbpf_get_error(btf);

	if (secs.btf_base_data) {
		dist_base_btf = btf_new(secs.btf_base_data->d_buf, secs.btf_base_data->d_size,
					NULL);
		if (IS_ERR(dist_base_btf)) {
			err = PTR_ERR(dist_base_btf);
			dist_base_btf = NULL;
			goto done;
		}
	}

	btf = btf_new(secs.btf_data->d_buf, secs.btf_data->d_size,
		      dist_base_btf ?: base_btf);
	if (IS_ERR(btf)) {
		err = PTR_ERR(btf);
		goto done;
	}
	if (dist_base_btf && base_btf) {
		err = btf__relocate(btf, base_btf);
		if (err)
			goto done;
		btf__free(dist_base_btf);
		dist_base_btf = NULL;
	}

	if (dist_base_btf)
		btf->owns_base = true;

	switch (gelf_getclass(elf)) {
	case ELFCLASS32:
@@ -1187,11 +1237,12 @@ static struct btf *btf_parse_elf(const char *path, struct btf *base_btf,
		break;
	}

	if (btf_ext && btf_ext_data) {
		*btf_ext = btf_ext__new(btf_ext_data->d_buf, btf_ext_data->d_size);
		err = libbpf_get_error(*btf_ext);
		if (err)
	if (btf_ext && secs.btf_ext_data) {
		*btf_ext = btf_ext__new(secs.btf_ext_data->d_buf, secs.btf_ext_data->d_size);
		if (IS_ERR(*btf_ext)) {
			err = PTR_ERR(*btf_ext);
			goto done;
		}
	} else if (btf_ext) {
		*btf_ext = NULL;
	}
@@ -1205,6 +1256,7 @@ static struct btf *btf_parse_elf(const char *path, struct btf *base_btf,

	if (btf_ext)
		btf_ext__free(*btf_ext);
	btf__free(dist_base_btf);
	btf__free(btf);

	return ERR_PTR(err);
@@ -1770,9 +1822,8 @@ static int btf_rewrite_str(struct btf_pipe *p, __u32 *str_off)
	return 0;
}

int btf__add_type(struct btf *btf, const struct btf *src_btf, const struct btf_type *src_type)
static int btf_add_type(struct btf_pipe *p, const struct btf_type *src_type)
{
	struct btf_pipe p = { .src = src_btf, .dst = btf };
	struct btf_field_iter it;
	struct btf_type *t;
	__u32 *str_off;
@@ -1783,10 +1834,10 @@ int btf__add_type(struct btf *btf, const struct btf *src_btf, const struct btf_t
		return libbpf_err(sz);

	/* deconstruct BTF, if necessary, and invalidate raw_data */
	if (btf_ensure_modifiable(btf))
	if (btf_ensure_modifiable(p->dst))
		return libbpf_err(-ENOMEM);

	t = btf_add_type_mem(btf, sz);
	t = btf_add_type_mem(p->dst, sz);
	if (!t)
		return libbpf_err(-ENOMEM);

@@ -1797,12 +1848,19 @@ int btf__add_type(struct btf *btf, const struct btf *src_btf, const struct btf_t
		return libbpf_err(err);

	while ((str_off = btf_field_iter_next(&it))) {
		err = btf_rewrite_str(&p, str_off);
		err = btf_rewrite_str(p, str_off);
		if (err)
			return libbpf_err(err);
	}

	return btf_commit_type(btf, sz);
	return btf_commit_type(p->dst, sz);
}

int btf__add_type(struct btf *btf, const struct btf *src_btf, const struct btf_type *src_type)
{
	struct btf_pipe p = { .src = src_btf, .dst = btf };

	return btf_add_type(&p, src_type);
}

static size_t btf_dedup_identity_hash_fn(long key, void *ctx);
@@ -5276,3 +5334,325 @@ int btf_ext_visit_str_offs(struct btf_ext *btf_ext, str_off_visit_fn visit, void

	return 0;
}

struct btf_distill {
	struct btf_pipe pipe;
	int *id_map;
	unsigned int split_start_id;
	unsigned int split_start_str;
	int diff_id;
};

static int btf_add_distilled_type_ids(struct btf_distill *dist, __u32 i)
{
	struct btf_type *split_t = btf_type_by_id(dist->pipe.src, i);
	struct btf_field_iter it;
	__u32 *id;
	int err;

	err = btf_field_iter_init(&it, split_t, BTF_FIELD_ITER_IDS);
	if (err)
		return err;
	while ((id = btf_field_iter_next(&it))) {
		struct btf_type *base_t;

		if (!*id)
			continue;
		/* split BTF id, not needed */
		if (*id >= dist->split_start_id)
			continue;
		/* already added ? */
		if (dist->id_map[*id] > 0)
			continue;

		/* only a subset of base BTF types should be referenced from
		 * split BTF; ensure nothing unexpected is referenced.
		 */
		base_t = btf_type_by_id(dist->pipe.src, *id);
		switch (btf_kind(base_t)) {
		case BTF_KIND_INT:
		case BTF_KIND_FLOAT:
		case BTF_KIND_FWD:
		case BTF_KIND_ARRAY:
		case BTF_KIND_STRUCT:
		case BTF_KIND_UNION:
		case BTF_KIND_TYPEDEF:
		case BTF_KIND_ENUM:
		case BTF_KIND_ENUM64:
		case BTF_KIND_PTR:
		case BTF_KIND_CONST:
		case BTF_KIND_RESTRICT:
		case BTF_KIND_VOLATILE:
		case BTF_KIND_FUNC_PROTO:
		case BTF_KIND_TYPE_TAG:
			dist->id_map[*id] = *id;
			break;
		default:
			pr_warn("unexpected reference to base type[%u] of kind [%u] when creating distilled base BTF.\n",
				*id, btf_kind(base_t));
			return -EINVAL;
		}
		/* If a base type is used, ensure types it refers to are
		 * marked as used also; so for example if we find a PTR to INT
		 * we need both the PTR and INT.
		 *
		 * The only exception is named struct/unions, since distilled
		 * base BTF composite types have no members.
		 */
		if (btf_is_composite(base_t) && base_t->name_off)
			continue;
		err = btf_add_distilled_type_ids(dist, *id);
		if (err)
			return err;
	}
	return 0;
}

static int btf_add_distilled_types(struct btf_distill *dist)
{
	bool adding_to_base = dist->pipe.dst->start_id == 1;
	int id = btf__type_cnt(dist->pipe.dst);
	struct btf_type *t;
	int i, err = 0;


	/* Add types for each of the required references to either distilled
	 * base or split BTF, depending on type characteristics.
	 */
	for (i = 1; i < dist->split_start_id; i++) {
		const char *name;
		int kind;

		if (!dist->id_map[i])
			continue;
		t = btf_type_by_id(dist->pipe.src, i);
		kind = btf_kind(t);
		name = btf__name_by_offset(dist->pipe.src, t->name_off);

		switch (kind) {
		case BTF_KIND_INT:
		case BTF_KIND_FLOAT:
		case BTF_KIND_FWD:
			/* Named int, float, fwd are added to base. */
			if (!adding_to_base)
				continue;
			err = btf_add_type(&dist->pipe, t);
			break;
		case BTF_KIND_STRUCT:
		case BTF_KIND_UNION:
			/* Named struct/union are added to base as 0-vlen
			 * struct/union of same size.  Anonymous struct/unions
			 * are added to split BTF as-is.
			 */
			if (adding_to_base) {
				if (!t->name_off)
					continue;
				err = btf_add_composite(dist->pipe.dst, kind, name, t->size);
			} else {
				if (t->name_off)
					continue;
				err = btf_add_type(&dist->pipe, t);
			}
			break;
		case BTF_KIND_ENUM:
		case BTF_KIND_ENUM64:
			/* Named enum[64]s are added to base as a sized
			 * enum; relocation will match with appropriately-named
			 * and sized enum or enum64.
			 *
			 * Anonymous enums are added to split BTF as-is.
			 */
			if (adding_to_base) {
				if (!t->name_off)
					continue;
				err = btf__add_enum(dist->pipe.dst, name, t->size);
			} else {
				if (t->name_off)
					continue;
				err = btf_add_type(&dist->pipe, t);
			}
			break;
		case BTF_KIND_ARRAY:
		case BTF_KIND_TYPEDEF:
		case BTF_KIND_PTR:
		case BTF_KIND_CONST:
		case BTF_KIND_RESTRICT:
		case BTF_KIND_VOLATILE:
		case BTF_KIND_FUNC_PROTO:
		case BTF_KIND_TYPE_TAG:
			/* All other types are added to split BTF. */
			if (adding_to_base)
				continue;
			err = btf_add_type(&dist->pipe, t);
			break;
		default:
			pr_warn("unexpected kind when adding base type '%s'[%u] of kind [%u] to distilled base BTF.\n",
				name, i, kind);
			return -EINVAL;

		}
		if (err < 0)
			break;
		dist->id_map[i] = id++;
	}
	return err;
}

/* Split BTF ids without a mapping will be shifted downwards since distilled
 * base BTF is smaller than the original base BTF.  For those that have a
 * mapping (either to base or updated split BTF), update the id based on
 * that mapping.
 */
static int btf_update_distilled_type_ids(struct btf_distill *dist, __u32 i)
{
	struct btf_type *t = btf_type_by_id(dist->pipe.dst, i);
	struct btf_field_iter it;
	__u32 *id;
	int err;

	err = btf_field_iter_init(&it, t, BTF_FIELD_ITER_IDS);
	if (err)
		return err;
	while ((id = btf_field_iter_next(&it))) {
		if (dist->id_map[*id])
			*id = dist->id_map[*id];
		else if (*id >= dist->split_start_id)
			*id -= dist->diff_id;
	}
	return 0;
}

/* Create updated split BTF with distilled base BTF; distilled base BTF
 * consists of BTF information required to clarify the types that split
 * BTF refers to, omitting unneeded details.  Specifically it will contain
 * base types and memberless definitions of named structs, unions and enumerated
 * types. Associated reference types like pointers, arrays and anonymous
 * structs, unions and enumerated types will be added to split BTF.
 * Size is recorded for named struct/unions to help guide matching to the
 * target base BTF during later relocation.
 *
 * The only case where structs, unions or enumerated types are fully represented
 * is when they are anonymous; in such cases, the anonymous type is added to
 * split BTF in full.
 *
 * We return newly-created split BTF where the split BTF refers to a newly-created
 * distilled base BTF. Both must be freed separately by the caller.
 */
int btf__distill_base(const struct btf *src_btf, struct btf **new_base_btf,
		      struct btf **new_split_btf)
{
	struct btf *new_base = NULL, *new_split = NULL;
	const struct btf *old_base;
	unsigned int n = btf__type_cnt(src_btf);
	struct btf_distill dist = {};
	struct btf_type *t;
	int i, err = 0;

	/* src BTF must be split BTF. */
	old_base = btf__base_btf(src_btf);
	if (!new_base_btf || !new_split_btf || !old_base)
		return libbpf_err(-EINVAL);

	new_base = btf__new_empty();
	if (!new_base)
		return libbpf_err(-ENOMEM);
	dist.id_map = calloc(n, sizeof(*dist.id_map));
	if (!dist.id_map) {
		err = -ENOMEM;
		goto done;
	}
	dist.pipe.src = src_btf;
	dist.pipe.dst = new_base;
	dist.pipe.str_off_map = hashmap__new(btf_dedup_identity_hash_fn, btf_dedup_equal_fn, NULL);
	if (IS_ERR(dist.pipe.str_off_map)) {
		err = -ENOMEM;
		goto done;
	}
	dist.split_start_id = btf__type_cnt(old_base);
	dist.split_start_str = old_base->hdr->str_len;

	/* Pass over src split BTF; generate the list of base BTF type ids it
	 * references; these will constitute our distilled BTF set to be
	 * distributed over base and split BTF as appropriate.
	 */
	for (i = src_btf->start_id; i < n; i++) {
		err = btf_add_distilled_type_ids(&dist, i);
		if (err < 0)
			goto done;
	}
	/* Next add types for each of the required references to base BTF and split BTF
	 * in turn.
	 */
	err = btf_add_distilled_types(&dist);
	if (err < 0)
		goto done;

	/* Create new split BTF with distilled base BTF as its base; the final
	 * state is split BTF with distilled base BTF that represents enough
	 * about its base references to allow it to be relocated with the base
	 * BTF available.
	 */
	new_split = btf__new_empty_split(new_base);
	if (!new_split_btf) {
		err = -errno;
		goto done;
	}
	dist.pipe.dst = new_split;
	/* First add all split types */
	for (i = src_btf->start_id; i < n; i++) {
		t = btf_type_by_id(src_btf, i);
		err = btf_add_type(&dist.pipe, t);
		if (err < 0)
			goto done;
	}
	/* Now add distilled types to split BTF that are not added to base. */
	err = btf_add_distilled_types(&dist);
	if (err < 0)
		goto done;

	/* All split BTF ids will be shifted downwards since there are less base
	 * BTF ids in distilled base BTF.
	 */
	dist.diff_id = dist.split_start_id - btf__type_cnt(new_base);

	n = btf__type_cnt(new_split);
	/* Now update base/split BTF ids. */
	for (i = 1; i < n; i++) {
		err = btf_update_distilled_type_ids(&dist, i);
		if (err < 0)
			break;
	}
done:
	free(dist.id_map);
	hashmap__free(dist.pipe.str_off_map);
	if (err) {
		btf__free(new_split);
		btf__free(new_base);
		return libbpf_err(err);
	}
	*new_base_btf = new_base;
	*new_split_btf = new_split;

	return 0;
}

const struct btf_header *btf_header(const struct btf *btf)
{
	return btf->hdr;
}

void btf_set_base_btf(struct btf *btf, const struct btf *base_btf)
{
	btf->base_btf = (struct btf *)base_btf;
	btf->start_id = btf__type_cnt(base_btf);
	btf->start_str_off = base_btf->hdr->str_len;
}

int btf__relocate(struct btf *btf, const struct btf *base_btf)
{
	int err = btf_relocate(btf, base_btf, NULL);

	if (!err)
		btf->owns_base = false;
	return libbpf_err(err);
}
+36 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ extern "C" {

#define BTF_ELF_SEC ".BTF"
#define BTF_EXT_ELF_SEC ".BTF.ext"
#define BTF_BASE_ELF_SEC ".BTF.base"
#define MAPS_ELF_SEC ".maps"

struct btf;
@@ -107,6 +108,27 @@ LIBBPF_API struct btf *btf__new_empty(void);
 */
LIBBPF_API struct btf *btf__new_empty_split(struct btf *base_btf);

/**
 * @brief **btf__distill_base()** creates new versions of the split BTF
 * *src_btf* and its base BTF. The new base BTF will only contain the types
 * needed to improve robustness of the split BTF to small changes in base BTF.
 * When that split BTF is loaded against a (possibly changed) base, this
 * distilled base BTF will help update references to that (possibly changed)
 * base BTF.
 *
 * Both the new split and its associated new base BTF must be freed by
 * the caller.
 *
 * If successful, 0 is returned and **new_base_btf** and **new_split_btf**
 * will point at new base/split BTF. Both the new split and its associated
 * new base BTF must be freed by the caller.
 *
 * A negative value is returned on error and the thread-local `errno` variable
 * is set to the error code as well.
 */
LIBBPF_API int btf__distill_base(const struct btf *src_btf, struct btf **new_base_btf,
				 struct btf **new_split_btf);

LIBBPF_API struct btf *btf__parse(const char *path, struct btf_ext **btf_ext);
LIBBPF_API struct btf *btf__parse_split(const char *path, struct btf *base_btf);
LIBBPF_API struct btf *btf__parse_elf(const char *path, struct btf_ext **btf_ext);
@@ -231,6 +253,20 @@ struct btf_dedup_opts {

LIBBPF_API int btf__dedup(struct btf *btf, const struct btf_dedup_opts *opts);

/**
 * @brief **btf__relocate()** will check the split BTF *btf* for references
 * to base BTF kinds, and verify those references are compatible with
 * *base_btf*; if they are, *btf* is adjusted such that is re-parented to
 * *base_btf* and type ids and strings are adjusted to accommodate this.
 *
 * If successful, 0 is returned and **btf** now has **base_btf** as its
 * base.
 *
 * A negative value is returned on error and the thread-local `errno` variable
 * is set to the error code as well.
 */
LIBBPF_API int btf__relocate(struct btf *btf, const struct btf *base_btf);

struct btf_dump;

struct btf_dump_opts {
+506 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading