libbpf: add btf__parse_elf API to load .BTF and .BTF.ext
Loading BTF and BTF.ext from ELF file is a common need. Instead of requiring every user to re-implement it, let's provide this API from libbpf itself. It's mostly copy/paste from `bpftool btf dump` implementation, which will be switched to libbpf's version in next patch. btf__parse_elf allows to load BTF and optionally BTF.ext. This is also useful for tests that need to load/work with BTF, loaded from test ELF files. Signed-off-by: Andrii Nakryiko <andriin@fb.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
		
							parent
							
								
									1d7a08b3bd
								
							
						
					
					
						commit
						e6c64855fd
					
				| @ -4,10 +4,12 @@ | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| #include <fcntl.h> | ||||
| #include <unistd.h> | ||||
| #include <errno.h> | ||||
| #include <linux/err.h> | ||||
| #include <linux/btf.h> | ||||
| #include <gelf.h> | ||||
| #include "btf.h" | ||||
| #include "bpf.h" | ||||
| #include "libbpf.h" | ||||
| @ -417,6 +419,132 @@ done: | ||||
| 	return btf; | ||||
| } | ||||
| 
 | ||||
| static bool btf_check_endianness(const GElf_Ehdr *ehdr) | ||||
| { | ||||
| #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ | ||||
| 	return ehdr->e_ident[EI_DATA] == ELFDATA2LSB; | ||||
| #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ | ||||
| 	return ehdr->e_ident[EI_DATA] == ELFDATA2MSB; | ||||
| #else | ||||
| # error "Unrecognized __BYTE_ORDER__" | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| struct btf *btf__parse_elf(const char *path, struct btf_ext **btf_ext) | ||||
| { | ||||
| 	Elf_Data *btf_data = NULL, *btf_ext_data = NULL; | ||||
| 	int err = 0, fd = -1, idx = 0; | ||||
| 	struct btf *btf = NULL; | ||||
| 	Elf_Scn *scn = NULL; | ||||
| 	Elf *elf = NULL; | ||||
| 	GElf_Ehdr ehdr; | ||||
| 
 | ||||
| 	if (elf_version(EV_CURRENT) == EV_NONE) { | ||||
| 		pr_warning("failed to init libelf for %s\n", path); | ||||
| 		return ERR_PTR(-LIBBPF_ERRNO__LIBELF); | ||||
| 	} | ||||
| 
 | ||||
| 	fd = open(path, O_RDONLY); | ||||
| 	if (fd < 0) { | ||||
| 		err = -errno; | ||||
| 		pr_warning("failed to open %s: %s\n", path, strerror(errno)); | ||||
| 		return ERR_PTR(err); | ||||
| 	} | ||||
| 
 | ||||
| 	err = -LIBBPF_ERRNO__FORMAT; | ||||
| 
 | ||||
| 	elf = elf_begin(fd, ELF_C_READ, NULL); | ||||
| 	if (!elf) { | ||||
| 		pr_warning("failed to open %s as ELF file\n", path); | ||||
| 		goto done; | ||||
| 	} | ||||
| 	if (!gelf_getehdr(elf, &ehdr)) { | ||||
| 		pr_warning("failed to get EHDR from %s\n", path); | ||||
| 		goto done; | ||||
| 	} | ||||
| 	if (!btf_check_endianness(&ehdr)) { | ||||
| 		pr_warning("non-native ELF endianness is not supported\n"); | ||||
| 		goto done; | ||||
| 	} | ||||
| 	if (!elf_rawdata(elf_getscn(elf, ehdr.e_shstrndx), NULL)) { | ||||
| 		pr_warning("failed to get e_shstrndx from %s\n", path); | ||||
| 		goto done; | ||||
| 	} | ||||
| 
 | ||||
| 	while ((scn = elf_nextscn(elf, scn)) != NULL) { | ||||
| 		GElf_Shdr sh; | ||||
| 		char *name; | ||||
| 
 | ||||
| 		idx++; | ||||
| 		if (gelf_getshdr(scn, &sh) != &sh) { | ||||
| 			pr_warning("failed to get section(%d) header from %s\n", | ||||
| 				   idx, path); | ||||
| 			goto done; | ||||
| 		} | ||||
| 		name = elf_strptr(elf, ehdr.e_shstrndx, sh.sh_name); | ||||
| 		if (!name) { | ||||
| 			pr_warning("failed to get section(%d) name from %s\n", | ||||
| 				   idx, path); | ||||
| 			goto done; | ||||
| 		} | ||||
| 		if (strcmp(name, BTF_ELF_SEC) == 0) { | ||||
| 			btf_data = elf_getdata(scn, 0); | ||||
| 			if (!btf_data) { | ||||
| 				pr_warning("failed to get section(%d, %s) data from %s\n", | ||||
| 					   idx, name, path); | ||||
| 				goto done; | ||||
| 			} | ||||
| 			continue; | ||||
| 		} else if (btf_ext && strcmp(name, BTF_EXT_ELF_SEC) == 0) { | ||||
| 			btf_ext_data = elf_getdata(scn, 0); | ||||
| 			if (!btf_ext_data) { | ||||
| 				pr_warning("failed to get section(%d, %s) data from %s\n", | ||||
| 					   idx, name, path); | ||||
| 				goto done; | ||||
| 			} | ||||
| 			continue; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	err = 0; | ||||
| 
 | ||||
| 	if (!btf_data) { | ||||
| 		err = -ENOENT; | ||||
| 		goto done; | ||||
| 	} | ||||
| 	btf = btf__new(btf_data->d_buf, btf_data->d_size); | ||||
| 	if (IS_ERR(btf)) | ||||
| 		goto done; | ||||
| 
 | ||||
| 	if (btf_ext && btf_ext_data) { | ||||
| 		*btf_ext = btf_ext__new(btf_ext_data->d_buf, | ||||
| 					btf_ext_data->d_size); | ||||
| 		if (IS_ERR(*btf_ext)) | ||||
| 			goto done; | ||||
| 	} else if (btf_ext) { | ||||
| 		*btf_ext = NULL; | ||||
| 	} | ||||
| done: | ||||
| 	if (elf) | ||||
| 		elf_end(elf); | ||||
| 	close(fd); | ||||
| 
 | ||||
| 	if (err) | ||||
| 		return ERR_PTR(err); | ||||
| 	/*
 | ||||
| 	 * btf is always parsed before btf_ext, so no need to clean up | ||||
| 	 * btf_ext, if btf loading failed | ||||
| 	 */ | ||||
| 	if (IS_ERR(btf)) | ||||
| 		return btf; | ||||
| 	if (btf_ext && IS_ERR(*btf_ext)) { | ||||
| 		btf__free(btf); | ||||
| 		err = PTR_ERR(*btf_ext); | ||||
| 		return ERR_PTR(err); | ||||
| 	} | ||||
| 	return btf; | ||||
| } | ||||
| 
 | ||||
| static int compare_vsi_off(const void *_a, const void *_b) | ||||
| { | ||||
| 	const struct btf_var_secinfo *a = _a; | ||||
|  | ||||
| @ -59,6 +59,8 @@ struct btf_ext_header { | ||||
| 
 | ||||
| LIBBPF_API void btf__free(struct btf *btf); | ||||
| LIBBPF_API struct btf *btf__new(__u8 *data, __u32 size); | ||||
| LIBBPF_API struct btf *btf__parse_elf(const char *path, | ||||
| 				      struct btf_ext **btf_ext); | ||||
| LIBBPF_API int btf__finalize_data(struct bpf_object *obj, struct btf *btf); | ||||
| LIBBPF_API int btf__load(struct btf *btf); | ||||
| LIBBPF_API __s32 btf__find_by_name(const struct btf *btf, | ||||
|  | ||||
| @ -164,3 +164,8 @@ LIBBPF_0.0.3 { | ||||
| 		bpf_map_freeze; | ||||
| 		btf__finalize_data; | ||||
| } LIBBPF_0.0.2; | ||||
| 
 | ||||
| LIBBPF_0.0.4 { | ||||
| 	global: | ||||
| 		btf__parse_elf; | ||||
| } LIBBPF_0.0.3; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user