diff options
Diffstat (limited to 'tools/perf/util/srcline.c')
-rw-r--r-- | tools/perf/util/srcline.c | 772 |
1 files changed, 29 insertions, 743 deletions
diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c index 3e3449e35dd4..27c0966611ab 100644 --- a/tools/perf/util/srcline.c +++ b/tools/perf/util/srcline.c @@ -1,32 +1,15 @@ // SPDX-License-Identifier: GPL-2.0 -#include <inttypes.h> -#include <signal.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/types.h> - -#include <linux/compiler.h> -#include <linux/kernel.h> -#include <linux/string.h> -#include <linux/zalloc.h> - -#include <api/io.h> - -#include "util/dso.h" -#include "util/debug.h" -#include "util/callchain.h" -#include "util/symbol_conf.h" -#ifdef HAVE_LIBLLVM_SUPPORT -#include "util/llvm-c-helpers.h" -#endif #include "srcline.h" -#include "string2.h" +#include "addr2line.h" +#include "dso.h" +#include "callchain.h" +#include "libbfd.h" +#include "llvm.h" #include "symbol.h" -#include "subcmd/run-command.h" -/* If addr2line doesn't return data for 1 second then timeout. */ -int addr2line_timeout_ms = 1 * 1000; +#include <inttypes.h> +#include <string.h> + bool srcline_full_filename; char *srcline__unknown = (char *)"??:0"; @@ -49,8 +32,7 @@ static const char *srcline_dso_name(struct dso *dso) return dso_name; } -static int inline_list__append(struct symbol *symbol, char *srcline, - struct inline_node *node) +int inline_list__append(struct symbol *symbol, char *srcline, struct inline_node *node) { struct inline_list *ilist; @@ -77,7 +59,7 @@ static const char *gnu_basename(const char *path) return base ? base + 1 : path; } -static char *srcline_from_fileline(const char *file, unsigned int line) +char *srcline_from_fileline(const char *file, unsigned int line) { char *srcline; @@ -93,9 +75,9 @@ static char *srcline_from_fileline(const char *file, unsigned int line) return srcline; } -static struct symbol *new_inline_sym(struct dso *dso, - struct symbol *base_sym, - const char *funcname) +struct symbol *new_inline_sym(struct dso *dso, + struct symbol *base_sym, + const char *funcname) { struct symbol *inline_sym; char *demangled = NULL; @@ -132,722 +114,23 @@ static struct symbol *new_inline_sym(struct dso *dso, return inline_sym; } -#define MAX_INLINE_NEST 1024 - -#ifdef HAVE_LIBLLVM_SUPPORT - -static void free_llvm_inline_frames(struct llvm_a2l_frame *inline_frames, - int num_frames) -{ - if (inline_frames != NULL) { - for (int i = 0; i < num_frames; ++i) { - zfree(&inline_frames[i].filename); - zfree(&inline_frames[i].funcname); - } - zfree(&inline_frames); - } -} - -static int addr2line(const char *dso_name, u64 addr, - char **file, unsigned int *line, struct dso *dso, - bool unwind_inlines, struct inline_node *node, +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) { - struct llvm_a2l_frame *inline_frames = NULL; - int num_frames = llvm_addr2line(dso_name, addr, file, line, - node && unwind_inlines, &inline_frames); - - if (num_frames == 0 || !inline_frames) { - /* Error, or we didn't want inlines. */ - return num_frames; - } - - for (int i = 0; i < num_frames; ++i) { - struct symbol *inline_sym = - new_inline_sym(dso, sym, inline_frames[i].funcname); - char *srcline = NULL; - - if (inline_frames[i].filename) { - srcline = - srcline_from_fileline(inline_frames[i].filename, - inline_frames[i].line); - } - if (inline_list__append(inline_sym, srcline, node) != 0) { - free_llvm_inline_frames(inline_frames, num_frames); - return 0; - } - } - free_llvm_inline_frames(inline_frames, num_frames); - - return num_frames; -} - -void dso__free_a2l(struct dso *dso __maybe_unused) -{ - /* Nothing to free. */ -} - -#elif defined(HAVE_LIBBFD_SUPPORT) - -/* - * Implement addr2line using libbfd. - */ -#define PACKAGE "perf" -#include <bfd.h> - -struct a2l_data { - const char *input; - u64 addr; - - bool found; - const char *filename; - const char *funcname; - unsigned 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; + int ret; -#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; + ret = llvm__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines, node, sym); + if (ret > 0) + return ret; - 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; + ret = libbfd__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines, node, sym); + if (ret > 0) + return ret; - 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; + return cmd__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines, node, sym); } -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); -} - -static int 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(struct dso *dso) -{ - struct a2l_data *a2l = dso__a2l(dso); - - if (!a2l) - return; - - addr2line_cleanup(a2l); - - dso__set_a2l(dso, NULL); -} - -#else /* HAVE_LIBBFD_SUPPORT */ - -static int filename_split(char *filename, unsigned int *line_nr) -{ - char *sep; - - sep = strchr(filename, '\n'); - if (sep) - *sep = '\0'; - - if (!strcmp(filename, "??:0")) - return 0; - - sep = strchr(filename, ':'); - if (sep) { - *sep++ = '\0'; - *line_nr = strtoul(sep, NULL, 0); - return 1; - } - pr_debug("addr2line missing ':' in filename split\n"); - return 0; -} - -static void addr2line_subprocess_cleanup(struct child_process *a2l) -{ - if (a2l->pid != -1) { - kill(a2l->pid, SIGKILL); - finish_command(a2l); /* ignore result, we don't care */ - a2l->pid = -1; - close(a2l->in); - close(a2l->out); - } - - free(a2l); -} - -static struct child_process *addr2line_subprocess_init(const char *addr2line_path, - const char *binary_path) -{ - const char *argv[] = { - addr2line_path ?: "addr2line", - "-e", binary_path, - "-a", "-i", "-f", NULL - }; - struct child_process *a2l = zalloc(sizeof(*a2l)); - int start_command_status = 0; - - if (a2l == NULL) { - pr_err("Failed to allocate memory for addr2line"); - return NULL; - } - - a2l->pid = -1; - a2l->in = -1; - a2l->out = -1; - a2l->no_stderr = 1; - - a2l->argv = argv; - start_command_status = start_command(a2l); - a2l->argv = NULL; /* it's not used after start_command; avoid dangling pointers */ - - if (start_command_status != 0) { - pr_warning("could not start addr2line (%s) for %s: start_command return code %d\n", - addr2line_path, binary_path, start_command_status); - addr2line_subprocess_cleanup(a2l); - return NULL; - } - - return a2l; -} - -enum a2l_style { - BROKEN, - GNU_BINUTILS, - LLVM, -}; - -static enum a2l_style addr2line_configure(struct child_process *a2l, const char *dso_name) -{ - static bool cached; - static enum a2l_style style; - - if (!cached) { - char buf[128]; - struct io io; - int ch; - int lines; - - if (write(a2l->in, ",\n", 2) != 2) - return BROKEN; - - io__init(&io, a2l->out, buf, sizeof(buf)); - ch = io__get_char(&io); - if (ch == ',') { - style = LLVM; - cached = true; - lines = 1; - pr_debug3("Detected LLVM addr2line style\n"); - } else if (ch == '0') { - style = GNU_BINUTILS; - cached = true; - lines = 3; - pr_debug3("Detected binutils addr2line style\n"); - } else { - if (!symbol_conf.disable_add2line_warn) { - char *output = NULL; - size_t output_len; - - io__getline(&io, &output, &output_len); - pr_warning("%s %s: addr2line configuration failed\n", - __func__, dso_name); - pr_warning("\t%c%s", ch, output); - } - pr_debug("Unknown/broken addr2line style\n"); - return BROKEN; - } - while (lines) { - ch = io__get_char(&io); - if (ch <= 0) - break; - if (ch == '\n') - lines--; - } - /* Ignore SIGPIPE in the event addr2line exits. */ - signal(SIGPIPE, SIG_IGN); - } - return style; -} - -static int read_addr2line_record(struct io *io, - enum a2l_style style, - const char *dso_name, - u64 addr, - bool first, - char **function, - char **filename, - unsigned int *line_nr) -{ - /* - * Returns: - * -1 ==> error - * 0 ==> sentinel (or other ill-formed) record read - * 1 ==> a genuine record read - */ - char *line = NULL; - size_t line_len = 0; - unsigned int dummy_line_nr = 0; - int ret = -1; - - if (function != NULL) - zfree(function); - - if (filename != NULL) - zfree(filename); - - if (line_nr != NULL) - *line_nr = 0; - - /* - * Read the first line. Without an error this will be: - * - for the first line an address like 0x1234, - * - the binutils sentinel 0x0000000000000000, - * - the llvm-addr2line the sentinel ',' character, - * - the function name line for an inlined function. - */ - if (io__getline(io, &line, &line_len) < 0 || !line_len) - goto error; - - pr_debug3("%s %s: addr2line read address for sentinel: %s", __func__, dso_name, line); - if (style == LLVM && line_len == 2 && line[0] == ',') { - /* Found the llvm-addr2line sentinel character. */ - zfree(&line); - return 0; - } else if (style == GNU_BINUTILS && (!first || addr != 0)) { - int zero_count = 0, non_zero_count = 0; - /* - * Check for binutils sentinel ignoring it for the case the - * requested address is 0. - */ - - /* A given address should always start 0x. */ - if (line_len >= 2 || line[0] != '0' || line[1] != 'x') { - for (size_t i = 2; i < line_len; i++) { - if (line[i] == '0') - zero_count++; - else if (line[i] != '\n') - non_zero_count++; - } - if (!non_zero_count) { - int ch; - - if (first && !zero_count) { - /* Line was erroneous just '0x'. */ - goto error; - } - /* - * Line was 0x0..0, the sentinel for binutils. Remove - * the function and filename lines. - */ - zfree(&line); - do { - ch = io__get_char(io); - } while (ch > 0 && ch != '\n'); - do { - ch = io__get_char(io); - } while (ch > 0 && ch != '\n'); - return 0; - } - } - } - /* Read the second function name line (if inline data then this is the first line). */ - if (first && (io__getline(io, &line, &line_len) < 0 || !line_len)) - goto error; - - pr_debug3("%s %s: addr2line read line: %s", __func__, dso_name, line); - if (function != NULL) - *function = strdup(strim(line)); - - zfree(&line); - line_len = 0; - - /* Read the third filename and line number line. */ - if (io__getline(io, &line, &line_len) < 0 || !line_len) - goto error; - - pr_debug3("%s %s: addr2line filename:number : %s", __func__, dso_name, line); - if (filename_split(line, line_nr == NULL ? &dummy_line_nr : line_nr) == 0 && - style == GNU_BINUTILS) { - ret = 0; - goto error; - } - - if (filename != NULL) - *filename = strdup(line); - - zfree(&line); - line_len = 0; - - return 1; - -error: - free(line); - if (function != NULL) - zfree(function); - if (filename != NULL) - zfree(filename); - return ret; -} - -static int inline_list__append_record(struct dso *dso, - struct inline_node *node, - struct symbol *sym, - const char *function, - const char *filename, - unsigned int line_nr) -{ - struct symbol *inline_sym = new_inline_sym(dso, sym, function); - - return inline_list__append(inline_sym, srcline_from_fileline(filename, line_nr), node); -} - -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 __maybe_unused) -{ - struct child_process *a2l = dso__a2l(dso); - char *record_function = NULL; - char *record_filename = NULL; - unsigned int record_line_nr = 0; - int record_status = -1; - int ret = 0; - size_t inline_count = 0; - int len; - char buf[128]; - ssize_t written; - struct io io = { .eof = false }; - enum a2l_style a2l_style; - - if (!a2l) { - if (!filename__has_section(dso_name, ".debug_line")) - goto out; - - dso__set_a2l(dso, - addr2line_subprocess_init(symbol_conf.addr2line_path, dso_name)); - a2l = dso__a2l(dso); - } - - if (a2l == NULL) { - if (!symbol_conf.disable_add2line_warn) - pr_warning("%s %s: addr2line_subprocess_init failed\n", __func__, dso_name); - goto out; - } - a2l_style = addr2line_configure(a2l, dso_name); - if (a2l_style == BROKEN) - goto out; - - /* - * Send our request and then *deliberately* send something that can't be - * interpreted as a valid address to ask addr2line about (namely, - * ","). This causes addr2line to first write out the answer to our - * request, in an unbounded/unknown number of records, and then to write - * out the lines "0x0...0", "??" and "??:0", for GNU binutils, or "," - * for llvm-addr2line, so that we can detect when it has finished giving - * us anything useful. - */ - len = snprintf(buf, sizeof(buf), "%016"PRIx64"\n,\n", addr); - written = len > 0 ? write(a2l->in, buf, len) : -1; - if (written != len) { - if (!symbol_conf.disable_add2line_warn) - pr_warning("%s %s: could not send request\n", __func__, dso_name); - goto out; - } - io__init(&io, a2l->out, buf, sizeof(buf)); - io.timeout_ms = addr2line_timeout_ms; - switch (read_addr2line_record(&io, a2l_style, dso_name, addr, /*first=*/true, - &record_function, &record_filename, &record_line_nr)) { - case -1: - if (!symbol_conf.disable_add2line_warn) - pr_warning("%s %s: could not read first record\n", __func__, dso_name); - goto out; - case 0: - /* - * The first record was invalid, so return failure, but first - * read another record, since we sent a sentinel ',' for the - * sake of detected the last inlined function. Treat this as the - * first of a record as the ',' generates a new start with GNU - * binutils, also force a non-zero address as we're no longer - * reading that record. - */ - switch (read_addr2line_record(&io, a2l_style, dso_name, - /*addr=*/1, /*first=*/true, - NULL, NULL, NULL)) { - case -1: - if (!symbol_conf.disable_add2line_warn) - pr_warning("%s %s: could not read sentinel record\n", - __func__, dso_name); - break; - case 0: - /* The sentinel as expected. */ - break; - default: - if (!symbol_conf.disable_add2line_warn) - pr_warning("%s %s: unexpected record instead of sentinel", - __func__, dso_name); - break; - } - goto out; - default: - /* First record as expected. */ - break; - } - - if (file) { - *file = strdup(record_filename); - ret = 1; - } - if (line_nr) - *line_nr = record_line_nr; - - if (unwind_inlines) { - if (node && inline_list__append_record(dso, node, sym, - record_function, - record_filename, - record_line_nr)) { - ret = 0; - goto out; - } - } - - /* - * We have to read the records even if we don't care about the inline - * info. This isn't the first record and force the address to non-zero - * as we're reading records beyond the first. - */ - while ((record_status = read_addr2line_record(&io, - a2l_style, - dso_name, - /*addr=*/1, - /*first=*/false, - &record_function, - &record_filename, - &record_line_nr)) == 1) { - if (unwind_inlines && node && inline_count++ < MAX_INLINE_NEST) { - if (inline_list__append_record(dso, node, sym, - record_function, - record_filename, - record_line_nr)) { - ret = 0; - goto out; - } - ret = 1; /* found at least one inline frame */ - } - } - -out: - free(record_function); - free(record_filename); - if (io.eof) { - dso__set_a2l(dso, NULL); - addr2line_subprocess_cleanup(a2l); - } - return ret; -} - -void dso__free_a2l(struct dso *dso) -{ - struct child_process *a2l = dso__a2l(dso); - - if (!a2l) - return; - - addr2line_subprocess_cleanup(a2l); - - dso__set_a2l(dso, NULL); -} - -#endif /* HAVE_LIBBFD_SUPPORT */ - static struct inline_node *addr2inlines(const char *dso_name, u64 addr, struct dso *dso, struct symbol *sym) { @@ -862,7 +145,9 @@ static struct inline_node *addr2inlines(const char *dso_name, u64 addr, INIT_LIST_HEAD(&node->val); node->addr = addr; - addr2line(dso_name, addr, NULL, NULL, dso, true, node, sym); + addr2line(dso_name, addr, /*file=*/NULL, /*line_nr=*/NULL, dso, + /*unwind_inlines=*/true, node, sym); + return node; } @@ -889,7 +174,7 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym, goto out_err; if (!addr2line(dso_name, addr, &file, &line, dso, - unwind_inlines, NULL, sym)) + unwind_inlines, /*node=*/NULL, sym)) goto out_err; srcline = srcline_from_fileline(file, line); @@ -935,7 +220,8 @@ char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line) if (dso_name == NULL) goto out_err; - if (!addr2line(dso_name, addr, &file, line, dso, true, NULL, NULL)) + if (!addr2line(dso_name, addr, &file, line, dso, /*unwind_inlines=*/true, + /*node=*/NULL, /*sym=*/NULL)) goto out_err; dso__set_a2l_fails(dso, 0); |