| // SPDX-License-Identifier: GPL-2.0 |
| #include "libbfd.h" |
| #include "annotate.h" |
| #include "bpf-event.h" |
| #include "bpf-utils.h" |
| #include "debug.h" |
| #include "dso.h" |
| #include "env.h" |
| #include "map.h" |
| #include "srcline.h" |
| #include "symbol.h" |
| #include "symbol_conf.h" |
| #include "util.h" |
| #include <tools/dis-asm-compat.h> |
| #ifdef HAVE_LIBBPF_SUPPORT |
| #include <bpf/bpf.h> |
| #include <bpf/btf.h> |
| #endif |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #define PACKAGE "perf" |
| #include <bfd.h> |
| |
| /* |
| * Implement addr2line using libbfd. |
| */ |
| struct a2l_data { |
| const char *input; |
| u64 addr; |
| |
| bool found; |
| const char *filename; |
| const char *funcname; |
| unsigned int line; |
| |
| bfd *abfd; |
| asymbol **syms; |
| }; |
| |
| static int bfd_error(const char *string) |
| { |
| const char *errmsg; |
| |
| errmsg = bfd_errmsg(bfd_get_error()); |
| fflush(stdout); |
| |
| if (string) |
| pr_debug("%s: %s\n", string, errmsg); |
| else |
| pr_debug("%s\n", errmsg); |
| |
| return -1; |
| } |
| |
| static int slurp_symtab(bfd *abfd, struct a2l_data *a2l) |
| { |
| long storage; |
| long symcount; |
| asymbol **syms; |
| bfd_boolean dynamic = FALSE; |
| |
| if ((bfd_get_file_flags(abfd) & HAS_SYMS) == 0) |
| return bfd_error(bfd_get_filename(abfd)); |
| |
| storage = bfd_get_symtab_upper_bound(abfd); |
| if (storage == 0L) { |
| storage = bfd_get_dynamic_symtab_upper_bound(abfd); |
| dynamic = TRUE; |
| } |
| if (storage < 0L) |
| return bfd_error(bfd_get_filename(abfd)); |
| |
| syms = malloc(storage); |
| if (dynamic) |
| symcount = bfd_canonicalize_dynamic_symtab(abfd, syms); |
| else |
| symcount = bfd_canonicalize_symtab(abfd, syms); |
| |
| if (symcount < 0) { |
| free(syms); |
| return bfd_error(bfd_get_filename(abfd)); |
| } |
| |
| a2l->syms = syms; |
| return 0; |
| } |
| |
| static void find_address_in_section(bfd *abfd, asection *section, void *data) |
| { |
| bfd_vma pc, vma; |
| bfd_size_type size; |
| struct a2l_data *a2l = data; |
| flagword flags; |
| |
| if (a2l->found) |
| return; |
| |
| #ifdef bfd_get_section_flags |
| flags = bfd_get_section_flags(abfd, section); |
| #else |
| flags = bfd_section_flags(section); |
| #endif |
| if ((flags & SEC_ALLOC) == 0) |
| return; |
| |
| pc = a2l->addr; |
| #ifdef bfd_get_section_vma |
| vma = bfd_get_section_vma(abfd, section); |
| #else |
| vma = bfd_section_vma(section); |
| #endif |
| #ifdef bfd_get_section_size |
| size = bfd_get_section_size(section); |
| #else |
| size = bfd_section_size(section); |
| #endif |
| |
| if (pc < vma || pc >= vma + size) |
| return; |
| |
| a2l->found = bfd_find_nearest_line(abfd, section, a2l->syms, pc - vma, |
| &a2l->filename, &a2l->funcname, |
| &a2l->line); |
| |
| if (a2l->filename && !strlen(a2l->filename)) |
| a2l->filename = NULL; |
| } |
| |
| static struct a2l_data *addr2line_init(const char *path) |
| { |
| bfd *abfd; |
| struct a2l_data *a2l = NULL; |
| |
| abfd = bfd_openr(path, NULL); |
| if (abfd == NULL) |
| return NULL; |
| |
| if (!bfd_check_format(abfd, bfd_object)) |
| goto out; |
| |
| a2l = zalloc(sizeof(*a2l)); |
| if (a2l == NULL) |
| goto out; |
| |
| a2l->abfd = abfd; |
| a2l->input = strdup(path); |
| if (a2l->input == NULL) |
| goto out; |
| |
| if (slurp_symtab(abfd, a2l)) |
| goto out; |
| |
| return a2l; |
| |
| out: |
| if (a2l) { |
| zfree((char **)&a2l->input); |
| free(a2l); |
| } |
| bfd_close(abfd); |
| return NULL; |
| } |
| |
| static void addr2line_cleanup(struct a2l_data *a2l) |
| { |
| if (a2l->abfd) |
| bfd_close(a2l->abfd); |
| zfree((char **)&a2l->input); |
| zfree(&a2l->syms); |
| free(a2l); |
| } |
| |
| static int inline_list__append_dso_a2l(struct dso *dso, |
| struct inline_node *node, |
| struct symbol *sym) |
| { |
| struct a2l_data *a2l = dso__a2l(dso); |
| struct symbol *inline_sym = new_inline_sym(dso, sym, a2l->funcname); |
| char *srcline = NULL; |
| |
| if (a2l->filename) |
| srcline = srcline_from_fileline(a2l->filename, a2l->line); |
| |
| return inline_list__append(inline_sym, srcline, node); |
| } |
| |
| int libbfd__addr2line(const char *dso_name, u64 addr, |
| char **file, unsigned int *line, struct dso *dso, |
| bool unwind_inlines, struct inline_node *node, |
| struct symbol *sym) |
| { |
| int ret = 0; |
| struct a2l_data *a2l = dso__a2l(dso); |
| |
| if (!a2l) { |
| a2l = addr2line_init(dso_name); |
| dso__set_a2l(dso, a2l); |
| } |
| |
| if (a2l == NULL) { |
| if (!symbol_conf.disable_add2line_warn) |
| pr_warning("addr2line_init failed for %s\n", dso_name); |
| return 0; |
| } |
| |
| a2l->addr = addr; |
| a2l->found = false; |
| |
| bfd_map_over_sections(a2l->abfd, find_address_in_section, a2l); |
| |
| if (!a2l->found) |
| return 0; |
| |
| if (unwind_inlines) { |
| int cnt = 0; |
| |
| if (node && inline_list__append_dso_a2l(dso, node, sym)) |
| return 0; |
| |
| while (bfd_find_inliner_info(a2l->abfd, &a2l->filename, |
| &a2l->funcname, &a2l->line) && |
| cnt++ < MAX_INLINE_NEST) { |
| |
| if (a2l->filename && !strlen(a2l->filename)) |
| a2l->filename = NULL; |
| |
| if (node != NULL) { |
| if (inline_list__append_dso_a2l(dso, node, sym)) |
| return 0; |
| // found at least one inline frame |
| ret = 1; |
| } |
| } |
| } |
| |
| if (file) { |
| *file = a2l->filename ? strdup(a2l->filename) : NULL; |
| ret = *file ? 1 : 0; |
| } |
| |
| if (line) |
| *line = a2l->line; |
| |
| return ret; |
| } |
| |
| void dso__free_a2l_libbfd(struct dso *dso) |
| { |
| struct a2l_data *a2l = dso__a2l(dso); |
| |
| if (!a2l) |
| return; |
| |
| addr2line_cleanup(a2l); |
| |
| dso__set_a2l(dso, NULL); |
| } |
| |
| static int bfd_symbols__cmpvalue(const void *a, const void *b) |
| { |
| const asymbol *as = *(const asymbol **)a, *bs = *(const asymbol **)b; |
| |
| if (bfd_asymbol_value(as) != bfd_asymbol_value(bs)) |
| return bfd_asymbol_value(as) - bfd_asymbol_value(bs); |
| |
| return bfd_asymbol_name(as)[0] - bfd_asymbol_name(bs)[0]; |
| } |
| |
| static int bfd2elf_binding(asymbol *symbol) |
| { |
| if (symbol->flags & BSF_WEAK) |
| return STB_WEAK; |
| if (symbol->flags & BSF_GLOBAL) |
| return STB_GLOBAL; |
| if (symbol->flags & BSF_LOCAL) |
| return STB_LOCAL; |
| return -1; |
| } |
| |
| int dso__load_bfd_symbols(struct dso *dso, const char *debugfile) |
| { |
| int err = -1; |
| long symbols_size, symbols_count, i; |
| asection *section; |
| asymbol **symbols, *sym; |
| struct symbol *symbol; |
| bfd *abfd; |
| u64 start, len; |
| |
| abfd = bfd_openr(debugfile, NULL); |
| if (!abfd) |
| return -1; |
| |
| if (!bfd_check_format(abfd, bfd_object)) { |
| pr_debug2("%s: cannot read %s bfd file.\n", __func__, |
| dso__long_name(dso)); |
| goto out_close; |
| } |
| |
| if (bfd_get_flavour(abfd) == bfd_target_elf_flavour) |
| goto out_close; |
| |
| symbols_size = bfd_get_symtab_upper_bound(abfd); |
| if (symbols_size == 0) { |
| bfd_close(abfd); |
| return 0; |
| } |
| |
| if (symbols_size < 0) |
| goto out_close; |
| |
| symbols = malloc(symbols_size); |
| if (!symbols) |
| goto out_close; |
| |
| symbols_count = bfd_canonicalize_symtab(abfd, symbols); |
| if (symbols_count < 0) |
| goto out_free; |
| |
| section = bfd_get_section_by_name(abfd, ".text"); |
| if (section) { |
| for (i = 0; i < symbols_count; ++i) { |
| if (!strcmp(bfd_asymbol_name(symbols[i]), "__ImageBase") || |
| !strcmp(bfd_asymbol_name(symbols[i]), "__image_base__")) |
| break; |
| } |
| if (i < symbols_count) { |
| /* PE symbols can only have 4 bytes, so use .text high bits */ |
| u64 text_offset = (section->vma - (u32)section->vma) |
| + (u32)bfd_asymbol_value(symbols[i]); |
| dso__set_text_offset(dso, text_offset); |
| dso__set_text_end(dso, (section->vma - text_offset) + section->size); |
| } else { |
| dso__set_text_offset(dso, section->vma - section->filepos); |
| dso__set_text_end(dso, section->filepos + section->size); |
| } |
| } |
| |
| qsort(symbols, symbols_count, sizeof(asymbol *), bfd_symbols__cmpvalue); |
| |
| #ifdef bfd_get_section |
| #define bfd_asymbol_section bfd_get_section |
| #endif |
| for (i = 0; i < symbols_count; ++i) { |
| sym = symbols[i]; |
| section = bfd_asymbol_section(sym); |
| if (bfd2elf_binding(sym) < 0) |
| continue; |
| |
| while (i + 1 < symbols_count && |
| bfd_asymbol_section(symbols[i + 1]) == section && |
| bfd2elf_binding(symbols[i + 1]) < 0) |
| i++; |
| |
| if (i + 1 < symbols_count && |
| bfd_asymbol_section(symbols[i + 1]) == section) |
| len = symbols[i + 1]->value - sym->value; |
| else |
| len = section->size - sym->value; |
| |
| start = bfd_asymbol_value(sym) - dso__text_offset(dso); |
| symbol = symbol__new(start, len, bfd2elf_binding(sym), STT_FUNC, |
| bfd_asymbol_name(sym)); |
| if (!symbol) |
| goto out_free; |
| |
| symbols__insert(dso__symbols(dso), symbol); |
| } |
| #ifdef bfd_get_section |
| #undef bfd_asymbol_section |
| #endif |
| |
| symbols__fixup_end(dso__symbols(dso), false); |
| symbols__fixup_duplicate(dso__symbols(dso)); |
| dso__set_adjust_symbols(dso, true); |
| |
| err = 0; |
| out_free: |
| free(symbols); |
| out_close: |
| bfd_close(abfd); |
| return err; |
| } |
| |
| int libbfd__read_build_id(const char *filename, struct build_id *bid, bool block) |
| { |
| size_t size = sizeof(bid->data); |
| int err = -1, fd; |
| bfd *abfd; |
| |
| fd = open(filename, block ? O_RDONLY : (O_RDONLY | O_NONBLOCK)); |
| if (fd < 0) |
| return -1; |
| |
| abfd = bfd_fdopenr(filename, /*target=*/NULL, fd); |
| if (!abfd) |
| return -1; |
| |
| if (!bfd_check_format(abfd, bfd_object)) { |
| pr_debug2("%s: cannot read %s bfd file.\n", __func__, filename); |
| goto out_close; |
| } |
| |
| if (!abfd->build_id || abfd->build_id->size > size) |
| goto out_close; |
| |
| memcpy(bid->data, abfd->build_id->data, abfd->build_id->size); |
| memset(bid->data + abfd->build_id->size, 0, size - abfd->build_id->size); |
| err = bid->size = abfd->build_id->size; |
| |
| out_close: |
| bfd_close(abfd); |
| return err; |
| } |
| |
| int libbfd_filename__read_debuglink(const char *filename, char *debuglink, |
| size_t size) |
| { |
| int err = -1; |
| asection *section; |
| bfd *abfd; |
| |
| abfd = bfd_openr(filename, NULL); |
| if (!abfd) |
| return -1; |
| |
| if (!bfd_check_format(abfd, bfd_object)) { |
| pr_debug2("%s: cannot read %s bfd file.\n", __func__, filename); |
| goto out_close; |
| } |
| |
| section = bfd_get_section_by_name(abfd, ".gnu_debuglink"); |
| if (!section) |
| goto out_close; |
| |
| if (section->size > size) |
| goto out_close; |
| |
| if (!bfd_get_section_contents(abfd, section, debuglink, 0, |
| section->size)) |
| goto out_close; |
| |
| err = 0; |
| |
| out_close: |
| bfd_close(abfd); |
| return err; |
| } |
| |
| int symbol__disassemble_bpf_libbfd(struct symbol *sym __maybe_unused, |
| struct annotate_args *args __maybe_unused) |
| { |
| #ifdef HAVE_LIBBPF_SUPPORT |
| struct annotation *notes = symbol__annotation(sym); |
| struct bpf_prog_linfo *prog_linfo = NULL; |
| struct bpf_prog_info_node *info_node; |
| int len = sym->end - sym->start; |
| disassembler_ftype disassemble; |
| struct map *map = args->ms.map; |
| struct perf_bpil *info_linear; |
| struct disassemble_info info; |
| struct dso *dso = map__dso(map); |
| int pc = 0, count, sub_id; |
| struct btf *btf = NULL; |
| char tpath[PATH_MAX]; |
| size_t buf_size; |
| int nr_skip = 0; |
| char *buf; |
| bfd *bfdf; |
| int ret; |
| FILE *s; |
| |
| if (dso__binary_type(dso) != DSO_BINARY_TYPE__BPF_PROG_INFO) |
| return SYMBOL_ANNOTATE_ERRNO__BPF_INVALID_FILE; |
| |
| pr_debug("%s: handling sym %s addr %" PRIx64 " len %" PRIx64 "\n", __func__, |
| sym->name, sym->start, sym->end - sym->start); |
| |
| memset(tpath, 0, sizeof(tpath)); |
| perf_exe(tpath, sizeof(tpath)); |
| |
| bfdf = bfd_openr(tpath, NULL); |
| if (bfdf == NULL) |
| abort(); |
| |
| if (!bfd_check_format(bfdf, bfd_object)) |
| abort(); |
| |
| s = open_memstream(&buf, &buf_size); |
| if (!s) { |
| ret = errno; |
| goto out; |
| } |
| init_disassemble_info_compat(&info, s, |
| (fprintf_ftype) fprintf, |
| fprintf_styled); |
| info.arch = bfd_get_arch(bfdf); |
| info.mach = bfd_get_mach(bfdf); |
| |
| info_node = perf_env__find_bpf_prog_info(dso__bpf_prog(dso)->env, |
| dso__bpf_prog(dso)->id); |
| if (!info_node) { |
| ret = SYMBOL_ANNOTATE_ERRNO__BPF_MISSING_BTF; |
| goto out; |
| } |
| info_linear = info_node->info_linear; |
| sub_id = dso__bpf_prog(dso)->sub_id; |
| |
| info.buffer = (void *)(uintptr_t)(info_linear->info.jited_prog_insns); |
| info.buffer_length = info_linear->info.jited_prog_len; |
| |
| if (info_linear->info.nr_line_info) |
| prog_linfo = bpf_prog_linfo__new(&info_linear->info); |
| |
| if (info_linear->info.btf_id) { |
| struct btf_node *node; |
| |
| node = perf_env__find_btf(dso__bpf_prog(dso)->env, |
| info_linear->info.btf_id); |
| if (node) |
| btf = btf__new((__u8 *)(node->data), |
| node->data_size); |
| } |
| |
| disassemble_init_for_target(&info); |
| |
| #ifdef DISASM_FOUR_ARGS_SIGNATURE |
| disassemble = disassembler(info.arch, |
| bfd_big_endian(bfdf), |
| info.mach, |
| bfdf); |
| #else |
| disassemble = disassembler(bfdf); |
| #endif |
| if (disassemble == NULL) |
| abort(); |
| |
| fflush(s); |
| do { |
| const struct bpf_line_info *linfo = NULL; |
| struct disasm_line *dl; |
| size_t prev_buf_size; |
| const char *srcline; |
| u64 addr; |
| |
| addr = pc + ((u64 *)(uintptr_t)(info_linear->info.jited_ksyms))[sub_id]; |
| count = disassemble(pc, &info); |
| |
| if (prog_linfo) |
| linfo = bpf_prog_linfo__lfind_addr_func(prog_linfo, |
| addr, sub_id, |
| nr_skip); |
| |
| if (linfo && btf) { |
| srcline = btf__name_by_offset(btf, linfo->line_off); |
| nr_skip++; |
| } else |
| srcline = NULL; |
| |
| fprintf(s, "\n"); |
| prev_buf_size = buf_size; |
| fflush(s); |
| |
| if (!annotate_opts.hide_src_code && srcline) { |
| args->offset = -1; |
| args->line = strdup(srcline); |
| args->line_nr = 0; |
| args->fileloc = NULL; |
| args->ms.sym = sym; |
| dl = disasm_line__new(args); |
| if (dl) { |
| annotation_line__add(&dl->al, |
| ¬es->src->source); |
| } |
| } |
| |
| args->offset = pc; |
| args->line = buf + prev_buf_size; |
| args->line_nr = 0; |
| args->fileloc = NULL; |
| args->ms.sym = sym; |
| dl = disasm_line__new(args); |
| if (dl) |
| annotation_line__add(&dl->al, ¬es->src->source); |
| |
| pc += count; |
| } while (count > 0 && pc < len); |
| |
| ret = 0; |
| out: |
| free(prog_linfo); |
| btf__free(btf); |
| fclose(s); |
| bfd_close(bfdf); |
| return ret; |
| #else |
| return SYMBOL_ANNOTATE_ERRNO__NO_LIBOPCODES_FOR_BPF; |
| #endif |
| } |