/tools/perf/util/annotate.c
C | 3175 lines | 2421 code | 563 blank | 191 comment | 547 complexity | b9bb8acf632791c4db799b04a17f432c MD5 | raw file
Possible License(s): LGPL-2.0, AGPL-1.0, GPL-2.0
Large files files are truncated, but you can click here to view the full file
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Copyright (C) 2011, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com>
- *
- * Parts came from builtin-annotate.c, see those files for further
- * copyright notes.
- */
- #include <errno.h>
- #include <inttypes.h>
- #include <libgen.h>
- #include <stdlib.h>
- #include <bpf/bpf.h>
- #include <bpf/btf.h>
- #include <bpf/libbpf.h>
- #include <linux/btf.h>
- #include "util.h" // hex_width()
- #include "ui/ui.h"
- #include "sort.h"
- #include "build-id.h"
- #include "color.h"
- #include "config.h"
- #include "dso.h"
- #include "env.h"
- #include "map.h"
- #include "maps.h"
- #include "symbol.h"
- #include "srcline.h"
- #include "units.h"
- #include "debug.h"
- #include "annotate.h"
- #include "evsel.h"
- #include "evlist.h"
- #include "bpf-event.h"
- #include "block-range.h"
- #include "string2.h"
- #include "util/event.h"
- #include "arch/common.h"
- #include <regex.h>
- #include <pthread.h>
- #include <linux/bitops.h>
- #include <linux/kernel.h>
- #include <linux/string.h>
- #include <bpf/libbpf.h>
- #include <subcmd/parse-options.h>
- #include <subcmd/run-command.h>
- /* FIXME: For the HE_COLORSET */
- #include "ui/browser.h"
- /*
- * FIXME: Using the same values as slang.h,
- * but that header may not be available everywhere
- */
- #define LARROW_CHAR ((unsigned char)',')
- #define RARROW_CHAR ((unsigned char)'+')
- #define DARROW_CHAR ((unsigned char)'.')
- #define UARROW_CHAR ((unsigned char)'-')
- #include <linux/ctype.h>
- struct annotation_options annotation__default_options = {
- .use_offset = true,
- .jump_arrows = true,
- .annotate_src = true,
- .offset_level = ANNOTATION__OFFSET_JUMP_TARGETS,
- .percent_type = PERCENT_PERIOD_LOCAL,
- };
- static regex_t file_lineno;
- static struct ins_ops *ins__find(struct arch *arch, const char *name);
- static void ins__sort(struct arch *arch);
- static int disasm_line__parse(char *line, const char **namep, char **rawp);
- struct arch {
- const char *name;
- struct ins *instructions;
- size_t nr_instructions;
- size_t nr_instructions_allocated;
- struct ins_ops *(*associate_instruction_ops)(struct arch *arch, const char *name);
- bool sorted_instructions;
- bool initialized;
- void *priv;
- unsigned int model;
- unsigned int family;
- int (*init)(struct arch *arch, char *cpuid);
- bool (*ins_is_fused)(struct arch *arch, const char *ins1,
- const char *ins2);
- struct {
- char comment_char;
- char skip_functions_char;
- } objdump;
- };
- static struct ins_ops call_ops;
- static struct ins_ops dec_ops;
- static struct ins_ops jump_ops;
- static struct ins_ops mov_ops;
- static struct ins_ops nop_ops;
- static struct ins_ops lock_ops;
- static struct ins_ops ret_ops;
- static int arch__grow_instructions(struct arch *arch)
- {
- struct ins *new_instructions;
- size_t new_nr_allocated;
- if (arch->nr_instructions_allocated == 0 && arch->instructions)
- goto grow_from_non_allocated_table;
- new_nr_allocated = arch->nr_instructions_allocated + 128;
- new_instructions = realloc(arch->instructions, new_nr_allocated * sizeof(struct ins));
- if (new_instructions == NULL)
- return -1;
- out_update_instructions:
- arch->instructions = new_instructions;
- arch->nr_instructions_allocated = new_nr_allocated;
- return 0;
- grow_from_non_allocated_table:
- new_nr_allocated = arch->nr_instructions + 128;
- new_instructions = calloc(new_nr_allocated, sizeof(struct ins));
- if (new_instructions == NULL)
- return -1;
- memcpy(new_instructions, arch->instructions, arch->nr_instructions);
- goto out_update_instructions;
- }
- static int arch__associate_ins_ops(struct arch* arch, const char *name, struct ins_ops *ops)
- {
- struct ins *ins;
- if (arch->nr_instructions == arch->nr_instructions_allocated &&
- arch__grow_instructions(arch))
- return -1;
- ins = &arch->instructions[arch->nr_instructions];
- ins->name = strdup(name);
- if (!ins->name)
- return -1;
- ins->ops = ops;
- arch->nr_instructions++;
- ins__sort(arch);
- return 0;
- }
- #include "arch/arc/annotate/instructions.c"
- #include "arch/arm/annotate/instructions.c"
- #include "arch/arm64/annotate/instructions.c"
- #include "arch/csky/annotate/instructions.c"
- #include "arch/x86/annotate/instructions.c"
- #include "arch/powerpc/annotate/instructions.c"
- #include "arch/s390/annotate/instructions.c"
- #include "arch/sparc/annotate/instructions.c"
- static struct arch architectures[] = {
- {
- .name = "arc",
- .init = arc__annotate_init,
- },
- {
- .name = "arm",
- .init = arm__annotate_init,
- },
- {
- .name = "arm64",
- .init = arm64__annotate_init,
- },
- {
- .name = "csky",
- .init = csky__annotate_init,
- },
- {
- .name = "x86",
- .init = x86__annotate_init,
- .instructions = x86__instructions,
- .nr_instructions = ARRAY_SIZE(x86__instructions),
- .ins_is_fused = x86__ins_is_fused,
- .objdump = {
- .comment_char = '#',
- },
- },
- {
- .name = "powerpc",
- .init = powerpc__annotate_init,
- },
- {
- .name = "s390",
- .init = s390__annotate_init,
- .objdump = {
- .comment_char = '#',
- },
- },
- {
- .name = "sparc",
- .init = sparc__annotate_init,
- .objdump = {
- .comment_char = '#',
- },
- },
- };
- static void ins__delete(struct ins_operands *ops)
- {
- if (ops == NULL)
- return;
- zfree(&ops->source.raw);
- zfree(&ops->source.name);
- zfree(&ops->target.raw);
- zfree(&ops->target.name);
- }
- static int ins__raw_scnprintf(struct ins *ins, char *bf, size_t size,
- struct ins_operands *ops, int max_ins_name)
- {
- return scnprintf(bf, size, "%-*s %s", max_ins_name, ins->name, ops->raw);
- }
- int ins__scnprintf(struct ins *ins, char *bf, size_t size,
- struct ins_operands *ops, int max_ins_name)
- {
- if (ins->ops->scnprintf)
- return ins->ops->scnprintf(ins, bf, size, ops, max_ins_name);
- return ins__raw_scnprintf(ins, bf, size, ops, max_ins_name);
- }
- bool ins__is_fused(struct arch *arch, const char *ins1, const char *ins2)
- {
- if (!arch || !arch->ins_is_fused)
- return false;
- return arch->ins_is_fused(arch, ins1, ins2);
- }
- static int call__parse(struct arch *arch, struct ins_operands *ops, struct map_symbol *ms)
- {
- char *endptr, *tok, *name;
- struct map *map = ms->map;
- struct addr_map_symbol target = {
- .ms = { .map = map, },
- };
- ops->target.addr = strtoull(ops->raw, &endptr, 16);
- name = strchr(endptr, '<');
- if (name == NULL)
- goto indirect_call;
- name++;
- if (arch->objdump.skip_functions_char &&
- strchr(name, arch->objdump.skip_functions_char))
- return -1;
- tok = strchr(name, '>');
- if (tok == NULL)
- return -1;
- *tok = '\0';
- ops->target.name = strdup(name);
- *tok = '>';
- if (ops->target.name == NULL)
- return -1;
- find_target:
- target.addr = map__objdump_2mem(map, ops->target.addr);
- if (maps__find_ams(ms->maps, &target) == 0 &&
- map__rip_2objdump(target.ms.map, map->map_ip(target.ms.map, target.addr)) == ops->target.addr)
- ops->target.sym = target.ms.sym;
- return 0;
- indirect_call:
- tok = strchr(endptr, '*');
- if (tok != NULL) {
- endptr++;
- /* Indirect call can use a non-rip register and offset: callq *0x8(%rbx).
- * Do not parse such instruction. */
- if (strstr(endptr, "(%r") == NULL)
- ops->target.addr = strtoull(endptr, NULL, 16);
- }
- goto find_target;
- }
- static int call__scnprintf(struct ins *ins, char *bf, size_t size,
- struct ins_operands *ops, int max_ins_name)
- {
- if (ops->target.sym)
- return scnprintf(bf, size, "%-*s %s", max_ins_name, ins->name, ops->target.sym->name);
- if (ops->target.addr == 0)
- return ins__raw_scnprintf(ins, bf, size, ops, max_ins_name);
- if (ops->target.name)
- return scnprintf(bf, size, "%-*s %s", max_ins_name, ins->name, ops->target.name);
- return scnprintf(bf, size, "%-*s *%" PRIx64, max_ins_name, ins->name, ops->target.addr);
- }
- static struct ins_ops call_ops = {
- .parse = call__parse,
- .scnprintf = call__scnprintf,
- };
- bool ins__is_call(const struct ins *ins)
- {
- return ins->ops == &call_ops || ins->ops == &s390_call_ops;
- }
- /*
- * Prevents from matching commas in the comment section, e.g.:
- * ffff200008446e70: b.cs ffff2000084470f4 <generic_exec_single+0x314> // b.hs, b.nlast
- */
- static inline const char *validate_comma(const char *c, struct ins_operands *ops)
- {
- if (ops->raw_comment && c > ops->raw_comment)
- return NULL;
- return c;
- }
- static int jump__parse(struct arch *arch, struct ins_operands *ops, struct map_symbol *ms)
- {
- struct map *map = ms->map;
- struct symbol *sym = ms->sym;
- struct addr_map_symbol target = {
- .ms = { .map = map, },
- };
- const char *c = strchr(ops->raw, ',');
- u64 start, end;
- ops->raw_comment = strchr(ops->raw, arch->objdump.comment_char);
- c = validate_comma(c, ops);
- /*
- * Examples of lines to parse for the _cpp_lex_token@@Base
- * function:
- *
- * 1159e6c: jne 115aa32 <_cpp_lex_token@@Base+0xf92>
- * 1159e8b: jne c469be <cpp_named_operator2name@@Base+0xa72>
- *
- * The first is a jump to an offset inside the same function,
- * the second is to another function, i.e. that 0xa72 is an
- * offset in the cpp_named_operator2name@@base function.
- */
- /*
- * skip over possible up to 2 operands to get to address, e.g.:
- * tbnz w0, #26, ffff0000083cd190 <security_file_permission+0xd0>
- */
- if (c++ != NULL) {
- ops->target.addr = strtoull(c, NULL, 16);
- if (!ops->target.addr) {
- c = strchr(c, ',');
- c = validate_comma(c, ops);
- if (c++ != NULL)
- ops->target.addr = strtoull(c, NULL, 16);
- }
- } else {
- ops->target.addr = strtoull(ops->raw, NULL, 16);
- }
- target.addr = map__objdump_2mem(map, ops->target.addr);
- start = map->unmap_ip(map, sym->start),
- end = map->unmap_ip(map, sym->end);
- ops->target.outside = target.addr < start || target.addr > end;
- /*
- * FIXME: things like this in _cpp_lex_token (gcc's cc1 program):
- cpp_named_operator2name@@Base+0xa72
- * Point to a place that is after the cpp_named_operator2name
- * boundaries, i.e. in the ELF symbol table for cc1
- * cpp_named_operator2name is marked as being 32-bytes long, but it in
- * fact is much larger than that, so we seem to need a symbols__find()
- * routine that looks for >= current->start and < next_symbol->start,
- * possibly just for C++ objects?
- *
- * For now lets just make some progress by marking jumps to outside the
- * current function as call like.
- *
- * Actual navigation will come next, with further understanding of how
- * the symbol searching and disassembly should be done.
- */
- if (maps__find_ams(ms->maps, &target) == 0 &&
- map__rip_2objdump(target.ms.map, map->map_ip(target.ms.map, target.addr)) == ops->target.addr)
- ops->target.sym = target.ms.sym;
- if (!ops->target.outside) {
- ops->target.offset = target.addr - start;
- ops->target.offset_avail = true;
- } else {
- ops->target.offset_avail = false;
- }
- return 0;
- }
- static int jump__scnprintf(struct ins *ins, char *bf, size_t size,
- struct ins_operands *ops, int max_ins_name)
- {
- const char *c;
- if (!ops->target.addr || ops->target.offset < 0)
- return ins__raw_scnprintf(ins, bf, size, ops, max_ins_name);
- if (ops->target.outside && ops->target.sym != NULL)
- return scnprintf(bf, size, "%-*s %s", max_ins_name, ins->name, ops->target.sym->name);
- c = strchr(ops->raw, ',');
- c = validate_comma(c, ops);
- if (c != NULL) {
- const char *c2 = strchr(c + 1, ',');
- c2 = validate_comma(c2, ops);
- /* check for 3-op insn */
- if (c2 != NULL)
- c = c2;
- c++;
- /* mirror arch objdump's space-after-comma style */
- if (*c == ' ')
- c++;
- }
- return scnprintf(bf, size, "%-*s %.*s%" PRIx64, max_ins_name,
- ins->name, c ? c - ops->raw : 0, ops->raw,
- ops->target.offset);
- }
- static struct ins_ops jump_ops = {
- .parse = jump__parse,
- .scnprintf = jump__scnprintf,
- };
- bool ins__is_jump(const struct ins *ins)
- {
- return ins->ops == &jump_ops;
- }
- static int comment__symbol(char *raw, char *comment, u64 *addrp, char **namep)
- {
- char *endptr, *name, *t;
- if (strstr(raw, "(%rip)") == NULL)
- return 0;
- *addrp = strtoull(comment, &endptr, 16);
- if (endptr == comment)
- return 0;
- name = strchr(endptr, '<');
- if (name == NULL)
- return -1;
- name++;
- t = strchr(name, '>');
- if (t == NULL)
- return 0;
- *t = '\0';
- *namep = strdup(name);
- *t = '>';
- return 0;
- }
- static int lock__parse(struct arch *arch, struct ins_operands *ops, struct map_symbol *ms)
- {
- ops->locked.ops = zalloc(sizeof(*ops->locked.ops));
- if (ops->locked.ops == NULL)
- return 0;
- if (disasm_line__parse(ops->raw, &ops->locked.ins.name, &ops->locked.ops->raw) < 0)
- goto out_free_ops;
- ops->locked.ins.ops = ins__find(arch, ops->locked.ins.name);
- if (ops->locked.ins.ops == NULL)
- goto out_free_ops;
- if (ops->locked.ins.ops->parse &&
- ops->locked.ins.ops->parse(arch, ops->locked.ops, ms) < 0)
- goto out_free_ops;
- return 0;
- out_free_ops:
- zfree(&ops->locked.ops);
- return 0;
- }
- static int lock__scnprintf(struct ins *ins, char *bf, size_t size,
- struct ins_operands *ops, int max_ins_name)
- {
- int printed;
- if (ops->locked.ins.ops == NULL)
- return ins__raw_scnprintf(ins, bf, size, ops, max_ins_name);
- printed = scnprintf(bf, size, "%-*s ", max_ins_name, ins->name);
- return printed + ins__scnprintf(&ops->locked.ins, bf + printed,
- size - printed, ops->locked.ops, max_ins_name);
- }
- static void lock__delete(struct ins_operands *ops)
- {
- struct ins *ins = &ops->locked.ins;
- if (ins->ops && ins->ops->free)
- ins->ops->free(ops->locked.ops);
- else
- ins__delete(ops->locked.ops);
- zfree(&ops->locked.ops);
- zfree(&ops->target.raw);
- zfree(&ops->target.name);
- }
- static struct ins_ops lock_ops = {
- .free = lock__delete,
- .parse = lock__parse,
- .scnprintf = lock__scnprintf,
- };
- static int mov__parse(struct arch *arch, struct ins_operands *ops, struct map_symbol *ms __maybe_unused)
- {
- char *s = strchr(ops->raw, ','), *target, *comment, prev;
- if (s == NULL)
- return -1;
- *s = '\0';
- ops->source.raw = strdup(ops->raw);
- *s = ',';
- if (ops->source.raw == NULL)
- return -1;
- target = ++s;
- comment = strchr(s, arch->objdump.comment_char);
- if (comment != NULL)
- s = comment - 1;
- else
- s = strchr(s, '\0') - 1;
- while (s > target && isspace(s[0]))
- --s;
- s++;
- prev = *s;
- *s = '\0';
- ops->target.raw = strdup(target);
- *s = prev;
- if (ops->target.raw == NULL)
- goto out_free_source;
- if (comment == NULL)
- return 0;
- comment = skip_spaces(comment);
- comment__symbol(ops->source.raw, comment + 1, &ops->source.addr, &ops->source.name);
- comment__symbol(ops->target.raw, comment + 1, &ops->target.addr, &ops->target.name);
- return 0;
- out_free_source:
- zfree(&ops->source.raw);
- return -1;
- }
- static int mov__scnprintf(struct ins *ins, char *bf, size_t size,
- struct ins_operands *ops, int max_ins_name)
- {
- return scnprintf(bf, size, "%-*s %s,%s", max_ins_name, ins->name,
- ops->source.name ?: ops->source.raw,
- ops->target.name ?: ops->target.raw);
- }
- static struct ins_ops mov_ops = {
- .parse = mov__parse,
- .scnprintf = mov__scnprintf,
- };
- static int dec__parse(struct arch *arch __maybe_unused, struct ins_operands *ops, struct map_symbol *ms __maybe_unused)
- {
- char *target, *comment, *s, prev;
- target = s = ops->raw;
- while (s[0] != '\0' && !isspace(s[0]))
- ++s;
- prev = *s;
- *s = '\0';
- ops->target.raw = strdup(target);
- *s = prev;
- if (ops->target.raw == NULL)
- return -1;
- comment = strchr(s, arch->objdump.comment_char);
- if (comment == NULL)
- return 0;
- comment = skip_spaces(comment);
- comment__symbol(ops->target.raw, comment + 1, &ops->target.addr, &ops->target.name);
- return 0;
- }
- static int dec__scnprintf(struct ins *ins, char *bf, size_t size,
- struct ins_operands *ops, int max_ins_name)
- {
- return scnprintf(bf, size, "%-*s %s", max_ins_name, ins->name,
- ops->target.name ?: ops->target.raw);
- }
- static struct ins_ops dec_ops = {
- .parse = dec__parse,
- .scnprintf = dec__scnprintf,
- };
- static int nop__scnprintf(struct ins *ins __maybe_unused, char *bf, size_t size,
- struct ins_operands *ops __maybe_unused, int max_ins_name)
- {
- return scnprintf(bf, size, "%-*s", max_ins_name, "nop");
- }
- static struct ins_ops nop_ops = {
- .scnprintf = nop__scnprintf,
- };
- static struct ins_ops ret_ops = {
- .scnprintf = ins__raw_scnprintf,
- };
- bool ins__is_ret(const struct ins *ins)
- {
- return ins->ops == &ret_ops;
- }
- bool ins__is_lock(const struct ins *ins)
- {
- return ins->ops == &lock_ops;
- }
- static int ins__key_cmp(const void *name, const void *insp)
- {
- const struct ins *ins = insp;
- return strcmp(name, ins->name);
- }
- static int ins__cmp(const void *a, const void *b)
- {
- const struct ins *ia = a;
- const struct ins *ib = b;
- return strcmp(ia->name, ib->name);
- }
- static void ins__sort(struct arch *arch)
- {
- const int nmemb = arch->nr_instructions;
- qsort(arch->instructions, nmemb, sizeof(struct ins), ins__cmp);
- }
- static struct ins_ops *__ins__find(struct arch *arch, const char *name)
- {
- struct ins *ins;
- const int nmemb = arch->nr_instructions;
- if (!arch->sorted_instructions) {
- ins__sort(arch);
- arch->sorted_instructions = true;
- }
- ins = bsearch(name, arch->instructions, nmemb, sizeof(struct ins), ins__key_cmp);
- return ins ? ins->ops : NULL;
- }
- static struct ins_ops *ins__find(struct arch *arch, const char *name)
- {
- struct ins_ops *ops = __ins__find(arch, name);
- if (!ops && arch->associate_instruction_ops)
- ops = arch->associate_instruction_ops(arch, name);
- return ops;
- }
- static int arch__key_cmp(const void *name, const void *archp)
- {
- const struct arch *arch = archp;
- return strcmp(name, arch->name);
- }
- static int arch__cmp(const void *a, const void *b)
- {
- const struct arch *aa = a;
- const struct arch *ab = b;
- return strcmp(aa->name, ab->name);
- }
- static void arch__sort(void)
- {
- const int nmemb = ARRAY_SIZE(architectures);
- qsort(architectures, nmemb, sizeof(struct arch), arch__cmp);
- }
- static struct arch *arch__find(const char *name)
- {
- const int nmemb = ARRAY_SIZE(architectures);
- static bool sorted;
- if (!sorted) {
- arch__sort();
- sorted = true;
- }
- return bsearch(name, architectures, nmemb, sizeof(struct arch), arch__key_cmp);
- }
- static struct annotated_source *annotated_source__new(void)
- {
- struct annotated_source *src = zalloc(sizeof(*src));
- if (src != NULL)
- INIT_LIST_HEAD(&src->source);
- return src;
- }
- static __maybe_unused void annotated_source__delete(struct annotated_source *src)
- {
- if (src == NULL)
- return;
- zfree(&src->histograms);
- zfree(&src->cycles_hist);
- free(src);
- }
- static int annotated_source__alloc_histograms(struct annotated_source *src,
- size_t size, int nr_hists)
- {
- size_t sizeof_sym_hist;
- /*
- * Add buffer of one element for zero length symbol.
- * When sample is taken from first instruction of
- * zero length symbol, perf still resolves it and
- * shows symbol name in perf report and allows to
- * annotate it.
- */
- if (size == 0)
- size = 1;
- /* Check for overflow when calculating sizeof_sym_hist */
- if (size > (SIZE_MAX - sizeof(struct sym_hist)) / sizeof(struct sym_hist_entry))
- return -1;
- sizeof_sym_hist = (sizeof(struct sym_hist) + size * sizeof(struct sym_hist_entry));
- /* Check for overflow in zalloc argument */
- if (sizeof_sym_hist > SIZE_MAX / nr_hists)
- return -1;
- src->sizeof_sym_hist = sizeof_sym_hist;
- src->nr_histograms = nr_hists;
- src->histograms = calloc(nr_hists, sizeof_sym_hist) ;
- return src->histograms ? 0 : -1;
- }
- /* The cycles histogram is lazily allocated. */
- static int symbol__alloc_hist_cycles(struct symbol *sym)
- {
- struct annotation *notes = symbol__annotation(sym);
- const size_t size = symbol__size(sym);
- notes->src->cycles_hist = calloc(size, sizeof(struct cyc_hist));
- if (notes->src->cycles_hist == NULL)
- return -1;
- return 0;
- }
- void symbol__annotate_zero_histograms(struct symbol *sym)
- {
- struct annotation *notes = symbol__annotation(sym);
- pthread_mutex_lock(¬es->lock);
- if (notes->src != NULL) {
- memset(notes->src->histograms, 0,
- notes->src->nr_histograms * notes->src->sizeof_sym_hist);
- if (notes->src->cycles_hist)
- memset(notes->src->cycles_hist, 0,
- symbol__size(sym) * sizeof(struct cyc_hist));
- }
- pthread_mutex_unlock(¬es->lock);
- }
- static int __symbol__account_cycles(struct cyc_hist *ch,
- u64 start,
- unsigned offset, unsigned cycles,
- unsigned have_start)
- {
- /*
- * For now we can only account one basic block per
- * final jump. But multiple could be overlapping.
- * Always account the longest one. So when
- * a shorter one has been already seen throw it away.
- *
- * We separately always account the full cycles.
- */
- ch[offset].num_aggr++;
- ch[offset].cycles_aggr += cycles;
- if (cycles > ch[offset].cycles_max)
- ch[offset].cycles_max = cycles;
- if (ch[offset].cycles_min) {
- if (cycles && cycles < ch[offset].cycles_min)
- ch[offset].cycles_min = cycles;
- } else
- ch[offset].cycles_min = cycles;
- if (!have_start && ch[offset].have_start)
- return 0;
- if (ch[offset].num) {
- if (have_start && (!ch[offset].have_start ||
- ch[offset].start > start)) {
- ch[offset].have_start = 0;
- ch[offset].cycles = 0;
- ch[offset].num = 0;
- if (ch[offset].reset < 0xffff)
- ch[offset].reset++;
- } else if (have_start &&
- ch[offset].start < start)
- return 0;
- }
- if (ch[offset].num < NUM_SPARKS)
- ch[offset].cycles_spark[ch[offset].num] = cycles;
- ch[offset].have_start = have_start;
- ch[offset].start = start;
- ch[offset].cycles += cycles;
- ch[offset].num++;
- return 0;
- }
- static int __symbol__inc_addr_samples(struct map_symbol *ms,
- struct annotated_source *src, int evidx, u64 addr,
- struct perf_sample *sample)
- {
- struct symbol *sym = ms->sym;
- unsigned offset;
- struct sym_hist *h;
- pr_debug3("%s: addr=%#" PRIx64 "\n", __func__, ms->map->unmap_ip(ms->map, addr));
- if ((addr < sym->start || addr >= sym->end) &&
- (addr != sym->end || sym->start != sym->end)) {
- pr_debug("%s(%d): ERANGE! sym->name=%s, start=%#" PRIx64 ", addr=%#" PRIx64 ", end=%#" PRIx64 "\n",
- __func__, __LINE__, sym->name, sym->start, addr, sym->end);
- return -ERANGE;
- }
- offset = addr - sym->start;
- h = annotated_source__histogram(src, evidx);
- if (h == NULL) {
- pr_debug("%s(%d): ENOMEM! sym->name=%s, start=%#" PRIx64 ", addr=%#" PRIx64 ", end=%#" PRIx64 ", func: %d\n",
- __func__, __LINE__, sym->name, sym->start, addr, sym->end, sym->type == STT_FUNC);
- return -ENOMEM;
- }
- h->nr_samples++;
- h->addr[offset].nr_samples++;
- h->period += sample->period;
- h->addr[offset].period += sample->period;
- pr_debug3("%#" PRIx64 " %s: period++ [addr: %#" PRIx64 ", %#" PRIx64
- ", evidx=%d] => nr_samples: %" PRIu64 ", period: %" PRIu64 "\n",
- sym->start, sym->name, addr, addr - sym->start, evidx,
- h->addr[offset].nr_samples, h->addr[offset].period);
- return 0;
- }
- static struct cyc_hist *symbol__cycles_hist(struct symbol *sym)
- {
- struct annotation *notes = symbol__annotation(sym);
- if (notes->src == NULL) {
- notes->src = annotated_source__new();
- if (notes->src == NULL)
- return NULL;
- goto alloc_cycles_hist;
- }
- if (!notes->src->cycles_hist) {
- alloc_cycles_hist:
- symbol__alloc_hist_cycles(sym);
- }
- return notes->src->cycles_hist;
- }
- struct annotated_source *symbol__hists(struct symbol *sym, int nr_hists)
- {
- struct annotation *notes = symbol__annotation(sym);
- if (notes->src == NULL) {
- notes->src = annotated_source__new();
- if (notes->src == NULL)
- return NULL;
- goto alloc_histograms;
- }
- if (notes->src->histograms == NULL) {
- alloc_histograms:
- annotated_source__alloc_histograms(notes->src, symbol__size(sym),
- nr_hists);
- }
- return notes->src;
- }
- static int symbol__inc_addr_samples(struct map_symbol *ms,
- struct evsel *evsel, u64 addr,
- struct perf_sample *sample)
- {
- struct symbol *sym = ms->sym;
- struct annotated_source *src;
- if (sym == NULL)
- return 0;
- src = symbol__hists(sym, evsel->evlist->core.nr_entries);
- return src ? __symbol__inc_addr_samples(ms, src, evsel->idx, addr, sample) : 0;
- }
- static int symbol__account_cycles(u64 addr, u64 start,
- struct symbol *sym, unsigned cycles)
- {
- struct cyc_hist *cycles_hist;
- unsigned offset;
- if (sym == NULL)
- return 0;
- cycles_hist = symbol__cycles_hist(sym);
- if (cycles_hist == NULL)
- return -ENOMEM;
- if (addr < sym->start || addr >= sym->end)
- return -ERANGE;
- if (start) {
- if (start < sym->start || start >= sym->end)
- return -ERANGE;
- if (start >= addr)
- start = 0;
- }
- offset = addr - sym->start;
- return __symbol__account_cycles(cycles_hist,
- start ? start - sym->start : 0,
- offset, cycles,
- !!start);
- }
- int addr_map_symbol__account_cycles(struct addr_map_symbol *ams,
- struct addr_map_symbol *start,
- unsigned cycles)
- {
- u64 saddr = 0;
- int err;
- if (!cycles)
- return 0;
- /*
- * Only set start when IPC can be computed. We can only
- * compute it when the basic block is completely in a single
- * function.
- * Special case the case when the jump is elsewhere, but
- * it starts on the function start.
- */
- if (start &&
- (start->ms.sym == ams->ms.sym ||
- (ams->ms.sym &&
- start->addr == ams->ms.sym->start + ams->ms.map->start)))
- saddr = start->al_addr;
- if (saddr == 0)
- pr_debug2("BB with bad start: addr %"PRIx64" start %"PRIx64" sym %"PRIx64" saddr %"PRIx64"\n",
- ams->addr,
- start ? start->addr : 0,
- ams->ms.sym ? ams->ms.sym->start + ams->ms.map->start : 0,
- saddr);
- err = symbol__account_cycles(ams->al_addr, saddr, ams->ms.sym, cycles);
- if (err)
- pr_debug2("account_cycles failed %d\n", err);
- return err;
- }
- static unsigned annotation__count_insn(struct annotation *notes, u64 start, u64 end)
- {
- unsigned n_insn = 0;
- u64 offset;
- for (offset = start; offset <= end; offset++) {
- if (notes->offsets[offset])
- n_insn++;
- }
- return n_insn;
- }
- static void annotation__count_and_fill(struct annotation *notes, u64 start, u64 end, struct cyc_hist *ch)
- {
- unsigned n_insn;
- unsigned int cover_insn = 0;
- u64 offset;
- n_insn = annotation__count_insn(notes, start, end);
- if (n_insn && ch->num && ch->cycles) {
- float ipc = n_insn / ((double)ch->cycles / (double)ch->num);
- /* Hide data when there are too many overlaps. */
- if (ch->reset >= 0x7fff)
- return;
- for (offset = start; offset <= end; offset++) {
- struct annotation_line *al = notes->offsets[offset];
- if (al && al->ipc == 0.0) {
- al->ipc = ipc;
- cover_insn++;
- }
- }
- if (cover_insn) {
- notes->hit_cycles += ch->cycles;
- notes->hit_insn += n_insn * ch->num;
- notes->cover_insn += cover_insn;
- }
- }
- }
- void annotation__compute_ipc(struct annotation *notes, size_t size)
- {
- s64 offset;
- if (!notes->src || !notes->src->cycles_hist)
- return;
- notes->total_insn = annotation__count_insn(notes, 0, size - 1);
- notes->hit_cycles = 0;
- notes->hit_insn = 0;
- notes->cover_insn = 0;
- pthread_mutex_lock(¬es->lock);
- for (offset = size - 1; offset >= 0; --offset) {
- struct cyc_hist *ch;
- ch = ¬es->src->cycles_hist[offset];
- if (ch && ch->cycles) {
- struct annotation_line *al;
- if (ch->have_start)
- annotation__count_and_fill(notes, ch->start, offset, ch);
- al = notes->offsets[offset];
- if (al && ch->num_aggr) {
- al->cycles = ch->cycles_aggr / ch->num_aggr;
- al->cycles_max = ch->cycles_max;
- al->cycles_min = ch->cycles_min;
- }
- notes->have_cycles = true;
- }
- }
- pthread_mutex_unlock(¬es->lock);
- }
- int addr_map_symbol__inc_samples(struct addr_map_symbol *ams, struct perf_sample *sample,
- struct evsel *evsel)
- {
- return symbol__inc_addr_samples(&ams->ms, evsel, ams->al_addr, sample);
- }
- int hist_entry__inc_addr_samples(struct hist_entry *he, struct perf_sample *sample,
- struct evsel *evsel, u64 ip)
- {
- return symbol__inc_addr_samples(&he->ms, evsel, ip, sample);
- }
- static void disasm_line__init_ins(struct disasm_line *dl, struct arch *arch, struct map_symbol *ms)
- {
- dl->ins.ops = ins__find(arch, dl->ins.name);
- if (!dl->ins.ops)
- return;
- if (dl->ins.ops->parse && dl->ins.ops->parse(arch, &dl->ops, ms) < 0)
- dl->ins.ops = NULL;
- }
- static int disasm_line__parse(char *line, const char **namep, char **rawp)
- {
- char tmp, *name = skip_spaces(line);
- if (name[0] == '\0')
- return -1;
- *rawp = name + 1;
- while ((*rawp)[0] != '\0' && !isspace((*rawp)[0]))
- ++*rawp;
- tmp = (*rawp)[0];
- (*rawp)[0] = '\0';
- *namep = strdup(name);
- if (*namep == NULL)
- goto out;
- (*rawp)[0] = tmp;
- *rawp = strim(*rawp);
- return 0;
- out:
- return -1;
- }
- struct annotate_args {
- struct arch *arch;
- struct map_symbol ms;
- struct evsel *evsel;
- struct annotation_options *options;
- s64 offset;
- char *line;
- int line_nr;
- };
- static void annotation_line__init(struct annotation_line *al,
- struct annotate_args *args,
- int nr)
- {
- al->offset = args->offset;
- al->line = strdup(args->line);
- al->line_nr = args->line_nr;
- al->data_nr = nr;
- }
- static void annotation_line__exit(struct annotation_line *al)
- {
- free_srcline(al->path);
- zfree(&al->line);
- }
- static size_t disasm_line_size(int nr)
- {
- struct annotation_line *al;
- return (sizeof(struct disasm_line) + (sizeof(al->data[0]) * nr));
- }
- /*
- * Allocating the disasm annotation line data with
- * following structure:
- *
- * -------------------------------------------
- * struct disasm_line | struct annotation_line
- * -------------------------------------------
- *
- * We have 'struct annotation_line' member as last member
- * of 'struct disasm_line' to have an easy access.
- */
- static struct disasm_line *disasm_line__new(struct annotate_args *args)
- {
- struct disasm_line *dl = NULL;
- int nr = 1;
- if (perf_evsel__is_group_event(args->evsel))
- nr = args->evsel->core.nr_members;
- dl = zalloc(disasm_line_size(nr));
- if (!dl)
- return NULL;
- annotation_line__init(&dl->al, args, nr);
- if (dl->al.line == NULL)
- goto out_delete;
- if (args->offset != -1) {
- if (disasm_line__parse(dl->al.line, &dl->ins.name, &dl->ops.raw) < 0)
- goto out_free_line;
- disasm_line__init_ins(dl, args->arch, &args->ms);
- }
- return dl;
- out_free_line:
- zfree(&dl->al.line);
- out_delete:
- free(dl);
- return NULL;
- }
- void disasm_line__free(struct disasm_line *dl)
- {
- if (dl->ins.ops && dl->ins.ops->free)
- dl->ins.ops->free(&dl->ops);
- else
- ins__delete(&dl->ops);
- zfree(&dl->ins.name);
- annotation_line__exit(&dl->al);
- free(dl);
- }
- int disasm_line__scnprintf(struct disasm_line *dl, char *bf, size_t size, bool raw, int max_ins_name)
- {
- if (raw || !dl->ins.ops)
- return scnprintf(bf, size, "%-*s %s", max_ins_name, dl->ins.name, dl->ops.raw);
- return ins__scnprintf(&dl->ins, bf, size, &dl->ops, max_ins_name);
- }
- static void annotation_line__add(struct annotation_line *al, struct list_head *head)
- {
- list_add_tail(&al->node, head);
- }
- struct annotation_line *
- annotation_line__next(struct annotation_line *pos, struct list_head *head)
- {
- list_for_each_entry_continue(pos, head, node)
- if (pos->offset >= 0)
- return pos;
- return NULL;
- }
- static const char *annotate__address_color(struct block_range *br)
- {
- double cov = block_range__coverage(br);
- if (cov >= 0) {
- /* mark red for >75% coverage */
- if (cov > 0.75)
- return PERF_COLOR_RED;
- /* mark dull for <1% coverage */
- if (cov < 0.01)
- return PERF_COLOR_NORMAL;
- }
- return PERF_COLOR_MAGENTA;
- }
- static const char *annotate__asm_color(struct block_range *br)
- {
- double cov = block_range__coverage(br);
- if (cov >= 0) {
- /* mark dull for <1% coverage */
- if (cov < 0.01)
- return PERF_COLOR_NORMAL;
- }
- return PERF_COLOR_BLUE;
- }
- static void annotate__branch_printf(struct block_range *br, u64 addr)
- {
- bool emit_comment = true;
- if (!br)
- return;
- #if 1
- if (br->is_target && br->start == addr) {
- struct block_range *branch = br;
- double p;
- /*
- * Find matching branch to our target.
- */
- while (!branch->is_branch)
- branch = block_range__next(branch);
- p = 100 *(double)br->entry / branch->coverage;
- if (p > 0.1) {
- if (emit_comment) {
- emit_comment = false;
- printf("\t#");
- }
- /*
- * The percentage of coverage joined at this target in relation
- * to the next branch.
- */
- printf(" +%.2f%%", p);
- }
- }
- #endif
- if (br->is_branch && br->end == addr) {
- double p = 100*(double)br->taken / br->coverage;
- if (p > 0.1) {
- if (emit_comment) {
- emit_comment = false;
- printf("\t#");
- }
- /*
- * The percentage of coverage leaving at this branch, and
- * its prediction ratio.
- */
- printf(" -%.2f%% (p:%.2f%%)", p, 100*(double)br->pred / br->taken);
- }
- }
- }
- static int disasm_line__print(struct disasm_line *dl, u64 start, int addr_fmt_width)
- {
- s64 offset = dl->al.offset;
- const u64 addr = start + offset;
- struct block_range *br;
- br = block_range__find(addr);
- color_fprintf(stdout, annotate__address_color(br), " %*" PRIx64 ":", addr_fmt_width, addr);
- color_fprintf(stdout, annotate__asm_color(br), "%s", dl->al.line);
- annotate__branch_printf(br, addr);
- return 0;
- }
- static int
- annotation_line__print(struct annotation_line *al, struct symbol *sym, u64 start,
- struct evsel *evsel, u64 len, int min_pcnt, int printed,
- int max_lines, struct annotation_line *queue, int addr_fmt_width,
- int percent_type)
- {
- struct disasm_line *dl = container_of(al, struct disasm_line, al);
- static const char *prev_line;
- static const char *prev_color;
- if (al->offset != -1) {
- double max_percent = 0.0;
- int i, nr_percent = 1;
- const char *color;
- struct annotation *notes = symbol__annotation(sym);
- for (i = 0; i < al->data_nr; i++) {
- double percent;
- percent = annotation_data__percent(&al->data[i],
- percent_type);
- if (percent > max_percent)
- max_percent = percent;
- }
- if (al->data_nr > nr_percent)
- nr_percent = al->data_nr;
- if (max_percent < min_pcnt)
- return -1;
- if (max_lines && printed >= max_lines)
- return 1;
- if (queue != NULL) {
- list_for_each_entry_from(queue, ¬es->src->source, node) {
- if (queue == al)
- break;
- annotation_line__print(queue, sym, start, evsel, len,
- 0, 0, 1, NULL, addr_fmt_width,
- percent_type);
- }
- }
- color = get_percent_color(max_percent);
- /*
- * Also color the filename and line if needed, with
- * the same color than the percentage. Don't print it
- * twice for close colored addr with the same filename:line
- */
- if (al->path) {
- if (!prev_line || strcmp(prev_line, al->path)
- || color != prev_color) {
- color_fprintf(stdout, color, " %s", al->path);
- prev_line = al->path;
- prev_color = color;
- }
- }
- for (i = 0; i < nr_percent; i++) {
- struct annotation_data *data = &al->data[i];
- double percent;
- percent = annotation_data__percent(data, percent_type);
- color = get_percent_color(percent);
- if (symbol_conf.show_total_period)
- color_fprintf(stdout, color, " %11" PRIu64,
- data->he.period);
- else if (symbol_conf.show_nr_samples)
- color_fprintf(stdout, color, " %7" PRIu64,
- data->he.nr_samples);
- else
- color_fprintf(stdout, color, " %7.2f", percent);
- }
- printf(" : ");
- disasm_line__print(dl, start, addr_fmt_width);
- printf("\n");
- } else if (max_lines && printed >= max_lines)
- return 1;
- else {
- int width = symbol_conf.show_total_period ? 12 : 8;
- if (queue)
- return -1;
- if (perf_evsel__is_group_event(evsel))
- width *= evsel->core.nr_members;
- if (!*al->line)
- printf(" %*s:\n", width, " ");
- else
- printf(" %*s: %*s %s\n", width, " ", addr_fmt_width, " ", al->line);
- }
- return 0;
- }
- /*
- * symbol__parse_objdump_line() parses objdump output (with -d --no-show-raw)
- * which looks like following
- *
- * 0000000000415500 <_init>:
- * 415500: sub $0x8,%rsp
- * 415504: mov 0x2f5ad5(%rip),%rax # 70afe0 <_DYNAMIC+0x2f8>
- * 41550b: test %rax,%rax
- * 41550e: je 415515 <_init+0x15>
- * 415510: callq 416e70 <__gmon_start__@plt>
- * 415515: add $0x8,%rsp
- * 415519: retq
- *
- * it will be parsed and saved into struct disasm_line as
- * <offset> <name> <ops.raw>
- *
- * The offset will be a relative offset from the start of the symbol and -1
- * means that it's not a disassembly line so should be treated differently.
- * The ops.raw part will be parsed further according to type of the instruction.
- */
- static int symbol__parse_objdump_line(struct symbol *sym,
- struct annotate_args *args,
- char *parsed_line, int *line_nr)
- {
- struct map *map = args->ms.map;
- struct annotation *notes = symbol__annotation(sym);
- struct disasm_line *dl;
- char *tmp;
- s64 line_ip, offset = -1;
- regmatch_t match[2];
- /* /filename:linenr ? Save line number and ignore. */
- if (regexec(&file_lineno, parsed_line, 2, match, 0) == 0) {
- *line_nr = atoi(parsed_line + match[1].rm_so);
- return 0;
- }
- /* Process hex address followed by ':'. */
- line_ip = strtoull(parsed_line, &tmp, 16);
- if (parsed_line != tmp && tmp[0] == ':' && tmp[1] != '\0') {
- u64 start = map__rip_2objdump(map, sym->start),
- end = map__rip_2objdump(map, sym->end);
- offset = line_ip - start;
- if ((u64)line_ip < start || (u64)line_ip >= end)
- offset = -1;
- else
- parsed_line = tmp + 1;
- }
- args->offset = offset;
- args->line = parsed_line;
- args->line_nr = *line_nr;
- args->ms.sym = sym;
- dl = disasm_line__new(args);
- (*line_nr)++;
- if (dl == NULL)
- return -1;
- if (!disasm_line__has_local_offset(dl)) {
- dl->ops.target.offset = dl->ops.target.addr -
- map__rip_2objdump(map, sym->start);
- dl->ops.target.offset_avail = true;
- }
- /* kcore has no symbols, so add the call target symbol */
- if (dl->ins.ops && ins__is_call(&dl->ins) && !dl->ops.target.sym) {
- struct addr_map_symbol target = {
- .addr = dl->ops.target.addr,
- .ms = { .map = map, },
- };
- if (!maps__find_ams(args->ms.maps, &target) &&
- target.ms.sym->start == target.al_addr)
- dl->ops.target.sym = target.ms.sym;
- }
- annotation_line__add(&dl->al, ¬es->src->source);
- return 0;
- }
- static __attribute__((constructor)) void symbol__init_regexpr(void)
- {
- regcomp(&file_lineno, "^/[^:]+:([0-9]+)", REG_EXTENDED);
- }
- static void delete_last_nop(struct symbol *sym)
- {
- struct annotation *notes = symbol__annotation(sym);
- struct list_head *list = ¬es->src->source;
- struct disasm_line *dl;
- while (!list_empty(list)) {
- dl = list_entry(list->prev, struct disasm_line, al.node);
- if (dl->ins.ops) {
- if (dl->ins.ops != &nop_ops)
- return;
- } else {
- if (!strstr(dl->al.line, " nop ") &&
- !strstr(dl->al.line, " nopl ") &&
- !strstr(dl->al.line, " nopw "))
- return;
- }
- list_del_init(&dl->al.node);
- disasm_line__free(dl);
- }
- }
- int symbol__strerror_disassemble(struct map_symbol *ms, int errnum, char *buf, size_t buflen)
- {
- struct dso *dso = ms->map->dso;
- BUG_ON(buflen == 0);
- if (errnum >= 0) {
- str_error_r(errnum, buf, buflen);
- return 0;
- }
- switch (errnum) {
- case SYMBOL_ANNOTATE_ERRNO__NO_VMLINUX: {
- char bf[SBUILD_ID_SIZE + 15] = " with build id ";
- char *build_id_msg = NULL;
- if (dso->has_build_id) {
- build_id__sprintf(dso->build_id,
- sizeof(dso->build_id), bf + 15);
- build_id_msg = bf;
- }
- scnprintf(buf, buflen,
- "No vmlinux file%s\nwas found in the path.\n\n"
- "Note that annotation using /proc/kcore requires CAP_SYS_RAWIO capability.\n\n"
- "Please use:\n\n"
- " perf buildid-cache -vu vmlinux\n\n"
- "or:\n\n"
- " --vmlinux vmlinux\n", build_id_msg ?: "");
- }
- break;
- case SYMBOL_ANNOTATE_ERRNO__NO_LIBOPCODES_FOR_BPF:
- scnprintf(buf, buflen, "Please link with binutils's libopcode to enable BPF annotation");
- break;
- case SYMBOL_ANNOTATE_ERRNO__ARCH_INIT_REGEXP:
- scnprintf(buf, buflen, "Problems with arch specific instruction name regular expressions.");
- break;
- case SYMBOL_ANNOTATE_ERRNO__ARCH_INIT_CPUID_PARSING:
- scnprintf(buf, buflen, "Problems while parsing the CPUID in the arch specific initialization.");
- break;
- case SYMBOL_ANNOTATE_ERRNO__BPF_INVALID_FILE:
- scnprintf(buf, buflen, "Invalid BPF file: %s.", dso->long_name);
- break;
- case SYMBOL_ANNOTATE_ERRNO__BPF_MISSING_BTF:
- scnprintf(buf, buflen, "The %s BPF file has no BTF section, compile with -g or use pahole -J.",
- dso->long_name);
- break;
- default:
- scnprintf(buf, buflen, "Internal error: Invalid %d error code\n", errnum);
- break;
- }
- return 0;
- }
- static int dso__disassemble_filename(struct dso *dso, char *filename, size_t filename_size)
- {
- char linkname[PATH_MAX];
- char *build_id_filename;
- char *build_id_path = NULL;
- char *pos;
- if (dso->symtab_type == DSO_BINARY_TYPE__KALLSYMS &&
- !dso__is_kcore(dso))
- return SYMBOL_ANNOTATE_ERRNO__NO_VMLINUX;
- build_id_filename = dso__build_id_filename(dso, NULL, 0, false);
- if (build_id_filename) {
- __symbol__join_symfs(filename, filename_size, build_id_filename);
- free(build_id_filename);
- } else {
- if (dso->has_build_id)
- return ENOMEM;
- goto fallback;
- }
- build_id_path = strdup(filename);
- if (!build_id_path)
- return ENOMEM;
- /*
- * old style build-id cache has name of XX/XXXXXXX.. while
- * new style has XX/XXXXXXX../{elf,kallsyms,vdso}.
- * extract the build-id part of dirname in the new style only.
- */
- pos = strrchr(build_id_path, '/');
- if (pos && strlen(pos) < SBUILD_ID_SIZE - 2)
- dirname(build_id_path);
- if (dso__is_kcore(dso) ||
- readlink(build_id_path, linkname, sizeof(linkname)) < 0 ||
- strstr(linkname, DSO__NAME_KALLSYMS) ||
- access(filename, R_OK)) {
- fallback:
- /*
- * If we don't have build-ids or the build-id file isn't in the
- * cache, or is just a kallsyms file, well, lets hope that this
- * DSO is the same as when 'perf record' ran.
- */
- __symbol__join_symfs(filename, filename_size, dso->long_name);
- }
- free(build_id_path);
- return 0;
- }
- #if defined(HAVE_LIBBFD_SUPPORT) && defined(HAVE_LIBBPF_SUPPORT)
- #define PACKAGE "perf"
- #include <bfd.h>
- #include <dis-asm.h>
- static int symbol__disassemble_bpf(struct symbol *sym,
- struct annotate_args *args)
- {
- struct annotation *notes = symbol__annotation(sym);
- struct annotation_options *opts = args->options;
- struct bpf_prog_info_linear *info_linear;
- struct bpf_prog_linfo *prog_linfo = NULL;
- struct bpf_prog_info_node *info_node;
- int len = sym->end - sym->start;
- disassembler_ftype disassemble;
- struct map *map = args->ms.map;
- struct disassemble_info info;
- struct dso *dso = map->dso;
- int pc = 0, count, sub_id;
- struct btf *btf = NULL;
- char tpath[PATH_MAX];
- size_t buf_size;
- int nr_skip = 0;
- char *buf;
- bfd *bfdf;
- int ret;
- FILE *s;
- if (dso->binary_type != DSO_BINARY_TYPE__BPF_PROG_INFO)
- return SYMBOL_ANNOTATE_ERRNO__BPF_INVALID_FILE;
- pr_debug("%s: handling sym %s addr %" PRIx64 " len %" PRIx64 "\n", __func__,
- sym->name, sym->start, sym->end - sym->start);
- memset(tpath, 0, sizeof(tpath));
- perf_exe(tpath, sizeof(tpath));
- bfdf = bfd_openr(tpath, NULL);
- assert(bfdf);
- assert(bfd_check_format(bfdf, bfd_object));
- s = open_memstream(&buf, &buf_size);
- if (!s) {
- ret = errno;
- goto out;
- }
- init_disassemble_info(&info, s,
- (fprintf_ftype) fprintf);
- info.arch = bfd_get_arch(bfdf);
- info.mach = bfd_get_mach(bfdf);
- info_node = perf_env__find_bpf_prog_info(dso->bpf_prog.env,
- dso->bpf_prog.id);
- if (!info_node) {
- ret = SYMBOL_ANNOTATE_ERRNO__BPF_MISSING_BTF;
- goto out;
- }
- info_linear = info_node->info_linear;
- sub_id = dso->bpf_prog.sub_id;
- info.buffer = (void *)(uintptr_t)(info_linear->info.jited_prog_insns);
- info.buffer_length = info_linear->info.jited_prog_len;
- if (info_linear->info.nr_line_info)
- prog_linfo = bpf_prog_linfo__new(&info_linear->info);
- if (info_linear->info.btf_id) {
- struct btf_node *node;
- node = perf_env__find_btf(dso->bpf_prog.env,
- info_linear->info.btf_id);
- if (node)
- btf = btf__new((__u8 *)(node->data),
- node->data_size);
- }
- disassemble_init_for_target(&info);
- #ifdef DISASM_FOUR_ARGS_SIGNATURE
- disassemble = disassembler(info.arch,
- bfd_big_endian(bfdf),
- info.mach,
- bfdf);
- #else
- disassemble = disassembler(bfdf);
- #endif
- assert(disassemble);
- fflush(s);
- do {
- const struct bpf_line_info *linfo = NULL;
- struct disasm_line *dl;
- size_t prev_buf_size;
- const char *srcline;
- u64 addr;
- addr = pc + ((u64 *)(uintptr_t)(info_linear->info.jited_ksyms))[sub_id];
- count = disassemble(pc, &info);
- if (prog_linfo)
- linfo = bpf_prog_linfo__lfind_addr_func(prog_linfo,
- addr, sub_id,
- nr_skip);
- if (linfo && btf) {
- srcline = btf__name_by_offset(btf, linfo->line_off);
- nr_skip++;
- } else
- srcline = NULL;
- fprintf(s, "\n");
- prev_buf_size = buf_size;
- fflush(s);
- if (!opts->hide_src_code && srcline) {
- args->offset = -1;
- args->line = strdup(srcline);
- args->line_nr = 0;
- args->ms.sym = sym;
- dl = disasm_line__new(args);
- if (dl) {
- annotation_line__add(&dl->al,
- ¬es->src->source);
- }
- }
- args->offset = pc;
- args->line = buf + prev_buf_size;
- args->line_nr = 0;
- args->ms.sym = sym;
- dl = disasm_line__new(args);
- if (dl)
- annotation_line__add(&dl->al, ¬es->src->source);
- pc += count;
- } while (count > 0 && pc < len);
- ret = 0;
- out:
- free(prog_linfo);
- free(btf);
- fclose(s);
- bfd_close(bfdf);
- return ret;
- }
- #else // defined(HAVE_LIBBFD_SUPPORT) && defined(HAVE_LIBBPF_SUPPORT)
- static int symbol__disassemble_bpf(struct symbol *sym __maybe_unused,
- struct annotate_args *args __maybe_unused)
- {
- return SYMBOL_ANNOTATE_ERRNO__NO_LIBOPCODES_FOR_BPF;
- }
- #endif // defined(HAVE_LIBBFD_SUPPORT) && defined(HAVE_LIBBPF_SUPPORT)
- /*
- * Possibly create a new version of line with tabs expanded. Returns the
- * existing or new line, storage is updated if a new line is allocated. If
- * allocation fails then NULL is returned.
- */
- static char *expand_tabs(char *line, char **storage, size_t *storage_len)
- {
- size_t i, src, dst, len, new_storage_len, num_tabs;
- char *new_line;
- size_t line_len = strlen(line);
- for (num_tabs = 0, i = 0; i < line_len; i++)
- if (line[i] == '\t')
- num_tabs++;
- if (num_tabs == 0)
- return line;
- /*
- * Space for the line and '\0', less the leading and trailing
- * spaces. Each tab may introduce 7 additional spaces.
- */
- new_storage_len = line_len + 1 + (num_tabs * 7);
- new_line = malloc(new_storage_len);
- if (new_line == NULL) {
- pr_err("Failure allocating memory for tab expansion\n");
- return NULL;
- }
- /*
- * Copy regions starting at src and expand tabs. If there are two
- * adjacent tabs then 'src == i', the memcpy is of size 0 and the spaces
- * are inserted.
- */
- for (i = 0, src = 0, dst = 0; i < line_len && num_tabs; i++) {
- if (line[i] == '\t') {
- len = i - src;
- memcpy(&new_line[dst], &line[src], len);
- dst += len;
- new_line[dst++] = ' ';
- while (dst % 8 != 0)
- new_line[dst++] = ' ';
- src = i + 1;
- num_tabs--;
- }
- }
- /* Expand the last region. */
- len = line_len - src;
- memcpy(&new_line[dst], &line[src], len);
- dst += len;
- new_line[dst] = '\0';
- free(*storage);
- *storage = new_line;
- *storage_len = new_storage_len;
- return new_line;
- }
- static int symbol__disassemble(struct symbol *sym, struct annotate_args *args)
- {
- struct annotation_options *opts = args->options;
- struct map *map = args->ms.map;
- struct dso *dso = map->dso;
- char *command;
- FILE *file;
- char symfs_filename[PATH_MAX];
- struct kcore_extract kce;
- bool delete_extract = false;
- bool decomp = false;
- int lineno = 0;
- int nline;
- char *line;
- size_t line_len;
- const char *objdump_argv[] = {
- "/bin/sh",
- "-c",
- NULL, /* Will be the objdump command to run. */
- "--",
- NULL, /* Will be the symfs path. */
- NULL,
- };
- struct child_process objdump_process;
- int err = dso__disassemble_filename(dso, symfs_filename, sizeof(symfs_filename));
- if (err)
- return err;
- pr_debug("%s: filename=%s, sym=%s, start=%#" PRIx64 ", end=%#" PRIx64 "\n", __func__,
- symfs_filename, sym->name, map->unmap_ip(map, sym->start),
- map->unmap_ip(map, sym->end));
- pr_debug("annotating [%p] %30s : [%p] %30s\n",
- dso, dso->long_name, sym, sym->name);
- if (dso->binary_type == DSO_BINARY_TYPE__BPF_PROG_INFO) {
- return symbol__disassemble_bpf(sym, args);
- } else if (dso__is_kcore(dso)) {
- kce.kcore_filename = symfs_filename;
- kce.addr = map__rip_2objdump(map, sym->start);
- kce.offs = sym->start;
- kce.len = sym->end - sym->start;
- if (!kcore_extract__create(&kce)) {
- delete_extract = true;
- strlcpy(symfs_filename, kce.extract_filename,
- sizeof(symfs_filename));
- }
- } else if (dso__needs_decompress(dso)) {
- char tmp[KMOD_DECOMP_LEN];
- if (dso__decompress_kmodule_path(dso, symfs_filename,
- tmp, sizeof(tmp)) < 0)
- return -1;
- decomp = true;
- strcpy(symfs_filename, tmp);
- }
- err = asprintf(&command,
- "%s %s%s --start-address=0x%016" PRIx64
- " --stop-address=0x%016" PRIx64
- " -l -d %s %s %s %c%s%c %s%s -C \"$1\"",
- opts->objdump_path ?: "objdump",
- opts->disassembler_style ? "-M " : "",
- opts->disassembler_style ?: "",
- map__rip_2objdump(map, sym->start),
- map__rip_2objdump(map, sym->end),
- opts->show_asm_raw ? "" : "--no-show-raw-insn",
- opts->annotate_src ? "-S" : "",
- opts->prefix ? "--prefix " : "",
- opts->prefix ? '"' : ' ',
- opts->prefix ?: "",
- opts->prefix ? '"' : ' ',
- opts->prefix_strip ? "--prefix-strip=" : "",
- opts->prefix_strip ?: "");
- if (err < 0) {
- pr_err("Failure allocating memory for the command to run\n");
- goto out_remove_tmp;
- }
- pr_debug("Executing: %s\n", command);
- objdump_argv[2] = command;
- objdump_argv[4] = symfs_filename;
- /* Create a pipe to read from for stdout */
- memset(&objdump_process, 0, sizeof(objdump_process));
- objdump_process.argv = objdump_argv;
- objdump_process.out = -1;
- if (start_command(&objdump_process)) {
- pr_err("Failure starting to run %s\n", command);
- err = -1;
- goto out_free_command;
- }
- file = fdopen(objdump_process.out, "r");
- if (!file) {
- pr_err("Failure creating FILE stream for %s\n", command);
- /*
- * If we were using debug info should retry with
- * original binary.
- */
- err = -1;
- goto out_close_stdout;
- }
- /* Storage for getline. */
- line = NULL;
- line_len = 0;
- nline = 0;
- while (!feof(file)) {
- const char *match;
- char *expanded_line;
- if (getline(&line, &line_len, file) < 0 || !line)
- break;
- /* Skip lines containing "filename:" */
- match = strstr(line, symfs_filename);
- if (match && match[strlen(symfs_filename)] == ':')
- continue;
- expanded_line = strim(line);
- expanded_line = expand_tabs(expanded_line…
Large files files are truncated, but you can click here to view the full file