[PATCH] perf, tools: Support srccode output

From: Andi Kleen
Date: Fri May 05 2017 - 19:00:42 EST


From: Andi Kleen <ak@xxxxxxxxxxxxxxx>

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 <ak@xxxxxxxxxxxxxxx>
---
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