diff options
Diffstat (limited to 'tools/perf/util/annotate.c')
-rw-r--r-- | tools/perf/util/annotate.c | 347 |
1 files changed, 240 insertions, 107 deletions
diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c index 7e5a1e8874ce..aeb5a441bd74 100644 --- a/tools/perf/util/annotate.c +++ b/tools/perf/util/annotate.c @@ -17,6 +17,7 @@ #include "debug.h" #include "annotate.h" #include "evsel.h" +#include "block-range.h" #include <regex.h> #include <pthread.h> #include <linux/bitops.h> @@ -53,7 +54,7 @@ int ins__scnprintf(struct ins *ins, char *bf, size_t size, return ins__raw_scnprintf(ins, bf, size, ops); } -static int call__parse(struct ins_operands *ops) +static int call__parse(struct ins_operands *ops, struct map *map) { char *endptr, *tok, *name; @@ -81,16 +82,16 @@ static int call__parse(struct ins_operands *ops) return ops->target.name == NULL ? -1 : 0; indirect_call: - tok = strchr(endptr, '('); - if (tok != NULL) { - ops->target.addr = 0; + tok = strchr(endptr, '*'); + if (tok == NULL) { + struct symbol *sym = map__find_symbol(map, map->map_ip(map, ops->target.addr)); + if (sym != NULL) + ops->target.name = strdup(sym->name); + else + ops->target.addr = 0; return 0; } - tok = strchr(endptr, '*'); - if (tok == NULL) - return -1; - ops->target.addr = strtoull(tok + 1, NULL, 16); return 0; } @@ -117,7 +118,7 @@ bool ins__is_call(const struct ins *ins) return ins->ops == &call_ops; } -static int jump__parse(struct ins_operands *ops) +static int jump__parse(struct ins_operands *ops, struct map *map __maybe_unused) { const char *s = strchr(ops->raw, '+'); @@ -172,7 +173,7 @@ static int comment__symbol(char *raw, char *comment, u64 *addrp, char **namep) return 0; } -static int lock__parse(struct ins_operands *ops) +static int lock__parse(struct ins_operands *ops, struct map *map) { char *name; @@ -193,7 +194,7 @@ static int lock__parse(struct ins_operands *ops) return 0; if (ops->locked.ins->ops->parse && - ops->locked.ins->ops->parse(ops->locked.ops) < 0) + ops->locked.ins->ops->parse(ops->locked.ops, map) < 0) goto out_free_ops; return 0; @@ -236,7 +237,7 @@ static struct ins_ops lock_ops = { .scnprintf = lock__scnprintf, }; -static int mov__parse(struct ins_operands *ops) +static int mov__parse(struct ins_operands *ops, struct map *map __maybe_unused) { char *s = strchr(ops->raw, ','), *target, *comment, prev; @@ -303,7 +304,7 @@ static struct ins_ops mov_ops = { .scnprintf = mov__scnprintf, }; -static int dec__parse(struct ins_operands *ops) +static int dec__parse(struct ins_operands *ops, struct map *map __maybe_unused) { char *target, *comment, *s, prev; @@ -354,6 +355,15 @@ 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; +} + static struct ins instructions[] = { { .name = "add", .ops = &mov_ops, }, { .name = "addl", .ops = &mov_ops, }, @@ -444,6 +454,7 @@ static struct ins instructions[] = { { .name = "xadd", .ops = &mov_ops, }, { .name = "xbeginl", .ops = &jump_ops, }, { .name = "xbeginq", .ops = &jump_ops, }, + { .name = "retq", .ops = &ret_ops, }, }; static int ins__key_cmp(const void *name, const void *insp) @@ -481,13 +492,6 @@ static struct ins *ins__find(const char *name) return bsearch(name, instructions, nmemb, sizeof(struct ins), ins__key_cmp); } -int symbol__annotate_init(struct map *map __maybe_unused, struct symbol *sym) -{ - struct annotation *notes = symbol__annotation(sym); - pthread_mutex_init(¬es->lock, NULL); - return 0; -} - int symbol__alloc_hist(struct symbol *sym) { struct annotation *notes = symbol__annotation(sym); @@ -705,7 +709,7 @@ int hist_entry__inc_addr_samples(struct hist_entry *he, int evidx, u64 ip) return symbol__inc_addr_samples(he->ms.sym, he->ms.map, evidx, ip); } -static void disasm_line__init_ins(struct disasm_line *dl) +static void disasm_line__init_ins(struct disasm_line *dl, struct map *map) { dl->ins = ins__find(dl->name); @@ -715,7 +719,7 @@ static void disasm_line__init_ins(struct disasm_line *dl) if (!dl->ins->ops) return; - if (dl->ins->ops->parse && dl->ins->ops->parse(&dl->ops) < 0) + if (dl->ins->ops->parse && dl->ins->ops->parse(&dl->ops, map) < 0) dl->ins = NULL; } @@ -757,7 +761,8 @@ out_free_name: } static struct disasm_line *disasm_line__new(s64 offset, char *line, - size_t privsize, int line_nr) + size_t privsize, int line_nr, + struct map *map) { struct disasm_line *dl = zalloc(sizeof(*dl) + privsize); @@ -772,7 +777,7 @@ static struct disasm_line *disasm_line__new(s64 offset, char *line, if (disasm_line__parse(dl->line, &dl->name, &dl->ops.raw) < 0) goto out_free_line; - disasm_line__init_ins(dl); + disasm_line__init_ins(dl, map); } } @@ -856,6 +861,89 @@ double disasm__calc_percent(struct annotation *notes, int evidx, s64 offset, return percent; } +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, struct symbol *sym, u64 start, struct perf_evsel *evsel, u64 len, int min_pcnt, int printed, int max_lines, struct disasm_line *queue) @@ -875,6 +963,7 @@ static int disasm_line__print(struct disasm_line *dl, struct symbol *sym, u64 st s64 offset = dl->offset; const u64 addr = start + offset; struct disasm_line *next; + struct block_range *br; next = disasm__get_next_ip_line(¬es->src->source, dl); @@ -944,8 +1033,12 @@ static int disasm_line__print(struct disasm_line *dl, struct symbol *sym, u64 st } printf(" : "); - color_fprintf(stdout, PERF_COLOR_MAGENTA, " %" PRIx64 ":", addr); - color_fprintf(stdout, PERF_COLOR_BLUE, "%s\n", dl->line); + + br = block_range__find(addr); + color_fprintf(stdout, annotate__address_color(br), " %" PRIx64 ":", addr); + color_fprintf(stdout, annotate__asm_color(br), "%s", dl->line); + annotate__branch_printf(br, addr); + printf("\n"); if (ppercents != &percent) free(ppercents); @@ -1056,7 +1149,7 @@ static int symbol__parse_objdump_line(struct symbol *sym, struct map *map, parsed_line = tmp2 + 1; } - dl = disasm_line__new(offset, parsed_line, privsize, *line_nr); + dl = disasm_line__new(offset, parsed_line, privsize, *line_nr, map); free(line); (*line_nr)++; @@ -1074,7 +1167,7 @@ static int symbol__parse_objdump_line(struct symbol *sym, struct map *map, .addr = dl->ops.target.addr, }; - if (!map_groups__find_ams(&target, NULL) && + if (!map_groups__find_ams(&target) && target.sym->start == target.al_addr) dl->ops.target.name = strdup(target.sym->name); } @@ -1113,75 +1206,99 @@ static void delete_last_nop(struct symbol *sym) } } -int symbol__annotate(struct symbol *sym, struct map *map, size_t privsize) +int symbol__strerror_disassemble(struct symbol *sym __maybe_unused, struct map *map, + int errnum, char *buf, size_t buflen) { struct dso *dso = map->dso; - char *filename = dso__build_id_filename(dso, NULL, 0); - bool free_filename = true; - char command[PATH_MAX * 2]; - FILE *file; - int err = 0; - char symfs_filename[PATH_MAX]; - struct kcore_extract kce; - bool delete_extract = false; - int lineno = 0; - int nline; - if (filename) - symbol__join_symfs(symfs_filename, filename); + 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 (filename == NULL) { if (dso->has_build_id) { - pr_err("Can't annotate %s: not enough memory\n", - sym->name); - return -ENOMEM; + 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; + 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; + + 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); + 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; - } else if (dso__is_kcore(dso)) { - goto fallback; - } else if (readlink(symfs_filename, command, sizeof(command)) < 0 || - strstr(command, DSO__NAME_KALLSYMS) || - access(symfs_filename, R_OK)) { - free(filename); + } + + if (dso__is_kcore(dso) || + readlink(filename, 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. */ - filename = (char *)dso->long_name; - symbol__join_symfs(symfs_filename, filename); - free_filename = false; + __symbol__join_symfs(filename, filename_size, dso->long_name); } - if (dso->symtab_type == DSO_BINARY_TYPE__KALLSYMS && - !dso__is_kcore(dso)) { - char bf[SBUILD_ID_SIZE + 15] = " with build id "; - char *build_id_msg = NULL; + return 0; +} - if (dso->annotate_warned) - goto out_free_filename; +int symbol__disassemble(struct symbol *sym, struct map *map, size_t privsize) +{ + struct dso *dso = map->dso; + char command[PATH_MAX * 2]; + FILE *file; + char symfs_filename[PATH_MAX]; + struct kcore_extract kce; + bool delete_extract = false; + int stdout_fd[2]; + int lineno = 0; + int nline; + pid_t pid; + int err = dso__disassemble_filename(dso, symfs_filename, sizeof(symfs_filename)); - if (dso->has_build_id) { - build_id__sprintf(dso->build_id, - sizeof(dso->build_id), bf + 15); - build_id_msg = bf; - } - err = -ENOENT; - dso->annotate_warned = 1; - pr_err("Can't annotate %s:\n\n" - "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", - sym->name, build_id_msg ?: ""); - goto out_free_filename; - } + if (err) + return err; pr_debug("%s: filename=%s, sym=%s, start=%#" PRIx64 ", end=%#" PRIx64 "\n", __func__, - filename, sym->name, map->unmap_ip(map, sym->start), + symfs_filename, sym->name, map->unmap_ip(map, sym->start), map->unmap_ip(map, sym->end)); pr_debug("annotating [%p] %30s : [%p] %30s\n", @@ -1196,11 +1313,6 @@ fallback: delete_extract = true; strlcpy(symfs_filename, kce.extract_filename, sizeof(symfs_filename)); - if (free_filename) { - free(filename); - free_filename = false; - } - filename = symfs_filename; } } else if (dso__needs_decompress(dso)) { char tmp[PATH_MAX]; @@ -1209,14 +1321,14 @@ fallback: bool ret; if (kmod_path__parse_ext(&m, symfs_filename)) - goto out_free_filename; + goto out; snprintf(tmp, PATH_MAX, "/tmp/perf-kmod-XXXXXX"); fd = mkstemp(tmp); if (fd < 0) { free(m.ext); - goto out_free_filename; + goto out; } ret = decompress_to_file(m.ext, symfs_filename, fd); @@ -1228,7 +1340,7 @@ fallback: close(fd); if (!ret) - goto out_free_filename; + goto out; strcpy(symfs_filename, tmp); } @@ -1244,13 +1356,36 @@ fallback: map__rip_2objdump(map, sym->end), symbol_conf.annotate_asm_raw ? "" : "--no-show-raw", symbol_conf.annotate_src ? "-S" : "", - symfs_filename, filename); + symfs_filename, symfs_filename); pr_debug("Executing: %s\n", command); - file = popen(command, "r"); + err = -1; + if (pipe(stdout_fd) < 0) { + pr_err("Failure creating the pipe to run %s\n", command); + goto out_remove_tmp; + } + + pid = fork(); + if (pid < 0) { + pr_err("Failure forking to run %s\n", command); + goto out_close_stdout; + } + + if (pid == 0) { + close(stdout_fd[0]); + dup2(stdout_fd[1], 1); + close(stdout_fd[1]); + execl("/bin/sh", "sh", "-c", command, NULL); + perror(command); + exit(-1); + } + + close(stdout_fd[1]); + + file = fdopen(stdout_fd[0], "r"); if (!file) { - pr_err("Failure running %s\n", command); + pr_err("Failure creating FILE stream for %s\n", command); /* * If we were using debug info should retry with * original binary. @@ -1276,17 +1411,22 @@ fallback: if (dso__is_kcore(dso)) delete_last_nop(sym); - pclose(file); - + fclose(file); + err = 0; out_remove_tmp: + close(stdout_fd[0]); + if (dso__needs_decompress(dso)) unlink(symfs_filename); -out_free_filename: + if (delete_extract) kcore_extract__delete(&kce); - if (free_filename) - free(filename); +out: return err; + +out_close_stdout: + close(stdout_fd[1]); + goto out_remove_tmp; } static void insert_source_line(struct rb_root *root, struct source_line *src_line) @@ -1512,13 +1652,14 @@ int symbol__annotate_printf(struct symbol *sym, struct map *map, const char *d_filename; const char *evsel_name = perf_evsel__name(evsel); struct annotation *notes = symbol__annotation(sym); + struct sym_hist *h = annotation__histogram(notes, evsel->idx); struct disasm_line *pos, *queue = NULL; u64 start = map__rip_2objdump(map, sym->start); int printed = 2, queue_len = 0; int more = 0; u64 len; int width = 8; - int namelen, evsel_name_len, graph_dotted_len; + int graph_dotted_len; filename = strdup(dso->long_name); if (!filename) @@ -1530,17 +1671,14 @@ int symbol__annotate_printf(struct symbol *sym, struct map *map, d_filename = basename(filename); len = symbol__size(sym); - namelen = strlen(d_filename); - evsel_name_len = strlen(evsel_name); if (perf_evsel__is_group_event(evsel)) width *= evsel->nr_members; - printf(" %-*.*s| Source code & Disassembly of %s for %s\n", - width, width, "Percent", d_filename, evsel_name); + graph_dotted_len = printf(" %-*.*s| Source code & Disassembly of %s for %s (%" PRIu64 " samples)\n", + width, width, "Percent", d_filename, evsel_name, h->sum); - graph_dotted_len = width + namelen + evsel_name_len; - printf("-%-*.*s-----------------------------------------\n", + printf("%-*.*s----\n", graph_dotted_len, graph_dotted_len, graph_dotted_line); if (verbose) @@ -1655,7 +1793,7 @@ int symbol__tty_annotate(struct symbol *sym, struct map *map, struct rb_root source_line = RB_ROOT; u64 len; - if (symbol__annotate(sym, map, 0) < 0) + if (symbol__disassemble(sym, map, 0) < 0) return -1; len = symbol__size(sym); @@ -1676,11 +1814,6 @@ int symbol__tty_annotate(struct symbol *sym, struct map *map, return 0; } -int hist_entry__annotate(struct hist_entry *he, size_t privsize) -{ - return symbol__annotate(he->ms.sym, he->ms.map, privsize); -} - bool ui__has_annotation(void) { return use_browser == 1 && perf_hpp_list.sym; |