| // SPDX-License-Identifier: GPL-2.0 |
| #include "srcline.h" |
| #include "addr2line.h" |
| #include "dso.h" |
| #include "callchain.h" |
| #include "libbfd.h" |
| #include "llvm.h" |
| #include "symbol.h" |
| |
| #include <inttypes.h> |
| #include <string.h> |
| |
| bool srcline_full_filename; |
| |
| char *srcline__unknown = (char *)"??:0"; |
| |
| static const char *srcline_dso_name(struct dso *dso) |
| { |
| const char *dso_name; |
| |
| if (dso__symsrc_filename(dso)) |
| dso_name = dso__symsrc_filename(dso); |
| else |
| dso_name = dso__long_name(dso); |
| |
| if (dso_name[0] == '[') |
| return NULL; |
| |
| if (is_perf_pid_map_name(dso_name)) |
| return NULL; |
| |
| return dso_name; |
| } |
| |
| int inline_list__append(struct symbol *symbol, char *srcline, struct inline_node *node) |
| { |
| struct inline_list *ilist; |
| |
| ilist = zalloc(sizeof(*ilist)); |
| if (ilist == NULL) |
| return -1; |
| |
| ilist->symbol = symbol; |
| ilist->srcline = srcline; |
| |
| if (callchain_param.order == ORDER_CALLEE) |
| list_add_tail(&ilist->list, &node->val); |
| else |
| list_add(&ilist->list, &node->val); |
| |
| return 0; |
| } |
| |
| /* basename version that takes a const input string */ |
| static const char *gnu_basename(const char *path) |
| { |
| const char *base = strrchr(path, '/'); |
| |
| return base ? base + 1 : path; |
| } |
| |
| char *srcline_from_fileline(const char *file, unsigned int line) |
| { |
| char *srcline; |
| |
| if (!file) |
| return NULL; |
| |
| if (!srcline_full_filename) |
| file = gnu_basename(file); |
| |
| if (asprintf(&srcline, "%s:%u", file, line) < 0) |
| return NULL; |
| |
| return srcline; |
| } |
| |
| struct symbol *new_inline_sym(struct dso *dso, |
| struct symbol *base_sym, |
| const char *funcname) |
| { |
| struct symbol *inline_sym; |
| char *demangled = NULL; |
| |
| if (!funcname) |
| funcname = "??"; |
| |
| if (dso) { |
| demangled = dso__demangle_sym(dso, 0, funcname); |
| if (demangled) |
| funcname = demangled; |
| } |
| |
| if (base_sym && strcmp(funcname, base_sym->name) == 0) { |
| /* reuse the real, existing symbol */ |
| inline_sym = base_sym; |
| /* ensure that we don't alias an inlined symbol, which could |
| * lead to double frees in inline_node__delete |
| */ |
| assert(!base_sym->inlined); |
| } else { |
| /* create a fake symbol for the inline frame */ |
| inline_sym = symbol__new(base_sym ? base_sym->start : 0, |
| base_sym ? (base_sym->end - base_sym->start) : 0, |
| base_sym ? base_sym->binding : 0, |
| base_sym ? base_sym->type : 0, |
| funcname); |
| if (inline_sym) |
| inline_sym->inlined = 1; |
| } |
| |
| free(demangled); |
| |
| return inline_sym; |
| } |
| |
| static int addr2line(const char *dso_name, u64 addr, char **file, unsigned int *line_nr, |
| struct dso *dso, bool unwind_inlines, struct inline_node *node, |
| struct symbol *sym) |
| { |
| int ret; |
| |
| ret = llvm__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines, node, sym); |
| if (ret > 0) |
| return ret; |
| |
| ret = libbfd__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines, node, sym); |
| if (ret > 0) |
| return ret; |
| |
| return cmd__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines, node, sym); |
| } |
| |
| static struct inline_node *addr2inlines(const char *dso_name, u64 addr, |
| struct dso *dso, struct symbol *sym) |
| { |
| struct inline_node *node; |
| |
| node = zalloc(sizeof(*node)); |
| if (node == NULL) { |
| perror("not enough memory for the inline node"); |
| return NULL; |
| } |
| |
| INIT_LIST_HEAD(&node->val); |
| node->addr = addr; |
| |
| addr2line(dso_name, addr, /*file=*/NULL, /*line_nr=*/NULL, dso, |
| /*unwind_inlines=*/true, node, sym); |
| |
| return node; |
| } |
| |
| /* |
| * Number of addr2line failures (without success) before disabling it for that |
| * dso. |
| */ |
| #define A2L_FAIL_LIMIT 123 |
| |
| char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym, |
| bool show_sym, bool show_addr, bool unwind_inlines, |
| u64 ip) |
| { |
| char *file = NULL; |
| unsigned line = 0; |
| char *srcline; |
| const char *dso_name; |
| |
| if (!dso__has_srcline(dso)) |
| goto out; |
| |
| dso_name = srcline_dso_name(dso); |
| if (dso_name == NULL) |
| goto out_err; |
| |
| if (!addr2line(dso_name, addr, &file, &line, dso, |
| unwind_inlines, /*node=*/NULL, sym)) |
| goto out_err; |
| |
| srcline = srcline_from_fileline(file, line); |
| free(file); |
| |
| if (!srcline) |
| goto out_err; |
| |
| dso__set_a2l_fails(dso, 0); |
| |
| return srcline; |
| |
| out_err: |
| dso__set_a2l_fails(dso, dso__a2l_fails(dso) + 1); |
| if (dso__a2l_fails(dso) > A2L_FAIL_LIMIT) { |
| dso__set_has_srcline(dso, false); |
| dso__free_a2l(dso); |
| } |
| out: |
| if (!show_addr) |
| return (show_sym && sym) ? |
| strndup(sym->name, sym->namelen) : SRCLINE_UNKNOWN; |
| |
| if (sym) { |
| if (asprintf(&srcline, "%s+%" PRIu64, show_sym ? sym->name : "", |
| ip - sym->start) < 0) |
| return SRCLINE_UNKNOWN; |
| } else if (asprintf(&srcline, "%s[%" PRIx64 "]", dso__short_name(dso), addr) < 0) |
| return SRCLINE_UNKNOWN; |
| return srcline; |
| } |
| |
| /* Returns filename and fills in line number in line */ |
| char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line) |
| { |
| char *file = NULL; |
| const char *dso_name; |
| |
| if (!dso__has_srcline(dso)) |
| return NULL; |
| |
| dso_name = srcline_dso_name(dso); |
| if (dso_name == NULL) |
| goto out_err; |
| |
| if (!addr2line(dso_name, addr, &file, line, dso, /*unwind_inlines=*/true, |
| /*node=*/NULL, /*sym=*/NULL)) |
| goto out_err; |
| |
| dso__set_a2l_fails(dso, 0); |
| return file; |
| |
| out_err: |
| dso__set_a2l_fails(dso, dso__a2l_fails(dso) + 1); |
| if (dso__a2l_fails(dso) > A2L_FAIL_LIMIT) { |
| dso__set_has_srcline(dso, false); |
| dso__free_a2l(dso); |
| } |
| |
| return NULL; |
| } |
| |
| void zfree_srcline(char **srcline) |
| { |
| if (*srcline == NULL) |
| return; |
| |
| if (*srcline != SRCLINE_UNKNOWN) |
| free(*srcline); |
| |
| *srcline = NULL; |
| } |
| |
| char *get_srcline(struct dso *dso, u64 addr, struct symbol *sym, |
| bool show_sym, bool show_addr, u64 ip) |
| { |
| return __get_srcline(dso, addr, sym, show_sym, show_addr, false, ip); |
| } |
| |
| struct srcline_node { |
| u64 addr; |
| char *srcline; |
| struct rb_node rb_node; |
| }; |
| |
| void srcline__tree_insert(struct rb_root_cached *tree, u64 addr, char *srcline) |
| { |
| struct rb_node **p = &tree->rb_root.rb_node; |
| struct rb_node *parent = NULL; |
| struct srcline_node *i, *node; |
| bool leftmost = true; |
| |
| node = zalloc(sizeof(struct srcline_node)); |
| if (!node) { |
| perror("not enough memory for the srcline node"); |
| return; |
| } |
| |
| node->addr = addr; |
| node->srcline = srcline; |
| |
| while (*p != NULL) { |
| parent = *p; |
| i = rb_entry(parent, struct srcline_node, rb_node); |
| if (addr < i->addr) |
| p = &(*p)->rb_left; |
| else { |
| p = &(*p)->rb_right; |
| leftmost = false; |
| } |
| } |
| rb_link_node(&node->rb_node, parent, p); |
| rb_insert_color_cached(&node->rb_node, tree, leftmost); |
| } |
| |
| char *srcline__tree_find(struct rb_root_cached *tree, u64 addr) |
| { |
| struct rb_node *n = tree->rb_root.rb_node; |
| |
| while (n) { |
| struct srcline_node *i = rb_entry(n, struct srcline_node, |
| rb_node); |
| |
| if (addr < i->addr) |
| n = n->rb_left; |
| else if (addr > i->addr) |
| n = n->rb_right; |
| else |
| return i->srcline; |
| } |
| |
| return NULL; |
| } |
| |
| void srcline__tree_delete(struct rb_root_cached *tree) |
| { |
| struct srcline_node *pos; |
| struct rb_node *next = rb_first_cached(tree); |
| |
| while (next) { |
| pos = rb_entry(next, struct srcline_node, rb_node); |
| next = rb_next(&pos->rb_node); |
| rb_erase_cached(&pos->rb_node, tree); |
| zfree_srcline(&pos->srcline); |
| zfree(&pos); |
| } |
| } |
| |
| struct inline_node *dso__parse_addr_inlines(struct dso *dso, u64 addr, |
| struct symbol *sym) |
| { |
| const char *dso_name; |
| |
| dso_name = srcline_dso_name(dso); |
| if (dso_name == NULL) |
| return NULL; |
| |
| return addr2inlines(dso_name, addr, dso, sym); |
| } |
| |
| void inline_node__delete(struct inline_node *node) |
| { |
| struct inline_list *ilist, *tmp; |
| |
| list_for_each_entry_safe(ilist, tmp, &node->val, list) { |
| list_del_init(&ilist->list); |
| zfree_srcline(&ilist->srcline); |
| /* only the inlined symbols are owned by the list */ |
| if (ilist->symbol && ilist->symbol->inlined) |
| symbol__delete(ilist->symbol); |
| free(ilist); |
| } |
| |
| free(node); |
| } |
| |
| void inlines__tree_insert(struct rb_root_cached *tree, |
| struct inline_node *inlines) |
| { |
| struct rb_node **p = &tree->rb_root.rb_node; |
| struct rb_node *parent = NULL; |
| const u64 addr = inlines->addr; |
| struct inline_node *i; |
| bool leftmost = true; |
| |
| while (*p != NULL) { |
| parent = *p; |
| i = rb_entry(parent, struct inline_node, rb_node); |
| if (addr < i->addr) |
| p = &(*p)->rb_left; |
| else { |
| p = &(*p)->rb_right; |
| leftmost = false; |
| } |
| } |
| rb_link_node(&inlines->rb_node, parent, p); |
| rb_insert_color_cached(&inlines->rb_node, tree, leftmost); |
| } |
| |
| struct inline_node *inlines__tree_find(struct rb_root_cached *tree, u64 addr) |
| { |
| struct rb_node *n = tree->rb_root.rb_node; |
| |
| while (n) { |
| struct inline_node *i = rb_entry(n, struct inline_node, |
| rb_node); |
| |
| if (addr < i->addr) |
| n = n->rb_left; |
| else if (addr > i->addr) |
| n = n->rb_right; |
| else |
| return i; |
| } |
| |
| return NULL; |
| } |
| |
| void inlines__tree_delete(struct rb_root_cached *tree) |
| { |
| struct inline_node *pos; |
| struct rb_node *next = rb_first_cached(tree); |
| |
| while (next) { |
| pos = rb_entry(next, struct inline_node, rb_node); |
| next = rb_next(&pos->rb_node); |
| rb_erase_cached(&pos->rb_node, tree); |
| inline_node__delete(pos); |
| } |
| } |