[PATCH 07/12] perf, tools, script: Resolve variable names for registers

From: Andi Kleen
Date: Mon Nov 27 2017 - 19:24:57 EST


From: Andi Kleen <ak@xxxxxxxxxxxxxxx>

Add a new iregvals output that uses the dwarf decoding code in
perf probe to resolve registers to names based on dwarf debuginfo.

This allows to trace the values of variables which are stored in registers,
and sampled by perf.

We also print the type.

This builds on top of Masami Hiramatsu's perf probe code, which
implements all the difficult dwarf magic.
But we use it for a 'reverse lookup' to figure out the variable names

We use the perf probe code to generate a list of variables
with their registers at the sample point, and then print
any variable for which we have a valid register.

Note when testing please compile the program with -O2 -g. Unoptimized
code stores variables generally on the stack, which we cannot
decode with this. -g is needed for the debug information

For example to track the first two arguments passed in
registers on x86-64:

% cat targ.c
% gcc -O2 -g -o targ targ.c
volatile c;

__attribute__((noinline)) f2(int a, int b)
{
c = a / b;
}

__attribute__((noinline)) f1(int a, int b)
{
f2(a, b);
f2(b, a);
}
main()
{
int i;
for (i = 0; i < 5000000; i++)
f1(1, 2);
}

% perf record -Idi,si ./targ
% perf script -F +iregvals
...
targ 8584 169763.761843: 2091795 cycles:ppp: 40041a main (targ)
targ 8584 169763.762520: 1913932 cycles:ppp: 400534 f1 (targ) { b = 0x2, int } { a = 0x1, int }
targ 8584 169763.763141: 1638913 cycles:ppp: 400523 f2 (targ) { b = 0x1, int } { a = 0x2, int }
targ 8584 169763.763672: 1516522 cycles:ppp: 400522 f2 (targ) { b = 0x1, int } { a = 0x2, int }
targ 8584 169763.764165: 1335501 cycles:ppp: 400523 f2 (targ) { b = 0x1, int } { a = 0x2, int }
targ 8584 169763.764598: 1253289 cycles:ppp: 400522 f2 (targ) { b = 0x2, int } { a = 0x1, int }
targ 8584 169763.765005: 1135131 cycles:ppp: 400534 f1 (targ) { b = 0x2, int } { a = 0x1, int }
targ 8584 169763.765373: 1080325 cycles:ppp: 400522 f2 (targ) { b = 0x2, int } { a = 0x1, int }
targ 8584 169763.765724: 1036999 cycles:ppp: 400522 f2 (targ) { b = 0x1, int } { a = 0x2, int }
targ 8584 169763.766061: 971213 cycles:ppp: 400534 f1 (targ) { b = 0x2, int } { a = 0x1, int }

It works for other variables too, as long as they are in integer registers
and the compiler generated correct dwarf debug information.

Signed-off-by: Andi Kleen <ak@xxxxxxxxxxxxxxx>
---
tools/perf/Documentation/perf-script.txt | 6 +-
tools/perf/builtin-script.c | 63 ++++++++++++++++-
tools/perf/util/Build | 1 +
tools/perf/util/dwarf-sample.c | 113 +++++++++++++++++++++++++++++++
tools/perf/util/dwarf-sample.h | 13 ++++
tools/perf/util/probe-event.c | 2 +-
tools/perf/util/probe-event.h | 3 +
7 files changed, 196 insertions(+), 5 deletions(-)
create mode 100644 tools/perf/util/dwarf-sample.c
create mode 100644 tools/perf/util/dwarf-sample.h

diff --git a/tools/perf/Documentation/perf-script.txt b/tools/perf/Documentation/perf-script.txt
index 2811fcf684cb..e296944cc03f 100644
--- a/tools/perf/Documentation/perf-script.txt
+++ b/tools/perf/Documentation/perf-script.txt
@@ -117,7 +117,7 @@ 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, uregs, brstack, brstacksym, flags, bpf-output, brstackinsn,
- brstackoff, callindent, insn, insnlen, synth, phys_addr.
+ brstackoff, callindent, insn, insnlen, synth, phys_addr, iregvals.
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
@@ -217,6 +217,10 @@ OPTIONS

The brstackoff field will print an offset into a specific dso/binary.

+ With iregvals perf script uses dwarf debug information to map sampled register values
+ (with perf record -I ...) to their symbolic names in the program. This requires availability
+ of debug information in the binaries.
+
-k::
--vmlinux=<file>::
vmlinux pathname
diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index cae4b13fc715..7913ec732620 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -34,6 +34,8 @@
#include "asm/bug.h"
#include "util/mem-events.h"
#include "util/dump-insn.h"
+#include "util/probe-finder.h"
+#include "util/dwarf-sample.h"
#include <dirent.h>
#include <errno.h>
#include <inttypes.h>
@@ -42,7 +44,6 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
-
#include "sane_ctype.h"

static char const *script_name;
@@ -91,6 +92,7 @@ enum perf_output_field {
PERF_OUTPUT_SYNTH = 1U << 25,
PERF_OUTPUT_PHYS_ADDR = 1U << 26,
PERF_OUTPUT_UREGS = 1U << 27,
+ PERF_OUTPUT_IREG_VALS = 1U << 28,
};

struct output_option {
@@ -125,6 +127,7 @@ struct output_option {
{.str = "brstackoff", .field = PERF_OUTPUT_BRSTACKOFF},
{.str = "synth", .field = PERF_OUTPUT_SYNTH},
{.str = "phys_addr", .field = PERF_OUTPUT_PHYS_ADDR},
+ {.str = "iregvals", .field = PERF_OUTPUT_IREG_VALS},
};

enum {
@@ -424,7 +427,7 @@ static int perf_evsel__check_attr(struct perf_evsel *evsel,
PERF_OUTPUT_CPU, allow_user_set))
return -EINVAL;

- if (PRINT_FIELD(IREGS) &&
+ if ((PRINT_FIELD(IREGS) || PRINT_FIELD(IREG_VALS)) &&
perf_evsel__check_stype(evsel, PERF_SAMPLE_REGS_INTR, "IREGS",
PERF_OUTPUT_IREGS))
return -EINVAL;
@@ -439,6 +442,11 @@ static int perf_evsel__check_attr(struct perf_evsel *evsel,
PERF_OUTPUT_PHYS_ADDR))
return -EINVAL;

+ if (PRINT_FIELD(IREG_VALS)) {
+ if (init_probe_symbol_maps(false) >= 0)
+ probe_conf.max_probes = MAX_PROBES;
+ }
+
return 0;
}

@@ -542,6 +550,51 @@ static int perf_session__check_output_opt(struct perf_session *session)
return 0;
}

+#ifdef HAVE_DWARF_SUPPORT
+/* Resolve registers in samples to their dwarf name */
+static void print_sample__fprintf_ireg_vals(struct perf_sample *sample,
+ struct perf_event_attr *attr,
+ struct thread *thread,
+ FILE *fp)
+{
+ struct regs_dump *regs = &sample->intr_regs;
+ uint64_t mask = attr->sample_regs_intr;
+ unsigned i = 0, r;
+ struct variable_list *vls;
+ int dret;
+
+ if (!regs)
+ return;
+
+ dret = dwarf_resolve_sample(sample, thread, &vls);
+ if (dret < 0)
+ return;
+
+ for_each_set_bit(r, (unsigned long *) &mask, sizeof(mask) * 8) {
+ u64 val = regs->regs[i++];
+ char *name;
+ char *type;
+
+ if (!dwarf_varlist_find_reg(vls, dret, r, &name, &type))
+ fprintf(fp, " { %s = %#" PRIx64 ", %.*s } ", name, val,
+ (int)strcspn(type, "\t"), type);
+ }
+
+ dwarf_free_varlist(vls, dret);
+}
+
+#else
+
+static void print_sample__fprintf_ireg_vals(
+ struct perf_sample *sample __maybe_unused,
+ struct perf_event_attr *attr __maybe_unused,
+ struct thread *thread __maybe_unused,
+ FILE *fp __maybe_unused)
+{
+}
+
+#endif
+
static int perf_sample__fprintf_iregs(struct perf_sample *sample,
struct perf_event_attr *attr, FILE *fp)
{
@@ -1558,6 +1611,9 @@ static void process_event(struct perf_script *script,
if (PRINT_FIELD(UREGS))
perf_sample__fprintf_uregs(sample, attr, fp);

+ if (PRINT_FIELD(IREG_VALS))
+ print_sample__fprintf_ireg_vals(sample, attr, thread, fp);
+
if (PRINT_FIELD(BRSTACK))
perf_sample__fprintf_brstack(sample, thread, attr, fp);
else if (PRINT_FIELD(BRSTACKSYM))
@@ -2952,7 +3008,8 @@ int cmd_script(int argc, const char **argv)
"Valid types: hw,sw,trace,raw,synth. "
"Fields: comm,tid,pid,time,cpu,event,trace,ip,sym,dso,"
"addr,symoff,period,iregs,uregs,brstack,brstacksym,flags,"
- "bpf-output,callindent,insn,insnlen,brstackinsn,synth,phys_addr",
+ "bpf-output,callindent,insn,insnlen,brstackinsn,synth,phys_addr,"
+ "iregvals",
parse_output_fields),
OPT_BOOLEAN('a', "all-cpus", &system_wide,
"system-wide collection from all CPUs"),
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 80c05329835a..361db92a4bfd 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -42,6 +42,7 @@ libperf-y += callchain.o
libperf-y += values.o
libperf-y += debug.o
libperf-y += machine.o
+libperf-y += dwarf-sample.o
libperf-y += map.o
libperf-y += pstack.o
libperf-y += session.o
diff --git a/tools/perf/util/dwarf-sample.c b/tools/perf/util/dwarf-sample.c
new file mode 100644
index 000000000000..ee2c1b0d3bd7
--- /dev/null
+++ b/tools/perf/util/dwarf-sample.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2017, Intel Corporation.
+ *
+ * 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.
+ *
+ */
+
+/* Resolve variable names from samples using DWARF. */
+#include <errno.h>
+#include "perf.h"
+#include "thread.h"
+#include "strbuf.h"
+#include "strlist.h"
+#include "util.h"
+#include "debug.h"
+#include "probe-finder.h"
+#include "dwarf-sample.h"
+
+/* Resolve dwarf variables at a sample IP */
+int dwarf_resolve_sample(struct perf_sample *sample,
+ struct thread *thread,
+ struct variable_list **vls)
+{
+ struct addr_location al;
+ int dret = -1;
+ struct perf_probe_event pev;
+ struct debuginfo *dinfo = NULL;
+
+ memset(&pev, 0, sizeof(struct perf_probe_event));
+ memset(&al, 0, sizeof(al));
+ thread__find_addr_map(thread, sample->cpumode, MAP__FUNCTION, sample->ip, &al);
+ if (al.map && al.map->dso->long_name) {
+ pev.target = build_id_cache__complement(al.map->dso->long_name);
+ if (pev.target) {
+ char *t = pev.target;
+ pev.target = build_id_cache__origname(pev.target);
+ free(t);
+ } else {
+ pev.target = strdup(al.map->dso->long_name);
+ }
+ if (!pev.target)
+ return -EIO;
+ al.sym = map__find_symbol(al.map, al.addr);
+ if (al.sym) {
+ pev.point.function = al.sym->name;
+ pev.point.offset = al.addr - al.sym->start;
+ }
+ dinfo = debuginfo_cache__open(pev.target, true);
+ if (dinfo)
+ dret = debuginfo__find_available_vars_at(dinfo, &pev, vls,
+ verbose == 0);
+ }
+
+ free(pev.target);
+
+ return dret;
+}
+
+/* Free resolved variable list */
+void dwarf_free_varlist(struct variable_list *vls, int dret)
+{
+ int i;
+
+ for (i = 0; i < dret; i++) {
+ zfree(&vls[i].point.symbol);
+ strlist__delete(vls[i].vars);
+ }
+ free(vls);
+}
+
+/* Find given register in resolved variables. */
+int dwarf_varlist_find_reg(struct variable_list *vls, int dret, int r, char **name,
+ char **type)
+{
+ int i;
+ const char *regn = perf_reg_name(r);
+
+ *name = NULL;
+ for (i = 0; i < dret; i++) {
+ if (vls[i].vars) {
+ struct str_node *node;
+
+ strlist__for_each_entry(node, vls[i].vars) {
+ struct variable_node *vn =
+ container_of(node, struct variable_node, snode);
+ char *rr, *value;
+
+ for (rr = vn->value; *rr; rr++)
+ *rr = toupper(*rr);
+ value = vn->value;
+ if (*value == '%')
+ value++;
+ if (strncmp(regn, value, strlen(regn)))
+ continue;
+ *name = vn->name;
+ if (type) {
+ *type = (char *)node->s;
+ if ((*type)[0] == ' ' && (*type)[1] == '(')
+ (*type) += 2;
+ }
+ return 0;
+ }
+ }
+ }
+ return -EINVAL;
+}
diff --git a/tools/perf/util/dwarf-sample.h b/tools/perf/util/dwarf-sample.h
new file mode 100644
index 000000000000..39ec1a6c45d3
--- /dev/null
+++ b/tools/perf/util/dwarf-sample.h
@@ -0,0 +1,13 @@
+#ifndef DWARF_SAMPLE_H
+#define DWARF_SAMPLE_H 1
+
+#include "probe-finder.h"
+
+int dwarf_resolve_sample(struct perf_sample *sample,
+ struct thread *thread,
+ struct variable_list **vls);
+void dwarf_free_varlist(struct variable_list *vls, int dret);
+int dwarf_varlist_find_reg(struct variable_list *vls, int dret, int r,
+ char **name, char **type);
+
+#endif
diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c
index 2f9469e862fb..4ef6ee967468 100644
--- a/tools/perf/util/probe-event.c
+++ b/tools/perf/util/probe-event.c
@@ -507,7 +507,7 @@ static struct debuginfo *open_debuginfo(const char *module, struct nsinfo *nsi,
static struct debuginfo *debuginfo_cache;
static char *debuginfo_cache_path;

-static struct debuginfo *debuginfo_cache__open(const char *module, bool silent)
+struct debuginfo *debuginfo_cache__open(const char *module, bool silent)
{
const char *path = module;

diff --git a/tools/perf/util/probe-event.h b/tools/perf/util/probe-event.h
index 45b14f020558..e694a3680e1b 100644
--- a/tools/perf/util/probe-event.h
+++ b/tools/perf/util/probe-event.h
@@ -190,4 +190,7 @@ struct map *get_target_map(const char *target, struct nsinfo *nsi, bool user);
void arch__post_process_probe_trace_events(struct perf_probe_event *pev,
int ntevs);

+struct debuginfo;
+struct debuginfo *debuginfo_cache__open(const char *module, bool silent);
+
#endif /*_PROBE_EVENT_H */
--
2.13.6