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 ... 4004c6 main main+39: 00000000004004cd insn: 7e e4 # PRED 5 for (i = 0; i < 10000000; i++) 00000000004004b3 insn: 8b 05 67 0b 20 00 6 v++; 00000000004004b9 insn: 83 c0 01 6 v++; 00000000004004bc insn: 89 05 5e 0b 20 00 6 v++; 00000000004004c2 insn: 83 45 fc 01 5 for (i = 0; i < 10000000; i++) 00000000004004c6 insn: 81 7d fc 7f 96 98 00 5 for (i = 0; i < 10000000; i++) 00000000004004cd insn: 7e e4 # PRED 5 for (i = 0; i < 10000000; i++) 00000000004004b3 insn: 8b 05 67 0b 20 00 6 v++; 00000000004004b9 insn: 83 c0 01 6 v++; 00000000004004bc insn: 89 05 5e 0b 20 00 6 v++; 00000000004004c2 insn: 83 45 fc 01 5 for (i = 0; i < 10000000; i++) 00000000004004c6 insn: 81 7d fc 7f 96 98 00 5 for (i = 0; i < 10000000; i++) 00000000004004cd insn: 7e e4 # PRED Not supported for callchains currently, would need some layout changes there. Signed-off-by: Andi Kleen <a...@linux.intel.com> --- tools/perf/Documentation/perf-script.txt | 3 +- tools/perf/builtin-script.c | 38 ++++++- tools/perf/util/Build | 1 + tools/perf/util/evsel_fprintf.c | 1 + tools/perf/util/map.c | 29 ++++++ tools/perf/util/map.h | 2 + tools/perf/util/srccode.c | 163 +++++++++++++++++++++++++++++++ tools/perf/util/srccode.h | 7 ++ 8 files changed, 242 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..7f078e39d474 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,19 @@ 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)) + printf("\n"); +} + static void print_jump(uint64_t ip, struct branch_entry *en, struct perf_insn *x, u8 *inbuf, int len, int insn) @@ -687,6 +702,7 @@ static void print_ip_sym(struct thread *thread, u8 cpumode, int cpu, if (PRINT_FIELD(SRCLINE)) map__fprintf_srcline(al.map, al.addr, "\t", stdout); putchar('\n'); + /* Could print srccode here too */ *lastsym = al.sym; } @@ -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,11 @@ 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)) + printf("\n"); + } } static struct { @@ -1195,6 +1226,11 @@ 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)) + 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..b682f18d2d0c 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,34 @@ int map__fprintf_srcline(struct map *map, u64 addr, const char *prefix, return ret; } +int map__fprintf_srccode(struct map *map, u64 addr, + const char *prefix, FILE *fp) +{ + char *srcline; + int ret = 0; + + if (map && map->dso) { + srcline_full_filename = true; + srcline = get_srcline(map->dso, + map__rip_2objdump(map, addr), NULL, + true, true); + if (srcline != SRCLINE_UNKNOWN) { + char srcfile[1024]; + int line, len; + char *srccode; + if (sscanf(srcline, "%1023[^:]:%d", srcfile, &line) + == 2 && + (srccode = find_sourceline(srcfile, line, &len)) + != NULL) + ret = fprintf(fp, "%s%-8d %.*s", + prefix, + line, len, srccode); + free_srcline(srcline); + } + } + return ret; +} + /** * 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..51c4bd9ced89 100644 --- a/tools/perf/util/map.h +++ b/tools/perf/util/map.h @@ -169,6 +169,8 @@ size_t map__fprintf(struct map *map, FILE *fp); size_t map__fprintf_dsoname(struct map *map, FILE *fp); int map__fprintf_srcline(struct map *map, u64 addr, const char *prefix, FILE *fp); +int map__fprintf_srccode(struct map *map, u64 addr, const char *prefix, + FILE *fp); int map__load(struct map *map); struct symbol *map__find_symbol(struct map *map, u64 addr); diff --git a/tools/perf/util/srccode.c b/tools/perf/util/srccode.c new file mode 100644 index 000000000000..8c15d3263d60 --- /dev/null +++ b/tools/perf/util/srccode.c @@ -0,0 +1,163 @@ +/* Manage printing source lines */ +#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" + +#define MAXSRCCACHE (32*1024*1024) + +struct srcfile { + struct list_head nd; + char *fn; + char **lines; + char *map; + unsigned numlines; + size_t maplen; +}; + +/* + * Could use a hash table, but for now assume move-to-front is good enough + * with spatial locality. + */ +static LIST_HEAD(srcfile_list); +static long map_total_sz; + +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); + map_total_sz -= sf->maplen; + munmap(sf->map, sf->maplen); + free(sf->lines); + free(sf->fn); + free(sf); +} + +static struct srcfile *find_srcfile(char *fn) +{ + struct stat st; + struct srcfile *h; + int fd; + unsigned long ps, sz; + bool first = true; + + list_for_each_entry (h, &srcfile_list, nd) { + if (!strcmp(fn, h->fn)) { + if (!first) { + /* Move to front */ + list_del(&h->nd); + list_add(&h->nd, &srcfile_list); + } + return h; + } + first = false; + } + + /* Only prune if there is more than one entry */ + while (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; + ps = sysconf(_SC_PAGE_SIZE); + sz = (h->maplen + ps - 1) & ~(ps - 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); + map_total_sz += h->maplen; + 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..868c4e2c8394 --- /dev/null +++ b/tools/perf/util/srccode.h @@ -0,0 +1,7 @@ +#ifndef SRCLINE_H +#define SRCLINE_H 1 + +/* Result is not 0 terminated */ +char *find_sourceline(char *fn, unsigned line, int *lenp); + +#endif -- 2.9.3