From: Andi Kleen <a...@linux.intel.com> When looking at PT or brstackinsn traces with perf script it can be very useful to see the source code. This adds a simple facility to print them with perf script, if the information is available through dwarf
% perf record ... % perf script -F insn,ip,sym,srccode ... 4004c6 main 5 for (i = 0; i < 10000000; i++) 4004cd main 5 for (i = 0; i < 10000000; i++) 4004c6 main 5 for (i = 0; i < 10000000; i++) 4004cd main 5 for (i = 0; i < 10000000; i++) 4004cd main 5 for (i = 0; i < 10000000; i++) 4004cd main 5 for (i = 0; i < 10000000; i++) 4004cd main 5 for (i = 0; i < 10000000; i++) 4004cd main 5 for (i = 0; i < 10000000; i++) 4004b3 main 6 v++; % perf record -b ... % perf script -F insn,ip,sym,srccode,brstackinsn ... main+22: 0000000000400543 insn: e8 ca ff ff ff # PRED |18 f1(); f1: 0000000000400512 insn: 55 |10 { 0000000000400513 insn: 48 89 e5 0000000000400516 insn: b8 00 00 00 00 |11 f2(); 000000000040051b insn: e8 d6 ff ff ff # PRED f2: 00000000004004f6 insn: 55 |5 { 00000000004004f7 insn: 48 89 e5 00000000004004fa insn: 8b 05 2c 0b 20 00 |6 c = a / b; 0000000000400500 insn: 8b 0d 2a 0b 20 00 0000000000400506 insn: 99 0000000000400507 insn: f7 f9 0000000000400509 insn: 89 05 29 0b 20 00 000000000040050f insn: 90 |7 } 0000000000400510 insn: 5d 0000000000400511 insn: c3 # PRED f1+14: 0000000000400520 insn: b8 00 00 00 00 |12 f2(); 0000000000400525 insn: e8 cc ff ff ff # PRED f2: 00000000004004f6 insn: 55 |5 { 00000000004004f7 insn: 48 89 e5 00000000004004fa insn: 8b 05 2c 0b 20 00 |6 c = a / b; Not supported for callchains currently, would need some layout changes there. v2: Limit number of cached source files Delete bogus comment. Directly use the line number / file from the bfd lookup code Add prefix to source to make it easier to handle for scripts Avoid redundant listing of lines Fix include guard in srccode.h Use a hash table for the source lookup. v3: Remove prefix. Remove unwind_inlines. Signed-off-by: Andi Kleen <a...@linux.intel.com> --- tools/perf/Documentation/perf-script.txt | 3 +- tools/perf/builtin-script.c | 40 ++++++- tools/perf/util/Build | 1 + tools/perf/util/evsel_fprintf.c | 1 + tools/perf/util/map.c | 49 ++++++++ tools/perf/util/map.h | 16 +++ tools/perf/util/srccode.c | 186 +++++++++++++++++++++++++++++++ tools/perf/util/srccode.h | 7 ++ tools/perf/util/srcline.c | 28 +++++ tools/perf/util/srcline.h | 1 + tools/perf/util/thread.c | 2 + tools/perf/util/thread.h | 2 + 12 files changed, 334 insertions(+), 2 deletions(-) create mode 100644 tools/perf/util/srccode.c create mode 100644 tools/perf/util/srccode.h diff --git a/tools/perf/Documentation/perf-script.txt b/tools/perf/Documentation/perf-script.txt index cb0eda3925e6..a8fdc798e259 100644 --- a/tools/perf/Documentation/perf-script.txt +++ b/tools/perf/Documentation/perf-script.txt @@ -117,7 +117,8 @@ OPTIONS Comma separated list of fields to print. Options are: comm, tid, pid, time, cpu, event, trace, ip, sym, dso, addr, symoff, srcline, period, iregs, brstack, brstacksym, flags, bpf-output, brstackinsn, - callindent, insn, insnlen. Field list can be prepended with the type, trace, sw or hw, + callindent, insn, insnlen, srccode. + Field list can be prepended with the type, trace, sw or hw, to indicate to which event type the field list applies. e.g., -F sw:comm,tid,time,ip,sym and -F trace:time,cpu,trace diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c index d05aec491cff..2094fd0a9f7c 100644 --- a/tools/perf/builtin-script.c +++ b/tools/perf/builtin-script.c @@ -85,6 +85,7 @@ enum perf_output_field { PERF_OUTPUT_INSN = 1U << 21, PERF_OUTPUT_INSNLEN = 1U << 22, PERF_OUTPUT_BRSTACKINSN = 1U << 23, + PERF_OUTPUT_SRCCODE = 1U << 24, }; struct output_option { @@ -115,6 +116,7 @@ struct output_option { {.str = "insn", .field = PERF_OUTPUT_INSN}, {.str = "insnlen", .field = PERF_OUTPUT_INSNLEN}, {.str = "brstackinsn", .field = PERF_OUTPUT_BRSTACKINSN}, + {.str = "srccode", .field = PERF_OUTPUT_SRCCODE}, }; /* default set to maintain compatibility with current format */ @@ -304,7 +306,7 @@ static int perf_evsel__check_attr(struct perf_evsel *evsel, "to DSO.\n"); return -EINVAL; } - if (PRINT_FIELD(SRCLINE) && !PRINT_FIELD(IP)) { + if ((PRINT_FIELD(SRCLINE) || PRINT_FIELD(SRCCODE)) && !PRINT_FIELD(IP)) { pr_err("Display of source line number requested but sample IP is not\n" "selected. Hence, no address to lookup the source line number.\n"); return -EINVAL; @@ -633,6 +635,20 @@ static int grab_bb(u8 *buffer, u64 start, u64 end, return len; } +static void print_srccode(struct thread *thread, u8 cpumode, uint64_t addr) +{ + struct addr_location al; + memset(&al, 0, sizeof(al)); + + thread__find_addr_map(thread, cpumode, MAP__FUNCTION, addr, &al); + if (!al.map) + thread__find_addr_map(thread, cpumode, MAP__VARIABLE, + addr, &al); + if (al.map && map__fprintf_srccode(al.map, al.addr, stdout, + &thread->srccode_state)) + printf("\n"); +} + static void print_jump(uint64_t ip, struct branch_entry *en, struct perf_insn *x, u8 *inbuf, int len, int insn) @@ -723,6 +739,8 @@ static void print_sample_brstackinsn(struct perf_sample *sample, br->entries[nr - 1].from, &lastsym, attr); print_jump(br->entries[nr - 1].from, &br->entries[nr - 1], &x, buffer, len, 0); + if (PRINT_FIELD(SRCCODE)) + print_srccode(thread, x.cpumode, br->entries[nr - 1].from); } /* Print all blocks */ @@ -751,12 +769,16 @@ static void print_sample_brstackinsn(struct perf_sample *sample, print_ip_sym(thread, x.cpumode, x.cpu, ip, &lastsym, attr); if (ip == end) { print_jump(ip, &br->entries[i], &x, buffer + off, len - off, insn); + if (PRINT_FIELD(SRCCODE)) + print_srccode(thread, x.cpumode, ip); break; } else { printf("\t%016" PRIx64 "\t%s\n", ip, dump_insn(&x, ip, buffer + off, len - off, &ilen)); if (ilen == 0) break; + if (PRINT_FIELD(SRCCODE)) + print_srccode(thread, x.cpumode, ip); insn++; } } @@ -787,6 +809,8 @@ static void print_sample_brstackinsn(struct perf_sample *sample, printf("\t%016" PRIx64 "\t%s\n", sample->ip, dump_insn(&x, sample->ip, buffer, len, NULL)); + if (PRINT_FIELD(SRCCODE)) + print_srccode(thread, x.cpumode, sample->ip); return; } for (off = 0; off <= end - start; off += ilen) { @@ -794,6 +818,8 @@ static void print_sample_brstackinsn(struct perf_sample *sample, dump_insn(&x, start + off, buffer + off, len - off, &ilen)); if (ilen == 0) break; + if (PRINT_FIELD(SRCCODE)) + print_srccode(thread, x.cpumode, start + off); } } @@ -947,6 +973,12 @@ static void print_sample_bts(struct perf_sample *sample, print_insn(sample, attr, thread, machine); printf("\n"); + + if (PRINT_FIELD(SRCCODE)) { + if (map__fprintf_srccode(al->map, al->addr, stdout, + &thread->srccode_state)) + printf("\n"); + } } static struct { @@ -1195,6 +1227,12 @@ static void process_event(struct perf_script *script, print_sample_bpf_output(sample); print_insn(sample, attr, thread, machine); printf("\n"); + + if (PRINT_FIELD(SRCCODE)) { + if (map__fprintf_srccode(al->map, al->addr, stdout, + &thread->srccode_state)) + printf("\n"); + } } static struct scripting_ops *scripting_ops; diff --git a/tools/perf/util/Build b/tools/perf/util/Build index 069583bdc670..c8b171a464ef 100644 --- a/tools/perf/util/Build +++ b/tools/perf/util/Build @@ -72,6 +72,7 @@ libperf-y += stat.o libperf-y += stat-shadow.o libperf-y += record.o libperf-y += srcline.o +libperf-y += srccode.o libperf-y += data.o libperf-y += tsc.o libperf-y += cloexec.o diff --git a/tools/perf/util/evsel_fprintf.c b/tools/perf/util/evsel_fprintf.c index e415aee6a245..e9bcd90c9cb5 100644 --- a/tools/perf/util/evsel_fprintf.c +++ b/tools/perf/util/evsel_fprintf.c @@ -168,6 +168,7 @@ int sample__fprintf_callchain(struct perf_sample *sample, int left_alignment, if (!print_oneline) printed += fprintf(fp, "\n"); + /* Add srccode here too? */ if (symbol_conf.bt_stop_list && node->sym && strlist__has_entry(symbol_conf.bt_stop_list, diff --git a/tools/perf/util/map.c b/tools/perf/util/map.c index ebfa5d92358a..4a7419f8a49e 100644 --- a/tools/perf/util/map.c +++ b/tools/perf/util/map.c @@ -17,6 +17,7 @@ #include <linux/string.h> #include "srcline.h" #include "unwind.h" +#include "srccode.h" static void __maps__insert(struct maps *maps, struct map *map); @@ -414,6 +415,54 @@ int map__fprintf_srcline(struct map *map, u64 addr, const char *prefix, return ret; } +int map__fprintf_srccode(struct map *map, u64 addr, + FILE *fp, + struct srccode_state *state) +{ + char *srcfile; + int ret = 0; + unsigned line; + int len; + char *srccode; + + if (!map || !map->dso) + return 0; + srcfile = get_srcline_split(map->dso, + map__rip_2objdump(map, addr), + &line); + if (!srcfile) + return 0; + + /* Avoid redundant printing */ + if (state && + state->srcfile && + !strcmp(state->srcfile, srcfile) && + state->line == line) { + free(srcfile); + return 0; + } + + srccode = find_sourceline(srcfile, line, &len); + if (!srccode) + goto out_free_line; + + ret = fprintf(fp, "|%-8d %.*s", line, len, srccode); + state->srcfile = srcfile; + state->line = line; + return ret; + +out_free_line: + free(srcfile); + return ret; +} + + +void srccode_state_free(struct srccode_state *state) +{ + zfree(&state->srcfile); + state->line = 0; +} + /** * map__rip_2objdump - convert symbol start address to objdump address. * @map: memory map diff --git a/tools/perf/util/map.h b/tools/perf/util/map.h index c8a5a644c0a9..3506acf8810e 100644 --- a/tools/perf/util/map.h +++ b/tools/perf/util/map.h @@ -170,6 +170,22 @@ size_t map__fprintf_dsoname(struct map *map, FILE *fp); int map__fprintf_srcline(struct map *map, u64 addr, const char *prefix, FILE *fp); +struct srccode_state { + char *srcfile; + unsigned line; +}; + +static inline void srccode_state_init(struct srccode_state *state) +{ + state->srcfile = NULL; + state->line = 0; +} + +void srccode_state_free(struct srccode_state *state); + +int map__fprintf_srccode(struct map *map, u64 addr, + FILE *fp, struct srccode_state *state); + int map__load(struct map *map); struct symbol *map__find_symbol(struct map *map, u64 addr); struct symbol *map__find_symbol_by_name(struct map *map, const char *name); diff --git a/tools/perf/util/srccode.c b/tools/perf/util/srccode.c new file mode 100644 index 000000000000..c22f6a3aa6ba --- /dev/null +++ b/tools/perf/util/srccode.c @@ -0,0 +1,186 @@ +/* + * Manage printing of source lines + * Copyright (c) 2017, Intel Corporation. + * Author: Andi Kleen + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ +#include "linux/list.h" +#include <stdlib.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/fcntl.h> +#include <unistd.h> +#include <assert.h> +#include <string.h> +#include "srccode.h" +#include "debug.h" +#include "util.h" + +#define MAXSRCCACHE (32*1024*1024) +#define MAXSRCFILES 64 +#define SRC_HTAB_SZ 64 + +struct srcfile { + struct hlist_node hash_nd; + struct list_head nd; + char *fn; + char **lines; + char *map; + unsigned numlines; + size_t maplen; +}; + +static struct hlist_head srcfile_htab[SRC_HTAB_SZ]; +static LIST_HEAD(srcfile_list); +static long map_total_sz; +static int num_srcfiles; + +static unsigned shash(unsigned char *s) +{ + unsigned h = 0; + while (*s) + h = 65599 * h + *s++; + return h ^ (h >> 16); +} + +static int countlines(char *map, int maplen) +{ + int numl; + char *end = map + maplen; + char *p = map; + + if (maplen == 0) + return 0; + numl = 0; + while (p < end && (p = memchr(p, '\n', end - p)) != NULL) { + numl++; + p++; + } + if (p < end) + numl++; + return numl; +} + +static void fill_lines(char **lines, int maxline, char *map, int maplen) +{ + int l; + char *end = map + maplen; + char *p = map; + + if (maplen == 0 || maxline == 0) + return; + l = 0; + lines[l++] = map; + while (p < end && (p = memchr(p, '\n', end - p)) != NULL) { + if (l >= maxline) + return; + lines[l++] = ++p; + } + if (p < end) + lines[l] = p; +} + +static void free_srcfile(struct srcfile *sf) +{ + list_del(&sf->nd); + hlist_del(&sf->hash_nd); + map_total_sz -= sf->maplen; + munmap(sf->map, sf->maplen); + free(sf->lines); + free(sf->fn); + free(sf); + num_srcfiles--; +} + +static struct srcfile *find_srcfile(char *fn) +{ + struct stat st; + struct srcfile *h; + int fd; + unsigned long sz; + unsigned hval = shash((unsigned char *)fn) % SRC_HTAB_SZ; + + hlist_for_each_entry (h, &srcfile_htab[hval], hash_nd) { + if (!strcmp(fn, h->fn)) { + /* Move to front */ + list_del(&h->nd); + list_add(&h->nd, &srcfile_list); + return h; + } + } + + /* Only prune if there is more than one entry */ + while ((num_srcfiles > MAXSRCFILES || map_total_sz > MAXSRCCACHE) && + srcfile_list.next != &srcfile_list) { + assert(!list_empty(&srcfile_list)); + h = list_entry(srcfile_list.prev, struct srcfile, nd); + free_srcfile(h); + } + + fd = open(fn, O_RDONLY); + if (fd < 0 || fstat(fd, &st) < 0) { + pr_debug("cannot open source file %s\n", fn); + return NULL; + } + + h = malloc(sizeof(struct srcfile)); + if (!h) + return NULL; + + h->fn = strdup(fn); + if (!h->fn) + goto out_h; + + h->maplen = st.st_size; + sz = (h->maplen + page_size - 1) & ~(page_size - 1); + h->map = mmap(NULL, sz, PROT_READ, MAP_SHARED, fd, 0); + close(fd); + if (h->map == (char *)-1) { + pr_debug("cannot mmap source file %s\n", fn); + goto out_fn; + } + h->numlines = countlines(h->map, h->maplen); + h->lines = calloc(h->numlines, sizeof(char *)); + if (!h->lines) + goto out_map; + fill_lines(h->lines, h->numlines, h->map, h->maplen); + list_add(&h->nd, &srcfile_list); + hlist_add_head(&h->hash_nd, &srcfile_htab[hval]); + map_total_sz += h->maplen; + num_srcfiles++; + return h; + +out_map: + munmap(h->map, sz); +out_fn: + free(h->fn); +out_h: + free(h); + return NULL; +} + +/* Result is not 0 terminated */ +char *find_sourceline(char *fn, unsigned line, int *lenp) +{ + char *l, *p; + struct srcfile *sf = find_srcfile(fn); + if (!sf) + return NULL; + line--; + if (line >= sf->numlines) + return NULL; + l = sf->lines[line]; + if (!l) + return NULL; + p = memchr(l, '\n', sf->map + sf->maplen - l); + *lenp = p - l; + return l; +} diff --git a/tools/perf/util/srccode.h b/tools/perf/util/srccode.h new file mode 100644 index 000000000000..e500a746d5f1 --- /dev/null +++ b/tools/perf/util/srccode.h @@ -0,0 +1,7 @@ +#ifndef SRCCODE_H +#define SRCCODE_H 1 + +/* Result is not 0 terminated */ +char *find_sourceline(char *fn, unsigned line, int *lenp); + +#endif diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c index df051a52393c..77a76bb7d121 100644 --- a/tools/perf/util/srcline.c +++ b/tools/perf/util/srcline.c @@ -479,6 +479,34 @@ char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym, 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) + goto out; + + dso_name = dso__name(dso); + if (dso_name == NULL) + goto out; + + if (!addr2line(dso_name, addr, &file, line, dso, true, NULL)) + goto out; + + dso->a2l_fails = 0; + return file; + +out: + if (dso->a2l_fails && ++dso->a2l_fails > A2L_FAIL_LIMIT) { + dso->has_srcline = 0; + dso__free_a2l(dso); + } + + return NULL; +} + void free_srcline(char *srcline) { if (srcline && strcmp(srcline, SRCLINE_UNKNOWN) != 0) diff --git a/tools/perf/util/srcline.h b/tools/perf/util/srcline.h index 7b52ba88676e..b518ff3f82ca 100644 --- a/tools/perf/util/srcline.h +++ b/tools/perf/util/srcline.h @@ -13,6 +13,7 @@ char *get_srcline(struct dso *dso, u64 addr, struct symbol *sym, char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym, bool show_sym, bool show_addr, bool unwind_inlines); void free_srcline(char *srcline); +char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line); #define SRCLINE_UNKNOWN ((char *) "??:0") diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c index 378c418ca0c1..404cabf252b0 100644 --- a/tools/perf/util/thread.c +++ b/tools/perf/util/thread.c @@ -59,6 +59,7 @@ struct thread *thread__new(pid_t pid, pid_t tid) list_add(&comm->list, &thread->comm_list); refcount_set(&thread->refcnt, 1); RB_CLEAR_NODE(&thread->rb_node); + srccode_state_init(&thread->srccode_state); } return thread; @@ -91,6 +92,7 @@ void thread__delete(struct thread *thread) comm__free(comm); } unwind__finish_access(thread); + srccode_state_free(&thread->srccode_state); free(thread); } diff --git a/tools/perf/util/thread.h b/tools/perf/util/thread.h index 4eb849e9098f..791c6d08508e 100644 --- a/tools/perf/util/thread.h +++ b/tools/perf/util/thread.h @@ -7,6 +7,7 @@ #include <unistd.h> #include <sys/types.h> #include "symbol.h" +#include "map.h" #include <strlist.h> #include <intlist.h> @@ -34,6 +35,7 @@ struct thread { void *priv; struct thread_stack *ts; + struct srccode_state srccode_state; #ifdef HAVE_LIBUNWIND_SUPPORT void *addr_space; struct unwind_libunwind_ops *unwind_libunwind_ops; -- 2.9.3