libbpf: Add NULL check to add_dummy_ksym_var
[linux-2.6-microblaze.git] / tools / lib / bpf / libbpf.c
index 4181d17..c41d9b2 100644 (file)
 #include "libbpf_internal.h"
 #include "hashmap.h"
 
-#ifndef EM_BPF
-#define EM_BPF 247
-#endif
-
 #ifndef BPF_FS_MAGIC
 #define BPF_FS_MAGIC           0xcafe4a11
 #endif
@@ -73,8 +69,7 @@
 #define __printf(a, b) __attribute__((format(printf, a, b)))
 
 static struct bpf_map *bpf_object__add_map(struct bpf_object *obj);
-static const struct btf_type *
-skip_mods_and_typedefs(const struct btf *btf, __u32 id, __u32 *res_id);
+static bool prog_is_subprog(const struct bpf_object *obj, const struct bpf_program *prog);
 
 static int __base_pr(enum libbpf_print_level level, const char *format,
                     va_list args)
@@ -178,6 +173,8 @@ enum kern_feature_id {
        FEAT_PROG_BIND_MAP,
        /* Kernel support for module BTFs */
        FEAT_MODULE_BTF,
+       /* BTF_KIND_FLOAT support */
+       FEAT_BTF_FLOAT,
        __FEAT_CNT,
 };
 
@@ -187,7 +184,9 @@ enum reloc_type {
        RELO_LD64,
        RELO_CALL,
        RELO_DATA,
-       RELO_EXTERN,
+       RELO_EXTERN_VAR,
+       RELO_EXTERN_FUNC,
+       RELO_SUBPROG_ADDR,
 };
 
 struct reloc_desc {
@@ -195,7 +194,6 @@ struct reloc_desc {
        int insn_idx;
        int map_idx;
        int sym_off;
-       bool processed;
 };
 
 struct bpf_sec_def;
@@ -275,6 +273,7 @@ struct bpf_program {
        bpf_program_clear_priv_t clear_priv;
 
        bool load;
+       bool mark_btf_static;
        enum bpf_prog_type type;
        enum bpf_attach_type expected_attach_type;
        int prog_ifindex;
@@ -501,8 +500,6 @@ static Elf_Scn *elf_sec_by_name(const struct bpf_object *obj, const char *name);
 static int elf_sec_hdr(const struct bpf_object *obj, Elf_Scn *scn, GElf_Shdr *hdr);
 static const char *elf_sec_name(const struct bpf_object *obj, Elf_Scn *scn);
 static Elf_Data *elf_sec_data(const struct bpf_object *obj, Elf_Scn *scn);
-static int elf_sym_by_sec_off(const struct bpf_object *obj, size_t sec_idx,
-                             size_t off, __u32 sym_type, GElf_Sym *sym);
 
 void bpf_program__unload(struct bpf_program *prog)
 {
@@ -574,6 +571,21 @@ static bool insn_is_subprog_call(const struct bpf_insn *insn)
               insn->off == 0;
 }
 
+static bool is_ldimm64_insn(struct bpf_insn *insn)
+{
+       return insn->code == (BPF_LD | BPF_IMM | BPF_DW);
+}
+
+static bool is_call_insn(const struct bpf_insn *insn)
+{
+       return insn->code == (BPF_JMP | BPF_CALL);
+}
+
+static bool insn_is_pseudo_func(struct bpf_insn *insn)
+{
+       return is_ldimm64_insn(insn) && insn->src_reg == BPF_PSEUDO_FUNC;
+}
+
 static int
 bpf_object__init_prog(struct bpf_object *obj, struct bpf_program *prog,
                      const char *name, size_t sec_idx, const char *sec_name,
@@ -628,25 +640,29 @@ static int
 bpf_object__add_programs(struct bpf_object *obj, Elf_Data *sec_data,
                         const char *sec_name, int sec_idx)
 {
+       Elf_Data *symbols = obj->efile.symbols;
        struct bpf_program *prog, *progs;
        void *data = sec_data->d_buf;
-       size_t sec_sz = sec_data->d_size, sec_off, prog_sz;
-       int nr_progs, err;
+       size_t sec_sz = sec_data->d_size, sec_off, prog_sz, nr_syms;
+       int nr_progs, err, i;
        const char *name;
        GElf_Sym sym;
 
        progs = obj->programs;
        nr_progs = obj->nr_programs;
+       nr_syms = symbols->d_size / sizeof(GElf_Sym);
        sec_off = 0;
 
-       while (sec_off < sec_sz) {
-               if (elf_sym_by_sec_off(obj, sec_idx, sec_off, STT_FUNC, &sym)) {
-                       pr_warn("sec '%s': failed to find program symbol at offset %zu\n",
-                               sec_name, sec_off);
-                       return -LIBBPF_ERRNO__FORMAT;
-               }
+       for (i = 0; i < nr_syms; i++) {
+               if (!gelf_getsym(symbols, i, &sym))
+                       continue;
+               if (sym.st_shndx != sec_idx)
+                       continue;
+               if (GELF_ST_TYPE(sym.st_info) != STT_FUNC)
+                       continue;
 
                prog_sz = sym.st_size;
+               sec_off = sym.st_value;
 
                name = elf_sym_str(obj, sym.st_name);
                if (!name) {
@@ -684,10 +700,17 @@ bpf_object__add_programs(struct bpf_object *obj, Elf_Data *sec_data,
                if (err)
                        return err;
 
+               /* if function is a global/weak symbol, but has hidden
+                * visibility (STV_HIDDEN), mark its BTF FUNC as static to
+                * enable more permissive BPF verification mode with more
+                * outside context available to BPF verifier
+                */
+               if (GELF_ST_BIND(sym.st_info) != STB_LOCAL
+                   && GELF_ST_VISIBILITY(sym.st_other) == STV_HIDDEN)
+                       prog->mark_btf_static = true;
+
                nr_progs++;
                obj->nr_programs = nr_progs;
-
-               sec_off += prog_sz;
        }
 
        return 0;
@@ -1121,11 +1144,6 @@ static void bpf_object__elf_finish(struct bpf_object *obj)
        obj->efile.obj_buf_sz = 0;
 }
 
-/* if libelf is old and doesn't support mmap(), fall back to read() */
-#ifndef ELF_C_READ_MMAP
-#define ELF_C_READ_MMAP ELF_C_READ
-#endif
-
 static int bpf_object__elf_init(struct bpf_object *obj)
 {
        int err = 0;
@@ -1886,7 +1904,7 @@ static int bpf_object__init_user_maps(struct bpf_object *obj, bool strict)
        return 0;
 }
 
-static const struct btf_type *
+const struct btf_type *
 skip_mods_and_typedefs(const struct btf *btf, __u32 id, __u32 *res_id)
 {
        const struct btf_type *t = btf__type_by_id(btf, id);
@@ -1917,9 +1935,9 @@ resolve_func_ptr(const struct btf *btf, __u32 id, __u32 *res_id)
        return btf_is_func_proto(t) ? t : NULL;
 }
 
-static const char *btf_kind_str(const struct btf_type *t)
+static const char *__btf_kind_str(__u16 kind)
 {
-       switch (btf_kind(t)) {
+       switch (kind) {
        case BTF_KIND_UNKN: return "void";
        case BTF_KIND_INT: return "int";
        case BTF_KIND_PTR: return "ptr";
@@ -1936,10 +1954,16 @@ static const char *btf_kind_str(const struct btf_type *t)
        case BTF_KIND_FUNC_PROTO: return "func_proto";
        case BTF_KIND_VAR: return "var";
        case BTF_KIND_DATASEC: return "datasec";
+       case BTF_KIND_FLOAT: return "float";
        default: return "unknown";
        }
 }
 
+const char *btf_kind_str(const struct btf_type *t)
+{
+       return __btf_kind_str(btf_kind(t));
+}
+
 /*
  * Fetch integer attribute of BTF map definition. Such attributes are
  * represented using a pointer to an array, in which dimensionality of array
@@ -1994,254 +2018,262 @@ static int build_map_pin_path(struct bpf_map *map, const char *path)
        return bpf_map__set_pin_path(map, buf);
 }
 
-
-static int parse_btf_map_def(struct bpf_object *obj,
-                            struct bpf_map *map,
-                            const struct btf_type *def,
-                            bool strict, bool is_inner,
-                            const char *pin_root_path)
+int parse_btf_map_def(const char *map_name, struct btf *btf,
+                     const struct btf_type *def_t, bool strict,
+                     struct btf_map_def *map_def, struct btf_map_def *inner_def)
 {
        const struct btf_type *t;
        const struct btf_member *m;
+       bool is_inner = inner_def == NULL;
        int vlen, i;
 
-       vlen = btf_vlen(def);
-       m = btf_members(def);
+       vlen = btf_vlen(def_t);
+       m = btf_members(def_t);
        for (i = 0; i < vlen; i++, m++) {
-               const char *name = btf__name_by_offset(obj->btf, m->name_off);
+               const char *name = btf__name_by_offset(btf, m->name_off);
 
                if (!name) {
-                       pr_warn("map '%s': invalid field #%d.\n", map->name, i);
+                       pr_warn("map '%s': invalid field #%d.\n", map_name, i);
                        return -EINVAL;
                }
                if (strcmp(name, "type") == 0) {
-                       if (!get_map_field_int(map->name, obj->btf, m,
-                                              &map->def.type))
+                       if (!get_map_field_int(map_name, btf, m, &map_def->map_type))
                                return -EINVAL;
-                       pr_debug("map '%s': found type = %u.\n",
-                                map->name, map->def.type);
+                       map_def->parts |= MAP_DEF_MAP_TYPE;
                } else if (strcmp(name, "max_entries") == 0) {
-                       if (!get_map_field_int(map->name, obj->btf, m,
-                                              &map->def.max_entries))
+                       if (!get_map_field_int(map_name, btf, m, &map_def->max_entries))
                                return -EINVAL;
-                       pr_debug("map '%s': found max_entries = %u.\n",
-                                map->name, map->def.max_entries);
+                       map_def->parts |= MAP_DEF_MAX_ENTRIES;
                } else if (strcmp(name, "map_flags") == 0) {
-                       if (!get_map_field_int(map->name, obj->btf, m,
-                                              &map->def.map_flags))
+                       if (!get_map_field_int(map_name, btf, m, &map_def->map_flags))
                                return -EINVAL;
-                       pr_debug("map '%s': found map_flags = %u.\n",
-                                map->name, map->def.map_flags);
+                       map_def->parts |= MAP_DEF_MAP_FLAGS;
                } else if (strcmp(name, "numa_node") == 0) {
-                       if (!get_map_field_int(map->name, obj->btf, m, &map->numa_node))
+                       if (!get_map_field_int(map_name, btf, m, &map_def->numa_node))
                                return -EINVAL;
-                       pr_debug("map '%s': found numa_node = %u.\n", map->name, map->numa_node);
+                       map_def->parts |= MAP_DEF_NUMA_NODE;
                } else if (strcmp(name, "key_size") == 0) {
                        __u32 sz;
 
-                       if (!get_map_field_int(map->name, obj->btf, m, &sz))
+                       if (!get_map_field_int(map_name, btf, m, &sz))
                                return -EINVAL;
-                       pr_debug("map '%s': found key_size = %u.\n",
-                                map->name, sz);
-                       if (map->def.key_size && map->def.key_size != sz) {
+                       if (map_def->key_size && map_def->key_size != sz) {
                                pr_warn("map '%s': conflicting key size %u != %u.\n",
-                                       map->name, map->def.key_size, sz);
+                                       map_name, map_def->key_size, sz);
                                return -EINVAL;
                        }
-                       map->def.key_size = sz;
+                       map_def->key_size = sz;
+                       map_def->parts |= MAP_DEF_KEY_SIZE;
                } else if (strcmp(name, "key") == 0) {
                        __s64 sz;
 
-                       t = btf__type_by_id(obj->btf, m->type);
+                       t = btf__type_by_id(btf, m->type);
                        if (!t) {
                                pr_warn("map '%s': key type [%d] not found.\n",
-                                       map->name, m->type);
+                                       map_name, m->type);
                                return -EINVAL;
                        }
                        if (!btf_is_ptr(t)) {
                                pr_warn("map '%s': key spec is not PTR: %s.\n",
-                                       map->name, btf_kind_str(t));
+                                       map_name, btf_kind_str(t));
                                return -EINVAL;
                        }
-                       sz = btf__resolve_size(obj->btf, t->type);
+                       sz = btf__resolve_size(btf, t->type);
                        if (sz < 0) {
                                pr_warn("map '%s': can't determine key size for type [%u]: %zd.\n",
-                                       map->name, t->type, (ssize_t)sz);
+                                       map_name, t->type, (ssize_t)sz);
                                return sz;
                        }
-                       pr_debug("map '%s': found key [%u], sz = %zd.\n",
-                                map->name, t->type, (ssize_t)sz);
-                       if (map->def.key_size && map->def.key_size != sz) {
+                       if (map_def->key_size && map_def->key_size != sz) {
                                pr_warn("map '%s': conflicting key size %u != %zd.\n",
-                                       map->name, map->def.key_size, (ssize_t)sz);
+                                       map_name, map_def->key_size, (ssize_t)sz);
                                return -EINVAL;
                        }
-                       map->def.key_size = sz;
-                       map->btf_key_type_id = t->type;
+                       map_def->key_size = sz;
+                       map_def->key_type_id = t->type;
+                       map_def->parts |= MAP_DEF_KEY_SIZE | MAP_DEF_KEY_TYPE;
                } else if (strcmp(name, "value_size") == 0) {
                        __u32 sz;
 
-                       if (!get_map_field_int(map->name, obj->btf, m, &sz))
+                       if (!get_map_field_int(map_name, btf, m, &sz))
                                return -EINVAL;
-                       pr_debug("map '%s': found value_size = %u.\n",
-                                map->name, sz);
-                       if (map->def.value_size && map->def.value_size != sz) {
+                       if (map_def->value_size && map_def->value_size != sz) {
                                pr_warn("map '%s': conflicting value size %u != %u.\n",
-                                       map->name, map->def.value_size, sz);
+                                       map_name, map_def->value_size, sz);
                                return -EINVAL;
                        }
-                       map->def.value_size = sz;
+                       map_def->value_size = sz;
+                       map_def->parts |= MAP_DEF_VALUE_SIZE;
                } else if (strcmp(name, "value") == 0) {
                        __s64 sz;
 
-                       t = btf__type_by_id(obj->btf, m->type);
+                       t = btf__type_by_id(btf, m->type);
                        if (!t) {
                                pr_warn("map '%s': value type [%d] not found.\n",
-                                       map->name, m->type);
+                                       map_name, m->type);
                                return -EINVAL;
                        }
                        if (!btf_is_ptr(t)) {
                                pr_warn("map '%s': value spec is not PTR: %s.\n",
-                                       map->name, btf_kind_str(t));
+                                       map_name, btf_kind_str(t));
                                return -EINVAL;
                        }
-                       sz = btf__resolve_size(obj->btf, t->type);
+                       sz = btf__resolve_size(btf, t->type);
                        if (sz < 0) {
                                pr_warn("map '%s': can't determine value size for type [%u]: %zd.\n",
-                                       map->name, t->type, (ssize_t)sz);
+                                       map_name, t->type, (ssize_t)sz);
                                return sz;
                        }
-                       pr_debug("map '%s': found value [%u], sz = %zd.\n",
-                                map->name, t->type, (ssize_t)sz);
-                       if (map->def.value_size && map->def.value_size != sz) {
+                       if (map_def->value_size && map_def->value_size != sz) {
                                pr_warn("map '%s': conflicting value size %u != %zd.\n",
-                                       map->name, map->def.value_size, (ssize_t)sz);
+                                       map_name, map_def->value_size, (ssize_t)sz);
                                return -EINVAL;
                        }
-                       map->def.value_size = sz;
-                       map->btf_value_type_id = t->type;
+                       map_def->value_size = sz;
+                       map_def->value_type_id = t->type;
+                       map_def->parts |= MAP_DEF_VALUE_SIZE | MAP_DEF_VALUE_TYPE;
                }
                else if (strcmp(name, "values") == 0) {
+                       char inner_map_name[128];
                        int err;
 
                        if (is_inner) {
                                pr_warn("map '%s': multi-level inner maps not supported.\n",
-                                       map->name);
+                                       map_name);
                                return -ENOTSUP;
                        }
                        if (i != vlen - 1) {
                                pr_warn("map '%s': '%s' member should be last.\n",
-                                       map->name, name);
+                                       map_name, name);
                                return -EINVAL;
                        }
-                       if (!bpf_map_type__is_map_in_map(map->def.type)) {
+                       if (!bpf_map_type__is_map_in_map(map_def->map_type)) {
                                pr_warn("map '%s': should be map-in-map.\n",
-                                       map->name);
+                                       map_name);
                                return -ENOTSUP;
                        }
-                       if (map->def.value_size && map->def.value_size != 4) {
+                       if (map_def->value_size && map_def->value_size != 4) {
                                pr_warn("map '%s': conflicting value size %u != 4.\n",
-                                       map->name, map->def.value_size);
+                                       map_name, map_def->value_size);
                                return -EINVAL;
                        }
-                       map->def.value_size = 4;
-                       t = btf__type_by_id(obj->btf, m->type);
+                       map_def->value_size = 4;
+                       t = btf__type_by_id(btf, m->type);
                        if (!t) {
                                pr_warn("map '%s': map-in-map inner type [%d] not found.\n",
-                                       map->name, m->type);
+                                       map_name, m->type);
                                return -EINVAL;
                        }
                        if (!btf_is_array(t) || btf_array(t)->nelems) {
                                pr_warn("map '%s': map-in-map inner spec is not a zero-sized array.\n",
-                                       map->name);
+                                       map_name);
                                return -EINVAL;
                        }
-                       t = skip_mods_and_typedefs(obj->btf, btf_array(t)->type,
-                                                  NULL);
+                       t = skip_mods_and_typedefs(btf, btf_array(t)->type, NULL);
                        if (!btf_is_ptr(t)) {
                                pr_warn("map '%s': map-in-map inner def is of unexpected kind %s.\n",
-                                       map->name, btf_kind_str(t));
+                                       map_name, btf_kind_str(t));
                                return -EINVAL;
                        }
-                       t = skip_mods_and_typedefs(obj->btf, t->type, NULL);
+                       t = skip_mods_and_typedefs(btf, t->type, NULL);
                        if (!btf_is_struct(t)) {
                                pr_warn("map '%s': map-in-map inner def is of unexpected kind %s.\n",
-                                       map->name, btf_kind_str(t));
+                                       map_name, btf_kind_str(t));
                                return -EINVAL;
                        }
 
-                       map->inner_map = calloc(1, sizeof(*map->inner_map));
-                       if (!map->inner_map)
-                               return -ENOMEM;
-                       map->inner_map->sec_idx = obj->efile.btf_maps_shndx;
-                       map->inner_map->name = malloc(strlen(map->name) +
-                                                     sizeof(".inner") + 1);
-                       if (!map->inner_map->name)
-                               return -ENOMEM;
-                       sprintf(map->inner_map->name, "%s.inner", map->name);
-
-                       err = parse_btf_map_def(obj, map->inner_map, t, strict,
-                                               true /* is_inner */, NULL);
+                       snprintf(inner_map_name, sizeof(inner_map_name), "%s.inner", map_name);
+                       err = parse_btf_map_def(inner_map_name, btf, t, strict, inner_def, NULL);
                        if (err)
                                return err;
+
+                       map_def->parts |= MAP_DEF_INNER_MAP;
                } else if (strcmp(name, "pinning") == 0) {
                        __u32 val;
-                       int err;
 
                        if (is_inner) {
-                               pr_debug("map '%s': inner def can't be pinned.\n",
-                                        map->name);
+                               pr_warn("map '%s': inner def can't be pinned.\n", map_name);
                                return -EINVAL;
                        }
-                       if (!get_map_field_int(map->name, obj->btf, m, &val))
+                       if (!get_map_field_int(map_name, btf, m, &val))
                                return -EINVAL;
-                       pr_debug("map '%s': found pinning = %u.\n",
-                                map->name, val);
-
-                       if (val != LIBBPF_PIN_NONE &&
-                           val != LIBBPF_PIN_BY_NAME) {
+                       if (val != LIBBPF_PIN_NONE && val != LIBBPF_PIN_BY_NAME) {
                                pr_warn("map '%s': invalid pinning value %u.\n",
-                                       map->name, val);
+                                       map_name, val);
                                return -EINVAL;
                        }
-                       if (val == LIBBPF_PIN_BY_NAME) {
-                               err = build_map_pin_path(map, pin_root_path);
-                               if (err) {
-                                       pr_warn("map '%s': couldn't build pin path.\n",
-                                               map->name);
-                                       return err;
-                               }
-                       }
+                       map_def->pinning = val;
+                       map_def->parts |= MAP_DEF_PINNING;
                } else {
                        if (strict) {
-                               pr_warn("map '%s': unknown field '%s'.\n",
-                                       map->name, name);
+                               pr_warn("map '%s': unknown field '%s'.\n", map_name, name);
                                return -ENOTSUP;
                        }
-                       pr_debug("map '%s': ignoring unknown field '%s'.\n",
-                                map->name, name);
+                       pr_debug("map '%s': ignoring unknown field '%s'.\n", map_name, name);
                }
        }
 
-       if (map->def.type == BPF_MAP_TYPE_UNSPEC) {
-               pr_warn("map '%s': map type isn't specified.\n", map->name);
+       if (map_def->map_type == BPF_MAP_TYPE_UNSPEC) {
+               pr_warn("map '%s': map type isn't specified.\n", map_name);
                return -EINVAL;
        }
 
        return 0;
 }
 
+static void fill_map_from_def(struct bpf_map *map, const struct btf_map_def *def)
+{
+       map->def.type = def->map_type;
+       map->def.key_size = def->key_size;
+       map->def.value_size = def->value_size;
+       map->def.max_entries = def->max_entries;
+       map->def.map_flags = def->map_flags;
+
+       map->numa_node = def->numa_node;
+       map->btf_key_type_id = def->key_type_id;
+       map->btf_value_type_id = def->value_type_id;
+
+       if (def->parts & MAP_DEF_MAP_TYPE)
+               pr_debug("map '%s': found type = %u.\n", map->name, def->map_type);
+
+       if (def->parts & MAP_DEF_KEY_TYPE)
+               pr_debug("map '%s': found key [%u], sz = %u.\n",
+                        map->name, def->key_type_id, def->key_size);
+       else if (def->parts & MAP_DEF_KEY_SIZE)
+               pr_debug("map '%s': found key_size = %u.\n", map->name, def->key_size);
+
+       if (def->parts & MAP_DEF_VALUE_TYPE)
+               pr_debug("map '%s': found value [%u], sz = %u.\n",
+                        map->name, def->value_type_id, def->value_size);
+       else if (def->parts & MAP_DEF_VALUE_SIZE)
+               pr_debug("map '%s': found value_size = %u.\n", map->name, def->value_size);
+
+       if (def->parts & MAP_DEF_MAX_ENTRIES)
+               pr_debug("map '%s': found max_entries = %u.\n", map->name, def->max_entries);
+       if (def->parts & MAP_DEF_MAP_FLAGS)
+               pr_debug("map '%s': found map_flags = %u.\n", map->name, def->map_flags);
+       if (def->parts & MAP_DEF_PINNING)
+               pr_debug("map '%s': found pinning = %u.\n", map->name, def->pinning);
+       if (def->parts & MAP_DEF_NUMA_NODE)
+               pr_debug("map '%s': found numa_node = %u.\n", map->name, def->numa_node);
+
+       if (def->parts & MAP_DEF_INNER_MAP)
+               pr_debug("map '%s': found inner map definition.\n", map->name);
+}
+
 static int bpf_object__init_user_btf_map(struct bpf_object *obj,
                                         const struct btf_type *sec,
                                         int var_idx, int sec_idx,
                                         const Elf_Data *data, bool strict,
                                         const char *pin_root_path)
 {
+       struct btf_map_def map_def = {}, inner_def = {};
        const struct btf_type *var, *def;
        const struct btf_var_secinfo *vi;
        const struct btf_var *var_extra;
        const char *map_name;
        struct bpf_map *map;
+       int err;
 
        vi = btf_var_secinfos(sec) + var_idx;
        var = btf__type_by_id(obj->btf, vi->type);
@@ -2295,7 +2327,35 @@ static int bpf_object__init_user_btf_map(struct bpf_object *obj,
        pr_debug("map '%s': at sec_idx %d, offset %zu.\n",
                 map_name, map->sec_idx, map->sec_offset);
 
-       return parse_btf_map_def(obj, map, def, strict, false, pin_root_path);
+       err = parse_btf_map_def(map->name, obj->btf, def, strict, &map_def, &inner_def);
+       if (err)
+               return err;
+
+       fill_map_from_def(map, &map_def);
+
+       if (map_def.pinning == LIBBPF_PIN_BY_NAME) {
+               err = build_map_pin_path(map, pin_root_path);
+               if (err) {
+                       pr_warn("map '%s': couldn't build pin path.\n", map->name);
+                       return err;
+               }
+       }
+
+       if (map_def.parts & MAP_DEF_INNER_MAP) {
+               map->inner_map = calloc(1, sizeof(*map->inner_map));
+               if (!map->inner_map)
+                       return -ENOMEM;
+               map->inner_map->fd = -1;
+               map->inner_map->sec_idx = sec_idx;
+               map->inner_map->name = malloc(strlen(map_name) + sizeof(".inner") + 1);
+               if (!map->inner_map->name)
+                       return -ENOMEM;
+               sprintf(map->inner_map->name, "%s.inner", map_name);
+
+               fill_map_from_def(map->inner_map, &inner_def);
+       }
+
+       return 0;
 }
 
 static int bpf_object__init_user_btf_maps(struct bpf_object *obj, bool strict,
@@ -2385,15 +2445,17 @@ static bool btf_needs_sanitization(struct bpf_object *obj)
 {
        bool has_func_global = kernel_supports(FEAT_BTF_GLOBAL_FUNC);
        bool has_datasec = kernel_supports(FEAT_BTF_DATASEC);
+       bool has_float = kernel_supports(FEAT_BTF_FLOAT);
        bool has_func = kernel_supports(FEAT_BTF_FUNC);
 
-       return !has_func || !has_datasec || !has_func_global;
+       return !has_func || !has_datasec || !has_func_global || !has_float;
 }
 
 static void bpf_object__sanitize_btf(struct bpf_object *obj, struct btf *btf)
 {
        bool has_func_global = kernel_supports(FEAT_BTF_GLOBAL_FUNC);
        bool has_datasec = kernel_supports(FEAT_BTF_DATASEC);
+       bool has_float = kernel_supports(FEAT_BTF_FLOAT);
        bool has_func = kernel_supports(FEAT_BTF_FUNC);
        struct btf_type *t;
        int i, j, vlen;
@@ -2446,6 +2508,13 @@ static void bpf_object__sanitize_btf(struct bpf_object *obj, struct btf *btf)
                } else if (!has_func_global && btf_is_func(t)) {
                        /* replace BTF_FUNC_GLOBAL with BTF_FUNC_STATIC */
                        t->info = BTF_INFO_ENC(BTF_KIND_FUNC, 0, 0);
+               } else if (!has_float && btf_is_float(t)) {
+                       /* replace FLOAT with an equally-sized empty STRUCT;
+                        * since C compilers do not accept e.g. "float" as a
+                        * valid struct name, make it anonymous
+                        */
+                       t->name_off = 0;
+                       t->info = BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 0);
                }
        }
 }
@@ -2588,7 +2657,7 @@ static int bpf_object__sanitize_and_load_btf(struct bpf_object *obj)
 {
        struct btf *kern_btf = obj->btf;
        bool btf_mandatory, sanitize;
-       int err = 0;
+       int i, err = 0;
 
        if (!obj->btf)
                return 0;
@@ -2602,6 +2671,38 @@ static int bpf_object__sanitize_and_load_btf(struct bpf_object *obj)
                return 0;
        }
 
+       /* Even though some subprogs are global/weak, user might prefer more
+        * permissive BPF verification process that BPF verifier performs for
+        * static functions, taking into account more context from the caller
+        * functions. In such case, they need to mark such subprogs with
+        * __attribute__((visibility("hidden"))) and libbpf will adjust
+        * corresponding FUNC BTF type to be marked as static and trigger more
+        * involved BPF verification process.
+        */
+       for (i = 0; i < obj->nr_programs; i++) {
+               struct bpf_program *prog = &obj->programs[i];
+               struct btf_type *t;
+               const char *name;
+               int j, n;
+
+               if (!prog->mark_btf_static || !prog_is_subprog(obj, prog))
+                       continue;
+
+               n = btf__get_nr_types(obj->btf);
+               for (j = 1; j <= n; j++) {
+                       t = btf_type_by_id(obj->btf, j);
+                       if (!btf_is_func(t) || btf_func_linkage(t) != BTF_FUNC_GLOBAL)
+                               continue;
+
+                       name = btf__str_by_offset(obj->btf, t->name_off);
+                       if (strcmp(name, prog->name) != 0)
+                               continue;
+
+                       t->info = btf_type_info(BTF_KIND_FUNC, BTF_FUNC_STATIC, 0);
+                       break;
+               }
+       }
+
        sanitize = btf_needs_sanitization(obj);
        if (sanitize) {
                const void *raw_data;
@@ -2752,26 +2853,6 @@ static Elf_Data *elf_sec_data(const struct bpf_object *obj, Elf_Scn *scn)
        return data;
 }
 
-static int elf_sym_by_sec_off(const struct bpf_object *obj, size_t sec_idx,
-                             size_t off, __u32 sym_type, GElf_Sym *sym)
-{
-       Elf_Data *symbols = obj->efile.symbols;
-       size_t n = symbols->d_size / sizeof(GElf_Sym);
-       int i;
-
-       for (i = 0; i < n; i++) {
-               if (!gelf_getsym(symbols, i, sym))
-                       continue;
-               if (sym->st_shndx != sec_idx || sym->st_value != off)
-                       continue;
-               if (GELF_ST_TYPE(sym->st_info) != sym_type)
-                       continue;
-               return 0;
-       }
-
-       return -ENOENT;
-}
-
 static bool is_sec_name_dwarf(const char *name)
 {
        /* approximation, but the actual list is too long */
@@ -2785,7 +2866,7 @@ static bool ignore_elf_section(GElf_Shdr *hdr, const char *name)
                return true;
 
        /* ignore .llvm_addrsig section as well */
-       if (hdr->sh_type == 0x6FFF4C03 /* SHT_LLVM_ADDRSIG */)
+       if (hdr->sh_type == SHT_LLVM_ADDRSIG)
                return true;
 
        /* no subprograms will lead to an empty .text section, ignore it */
@@ -2975,10 +3056,27 @@ static bool sym_is_extern(const GElf_Sym *sym)
               GELF_ST_TYPE(sym->st_info) == STT_NOTYPE;
 }
 
+static bool sym_is_subprog(const GElf_Sym *sym, int text_shndx)
+{
+       int bind = GELF_ST_BIND(sym->st_info);
+       int type = GELF_ST_TYPE(sym->st_info);
+
+       /* in .text section */
+       if (sym->st_shndx != text_shndx)
+               return false;
+
+       /* local function */
+       if (bind == STB_LOCAL && type == STT_SECTION)
+               return true;
+
+       /* global function */
+       return bind == STB_GLOBAL && type == STT_FUNC;
+}
+
 static int find_extern_btf_id(const struct btf *btf, const char *ext_name)
 {
        const struct btf_type *t;
-       const char *var_name;
+       const char *tname;
        int i, n;
 
        if (!btf)
@@ -2988,14 +3086,18 @@ static int find_extern_btf_id(const struct btf *btf, const char *ext_name)
        for (i = 1; i <= n; i++) {
                t = btf__type_by_id(btf, i);
 
-               if (!btf_is_var(t))
+               if (!btf_is_var(t) && !btf_is_func(t))
                        continue;
 
-               var_name = btf__name_by_offset(btf, t->name_off);
-               if (strcmp(var_name, ext_name))
+               tname = btf__name_by_offset(btf, t->name_off);
+               if (strcmp(tname, ext_name))
                        continue;
 
-               if (btf_var(t)->linkage != BTF_VAR_GLOBAL_EXTERN)
+               if (btf_is_var(t) &&
+                   btf_var(t)->linkage != BTF_VAR_GLOBAL_EXTERN)
+                       return -EINVAL;
+
+               if (btf_is_func(t) && btf_func_linkage(t) != BTF_FUNC_EXTERN)
                        return -EINVAL;
 
                return i;
@@ -3108,12 +3210,51 @@ static int find_int_btf_id(const struct btf *btf)
        return 0;
 }
 
+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_SEC,
+                                           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_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,
+                                       "dummy_ksym",
+                                       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 bpf_object__collect_externs(struct bpf_object *obj)
 {
        struct btf_type *sec, *kcfg_sec = NULL, *ksym_sec = NULL;
        const struct btf_type *t;
        struct extern_desc *ext;
-       int i, n, off;
+       int i, n, off, dummy_var_btf_id;
        const char *ext_name, *sec_name;
        Elf_Scn *scn;
        GElf_Shdr sh;
@@ -3125,6 +3266,10 @@ static int bpf_object__collect_externs(struct bpf_object *obj)
        if (elf_sec_hdr(obj, scn, &sh))
                return -LIBBPF_ERRNO__FORMAT;
 
+       dummy_var_btf_id = add_dummy_ksym_var(obj->btf);
+       if (dummy_var_btf_id < 0)
+               return dummy_var_btf_id;
+
        n = sh.sh_size / sh.sh_entsize;
        pr_debug("looking for externs among %d symbols...\n", n);
 
@@ -3169,6 +3314,11 @@ static int bpf_object__collect_externs(struct bpf_object *obj)
                sec_name = btf__name_by_offset(obj->btf, sec->name_off);
 
                if (strcmp(sec_name, KCONFIG_SEC) == 0) {
+                       if (btf_is_func(t)) {
+                               pr_warn("extern function %s is unsupported under %s section\n",
+                                       ext->name, KCONFIG_SEC);
+                               return -ENOTSUP;
+                       }
                        kcfg_sec = sec;
                        ext->type = EXT_KCFG;
                        ext->kcfg.sz = btf__resolve_size(obj->btf, t->type);
@@ -3190,6 +3340,11 @@ static int bpf_object__collect_externs(struct bpf_object *obj)
                                return -ENOTSUP;
                        }
                } else if (strcmp(sec_name, KSYMS_SEC) == 0) {
+                       if (btf_is_func(t) && ext->is_weak) {
+                               pr_warn("extern weak function %s is unsupported\n",
+                                       ext->name);
+                               return -ENOTSUP;
+                       }
                        ksym_sec = sec;
                        ext->type = EXT_KSYM;
                        skip_mods_and_typedefs(obj->btf, t->type,
@@ -3216,7 +3371,14 @@ static int bpf_object__collect_externs(struct bpf_object *obj)
                 * extern variables in DATASEC
                 */
                int 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.
+                */
+               const struct btf_type *dummy_var;
 
+               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)
@@ -3235,12 +3397,32 @@ static int bpf_object__collect_externs(struct bpf_object *obj)
                        ext_name = btf__name_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 var '%s'\n",
-                                       ext_name);
+                               pr_warn("failed to find extern definition for BTF %s '%s'\n",
+                                       btf_kind_str(vt), ext_name);
                                return -ESRCH;
                        }
-                       btf_var(vt)->linkage = BTF_VAR_GLOBAL_ALLOCATED;
-                       vt->type = int_btf_id;
+                       if (btf_is_func(vt)) {
+                               const struct btf_type *func_proto;
+                               struct btf_param *param;
+                               int j;
+
+                               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);
                }
@@ -3370,33 +3552,7 @@ static int bpf_program__record_reloc(struct bpf_program *prog,
        const char *sym_sec_name;
        struct bpf_map *map;
 
-       reloc_desc->processed = false;
-
-       /* sub-program call relocation */
-       if (insn->code == (BPF_JMP | BPF_CALL)) {
-               if (insn->src_reg != BPF_PSEUDO_CALL) {
-                       pr_warn("prog '%s': incorrect bpf_call opcode\n", prog->name);
-                       return -LIBBPF_ERRNO__RELOC;
-               }
-               /* text_shndx can be 0, if no default "main" program exists */
-               if (!shdr_idx || shdr_idx != obj->efile.text_shndx) {
-                       sym_sec_name = elf_sec_name(obj, elf_sec_by_idx(obj, shdr_idx));
-                       pr_warn("prog '%s': bad call relo against '%s' in section '%s'\n",
-                               prog->name, sym_name, sym_sec_name);
-                       return -LIBBPF_ERRNO__RELOC;
-               }
-               if (sym->st_value % BPF_INSN_SZ) {
-                       pr_warn("prog '%s': bad call relo against '%s' at offset %zu\n",
-                               prog->name, sym_name, (size_t)sym->st_value);
-                       return -LIBBPF_ERRNO__RELOC;
-               }
-               reloc_desc->type = RELO_CALL;
-               reloc_desc->insn_idx = insn_idx;
-               reloc_desc->sym_off = sym->st_value;
-               return 0;
-       }
-
-       if (insn->code != (BPF_LD | BPF_IMM | BPF_DW)) {
+       if (!is_call_insn(insn) && !is_ldimm64_insn(insn)) {
                pr_warn("prog '%s': invalid relo against '%s' for insns[%d].code 0x%x\n",
                        prog->name, sym_name, insn_idx, insn->code);
                return -LIBBPF_ERRNO__RELOC;
@@ -3419,18 +3575,62 @@ static int bpf_program__record_reloc(struct bpf_program *prog,
                }
                pr_debug("prog '%s': found extern #%d '%s' (sym %d) for insn #%u\n",
                         prog->name, i, ext->name, ext->sym_idx, insn_idx);
-               reloc_desc->type = RELO_EXTERN;
+               if (insn->code == (BPF_JMP | BPF_CALL))
+                       reloc_desc->type = RELO_EXTERN_FUNC;
+               else
+                       reloc_desc->type = RELO_EXTERN_VAR;
                reloc_desc->insn_idx = insn_idx;
                reloc_desc->sym_off = i; /* sym_off stores extern index */
                return 0;
        }
 
+       /* sub-program call relocation */
+       if (is_call_insn(insn)) {
+               if (insn->src_reg != BPF_PSEUDO_CALL) {
+                       pr_warn("prog '%s': incorrect bpf_call opcode\n", prog->name);
+                       return -LIBBPF_ERRNO__RELOC;
+               }
+               /* text_shndx can be 0, if no default "main" program exists */
+               if (!shdr_idx || shdr_idx != obj->efile.text_shndx) {
+                       sym_sec_name = elf_sec_name(obj, elf_sec_by_idx(obj, shdr_idx));
+                       pr_warn("prog '%s': bad call relo against '%s' in section '%s'\n",
+                               prog->name, sym_name, sym_sec_name);
+                       return -LIBBPF_ERRNO__RELOC;
+               }
+               if (sym->st_value % BPF_INSN_SZ) {
+                       pr_warn("prog '%s': bad call relo against '%s' at offset %zu\n",
+                               prog->name, sym_name, (size_t)sym->st_value);
+                       return -LIBBPF_ERRNO__RELOC;
+               }
+               reloc_desc->type = RELO_CALL;
+               reloc_desc->insn_idx = insn_idx;
+               reloc_desc->sym_off = sym->st_value;
+               return 0;
+       }
+
        if (!shdr_idx || shdr_idx >= SHN_LORESERVE) {
                pr_warn("prog '%s': invalid relo against '%s' in special section 0x%x; forgot to initialize global var?..\n",
                        prog->name, sym_name, shdr_idx);
                return -LIBBPF_ERRNO__RELOC;
        }
 
+       /* loading subprog addresses */
+       if (sym_is_subprog(sym, obj->efile.text_shndx)) {
+               /* global_func: sym->st_value = offset in the section, insn->imm = 0.
+                * local_func: sym->st_value = 0, insn->imm = offset in the section.
+                */
+               if ((sym->st_value % BPF_INSN_SZ) || (insn->imm % BPF_INSN_SZ)) {
+                       pr_warn("prog '%s': bad subprog addr relo against '%s' at offset %zu+%d\n",
+                               prog->name, sym_name, (size_t)sym->st_value, insn->imm);
+                       return -LIBBPF_ERRNO__RELOC;
+               }
+
+               reloc_desc->type = RELO_SUBPROG_ADDR;
+               reloc_desc->insn_idx = insn_idx;
+               reloc_desc->sym_off = sym->st_value;
+               return 0;
+       }
+
        type = bpf_object__section_to_libbpf_map_type(obj, shdr_idx);
        sym_sec_name = elf_sec_name(obj, elf_sec_by_idx(obj, shdr_idx));
 
@@ -3534,11 +3734,16 @@ bpf_object__collect_prog_relos(struct bpf_object *obj, GElf_Shdr *shdr, Elf_Data
        int err, i, nrels;
        const char *sym_name;
        __u32 insn_idx;
+       Elf_Scn *scn;
+       Elf_Data *scn_data;
        GElf_Sym sym;
        GElf_Rel rel;
 
+       scn = elf_sec_by_idx(obj, sec_idx);
+       scn_data = elf_sec_data(obj, scn);
+
        relo_sec_name = elf_sec_str(obj, shdr->sh_name);
-       sec_name = elf_sec_name(obj, elf_sec_by_idx(obj, sec_idx));
+       sec_name = elf_sec_name(obj, scn);
        if (!relo_sec_name || !sec_name)
                return -EINVAL;
 
@@ -3556,7 +3761,8 @@ bpf_object__collect_prog_relos(struct bpf_object *obj, GElf_Shdr *shdr, Elf_Data
                                relo_sec_name, (size_t)GELF_R_SYM(rel.r_info), i);
                        return -LIBBPF_ERRNO__FORMAT;
                }
-               if (rel.r_offset % BPF_INSN_SZ) {
+
+               if (rel.r_offset % BPF_INSN_SZ || rel.r_offset >= scn_data->d_size) {
                        pr_warn("sec '%s': invalid offset 0x%zx for relo #%d\n",
                                relo_sec_name, (size_t)GELF_R_SYM(rel.r_info), i);
                        return -LIBBPF_ERRNO__FORMAT;
@@ -3580,9 +3786,9 @@ bpf_object__collect_prog_relos(struct bpf_object *obj, GElf_Shdr *shdr, Elf_Data
 
                prog = find_prog_by_sec_insn(obj, sec_idx, insn_idx);
                if (!prog) {
-                       pr_warn("sec '%s': relo #%d: program not found in section '%s' for insn #%u\n",
+                       pr_debug("sec '%s': relo #%d: couldn't find program in section '%s' for insn #%u, probably overridden weak function, skipping...\n",
                                relo_sec_name, i, sec_name, insn_idx);
-                       return -LIBBPF_ERRNO__RELOC;
+                       continue;
                }
 
                relos = libbpf_reallocarray(prog->reloc_desc,
@@ -3697,6 +3903,14 @@ __u32 bpf_map__max_entries(const struct bpf_map *map)
        return map->def.max_entries;
 }
 
+struct bpf_map *bpf_map__inner_map(struct bpf_map *map)
+{
+       if (!bpf_map_type__is_map_in_map(map->def.type))
+               return NULL;
+
+       return map->inner_map;
+}
+
 int bpf_map__set_max_entries(struct bpf_map *map, __u32 max_entries)
 {
        if (map->fd >= 0)
@@ -3883,6 +4097,18 @@ static int probe_kern_btf_datasec(void)
                                             strs, sizeof(strs)));
 }
 
+static int probe_kern_btf_float(void)
+{
+       static const char strs[] = "\0float";
+       __u32 types[] = {
+               /* float */
+               BTF_TYPE_FLOAT_ENC(1, 4),
+       };
+
+       return probe_fd(libbpf__load_raw_btf((char *)types, sizeof(types),
+                                            strs, sizeof(strs)));
+}
+
 static int probe_kern_array_mmap(void)
 {
        struct bpf_create_map_attr attr = {
@@ -4062,6 +4288,9 @@ static struct kern_feature_desc {
        [FEAT_MODULE_BTF] = {
                "module BTF support", probe_module_btf,
        },
+       [FEAT_BTF_FLOAT] = {
+               "BTF_KIND_FLOAT support", probe_kern_btf_float,
+       },
 };
 
 static bool kernel_supports(enum kern_feature_id feat_id)
@@ -4796,8 +5025,8 @@ static int load_module_btfs(struct bpf_object *obj)
                        goto err_out;
                }
 
-               err = btf_ensure_mem((void **)&obj->btf_modules, &obj->btf_module_cap,
-                                    sizeof(*obj->btf_modules), obj->btf_module_cnt + 1);
+               err = libbpf_ensure_mem((void **)&obj->btf_modules, &obj->btf_module_cap,
+                                       sizeof(*obj->btf_modules), obj->btf_module_cnt + 1);
                if (err)
                        goto err_out;
 
@@ -4889,6 +5118,7 @@ err_out:
  *     least one of enums should be anonymous;
  *   - for ENUMs, check sizes, names are ignored;
  *   - for INT, size and signedness are ignored;
+ *   - any two FLOATs are always compatible;
  *   - for ARRAY, dimensionality is ignored, element types are checked for
  *     compatibility recursively;
  *   - everything else shouldn't be ever a target of relocation.
@@ -4915,6 +5145,7 @@ recur:
 
        switch (btf_kind(local_type)) {
        case BTF_KIND_PTR:
+       case BTF_KIND_FLOAT:
                return 1;
        case BTF_KIND_FWD:
        case BTF_KIND_ENUM: {
@@ -5567,11 +5798,6 @@ static void bpf_core_poison_insn(struct bpf_program *prog, int relo_idx,
        insn->imm = 195896080; /* => 0xbad2310 => "bad relo" */
 }
 
-static bool is_ldimm64(struct bpf_insn *insn)
-{
-       return insn->code == (BPF_LD | BPF_IMM | BPF_DW);
-}
-
 static int insn_bpf_size_to_bytes(struct bpf_insn *insn)
 {
        switch (BPF_SIZE(insn->code)) {
@@ -5637,7 +5863,7 @@ poison:
                /* poison second part of ldimm64 to avoid confusing error from
                 * verifier about "unknown opcode 00"
                 */
-               if (is_ldimm64(insn))
+               if (is_ldimm64_insn(insn))
                        bpf_core_poison_insn(prog, relo_idx, insn_idx + 1, insn + 1);
                bpf_core_poison_insn(prog, relo_idx, insn_idx, insn);
                return 0;
@@ -5713,7 +5939,7 @@ poison:
        case BPF_LD: {
                __u64 imm;
 
-               if (!is_ldimm64(insn) ||
+               if (!is_ldimm64_insn(insn) ||
                    insn[0].src_reg != 0 || insn[0].off != 0 ||
                    insn_idx + 1 >= prog->insns_cnt ||
                    insn[1].code != 0 || insn[1].dst_reg != 0 ||
@@ -6024,8 +6250,8 @@ patch_insn:
        /* bpf_core_patch_insn() should know how to handle missing targ_spec */
        err = bpf_core_patch_insn(prog, relo, relo_idx, &targ_res);
        if (err) {
-               pr_warn("prog '%s': relo #%d: failed to patch insn at offset %d: %d\n",
-                       prog->name, relo_idx, relo->insn_off, err);
+               pr_warn("prog '%s': relo #%d: failed to patch insn #%zu: %d\n",
+                       prog->name, relo_idx, relo->insn_off / BPF_INSN_SZ, err);
                return -EINVAL;
        }
 
@@ -6147,15 +6373,13 @@ bpf_object__relocate_data(struct bpf_object *obj, struct bpf_program *prog)
                case RELO_LD64:
                        insn[0].src_reg = BPF_PSEUDO_MAP_FD;
                        insn[0].imm = obj->maps[relo->map_idx].fd;
-                       relo->processed = true;
                        break;
                case RELO_DATA:
                        insn[0].src_reg = BPF_PSEUDO_MAP_VALUE;
                        insn[1].imm = insn[0].imm + relo->sym_off;
                        insn[0].imm = obj->maps[relo->map_idx].fd;
-                       relo->processed = true;
                        break;
-               case RELO_EXTERN:
+               case RELO_EXTERN_VAR:
                        ext = &obj->externs[relo->sym_off];
                        if (ext->type == EXT_KCFG) {
                                insn[0].src_reg = BPF_PSEUDO_MAP_VALUE;
@@ -6171,7 +6395,15 @@ bpf_object__relocate_data(struct bpf_object *obj, struct bpf_program *prog)
                                        insn[1].imm = ext->ksym.addr >> 32;
                                }
                        }
-                       relo->processed = true;
+                       break;
+               case RELO_EXTERN_FUNC:
+                       ext = &obj->externs[relo->sym_off];
+                       insn[0].src_reg = BPF_PSEUDO_KFUNC_CALL;
+                       insn[0].imm = ext->ksym.kernel_btf_id;
+                       break;
+               case RELO_SUBPROG_ADDR:
+                       insn[0].src_reg = BPF_PSEUDO_FUNC;
+                       /* will be handled as a follow up pass */
                        break;
                case RELO_CALL:
                        /* will be handled as a follow up pass */
@@ -6359,11 +6591,11 @@ bpf_object__reloc_code(struct bpf_object *obj, struct bpf_program *main_prog,
 
        for (insn_idx = 0; insn_idx < prog->sec_insn_cnt; insn_idx++) {
                insn = &main_prog->insns[prog->sub_insn_off + insn_idx];
-               if (!insn_is_subprog_call(insn))
+               if (!insn_is_subprog_call(insn) && !insn_is_pseudo_func(insn))
                        continue;
 
                relo = find_prog_insn_relo(prog, insn_idx);
-               if (relo && relo->type != RELO_CALL) {
+               if (relo && relo->type != RELO_CALL && relo->type != RELO_SUBPROG_ADDR) {
                        pr_warn("prog '%s': unexpected relo for insn #%zu, type %d\n",
                                prog->name, insn_idx, relo->type);
                        return -LIBBPF_ERRNO__RELOC;
@@ -6375,8 +6607,22 @@ bpf_object__reloc_code(struct bpf_object *obj, struct bpf_program *main_prog,
                         * call always has imm = -1, but for static functions
                         * relocation is against STT_SECTION and insn->imm
                         * points to a start of a static function
+                        *
+                        * for subprog addr relocation, the relo->sym_off + insn->imm is
+                        * the byte offset in the corresponding section.
+                        */
+                       if (relo->type == RELO_CALL)
+                               sub_insn_idx = relo->sym_off / BPF_INSN_SZ + insn->imm + 1;
+                       else
+                               sub_insn_idx = (relo->sym_off + insn->imm) / BPF_INSN_SZ;
+               } else if (insn_is_pseudo_func(insn)) {
+                       /*
+                        * RELO_SUBPROG_ADDR relo is always emitted even if both
+                        * functions are in the same section, so it shouldn't reach here.
                         */
-                       sub_insn_idx = relo->sym_off / BPF_INSN_SZ + insn->imm + 1;
+                       pr_warn("prog '%s': missing subprog addr relo for insn #%zu\n",
+                               prog->name, insn_idx);
+                       return -LIBBPF_ERRNO__RELOC;
                } else {
                        /* if subprogram call is to a static function within
                         * the same ELF section, there won't be any relocation
@@ -6439,9 +6685,6 @@ bpf_object__reloc_code(struct bpf_object *obj, struct bpf_program *main_prog,
                 * different main programs */
                insn->imm = subprog->sub_insn_off - (prog->sub_insn_off + insn_idx) - 1;
 
-               if (relo)
-                       relo->processed = true;
-
                pr_debug("prog '%s': insn #%zu relocated, imm %d points to subprog '%s' (now at %zu offset)\n",
                         prog->name, insn_idx, insn->imm, subprog->name, subprog->sub_insn_off);
        }
@@ -6534,7 +6777,7 @@ static int
 bpf_object__relocate_calls(struct bpf_object *obj, struct bpf_program *prog)
 {
        struct bpf_program *subprog;
-       int i, j, err;
+       int i, err;
 
        /* mark all subprogs as not relocated (yet) within the context of
         * current main program
@@ -6545,9 +6788,6 @@ bpf_object__relocate_calls(struct bpf_object *obj, struct bpf_program *prog)
                        continue;
 
                subprog->sub_insn_off = 0;
-               for (j = 0; j < subprog->nr_reloc; j++)
-                       if (subprog->reloc_desc[j].type == RELO_CALL)
-                               subprog->reloc_desc[j].processed = false;
        }
 
        err = bpf_object__reloc_code(obj, prog, prog);
@@ -6794,7 +7034,7 @@ static bool insn_is_helper_call(struct bpf_insn *insn, enum bpf_func_id *func_id
        return false;
 }
 
-static int bpf_object__sanitize_prog(struct bpf_objectobj, struct bpf_program *prog)
+static int bpf_object__sanitize_prog(struct bpf_object *obj, struct bpf_program *prog)
 {
        struct bpf_insn *insn = prog->insns;
        enum bpf_func_id func_id;
@@ -7275,6 +7515,7 @@ static int bpf_object__read_kallsyms_file(struct bpf_object *obj)
 {
        char sym_type, sym_name[500];
        unsigned long long sym_addr;
+       const struct btf_type *t;
        struct extern_desc *ext;
        int ret, err = 0;
        FILE *f;
@@ -7301,6 +7542,10 @@ static int bpf_object__read_kallsyms_file(struct bpf_object *obj)
                if (!ext || ext->type != EXT_KSYM)
                        continue;
 
+               t = btf__type_by_id(obj->btf, ext->btf_id);
+               if (!btf_is_var(t))
+                       continue;
+
                if (ext->is_set && ext->ksym.addr != sym_addr) {
                        pr_warn("extern (ksym) '%s' resolution is ambiguous: 0x%llx or 0x%llx\n",
                                sym_name, ext->ksym.addr, sym_addr);
@@ -7319,75 +7564,151 @@ out:
        return err;
 }
 
-static int bpf_object__resolve_ksyms_btf_id(struct bpf_object *obj)
+static int find_ksym_btf_id(struct bpf_object *obj, const char *ksym_name,
+                           __u16 kind, struct btf **res_btf,
+                           int *res_btf_fd)
 {
-       struct extern_desc *ext;
+       int i, id, btf_fd, err;
        struct btf *btf;
-       int i, j, id, btf_fd, err;
 
-       for (i = 0; i < obj->nr_extern; i++) {
-               const struct btf_type *targ_var, *targ_type;
-               __u32 targ_type_id, local_type_id;
-               const char *targ_var_name;
-               int ret;
-
-               ext = &obj->externs[i];
-               if (ext->type != EXT_KSYM || !ext->ksym.type_id)
-                       continue;
+       btf = obj->btf_vmlinux;
+       btf_fd = 0;
+       id = btf__find_by_name_kind(btf, ksym_name, kind);
 
-               btf = obj->btf_vmlinux;
-               btf_fd = 0;
-               id = btf__find_by_name_kind(btf, ext->name, BTF_KIND_VAR);
-               if (id == -ENOENT) {
-                       err = load_module_btfs(obj);
-                       if (err)
-                               return err;
+       if (id == -ENOENT) {
+               err = load_module_btfs(obj);
+               if (err)
+                       return err;
 
-                       for (j = 0; j < obj->btf_module_cnt; j++) {
-                               btf = obj->btf_modules[j].btf;
-                               /* we assume module BTF FD is always >0 */
-                               btf_fd = obj->btf_modules[j].fd;
-                               id = btf__find_by_name_kind(btf, ext->name, BTF_KIND_VAR);
-                               if (id != -ENOENT)
-                                       break;
-                       }
-               }
-               if (id <= 0) {
-                       pr_warn("extern (ksym) '%s': failed to find BTF ID in kernel BTF(s).\n",
-                               ext->name);
-                       return -ESRCH;
+               for (i = 0; i < obj->btf_module_cnt; i++) {
+                       btf = obj->btf_modules[i].btf;
+                       /* we assume module BTF FD is always >0 */
+                       btf_fd = obj->btf_modules[i].fd;
+                       id = btf__find_by_name_kind(btf, ksym_name, kind);
+                       if (id != -ENOENT)
+                               break;
                }
+       }
+       if (id <= 0) {
+               pr_warn("extern (%s ksym) '%s': failed to find BTF ID in kernel BTF(s).\n",
+                       __btf_kind_str(kind), ksym_name);
+               return -ESRCH;
+       }
+
+       *res_btf = btf;
+       *res_btf_fd = btf_fd;
+       return id;
+}
 
-               /* find local type_id */
-               local_type_id = ext->ksym.type_id;
+static int bpf_object__resolve_ksym_var_btf_id(struct bpf_object *obj,
+                                              struct extern_desc *ext)
+{
+       const struct btf_type *targ_var, *targ_type;
+       __u32 targ_type_id, local_type_id;
+       const char *targ_var_name;
+       int id, btf_fd = 0, err;
+       struct btf *btf = NULL;
 
-               /* find target type_id */
-               targ_var = btf__type_by_id(btf, id);
-               targ_var_name = btf__name_by_offset(btf, targ_var->name_off);
-               targ_type = skip_mods_and_typedefs(btf, targ_var->type, &targ_type_id);
+       id = find_ksym_btf_id(obj, ext->name, BTF_KIND_VAR, &btf, &btf_fd);
+       if (id < 0)
+               return id;
 
-               ret = bpf_core_types_are_compat(obj->btf, local_type_id,
-                                               btf, targ_type_id);
-               if (ret <= 0) {
-                       const struct btf_type *local_type;
-                       const char *targ_name, *local_name;
+       /* find local type_id */
+       local_type_id = ext->ksym.type_id;
 
-                       local_type = btf__type_by_id(obj->btf, local_type_id);
-                       local_name = btf__name_by_offset(obj->btf, local_type->name_off);
-                       targ_name = btf__name_by_offset(btf, targ_type->name_off);
+       /* find target type_id */
+       targ_var = btf__type_by_id(btf, id);
+       targ_var_name = btf__name_by_offset(btf, targ_var->name_off);
+       targ_type = skip_mods_and_typedefs(btf, targ_var->type, &targ_type_id);
 
-                       pr_warn("extern (ksym) '%s': incompatible types, expected [%d] %s %s, but kernel has [%d] %s %s\n",
-                               ext->name, local_type_id,
-                               btf_kind_str(local_type), local_name, targ_type_id,
-                               btf_kind_str(targ_type), targ_name);
-                       return -EINVAL;
-               }
+       err = bpf_core_types_are_compat(obj->btf, local_type_id,
+                                       btf, targ_type_id);
+       if (err <= 0) {
+               const struct btf_type *local_type;
+               const char *targ_name, *local_name;
+
+               local_type = btf__type_by_id(obj->btf, local_type_id);
+               local_name = btf__name_by_offset(obj->btf, local_type->name_off);
+               targ_name = btf__name_by_offset(btf, targ_type->name_off);
+
+               pr_warn("extern (var ksym) '%s': incompatible types, expected [%d] %s %s, but kernel has [%d] %s %s\n",
+                       ext->name, local_type_id,
+                       btf_kind_str(local_type), local_name, targ_type_id,
+                       btf_kind_str(targ_type), targ_name);
+               return -EINVAL;
+       }
+
+       ext->is_set = true;
+       ext->ksym.kernel_btf_obj_fd = btf_fd;
+       ext->ksym.kernel_btf_id = id;
+       pr_debug("extern (var ksym) '%s': resolved to [%d] %s %s\n",
+                ext->name, id, btf_kind_str(targ_var), targ_var_name);
+
+       return 0;
+}
+
+static int bpf_object__resolve_ksym_func_btf_id(struct bpf_object *obj,
+                                               struct extern_desc *ext)
+{
+       int local_func_proto_id, kfunc_proto_id, kfunc_id;
+       const struct btf_type *kern_func;
+       struct btf *kern_btf = NULL;
+       int ret, kern_btf_fd = 0;
+
+       local_func_proto_id = ext->ksym.type_id;
+
+       kfunc_id = find_ksym_btf_id(obj, ext->name, BTF_KIND_FUNC,
+                                   &kern_btf, &kern_btf_fd);
+       if (kfunc_id < 0) {
+               pr_warn("extern (func ksym) '%s': not found in kernel BTF\n",
+                       ext->name);
+               return kfunc_id;
+       }
+
+       if (kern_btf != obj->btf_vmlinux) {
+               pr_warn("extern (func ksym) '%s': function in kernel module is not supported\n",
+                       ext->name);
+               return -ENOTSUP;
+       }
 
-               ext->is_set = true;
-               ext->ksym.kernel_btf_obj_fd = btf_fd;
-               ext->ksym.kernel_btf_id = id;
-               pr_debug("extern (ksym) '%s': resolved to [%d] %s %s\n",
-                        ext->name, id, btf_kind_str(targ_var), targ_var_name);
+       kern_func = btf__type_by_id(kern_btf, kfunc_id);
+       kfunc_proto_id = kern_func->type;
+
+       ret = bpf_core_types_are_compat(obj->btf, local_func_proto_id,
+                                       kern_btf, kfunc_proto_id);
+       if (ret <= 0) {
+               pr_warn("extern (func ksym) '%s': func_proto [%d] incompatible with kernel [%d]\n",
+                       ext->name, local_func_proto_id, kfunc_proto_id);
+               return -EINVAL;
+       }
+
+       ext->is_set = true;
+       ext->ksym.kernel_btf_obj_fd = kern_btf_fd;
+       ext->ksym.kernel_btf_id = kfunc_id;
+       pr_debug("extern (func ksym) '%s': resolved to kernel [%d]\n",
+                ext->name, kfunc_id);
+
+       return 0;
+}
+
+static int bpf_object__resolve_ksyms_btf_id(struct bpf_object *obj)
+{
+       const struct btf_type *t;
+       struct extern_desc *ext;
+       int i, err;
+
+       for (i = 0; i < obj->nr_extern; i++) {
+               ext = &obj->externs[i];
+               if (ext->type != EXT_KSYM || !ext->ksym.type_id)
+                       continue;
+
+               t = btf__type_by_id(obj->btf, ext->btf_id);
+               if (btf_is_var(t))
+                       err = bpf_object__resolve_ksym_var_btf_id(obj, ext);
+               else
+                       err = bpf_object__resolve_ksym_func_btf_id(obj, ext);
+               if (err)
+                       return err;
        }
        return 0;
 }
@@ -8194,6 +8515,16 @@ int bpf_object__btf_fd(const struct bpf_object *obj)
        return obj->btf ? btf__fd(obj->btf) : -1;
 }
 
+int bpf_object__set_kversion(struct bpf_object *obj, __u32 kern_version)
+{
+       if (obj->loaded)
+               return -EINVAL;
+
+       obj->kern_version = kern_version;
+
+       return 0;
+}
+
 int bpf_object__set_priv(struct bpf_object *obj, void *priv,
                         bpf_object_clear_priv_t clear_priv)
 {
@@ -8382,7 +8713,7 @@ int bpf_program__nth_fd(const struct bpf_program *prog, int n)
        return fd;
 }
 
-enum bpf_prog_type bpf_program__get_type(struct bpf_program *prog)
+enum bpf_prog_type bpf_program__get_type(const struct bpf_program *prog)
 {
        return prog->type;
 }
@@ -8427,7 +8758,7 @@ BPF_PROG_TYPE_FNS(extension, BPF_PROG_TYPE_EXT);
 BPF_PROG_TYPE_FNS(sk_lookup, BPF_PROG_TYPE_SK_LOOKUP);
 
 enum bpf_attach_type
-bpf_program__get_expected_attach_type(struct bpf_program *prog)
+bpf_program__get_expected_attach_type(const struct bpf_program *prog)
 {
        return prog->expected_attach_type;
 }
@@ -9203,6 +9534,7 @@ int bpf_map__set_inner_map_fd(struct bpf_map *map, int fd)
                pr_warn("error: inner_map_fd already specified\n");
                return -EINVAL;
        }
+       zfree(&map->inner_map);
        map->inner_map_fd = fd;
        return 0;
 }