This code heavily borrows from bpf_object__collect_externs in libbpf. Here we walk the symbol table and attempt to determine which symbols correspond to external relocations, specifically kconfig options and kernel or module symbols. Signed-off-by: Blaise Boscaccy <bboscaccy@xxxxxxxxxxxxxxxxxxx> --- kernel/bpf/syscall.c | 337 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 337 insertions(+) diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 03ab0bb7bf076..51b14cb9c4ca1 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -6245,6 +6245,339 @@ static int elf_collect(struct bpf_obj *obj) return err; } +static enum bpf_kcfg_type find_kcfg_type(const struct btf *btf, int id, + bool *is_signed) +{ + const struct btf_type *t; + const char *name; + + t = skip_mods_and_typedefs(btf, id, NULL); + name = btf_str_by_offset(btf, t->name_off); + + if (is_signed) + *is_signed = false; + switch (btf_kind(t)) { + case BTF_KIND_INT: { + int enc = btf_int_encoding(t); + + if (enc & BTF_INT_BOOL) + return t->size == 1 ? KCFG_BOOL : KCFG_UNKNOWN; + if (is_signed) + *is_signed = enc & BTF_INT_SIGNED; + if (t->size == 1) + return KCFG_CHAR; + if (t->size < 1 || t->size > 8 || (t->size & (t->size - 1))) + return KCFG_UNKNOWN; + return KCFG_INT; + } + case BTF_KIND_ENUM: + if (t->size != 4) + return KCFG_UNKNOWN; + if (strcmp(name, "libbpf_tristate")) + return KCFG_UNKNOWN; + return KCFG_TRISTATE; + case BTF_KIND_ENUM64: + if (strcmp(name, "libbpf_tristate")) + return KCFG_UNKNOWN; + return KCFG_TRISTATE; + case BTF_KIND_ARRAY: + if (btf_array(t)->nelems == 0) + return KCFG_UNKNOWN; + if (find_kcfg_type(btf, btf_array(t)->type, NULL) != KCFG_CHAR) + return KCFG_UNKNOWN; + return KCFG_CHAR_ARR; + default: + return KCFG_UNKNOWN; + } +} + +static int cmp_externs(const void *_a, const void *_b) +{ + const struct bpf_extern_desc *a = _a; + const struct bpf_extern_desc *b = _b; + + if (a->type != b->type) + return a->type < b->type ? -1 : 1; + + if (a->type == EXT_KCFG) { + /* descending order by alignment requirements */ + if (a->kcfg.align != b->kcfg.align) + return a->kcfg.align > b->kcfg.align ? -1 : 1; + /* ascending order by size, within same alignment class */ + if (a->kcfg.sz != b->kcfg.sz) + return a->kcfg.sz < b->kcfg.sz ? -1 : 1; + } + + /* resolve ties by name */ + return strcmp(a->name, b->name); +} + +static int find_int_btf_id(const struct btf *btf) +{ + const struct btf_type *t; + int i, n; + + n = btf_type_cnt(btf); + for (i = 1; i < n; i++) { + t = btf_type_by_id(btf, i); + + if (btf_type_is_int(t) && btf_type_int_bits(t) == 32) + return i; + } + return 0; +} + +static struct bpf_extern_desc *find_extern_by_name(const struct bpf_obj *obj, + const void *name) +{ + int i; + + for (i = 0; i < obj->nr_extern; i++) { + if (strcmp(obj->externs[i].name, name) == 0) + return &obj->externs[i]; + } + return NULL; +} + +static int add_dummy_ksym_var(struct btf *btf) +{ + int i, int_btf_id, sec_btf_id, dummy_var_btf_id; + const struct btf_var_secinfo *vs; + const struct btf_type *sec; + + if (!btf) + return 0; + + sec_btf_id = btf_find_by_name_kind(btf, ".ksyms", + BTF_KIND_DATASEC); + if (sec_btf_id < 0) + return 0; + + sec = btf_type_by_id(btf, sec_btf_id); + vs = btf_var_secinfos(sec); + for (i = 0; i < btf_vlen(sec); i++, vs++) { + const struct btf_type *vt; + + vt = btf_type_by_id(btf, vs->type); + if (btf_type_is_func(vt)) + break; + } + + /* No func in ksyms sec. No need to add dummy var. */ + if (i == btf_vlen(sec)) + return 0; + + int_btf_id = find_int_btf_id(btf); + + dummy_var_btf_id = btf_add_var(btf, + sec->name_off, + BTF_VAR_GLOBAL_ALLOCATED, + int_btf_id); + if (dummy_var_btf_id < 0) + pr_warn("cannot create a dummy_ksym var\n"); + + return dummy_var_btf_id; +} + +static int collect_externs(struct bpf_obj *obj) +{ + int i, n, off, dummy_var_btf_id; + Elf_Shdr *symsec = &obj->sechdrs[obj->index.sym]; + Elf_Sym *sym = (void *)obj->hdr + symsec->sh_offset; + const char *ext_name; + const char *sec_name; + struct bpf_extern_desc *ext; + const struct btf_type *t; + size_t ext_essent_len; + struct btf_type *sec, *kcfg_sec = NULL, *ksym_sec = NULL; + int size; + int int_btf_id; + const struct btf_type *dummy_var; + struct btf_type *vt; + struct btf_var_secinfo *vs; + const struct btf_type *func_proto; + struct btf_param *param; + int j; + + dummy_var_btf_id = add_dummy_ksym_var(obj->btf); + if (dummy_var_btf_id < 0) + return dummy_var_btf_id; + + for (i = 1; i < symsec->sh_size / sizeof(Elf_Sym); i++) { + if (!sym_is_extern(&sym[i])) + continue; + + ext_name = obj->strtab + sym[i].st_name; + ext = krealloc_array(obj->externs, + obj->nr_extern + 1, + sizeof(struct bpf_extern_desc), + GFP_KERNEL); + if (!ext) + return -ENOMEM; + + obj->externs = ext; + ext = &ext[obj->nr_extern]; + memset(ext, 0, sizeof(*ext)); + obj->nr_extern++; + + ext->btf_id = find_extern_btf_id(obj->btf, ext_name); + if (ext->btf_id <= 0) + return ext->btf_id; + + t = btf_type_by_id(obj->btf, ext->btf_id); + ext->name = btf_str_by_offset(obj->btf, t->name_off); + ext->sym_idx = i; + ext->is_weak = ELF64_ST_BIND(sym->st_info) == STB_WEAK; + + ext_essent_len = bpf_core_essential_name_len(ext->name); + ext->essent_name = NULL; + if (ext_essent_len != strlen(ext->name)) { + ext->essent_name = kstrndup(ext->name, ext_essent_len, GFP_KERNEL); + if (!ext->essent_name) + return -ENOMEM; + } + + ext->sec_btf_id = find_extern_sec_btf_id(obj->btf, ext->btf_id); + if (ext->sec_btf_id <= 0) { + pr_warn("failed to find BTF for extern '%s' [%d] section: %d\n", + ext_name, ext->btf_id, ext->sec_btf_id); + return ext->sec_btf_id; + } + + sec = (void *)btf_type_by_id(obj->btf, ext->sec_btf_id); + sec_name = btf_str_by_offset(obj->btf, sec->name_off); + + if (strcmp(sec_name, ".kconfig") == 0) { + if (btf_type_is_func(t)) { + pr_warn("extern function %s is unsupported under .kconfig section\n", + ext->name); + return -EOPNOTSUPP; + } + kcfg_sec = sec; + ext->type = EXT_KCFG; + + if (!btf_resolve_size(obj->btf, t, &size)) { + pr_warn("failed to resolve size of extern (kcfg) '%s': %d\n", + ext_name, ext->kcfg.sz); + return -EINVAL; + } + ext->kcfg.sz = size; + ext->kcfg.align = btf_align_of(obj->btf, t->type); + if (ext->kcfg.align <= 0) { + pr_warn("failed to determine alignment of extern (kcfg) '%s': %d\n", + ext_name, ext->kcfg.align); + return -EINVAL; + } + ext->kcfg.type = find_kcfg_type(obj->btf, t->type, + &ext->kcfg.is_signed); + if (ext->kcfg.type == KCFG_UNKNOWN) { + pr_warn("extern (kcfg) '%s': type is unsupported\n", ext_name); + return -EOPNOTSUPP; + } + } else if (strcmp(sec_name, ".ksyms") == 0) { + ksym_sec = sec; + ext->type = EXT_KSYM; + skip_mods_and_typedefs(obj->btf, t->type, + &ext->ksym.type_id); + } else { + pr_warn("unrecognized extern section '%s'\n", sec_name); + return -EOPNOTSUPP; + } + } + + sort(obj->externs, obj->nr_extern, sizeof(struct bpf_extern_desc), + cmp_externs, NULL); + + if (ksym_sec) { + /* find existing 4-byte integer type in BTF to use for fake + * extern variables in DATASEC + */ + int_btf_id = find_int_btf_id(obj->btf); + + /* For extern function, a dummy_var added earlier + * will be used to replace the vs->type and + * its name string will be used to refill + * the missing param's name. + */ + dummy_var = btf_type_by_id(obj->btf, dummy_var_btf_id); + for (i = 0; i < obj->nr_extern; i++) { + ext = &obj->externs[i]; + if (ext->type != EXT_KSYM) + continue; + pr_debug("extern (ksym) #%d: symbol %d, name %s\n", + i, ext->sym_idx, ext->name); + } + + sec = ksym_sec; + n = btf_vlen(sec); + for (i = 0, off = 0; i < n; i++, off += sizeof(int)) { + vs = btf_var_secinfos(sec) + i; + vt = (void *)btf_type_by_id(obj->btf, vs->type); + ext_name = btf_str_by_offset(obj->btf, vt->name_off); + ext = find_extern_by_name(obj, ext_name); + if (!ext) { + pr_warn("failed to find extern definition for BTF %s\n", + ext_name); + return -ESRCH; + } + if (btf_type_is_func(vt)) { + func_proto = btf_type_by_id(obj->btf, + vt->type); + param = btf_params(func_proto); + /* Reuse the dummy_var string if the + * func proto does not have param name. + */ + for (j = 0; j < btf_vlen(func_proto); j++) + if (param[j].type && !param[j].name_off) + param[j].name_off = + dummy_var->name_off; + vs->type = dummy_var_btf_id; + vt->info &= ~0xffff; + vt->info |= BTF_FUNC_GLOBAL; + } else { + btf_var(vt)->linkage = BTF_VAR_GLOBAL_ALLOCATED; + vt->type = int_btf_id; + } + vs->offset = off; + vs->size = sizeof(int); + } + sec->size = off; + } + + if (kcfg_sec) { + sec = kcfg_sec; + /* for kcfg externs calculate their offsets within a .kconfig map */ + off = 0; + for (i = 0; i < obj->nr_extern; i++) { + ext = &obj->externs[i]; + if (ext->type != EXT_KCFG) + continue; + + ext->kcfg.data_off = roundup(off, ext->kcfg.align); + off = ext->kcfg.data_off + ext->kcfg.sz; + pr_debug("extern (kcfg) #%d: symbol %d, off %u, name %s\n", + i, ext->sym_idx, ext->kcfg.data_off, ext->name); + } + sec->size = off; + n = btf_vlen(sec); + for (i = 0; i < n; i++) { + vs = btf_var_secinfos(sec) + i; + t = btf_type_by_id(obj->btf, vs->type); + ext_name = btf_str_by_offset(obj->btf, t->name_off); + + ext = find_extern_by_name(obj, ext_name); + if (!ext) { + pr_warn("failed to find extern definition for BTF var '%s'\n", + ext_name); + return -ESRCH; + } + btf_var(t)->linkage = BTF_VAR_GLOBAL_ALLOCATED; + vs->offset = ext->kcfg.data_off; + } + } + return 0; +} + static void free_bpf_obj(struct bpf_obj *obj) { int i; @@ -6480,6 +6813,10 @@ static int load_fd(union bpf_attr *attr) if (err < 0) goto free; + err = collect_externs(obj); + if (err < 0) + goto free; + return obj_f; free: free_bpf_obj(obj); -- 2.47.1