|  | // 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); | 
|  | } | 
|  | } |