diff mbox series

[bpf-next,4/6] libbpf: add kernel module BTF support for CO-RE relocations

Message ID 20201119232244.2776720-5-andrii@kernel.org
State Superseded
Headers show
Series libbpf: add support for kernel module BTF CO-RE relocations | expand

Commit Message

Andrii Nakryiko Nov. 19, 2020, 11:22 p.m. UTC
Teach libbpf to search for candidate types for CO-RE relocations across kernel
modules BTFs, in addition to vmlinux BTF. If at least one candidate type is
found in vmlinux BTF, kernel module BTFs are not iterated. If vmlinux BTF has
no matching candidates, then find all kernel module BTFs and search for all
matching candidates across all of them.

Kernel's support for module BTFs are inferred from the support for BTF name
pointer in BPF UAPI.

Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
---
 tools/lib/bpf/libbpf.c | 185 ++++++++++++++++++++++++++++++++++++++---
 1 file changed, 172 insertions(+), 13 deletions(-)

Comments

Maciej Fijalkowski Nov. 20, 2020, 12:46 a.m. UTC | #1
On Thu, Nov 19, 2020 at 03:22:42PM -0800, Andrii Nakryiko wrote:
> Teach libbpf to search for candidate types for CO-RE relocations across kernel
> modules BTFs, in addition to vmlinux BTF. If at least one candidate type is
> found in vmlinux BTF, kernel module BTFs are not iterated. If vmlinux BTF has
> no matching candidates, then find all kernel module BTFs and search for all
> matching candidates across all of them.
> 
> Kernel's support for module BTFs are inferred from the support for BTF name
> pointer in BPF UAPI.
> 
> Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
> ---
>  tools/lib/bpf/libbpf.c | 185 ++++++++++++++++++++++++++++++++++++++---
>  1 file changed, 172 insertions(+), 13 deletions(-)
> 

[...]

> +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 */
> +
> +	len = sizeof(info);

nit: reinit of len

> +	memset(&info, 0, sizeof(info));

use len in memset

> +	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;
> +}

[...]
Andrii Nakryiko Nov. 20, 2020, 1:24 a.m. UTC | #2
On Thu, Nov 19, 2020 at 4:55 PM Maciej Fijalkowski
<maciej.fijalkowski@intel.com> wrote:
>
> On Thu, Nov 19, 2020 at 03:22:42PM -0800, Andrii Nakryiko wrote:
> > Teach libbpf to search for candidate types for CO-RE relocations across kernel
> > modules BTFs, in addition to vmlinux BTF. If at least one candidate type is
> > found in vmlinux BTF, kernel module BTFs are not iterated. If vmlinux BTF has
> > no matching candidates, then find all kernel module BTFs and search for all
> > matching candidates across all of them.
> >
> > Kernel's support for module BTFs are inferred from the support for BTF name
> > pointer in BPF UAPI.
> >
> > Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
> > ---
> >  tools/lib/bpf/libbpf.c | 185 ++++++++++++++++++++++++++++++++++++++---
> >  1 file changed, 172 insertions(+), 13 deletions(-)
> >
>
> [...]
>
> > +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 */
> > +
> > +     len = sizeof(info);
>
> nit: reinit of len
>

oops, right, I'll remove it


> > +     memset(&info, 0, sizeof(info));
>
> use len in memset

why?

>
> > +     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;
> > +}
>
> [...]
Maciej Fijalkowski Nov. 20, 2020, 2:05 a.m. UTC | #3
On Thu, Nov 19, 2020 at 05:24:43PM -0800, Andrii Nakryiko wrote:
> On Thu, Nov 19, 2020 at 4:55 PM Maciej Fijalkowski
> <maciej.fijalkowski@intel.com> wrote:
> >
> > On Thu, Nov 19, 2020 at 03:22:42PM -0800, Andrii Nakryiko wrote:
> > > Teach libbpf to search for candidate types for CO-RE relocations across kernel
> > > modules BTFs, in addition to vmlinux BTF. If at least one candidate type is
> > > found in vmlinux BTF, kernel module BTFs are not iterated. If vmlinux BTF has
> > > no matching candidates, then find all kernel module BTFs and search for all
> > > matching candidates across all of them.
> > >
> > > Kernel's support for module BTFs are inferred from the support for BTF name
> > > pointer in BPF UAPI.
> > >
> > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
> > > ---
> > >  tools/lib/bpf/libbpf.c | 185 ++++++++++++++++++++++++++++++++++++++---
> > >  1 file changed, 172 insertions(+), 13 deletions(-)
> > >
> >
> > [...]
> >
> > > +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 */
> > > +
> > > +     len = sizeof(info);
> >
> > nit: reinit of len
> >
> 
> oops, right, I'll remove it
> 
> 
> > > +     memset(&info, 0, sizeof(info));
> >
> > use len in memset
> 
> why?

Hm, just to make use of local var? We might argue that current version is
more readable, but then again I would question the len's existence.

Do whatever you want, these were just nits :)

> 
> >
> > > +     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;
> > > +}
> >
> > [...]
Andrii Nakryiko Nov. 20, 2020, 3:31 a.m. UTC | #4
On Thu, Nov 19, 2020 at 6:14 PM Maciej Fijalkowski
<maciej.fijalkowski@intel.com> wrote:
>
> On Thu, Nov 19, 2020 at 05:24:43PM -0800, Andrii Nakryiko wrote:
> > On Thu, Nov 19, 2020 at 4:55 PM Maciej Fijalkowski
> > <maciej.fijalkowski@intel.com> wrote:
> > >
> > > On Thu, Nov 19, 2020 at 03:22:42PM -0800, Andrii Nakryiko wrote:
> > > > Teach libbpf to search for candidate types for CO-RE relocations across kernel
> > > > modules BTFs, in addition to vmlinux BTF. If at least one candidate type is
> > > > found in vmlinux BTF, kernel module BTFs are not iterated. If vmlinux BTF has
> > > > no matching candidates, then find all kernel module BTFs and search for all
> > > > matching candidates across all of them.
> > > >
> > > > Kernel's support for module BTFs are inferred from the support for BTF name
> > > > pointer in BPF UAPI.
> > > >
> > > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
> > > > ---
> > > >  tools/lib/bpf/libbpf.c | 185 ++++++++++++++++++++++++++++++++++++++---
> > > >  1 file changed, 172 insertions(+), 13 deletions(-)
> > > >
> > >
> > > [...]
> > >
> > > > +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 */
> > > > +
> > > > +     len = sizeof(info);
> > >
> > > nit: reinit of len
> > >
> >
> > oops, right, I'll remove it
> >
> >
> > > > +     memset(&info, 0, sizeof(info));
> > >
> > > use len in memset
> >
> > why?
>
> Hm, just to make use of local var? We might argue that current version is

I agree, I think sizeof(info) is more readable. But my point is that
if you suggest something, please provide at least some argument for
why you think it's better or why existing code is worse or wrong (if
you think it is).

> more readable, but then again I would question the len's existence.

len is passed to the kernel by reference and the kernel is updating it
with the actual length it has (which could be <, ==, or > than what
the program specified). So it has to be in a variable.

>
> Do whatever you want, these were just nits :)
>
> >
> > >
> > > > +     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);

here -------------------------------------------------^^^^

> > > > +     close(fd);
> > > > +     return !err;
> > > > +}
> > >
> > > [...]
Martin KaFai Lau Nov. 20, 2020, 11:05 p.m. UTC | #5
On Thu, Nov 19, 2020 at 03:22:42PM -0800, Andrii Nakryiko wrote:
[ ... ]

> +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, 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;
> +		}
> +

[ ... ]

> @@ -8656,9 +8815,6 @@ static inline int __find_vmlinux_btf_id(struct btf *btf, const char *name,
>  	else
>  		err = btf__find_by_name_kind(btf, name, BTF_KIND_FUNC);
>  
> -	if (err <= 0)
> -		pr_warn("%s is not found in vmlinux BTF\n", name);
> -
>  	return err;
>  }
>  
> @@ -8675,6 +8831,9 @@ int libbpf_find_vmlinux_btf_id(const char *name,
>  	}
>  
>  	err = __find_vmlinux_btf_id(btf, name, attach_type);
> +	if (err <= 0)
> +		pr_warn("%s is not found in vmlinux BTF\n", name);
> +
Please explain this move in the commit message.

>  	btf__free(btf);
>  	return err;
>  }
> -- 
> 2.24.1
>
Andrii Nakryiko Nov. 20, 2020, 11:12 p.m. UTC | #6
On Fri, Nov 20, 2020 at 3:06 PM Martin KaFai Lau <kafai@fb.com> wrote:
>
> On Thu, Nov 19, 2020 at 03:22:42PM -0800, Andrii Nakryiko wrote:
> [ ... ]
>
> > +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, 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;
> > +             }
> > +
>
> [ ... ]
>
> > @@ -8656,9 +8815,6 @@ static inline int __find_vmlinux_btf_id(struct btf *btf, const char *name,
> >       else
> >               err = btf__find_by_name_kind(btf, name, BTF_KIND_FUNC);
> >
> > -     if (err <= 0)
> > -             pr_warn("%s is not found in vmlinux BTF\n", name);
> > -
> >       return err;
> >  }
> >
> > @@ -8675,6 +8831,9 @@ int libbpf_find_vmlinux_btf_id(const char *name,
> >       }
> >
> >       err = __find_vmlinux_btf_id(btf, name, attach_type);
> > +     if (err <= 0)
> > +             pr_warn("%s is not found in vmlinux BTF\n", name);
> > +
> Please explain this move in the commit message.

ok, I'll add something about that. The short answer is that
__find_vmlinux_btf_id() is now expected to not find a type in vmlinux
BTF, so emitting error would be wrong. So I moved it up a level where
it's not expected.

>
> >       btf__free(btf);
> >       return err;
> >  }
> > --
> > 2.24.1
> >
diff mbox series

Patch

diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index c4a49e8eb7b5..ac1ff4e7741a 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -176,6 +176,8 @@  enum kern_feature_id {
 	FEAT_PROBE_READ_KERN,
 	/* BPF_PROG_BIND_MAP is supported */
 	FEAT_PROG_BIND_MAP,
+	/* Kernel support for module BTFs */
+	FEAT_MODULE_BTF,
 	__FEAT_CNT,
 };
 
@@ -402,6 +404,12 @@  struct extern_desc {
 
 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];
@@ -470,6 +478,11 @@  struct bpf_object {
 	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;
@@ -3960,6 +3973,36 @@  static int probe_prog_bind_map(void)
 	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 */
+
+	len = sizeof(info);
+	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,
@@ -4003,7 +4046,10 @@  static struct kern_feature_desc {
 	},
 	[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)
@@ -4674,13 +4720,95 @@  static int bpf_core_add_cands(struct core_cand *local_cand,
 	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, 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);
+			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);
@@ -4697,15 +4825,38 @@  bpf_core_find_cands(struct bpf_object *obj, const struct btf *local_btf, __u32 l
 		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
@@ -5756,7 +5907,7 @@  static int bpf_core_apply_relo(struct bpf_program *prog,
 	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);
@@ -5944,7 +6095,7 @@  bpf_object__relocate_core(struct bpf_object *obj, const char *targ_btf_path)
 	}
 
 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;
 
@@ -7303,6 +7454,14 @@  int bpf_object__load_xattr(struct bpf_object_load_attr *attr)
 	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;
 
@@ -8656,9 +8815,6 @@  static inline int __find_vmlinux_btf_id(struct btf *btf, const char *name,
 	else
 		err = btf__find_by_name_kind(btf, name, BTF_KIND_FUNC);
 
-	if (err <= 0)
-		pr_warn("%s is not found in vmlinux BTF\n", name);
-
 	return err;
 }
 
@@ -8675,6 +8831,9 @@  int libbpf_find_vmlinux_btf_id(const char *name,
 	}
 
 	err = __find_vmlinux_btf_id(btf, name, attach_type);
+	if (err <= 0)
+		pr_warn("%s is not found in vmlinux BTF\n", name);
+
 	btf__free(btf);
 	return err;
 }