// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) /* Copyright (c) 2024, Oracle and/or its affiliates. */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #ifdef __KERNEL__ #include #include #include #include #include #include #define btf_type_by_id (struct btf_type *)btf_type_by_id #define btf__type_cnt btf_nr_types #define btf__base_btf btf_base_btf #define btf__name_by_offset btf_name_by_offset #define btf__str_by_offset btf_str_by_offset #define btf_kflag btf_type_kflag #define calloc(nmemb, sz) kvcalloc(nmemb, sz, GFP_KERNEL | __GFP_NOWARN) #define free(ptr) kvfree(ptr) #define qsort(base, num, sz, cmp) sort(base, num, sz, cmp, NULL) #else #include "btf.h" #include "bpf.h" #include "libbpf.h" #include "libbpf_internal.h" #endif /* __KERNEL__ */ struct btf; struct btf_relocate { struct btf *btf; const struct btf *base_btf; const struct btf *dist_base_btf; unsigned int nr_base_types; unsigned int nr_split_types; unsigned int nr_dist_base_types; int dist_str_len; int base_str_len; __u32 *id_map; __u32 *str_map; }; /* Set temporarily in relocation id_map if distilled base struct/union is * embedded in a split BTF struct/union; in such a case, size information must * match between distilled base BTF and base BTF representation of type. */ #define BTF_IS_EMBEDDED ((__u32)-1) /* triple used in sorting/searching distilled base BTF. */ struct btf_name_info { const char *name; /* set when search requires a size match */ bool needs_size: 1; unsigned int size: 31; __u32 id; }; static int btf_relocate_rewrite_type_id(struct btf_relocate *r, __u32 i) { struct btf_type *t = btf_type_by_id(r->btf, 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))) *id = r->id_map[*id]; return 0; } /* Simple string comparison used for sorting within BTF, since all distilled * types are named. If strings match, and size is non-zero for both elements * fall back to using size for ordering. */ static int cmp_btf_name_size(const void *n1, const void *n2) { const struct btf_name_info *ni1 = n1; const struct btf_name_info *ni2 = n2; int name_diff = strcmp(ni1->name, ni2->name); if (!name_diff && ni1->needs_size && ni2->needs_size) return ni2->size - ni1->size; return name_diff; } /* Binary search with a small twist; find leftmost element that matches * so that we can then iterate through all exact matches. So for example * searching { "a", "bb", "bb", "c" } we would always match on the * leftmost "bb". */ static struct btf_name_info *search_btf_name_size(struct btf_name_info *key, struct btf_name_info *vals, int nelems) { struct btf_name_info *ret = NULL; int high = nelems - 1; int low = 0; while (low <= high) { int mid = (low + high)/2; struct btf_name_info *val = &vals[mid]; int diff = cmp_btf_name_size(key, val); if (diff == 0) ret = val; /* even if found, keep searching for leftmost match */ if (diff <= 0) high = mid - 1; else low = mid + 1; } return ret; } /* If a member of a split BTF struct/union refers to a base BTF * struct/union, mark that struct/union id temporarily in the id_map * with BTF_IS_EMBEDDED. Members can be const/restrict/volatile/typedef * reference types, but if a pointer is encountered, the type is no longer * considered embedded. */ static int btf_mark_embedded_composite_type_ids(struct btf_relocate *r, __u32 i) { struct btf_type *t = btf_type_by_id(r->btf, i); struct btf_field_iter it; __u32 *id; int err; if (!btf_is_composite(t)) return 0; err = btf_field_iter_init(&it, t, BTF_FIELD_ITER_IDS); if (err) return err; while ((id = btf_field_iter_next(&it))) { __u32 next_id = *id; while (next_id) { t = btf_type_by_id(r->btf, next_id); switch (btf_kind(t)) { case BTF_KIND_CONST: case BTF_KIND_RESTRICT: case BTF_KIND_VOLATILE: case BTF_KIND_TYPEDEF: case BTF_KIND_TYPE_TAG: next_id = t->type; break; case BTF_KIND_ARRAY: { struct btf_array *a = btf_array(t); next_id = a->type; break; } case BTF_KIND_STRUCT: case BTF_KIND_UNION: if (next_id < r->nr_dist_base_types) r->id_map[next_id] = BTF_IS_EMBEDDED; next_id = 0; break; default: next_id = 0; break; } } } return 0; } /* Build a map from distilled base BTF ids to base BTF ids. To do so, iterate * through base BTF looking up distilled type (using binary search) equivalents. */ static int btf_relocate_map_distilled_base(struct btf_relocate *r) { struct btf_name_info *info, *info_end; struct btf_type *base_t, *dist_t; __u8 *base_name_cnt = NULL; int err = 0; __u32 id; /* generate a sort index array of name/type ids sorted by name for * distilled base BTF to speed name-based lookups. */ info = calloc(r->nr_dist_base_types, sizeof(*info)); if (!info) { err = -ENOMEM; goto done; } info_end = info + r->nr_dist_base_types; for (id = 0; id < r->nr_dist_base_types; id++) { dist_t = btf_type_by_id(r->dist_base_btf, id); info[id].name = btf__name_by_offset(r->dist_base_btf, dist_t->name_off); info[id].id = id; info[id].size = dist_t->size; info[id].needs_size = true; } qsort(info, r->nr_dist_base_types, sizeof(*info), cmp_btf_name_size); /* Mark distilled base struct/union members of split BTF structs/unions * in id_map with BTF_IS_EMBEDDED; this signals that these types * need to match both name and size, otherwise embedding the base * struct/union in the split type is invalid. */ for (id = r->nr_dist_base_types; id < r->nr_split_types; id++) { err = btf_mark_embedded_composite_type_ids(r, id); if (err) goto done; } /* Collect name counts for composite types in base BTF. If multiple * instances of a struct/union of the same name exist, we need to use * size to determine which to map to since name alone is ambiguous. */ base_name_cnt = calloc(r->base_str_len, sizeof(*base_name_cnt)); if (!base_name_cnt) { err = -ENOMEM; goto done; } for (id = 1; id < r->nr_base_types; id++) { base_t = btf_type_by_id(r->base_btf, id); if (!btf_is_composite(base_t) || !base_t->name_off) continue; if (base_name_cnt[base_t->name_off] < 255) base_name_cnt[base_t->name_off]++; } /* Now search base BTF for matching distilled base BTF types. */ for (id = 1; id < r->nr_base_types; id++) { struct btf_name_info *dist_info, base_info = {}; int dist_kind, base_kind; base_t = btf_type_by_id(r->base_btf, id); /* distilled base consists of named types only. */ if (!base_t->name_off) continue; base_kind = btf_kind(base_t); base_info.id = id; base_info.name = btf__name_by_offset(r->base_btf, base_t->name_off); switch (base_kind) { case BTF_KIND_INT: case BTF_KIND_FLOAT: case BTF_KIND_ENUM: case BTF_KIND_ENUM64: /* These types should match both name and size */ base_info.needs_size = true; base_info.size = base_t->size; break; case BTF_KIND_FWD: /* No size considerations for fwds. */ break; case BTF_KIND_STRUCT: case BTF_KIND_UNION: /* Size only needs to be used for struct/union if there * are multiple types in base BTF with the same name. * If there are multiple _distilled_ types with the same * name (a very unlikely scenario), that doesn't matter * unless corresponding _base_ types to match them are * missing. */ base_info.needs_size = base_name_cnt[base_t->name_off] > 1; base_info.size = base_t->size; break; default: continue; } /* iterate over all matching distilled base types */ for (dist_info = search_btf_name_size(&base_info, info, r->nr_dist_base_types); dist_info != NULL && dist_info < info_end && cmp_btf_name_size(&base_info, dist_info) == 0; dist_info++) { if (!dist_info->id || dist_info->id >= r->nr_dist_base_types) { pr_warn("base BTF id [%d] maps to invalid distilled base BTF id [%d]\n", id, dist_info->id); err = -EINVAL; goto done; } dist_t = btf_type_by_id(r->dist_base_btf, dist_info->id); dist_kind = btf_kind(dist_t); /* Validate that the found distilled type is compatible. * Do not error out on mismatch as another match may * occur for an identically-named type. */ switch (dist_kind) { case BTF_KIND_FWD: switch (base_kind) { case BTF_KIND_FWD: if (btf_kflag(dist_t) != btf_kflag(base_t)) continue; break; case BTF_KIND_STRUCT: if (btf_kflag(base_t)) continue; break; case BTF_KIND_UNION: if (!btf_kflag(base_t)) continue; break; default: continue; } break; case BTF_KIND_INT: if (dist_kind != base_kind || btf_int_encoding(base_t) != btf_int_encoding(dist_t)) continue; break; case BTF_KIND_FLOAT: if (dist_kind != base_kind) continue; break; case BTF_KIND_ENUM: /* ENUM and ENUM64 are encoded as sized ENUM in * distilled base BTF. */ if (base_kind != dist_kind && base_kind != BTF_KIND_ENUM64) continue; break; case BTF_KIND_STRUCT: case BTF_KIND_UNION: /* size verification is required for embedded * struct/unions. */ if (r->id_map[dist_info->id] == BTF_IS_EMBEDDED && base_t->size != dist_t->size) continue; break; default: continue; } if (r->id_map[dist_info->id] && r->id_map[dist_info->id] != BTF_IS_EMBEDDED) { /* we already have a match; this tells us that * multiple base types of the same name * have the same size, since for cases where * multiple types have the same name we match * on name and size. In this case, we have * no way of determining which to relocate * to in base BTF, so error out. */ pr_warn("distilled base BTF type '%s' [%u], size %u has multiple candidates of the same size (ids [%u, %u]) in base BTF\n", base_info.name, dist_info->id, base_t->size, id, r->id_map[dist_info->id]); err = -EINVAL; goto done; } /* map id and name */ r->id_map[dist_info->id] = id; r->str_map[dist_t->name_off] = base_t->name_off; } } /* ensure all distilled BTF ids now have a mapping... */ for (id = 1; id < r->nr_dist_base_types; id++) { const char *name; if (r->id_map[id] && r->id_map[id] != BTF_IS_EMBEDDED) continue; dist_t = btf_type_by_id(r->dist_base_btf, id); name = btf__name_by_offset(r->dist_base_btf, dist_t->name_off); pr_warn("distilled base BTF type '%s' [%d] is not mapped to base BTF id\n", name, id); err = -EINVAL; break; } done: free(base_name_cnt); free(info); return err; } /* distilled base should only have named int/float/enum/fwd/struct/union types. */ static int btf_relocate_validate_distilled_base(struct btf_relocate *r) { unsigned int i; for (i = 1; i < r->nr_dist_base_types; i++) { struct btf_type *t = btf_type_by_id(r->dist_base_btf, i); int kind = btf_kind(t); switch (kind) { case BTF_KIND_INT: case BTF_KIND_FLOAT: case BTF_KIND_ENUM: case BTF_KIND_STRUCT: case BTF_KIND_UNION: case BTF_KIND_FWD: if (t->name_off) break; pr_warn("type [%d], kind [%d] is invalid for distilled base BTF; it is anonymous\n", i, kind); return -EINVAL; default: pr_warn("type [%d] in distilled based BTF has unexpected kind [%d]\n", i, kind); return -EINVAL; } } return 0; } static int btf_relocate_rewrite_strs(struct btf_relocate *r, __u32 i) { struct btf_type *t = btf_type_by_id(r->btf, i); struct btf_field_iter it; __u32 *str_off; int off, err; err = btf_field_iter_init(&it, t, BTF_FIELD_ITER_STRS); if (err) return err; while ((str_off = btf_field_iter_next(&it))) { if (!*str_off) continue; if (*str_off >= r->dist_str_len) { *str_off += r->base_str_len - r->dist_str_len; } else { off = r->str_map[*str_off]; if (!off) { pr_warn("string '%s' [offset %u] is not mapped to base BTF\n", btf__str_by_offset(r->btf, off), *str_off); return -ENOENT; } *str_off = off; } } return 0; } /* If successful, output of relocation is updated BTF with base BTF pointing * at base_btf, and type ids, strings adjusted accordingly. */ int btf_relocate(struct btf *btf, const struct btf *base_btf, __u32 **id_map) { unsigned int nr_types = btf__type_cnt(btf); const struct btf_header *dist_base_hdr; const struct btf_header *base_hdr; struct btf_relocate r = {}; int err = 0; __u32 id, i; r.dist_base_btf = btf__base_btf(btf); if (!base_btf || r.dist_base_btf == base_btf) return -EINVAL; r.nr_dist_base_types = btf__type_cnt(r.dist_base_btf); r.nr_base_types = btf__type_cnt(base_btf); r.nr_split_types = nr_types - r.nr_dist_base_types; r.btf = btf; r.base_btf = base_btf; r.id_map = calloc(nr_types, sizeof(*r.id_map)); r.str_map = calloc(btf_header(r.dist_base_btf)->str_len, sizeof(*r.str_map)); dist_base_hdr = btf_header(r.dist_base_btf); base_hdr = btf_header(r.base_btf); r.dist_str_len = dist_base_hdr->str_len; r.base_str_len = base_hdr->str_len; if (!r.id_map || !r.str_map) { err = -ENOMEM; goto err_out; } err = btf_relocate_validate_distilled_base(&r); if (err) goto err_out; /* Split BTF ids need to be adjusted as base and distilled base * have different numbers of types, changing the start id of split * BTF. */ for (id = r.nr_dist_base_types; id < nr_types; id++) r.id_map[id] = id + r.nr_base_types - r.nr_dist_base_types; /* Build a map from distilled base ids to actual base BTF ids; it is used * to update split BTF id references. Also build a str_map mapping from * distilled base BTF names to base BTF names. */ err = btf_relocate_map_distilled_base(&r); if (err) goto err_out; /* Next, rewrite type ids in split BTF, replacing split ids with updated * ids based on number of types in base BTF, and base ids with * relocated ids from base_btf. */ for (i = 0, id = r.nr_dist_base_types; i < r.nr_split_types; i++, id++) { err = btf_relocate_rewrite_type_id(&r, id); if (err) goto err_out; } /* String offsets now need to be updated using the str_map. */ for (i = 0; i < r.nr_split_types; i++) { err = btf_relocate_rewrite_strs(&r, i + r.nr_dist_base_types); if (err) goto err_out; } /* Finally reset base BTF to be base_btf */ btf_set_base_btf(btf, base_btf); if (id_map) { *id_map = r.id_map; r.id_map = NULL; } err_out: free(r.id_map); free(r.str_map); return err; }