From: Anton Protopopov resolve_btfids is the tool that sees vmlinux and each module after the link, with BTF at hand. A later patch lets it read the machine code of the functions, to find the errors that they return. The two kinds of files differ: vmlinux module ELF file linked image relocatable (ET_REL) address virtual address each section starts at 0 field of a jump final value 0, a relocation has the target A field is the bytes of an instruction that hold the target. In a module these are fields with a relocation: - the jump to __x86_return_thunk, which is the return of the function - the jump to the cold subfunction Add struct image. It gives both kinds of files one address space: vmlinux addr = virtual address module addr = section index << 32 | offset undefined symbol addr = 16 * symbol index (section 0) image_bytes(addr) the bytes at addr, and how many follow image_reloc(addr, len) the target of a field in the len bytes image_symbol(name) the address of a symbol image_func(name) name, section, address and size The user of struct image works in three steps: decode the instruction at addr -> len image_reloc(addr, len) -> the address of the target compare it with image_symbol("__x86_return_thunk") BTF knows a function only by its name. image_func() returns NULL if two functions have the name: nobody can tell which of them the FUNC is. Signed-off-by: Anton Protopopov Co-developed-by: Alexei Starovoitov Signed-off-by: Alexei Starovoitov --- tools/bpf/resolve_btfids/Build | 1 + tools/bpf/resolve_btfids/image.c | 222 +++++++++++++++++++++++++++++++ tools/bpf/resolve_btfids/image.h | 62 +++++++++ 3 files changed, 285 insertions(+) create mode 100644 tools/bpf/resolve_btfids/image.c create mode 100644 tools/bpf/resolve_btfids/image.h diff --git a/tools/bpf/resolve_btfids/Build b/tools/bpf/resolve_btfids/Build index 602e1508bf09..65af9dee7659 100644 --- a/tools/bpf/resolve_btfids/Build +++ b/tools/bpf/resolve_btfids/Build @@ -2,6 +2,7 @@ hostprogs := resolve_btfids resolve_btfids-y += main.o resolve_btfids-y += btf_colors.o +resolve_btfids-y += image.o resolve_btfids-y += rbtree.o resolve_btfids-y += zalloc.o resolve_btfids-y += string.o diff --git a/tools/bpf/resolve_btfids/image.c b/tools/bpf/resolve_btfids/image.c new file mode 100644 index 000000000000..7a97a49e5364 --- /dev/null +++ b/tools/bpf/resolve_btfids/image.c @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* The code of an ELF file, by address. See image.h */ + +#include +#include +#include + +#include "image.h" + +static uint64_t image_address(const struct image *img, size_t shndx, uint64_t value) +{ + return img->relocatable ? (uint64_t)shndx << 32 | value : value; +} + +static uint64_t symbol_address(const struct image *img, size_t idx, const GElf_Sym *sym) +{ + if (sym->st_shndx == SHN_UNDEF) + return img->relocatable ? idx << 4 : 0; + if (sym->st_shndx >= SHN_LORESERVE) + return 0; + return image_address(img, sym->st_shndx, sym->st_value); +} + +uint64_t image_symbol(const struct image *img, const char *name) +{ + size_t i; + + for (i = 1; i < img->nr_symbols; i++) { + const char *s; + GElf_Sym sym; + + if (!gelf_getsym(img->symbols, i, &sym) || !sym.st_name) + continue; + s = elf_strptr(img->elf, img->strndx, sym.st_name); + if (s && !strcmp(s, name)) + return symbol_address(img, i, &sym); + } + return 0; +} + +static int cmp_func(const void *va, const void *vb) +{ + const struct image_func *a = va, *b = vb; + + return strcmp(a->name, b->name); +} + +const struct image_func *image_func(const struct image *img, const char *name) +{ + const struct image_func key = { .name = name }, *end = img->funcs + img->nr_funcs, *f; + + f = bsearch(&key, img->funcs, img->nr_funcs, sizeof(*f), cmp_func); + if (!f || (f > img->funcs && !strcmp(f[-1].name, name)) || + (f + 1 < end && !strcmp(f[1].name, name))) + return NULL; + return f; +} + +const unsigned char *image_bytes(const struct image *img, uint64_t addr, size_t *avail) +{ + const struct image_section *s; + size_t i; + + for (i = img->relocatable ? addr >> 32 : 1; i < img->nr_sections; i++) { + s = &img->sections[i]; + if (addr - s->addr < s->size) { + *avail = s->size - (addr - s->addr); + return s->data + (addr - s->addr); + } + } + return NULL; +} + +const struct image_reloc *image_reloc(const struct image *img, uint64_t addr, size_t len) +{ + size_t lo = 0, hi = img->nr_relocs; + + while (lo < hi) { + size_t mid = lo + (hi - lo) / 2; + + if (img->relocs[mid].addr < addr) + lo = mid + 1; + else + hi = mid; + } + return lo < img->nr_relocs && img->relocs[lo].addr - addr < len ? &img->relocs[lo] : NULL; +} + +static int cmp_reloc(const void *va, const void *vb) +{ + const struct image_reloc *a = va, *b = vb; + + return a->addr > b->addr ? 1 : a->addr < b->addr ? -1 : 0; +} + +static int read_relocs(struct image *img, Elf_Scn *scn, const GElf_Shdr *shdr) +{ + size_t nr = shdr->sh_size / shdr->sh_entsize, i; + Elf_Data *data = elf_getdata(scn, NULL); + struct image_reloc *relocs; + + if (!data) + return -EINVAL; + relocs = realloc(img->relocs, (img->nr_relocs + nr) * sizeof(*relocs)); + if (!relocs) + return -ENOMEM; + img->relocs = relocs; + for (i = 0; i < nr; i++) { + GElf_Rela rela; + GElf_Sym sym; + uint64_t s; + + if (!gelf_getrela(data, i, &rela) || + !gelf_getsym(img->symbols, GELF_R_SYM(rela.r_info), &sym)) + return -EINVAL; + s = symbol_address(img, GELF_R_SYM(rela.r_info), &sym); + if (!s) + continue; + relocs[img->nr_relocs].addr = image_address(img, shdr->sh_info, rela.r_offset); + relocs[img->nr_relocs++].target = s + rela.r_addend; + } + return 0; +} + +static int read_funcs(struct image *img) +{ + size_t i; + + img->funcs = calloc(img->nr_symbols, sizeof(*img->funcs)); + if (!img->funcs) + return -ENOMEM; + for (i = 1; i < img->nr_symbols; i++) { + struct image_func *f = &img->funcs[img->nr_funcs]; + GElf_Sym sym; + + if (!gelf_getsym(img->symbols, i, &sym)) + return -EINVAL; + if (sym.st_shndx == SHN_UNDEF ? !img->relocatable : + GELF_ST_TYPE(sym.st_info) != STT_FUNC || sym.st_shndx >= img->nr_sections) + continue; + f->name = elf_strptr(img->elf, img->strndx, sym.st_name); + if (!f->name) + return -EINVAL; + f->section = img->sections[sym.st_shndx].name ?: ""; + f->addr = symbol_address(img, i, &sym); + f->size = sym.st_size; + img->nr_funcs++; + } + qsort(img->funcs, img->nr_funcs, sizeof(*img->funcs), cmp_func); + return 0; +} + +int image_open(struct image *img, Elf *elf) +{ + size_t i, shstrndx; + GElf_Ehdr ehdr; + GElf_Shdr shdr; + Elf_Scn *scn; + int err; + + memset(img, 0, sizeof(*img)); + if (!gelf_getehdr(elf, &ehdr) || elf_getshdrnum(elf, &img->nr_sections) || + elf_getshdrstrndx(elf, &shstrndx)) + return -EINVAL; + img->elf = elf; + img->relocatable = ehdr.e_type == ET_REL; + img->sections = calloc(img->nr_sections, sizeof(*img->sections)); + if (!img->sections) + return -ENOMEM; + + for (i = 1; i < img->nr_sections; i++) { + struct image_section *s = &img->sections[i]; + Elf_Data *data; + + scn = elf_getscn(elf, i); + if (!scn || !gelf_getshdr(scn, &shdr)) + return -EINVAL; + s->name = elf_strptr(elf, shstrndx, shdr.sh_name); + if (!s->name) + return -EINVAL; + if (shdr.sh_type == SHT_SYMTAB) { + img->symbols = elf_getdata(scn, NULL); + img->nr_symbols = shdr.sh_size / shdr.sh_entsize; + img->strndx = shdr.sh_link; + } + if (!(shdr.sh_flags & SHF_EXECINSTR) || shdr.sh_type != SHT_PROGBITS) + continue; + data = elf_getdata(scn, NULL); + if (!data || data->d_size != shdr.sh_size) + return -EINVAL; + s->data = data->d_buf; + s->addr = image_address(img, i, shdr.sh_addr); + s->size = shdr.sh_size; + } + if (!img->symbols) + return -EINVAL; + err = read_funcs(img); + if (err) + return err; + + for (i = 1; img->relocatable && i < img->nr_sections; i++) { + scn = elf_getscn(elf, i); + if (!gelf_getshdr(scn, &shdr)) + return -EINVAL; + if (shdr.sh_type != SHT_RELA || !img->sections[shdr.sh_info].size) + continue; + err = read_relocs(img, scn, &shdr); + if (err) + return err; + } + if (img->nr_relocs) + qsort(img->relocs, img->nr_relocs, sizeof(*img->relocs), cmp_reloc); + return 0; +} + +void image_close(struct image *img) +{ + free(img->sections); + free(img->relocs); + free(img->funcs); + memset(img, 0, sizeof(*img)); +} diff --git a/tools/bpf/resolve_btfids/image.h b/tools/bpf/resolve_btfids/image.h new file mode 100644 index 000000000000..5ae25f6a362b --- /dev/null +++ b/tools/bpf/resolve_btfids/image.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __RESOLVE_BTFIDS_IMAGE_H +#define __RESOLVE_BTFIDS_IMAGE_H + +#include +#include +#include +#include + +/* + * The code of an ELF file, by address. + * + * In a linked image (vmlinux) an address is the virtual address and all fields + * hold their final values. A relocatable file (a module) has neither: there the + * address of a byte is the index of its section in the upper 32 bits and its + * offset in the lower ones, and what a field refers to is taken from its + * relocation. An undefined symbol lives in section 0, at 16 times its index. + */ +struct image_section { + const char *name; + /* Of a section with code */ + uint64_t addr, size; + const unsigned char *data; +}; + +struct image_reloc { + uint64_t addr; /* of the field */ + uint64_t target; /* symbol + addend */ +}; + +/* A function of the file, or an undefined symbol of a relocatable file: no section, no size */ +struct image_func { + const char *name, *section; + uint64_t addr, size; +}; + +struct image { + Elf *elf; + bool relocatable; + struct image_section *sections; + size_t nr_sections; + struct image_reloc *relocs; /* sorted by address */ + size_t nr_relocs; + struct image_func *funcs; /* sorted by name */ + size_t nr_funcs; + Elf_Data *symbols; + size_t nr_symbols, strndx; +}; + +int image_open(struct image *img, Elf *elf); +void image_close(struct image *img); + +/* Return the address of the symbol NAME, or 0 */ +uint64_t image_symbol(const struct image *img, const char *name); +/* Return the function NAME, or NULL if there is none or more than one */ +const struct image_func *image_func(const struct image *img, const char *name); +/* Return the bytes at ADDR and set AVAIL to how many follow in the section */ +const unsigned char *image_bytes(const struct image *img, uint64_t addr, size_t *avail); +/* Return the relocation of a field in the LEN bytes at ADDR, or NULL */ +const struct image_reloc *image_reloc(const struct image *img, uint64_t addr, size_t len); + +#endif -- 2.55.0