| /* Copyright (c) 2011-2015 Linux Perf Authors |
| * |
| * This source code is licensed under the GNU General Public License Version 2. |
| * See the file LICENSE-gpl-2.0.txt for more details. */ |
| |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <inttypes.h> |
| #include <stdbool.h> |
| |
| #include <gelf.h> |
| #include <libelf.h> |
| #include "xlib.h" |
| #include "elf.h" |
| |
| #define pr_err(args...) fprintf(stderr, args) |
| #define pr_debug2(args...) pr_err(args) |
| |
| #define PERF_ELF_C_READ_MMAP ELF_C_READ_MMAP |
| |
| enum map_type { |
| MAP__FUNCTION = 0, |
| MAP__VARIABLE, |
| }; |
| |
| static inline char *bfd_demangle(void *v, const char *c, int i) |
| { |
| return NULL; |
| } |
| |
| #ifndef NT_GNU_BUILD_ID |
| #define NT_GNU_BUILD_ID 3 |
| #endif |
| |
| /** |
| * elf_symtab__for_each_symbol - iterate thru all the symbols |
| * |
| * @syms: struct elf_symtab instance to iterate |
| * @idx: uint32_t idx |
| * @sym: GElf_Sym iterator |
| */ |
| #define elf_symtab__for_each_symbol(syms, nr_syms, idx, sym) \ |
| for (idx = 0, gelf_getsym(syms, idx, &sym);\ |
| idx < nr_syms; \ |
| idx++, gelf_getsym(syms, idx, &sym)) |
| |
| static inline uint8_t elf_sym__type(const GElf_Sym *sym) |
| { |
| return GELF_ST_TYPE(sym->st_info); |
| } |
| |
| #ifndef STT_GNU_IFUNC |
| #define STT_GNU_IFUNC 10 |
| #endif |
| |
| static inline int elf_sym__is_function(const GElf_Sym *sym) |
| { |
| return (elf_sym__type(sym) == STT_FUNC || |
| elf_sym__type(sym) == STT_GNU_IFUNC) && |
| sym->st_name != 0 && |
| sym->st_shndx != SHN_UNDEF; |
| } |
| |
| static inline bool elf_sym__is_object(const GElf_Sym *sym) |
| { |
| return elf_sym__type(sym) == STT_OBJECT && |
| sym->st_name != 0 && |
| sym->st_shndx != SHN_UNDEF; |
| } |
| |
| static inline int elf_sym__is_label(const GElf_Sym *sym) |
| { |
| return elf_sym__type(sym) == STT_NOTYPE && |
| sym->st_name != 0 && |
| sym->st_shndx != SHN_UNDEF && |
| sym->st_shndx != SHN_ABS; |
| } |
| |
| static bool elf_sym__is_a(GElf_Sym *sym, enum map_type type) |
| { |
| switch (type) { |
| case MAP__FUNCTION: |
| return elf_sym__is_function(sym); |
| case MAP__VARIABLE: |
| return elf_sym__is_object(sym); |
| default: |
| return false; |
| } |
| } |
| |
| static inline const char *elf_sym__name(const GElf_Sym *sym, |
| const Elf_Data *symstrs) |
| { |
| return symstrs->d_buf + sym->st_name; |
| } |
| |
| static inline const char *elf_sec__name(const GElf_Shdr *shdr, |
| const Elf_Data *secstrs) |
| { |
| return secstrs->d_buf + shdr->sh_name; |
| } |
| |
| static inline int elf_sec__is_text(const GElf_Shdr *shdr, |
| const Elf_Data *secstrs) |
| { |
| return strstr(elf_sec__name(shdr, secstrs), "text") != NULL; |
| } |
| |
| static inline bool elf_sec__is_data(const GElf_Shdr *shdr, |
| const Elf_Data *secstrs) |
| { |
| return strstr(elf_sec__name(shdr, secstrs), "data") != NULL; |
| } |
| |
| static bool elf_sec__is_a(GElf_Shdr *shdr, Elf_Data *secstrs, |
| enum map_type type) |
| { |
| switch (type) { |
| case MAP__FUNCTION: |
| return elf_sec__is_text(shdr, secstrs); |
| case MAP__VARIABLE: |
| return elf_sec__is_data(shdr, secstrs); |
| default: |
| return false; |
| } |
| } |
| |
| static size_t elf_addr_to_index(Elf *elf, GElf_Addr addr) |
| { |
| Elf_Scn *sec = NULL; |
| GElf_Shdr shdr; |
| size_t cnt = 1; |
| |
| while ((sec = elf_nextscn(elf, sec)) != NULL) { |
| gelf_getshdr(sec, &shdr); |
| |
| if ((addr >= shdr.sh_addr) && |
| (addr < (shdr.sh_addr + shdr.sh_size))) |
| return cnt; |
| |
| ++cnt; |
| } |
| |
| return -1; |
| } |
| |
| Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep, |
| GElf_Shdr *shp, const char *name, size_t *idx) |
| { |
| Elf_Scn *sec = NULL; |
| size_t cnt = 1; |
| |
| /* Elf is corrupted/truncated, avoid calling elf_strptr. */ |
| if (!elf_rawdata(elf_getscn(elf, ep->e_shstrndx), NULL)) |
| return NULL; |
| |
| while ((sec = elf_nextscn(elf, sec)) != NULL) { |
| char *str; |
| |
| gelf_getshdr(sec, shp); |
| str = elf_strptr(elf, ep->e_shstrndx, shp->sh_name); |
| if (str && !strcmp(name, str)) { |
| if (idx) |
| *idx = cnt; |
| return sec; |
| } |
| ++cnt; |
| } |
| |
| return NULL; |
| } |
| |
| #define elf_section__for_each_rel(reldata, pos, pos_mem, idx, nr_entries) \ |
| for (idx = 0, pos = gelf_getrel(reldata, 0, &pos_mem); \ |
| idx < nr_entries; \ |
| ++idx, pos = gelf_getrel(reldata, idx, &pos_mem)) |
| |
| #define elf_section__for_each_rela(reldata, pos, pos_mem, idx, nr_entries) \ |
| for (idx = 0, pos = gelf_getrela(reldata, 0, &pos_mem); \ |
| idx < nr_entries; \ |
| ++idx, pos = gelf_getrela(reldata, idx, &pos_mem)) |
| |
| |
| /* |
| * Align offset to 4 bytes as needed for note name and descriptor data. |
| */ |
| #define NOTE_ALIGN(n) (((n) + 3) & -4U) |
| |
| static int elf_read_build_id(Elf *elf, void *bf, size_t size) |
| { |
| int err = -1; |
| GElf_Ehdr ehdr; |
| GElf_Shdr shdr; |
| Elf_Data *data; |
| Elf_Scn *sec; |
| Elf_Kind ek; |
| void *ptr; |
| |
| if (size < BUILD_ID_SIZE) |
| goto out; |
| |
| ek = elf_kind(elf); |
| if (ek != ELF_K_ELF) |
| goto out; |
| |
| if (gelf_getehdr(elf, &ehdr) == NULL) { |
| pr_err("%s: cannot get elf header.\n", __func__); |
| goto out; |
| } |
| |
| /* |
| * Check following sections for notes: |
| * '.note.gnu.build-id' |
| * '.notes' |
| * '.note' (VDSO specific) |
| */ |
| do { |
| sec = elf_section_by_name(elf, &ehdr, &shdr, |
| ".note.gnu.build-id", NULL); |
| if (sec) |
| break; |
| |
| sec = elf_section_by_name(elf, &ehdr, &shdr, |
| ".notes", NULL); |
| if (sec) |
| break; |
| |
| sec = elf_section_by_name(elf, &ehdr, &shdr, |
| ".note", NULL); |
| if (sec) |
| break; |
| |
| return err; |
| |
| } while (0); |
| |
| data = elf_getdata(sec, NULL); |
| if (data == NULL) |
| goto out; |
| |
| ptr = data->d_buf; |
| while (ptr < (data->d_buf + data->d_size)) { |
| GElf_Nhdr *nhdr = ptr; |
| size_t namesz = NOTE_ALIGN(nhdr->n_namesz), |
| descsz = NOTE_ALIGN(nhdr->n_descsz); |
| const char *name; |
| |
| ptr += sizeof(*nhdr); |
| name = ptr; |
| ptr += namesz; |
| if (nhdr->n_type == NT_GNU_BUILD_ID && |
| nhdr->n_namesz == sizeof("GNU")) { |
| if (memcmp(name, "GNU", sizeof("GNU")) == 0) { |
| size_t sz = min(size, descsz); |
| memcpy(bf, ptr, sz); |
| memset(bf + sz, 0, size - sz); |
| err = descsz; |
| break; |
| } |
| } |
| ptr += descsz; |
| } |
| |
| out: |
| return err; |
| } |
| |
| int filename__read_build_id(const char *filename, void *bf, size_t size) |
| { |
| int fd, err = -1; |
| Elf *elf; |
| |
| if (size < BUILD_ID_SIZE) |
| goto out; |
| |
| fd = open(filename, O_RDONLY); |
| if (fd < 0) |
| goto out; |
| |
| elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL); |
| if (elf == NULL) { |
| pr_debug2("%s: cannot read %s ELF file.\n", __func__, filename); |
| goto out_close; |
| } |
| |
| err = elf_read_build_id(elf, bf, size); |
| |
| elf_end(elf); |
| out_close: |
| close(fd); |
| out: |
| return err; |
| } |
| |
| void symbol__elf_init(void) |
| { |
| elf_version(EV_CURRENT); |
| } |