FEAT_PROBE_READ_KERN,
        /* BPF_PROG_BIND_MAP is supported */
        FEAT_PROG_BIND_MAP,
+       /* Kernel support for module BTFs */
+       FEAT_MODULE_BTF,
        __FEAT_CNT,
 };
 
 
 static LIST_HEAD(bpf_objects_list);
 
+struct module_btf {
+       struct btf *btf;
+       char *name;
+       __u32 id;
+};
+
 struct bpf_object {
        char name[BPF_OBJ_NAME_LEN];
        char license[64];
        struct btf *btf_vmlinux;
        /* vmlinux BTF override for CO-RE relocations */
        struct btf *btf_vmlinux_override;
+       /* Lazily initialized kernel module BTFs */
+       struct module_btf *btf_modules;
+       bool btf_modules_loaded;
+       size_t btf_module_cnt;
+       size_t btf_module_cap;
 
        void *priv;
        bpf_object_clear_priv_t clear_priv;
        return ret >= 0;
 }
 
+static int probe_module_btf(void)
+{
+       static const char strs[] = "\0int";
+       __u32 types[] = {
+               /* int */
+               BTF_TYPE_INT_ENC(1, BTF_INT_SIGNED, 0, 32, 4),
+       };
+       struct bpf_btf_info info;
+       __u32 len = sizeof(info);
+       char name[16];
+       int fd, err;
+
+       fd = libbpf__load_raw_btf((char *)types, sizeof(types), strs, sizeof(strs));
+       if (fd < 0)
+               return 0; /* BTF not supported at all */
+
+       memset(&info, 0, sizeof(info));
+       info.name = ptr_to_u64(name);
+       info.name_len = sizeof(name);
+
+       /* check that BPF_OBJ_GET_INFO_BY_FD supports specifying name pointer;
+        * kernel's module BTF support coincides with support for
+        * name/name_len fields in struct bpf_btf_info.
+        */
+       err = bpf_obj_get_info_by_fd(fd, &info, &len);
+       close(fd);
+       return !err;
+}
+
 enum kern_feature_result {
        FEAT_UNKNOWN = 0,
        FEAT_SUPPORTED = 1,
        },
        [FEAT_PROG_BIND_MAP] = {
                "BPF_PROG_BIND_MAP support", probe_prog_bind_map,
-       }
+       },
+       [FEAT_MODULE_BTF] = {
+               "module BTF support", probe_module_btf,
+       },
 };
 
 static bool kernel_supports(enum kern_feature_id feat_id)
        return 0;
 }
 
+static int load_module_btfs(struct bpf_object *obj)
+{
+       struct bpf_btf_info info;
+       struct module_btf *mod_btf;
+       struct btf *btf;
+       char name[64];
+       __u32 id = 0, len;
+       int err, fd;
+
+       if (obj->btf_modules_loaded)
+               return 0;
+
+       /* don't do this again, even if we find no module BTFs */
+       obj->btf_modules_loaded = true;
+
+       /* kernel too old to support module BTFs */
+       if (!kernel_supports(FEAT_MODULE_BTF))
+               return 0;
+
+       while (true) {
+               err = bpf_btf_get_next_id(id, &id);
+               if (err && errno == ENOENT)
+                       return 0;
+               if (err) {
+                       err = -errno;
+                       pr_warn("failed to iterate BTF objects: %d\n", err);
+                       return err;
+               }
+
+               fd = bpf_btf_get_fd_by_id(id);
+               if (fd < 0) {
+                       if (errno == ENOENT)
+                               continue; /* expected race: BTF was unloaded */
+                       err = -errno;
+                       pr_warn("failed to get BTF object #%d FD: %d\n", id, err);
+                       return err;
+               }
+
+               len = sizeof(info);
+               memset(&info, 0, sizeof(info));
+               info.name = ptr_to_u64(name);
+               info.name_len = sizeof(name);
+
+               err = bpf_obj_get_info_by_fd(fd, &info, &len);
+               if (err) {
+                       err = -errno;
+                       pr_warn("failed to get BTF object #%d info: %d\n", id, err);
+                       close(fd);
+                       return err;
+               }
+
+               /* ignore non-module BTFs */
+               if (!info.kernel_btf || strcmp(name, "vmlinux") == 0) {
+                       close(fd);
+                       continue;
+               }
+
+               btf = btf_get_from_fd(fd, obj->btf_vmlinux);
+               close(fd);
+               if (IS_ERR(btf)) {
+                       pr_warn("failed to load module [%s]'s BTF object #%d: %ld\n",
+                               name, id, PTR_ERR(btf));
+                       return PTR_ERR(btf);
+               }
+
+               err = btf_ensure_mem((void **)&obj->btf_modules, &obj->btf_module_cap,
+                                    sizeof(*obj->btf_modules), obj->btf_module_cnt + 1);
+               if (err)
+                       return err;
+
+               mod_btf = &obj->btf_modules[obj->btf_module_cnt++];
+
+               mod_btf->btf = btf;
+               mod_btf->id = id;
+               mod_btf->name = strdup(name);
+               if (!mod_btf->name)
+                       return -ENOMEM;
+       }
+
+       return 0;
+}
+
 static struct core_cand_list *
 bpf_core_find_cands(struct bpf_object *obj, const struct btf *local_btf, __u32 local_type_id)
 {
        struct core_cand local_cand = {};
        struct core_cand_list *cands;
+       const struct btf *main_btf;
        size_t local_essent_len;
-       int err;
+       int err, i;
 
        local_cand.btf = local_btf;
        local_cand.t = btf__type_by_id(local_btf, local_type_id);
                return ERR_PTR(-ENOMEM);
 
        /* Attempt to find target candidates in vmlinux BTF first */
-       err = bpf_core_add_cands(&local_cand, local_essent_len,
-                                obj->btf_vmlinux_override ?: obj->btf_vmlinux,
-                                "vmlinux", 1, cands);
-       if (err) {
-               bpf_core_free_cands(cands);
-               return ERR_PTR(err);
+       main_btf = obj->btf_vmlinux_override ?: obj->btf_vmlinux;
+       err = bpf_core_add_cands(&local_cand, local_essent_len, main_btf, "vmlinux", 1, cands);
+       if (err)
+               goto err_out;
+
+       /* if vmlinux BTF has any candidate, don't got for module BTFs */
+       if (cands->len)
+               return cands;
+
+       /* if vmlinux BTF was overridden, don't attempt to load module BTFs */
+       if (obj->btf_vmlinux_override)
+               return cands;
+
+       /* now look through module BTFs, trying to still find candidates */
+       err = load_module_btfs(obj);
+       if (err)
+               goto err_out;
+
+       for (i = 0; i < obj->btf_module_cnt; i++) {
+               err = bpf_core_add_cands(&local_cand, local_essent_len,
+                                        obj->btf_modules[i].btf,
+                                        obj->btf_modules[i].name,
+                                        btf__get_nr_types(obj->btf_vmlinux) + 1,
+                                        cands);
+               if (err)
+                       goto err_out;
        }
 
        return cands;
+err_out:
+       bpf_core_free_cands(cands);
+       return ERR_PTR(err);
 }
 
 /* Check two types for compatibility for the purpose of field access
        if (!hashmap__find(cand_cache, type_key, (void **)&cands)) {
                cands = bpf_core_find_cands(prog->obj, local_btf, local_id);
                if (IS_ERR(cands)) {
-                       pr_warn("prog '%s': relo #%d: target candidate search failed for [%d] %s %s: %ld",
+                       pr_warn("prog '%s': relo #%d: target candidate search failed for [%d] %s %s: %ld\n",
                                prog->name, relo_idx, local_id, btf_kind_str(local_type),
                                local_name, PTR_ERR(cands));
                        return PTR_ERR(cands);
        }
 
 out:
-       /* obj->btf_vmlinux is freed at the end of object load phase */
+       /* obj->btf_vmlinux and module BTFs are freed after object load */
        btf__free(obj->btf_vmlinux_override);
        obj->btf_vmlinux_override = NULL;
 
        err = err ? : bpf_object__relocate(obj, attr->target_btf_path);
        err = err ? : bpf_object__load_progs(obj, attr->log_level);
 
+       /* clean up module BTFs */
+       for (i = 0; i < obj->btf_module_cnt; i++) {
+               btf__free(obj->btf_modules[i].btf);
+               free(obj->btf_modules[i].name);
+       }
+       free(obj->btf_modules);
+
+       /* clean up vmlinux BTF */
        btf__free(obj->btf_vmlinux);
        obj->btf_vmlinux = NULL;