summaryrefslogtreecommitdiff
path: root/tools/perf/util/srcline.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/perf/util/srcline.c')
-rw-r--r--tools/perf/util/srcline.c407
1 files changed, 407 insertions, 0 deletions
diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c
new file mode 100644
index 000000000000..27c0966611ab
--- /dev/null
+++ b/tools/perf/util/srcline.c
@@ -0,0 +1,407 @@
+// 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);
+ }
+}