root/tools/perf/util/srcline.c
// 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 "libdw.h"
#include "debug.h"

#include <inttypes.h>
#include <string.h>
#include <linux/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;
}

int inline_list__append_tail(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(&ilist->list, &node->val);
        else
                list_add_tail(&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 = 0;

        if (symbol_conf.addr2line_style[0] == A2L_STYLE_UNKNOWN) {
                int i = 0;

                /* Default addr2line fallback order. */
#ifdef HAVE_LIBDW_SUPPORT
                symbol_conf.addr2line_style[i++] = A2L_STYLE_LIBDW;
#endif
#ifdef HAVE_LIBLLVM_SUPPORT
                symbol_conf.addr2line_style[i++] = A2L_STYLE_LLVM;
#endif
#ifdef HAVE_LIBBFD_SUPPORT
                symbol_conf.addr2line_style[i++] = A2L_STYLE_LIBBFD;
#endif
                symbol_conf.addr2line_style[i++] = A2L_STYLE_CMD;
        }

        for (size_t i = 0; i < ARRAY_SIZE(symbol_conf.addr2line_style); i++) {
                switch (symbol_conf.addr2line_style[i]) {
                case A2L_STYLE_LIBDW:
                        ret = libdw__addr2line(addr, file, line_nr, dso, unwind_inlines,
                                               node, sym);
                        break;
                case A2L_STYLE_LLVM:
                        ret = llvm__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines,
                                              node, sym);
                        break;
                case A2L_STYLE_LIBBFD:
                        ret = libbfd__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines,
                                                node, sym);
                        break;
                case A2L_STYLE_CMD:
                        ret = cmd__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines,
                                             node, sym);
                        break;
                case A2L_STYLE_UNKNOWN:
                default:
                        break;
                }
                if (ret > 0)
                        return ret;
        }

        return 0;
}

int addr2line_configure(const char *var, const char *value, void *cb __maybe_unused)
{
        static const char * const a2l_style_names[] = {
                [A2L_STYLE_LIBDW] = "libdw",
                [A2L_STYLE_LLVM] = "llvm",
                [A2L_STYLE_LIBBFD] = "libbfd",
                [A2L_STYLE_CMD] = "addr2line",
                NULL
        };

        char *s, *p, *saveptr;
        size_t i = 0;

        if (strcmp(var, "addr2line.style"))
                return 0;

        if (!value)
                return -1;

        s = strdup(value);
        if (!s)
                return -1;

        p = strtok_r(s, ",", &saveptr);
        while (p && i < ARRAY_SIZE(symbol_conf.addr2line_style)) {
                bool found = false;
                char *q = strim(p);

                for (size_t j = A2L_STYLE_LIBDW; j < MAX_A2L_STYLE; j++) {
                        if (!strcasecmp(q, a2l_style_names[j])) {
                                symbol_conf.addr2line_style[i++] = j;
                                found = true;
                                break;
                        }
                }
                if (!found)
                        pr_warning("Unknown addr2line style: %s\n", q);
                p = strtok_r(NULL, ",", &saveptr);
        }

        free(s);
        return 0;
}

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