lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Mon, 27 Nov 2017 16:23:16 -0800
From:   Andi Kleen <andi@...stfloor.org>
To:     acme@...nel.org
Cc:     jolsa@...nel.org, mhiramat@...nel.org, adrian.hunter@...el.com,
        linux-kernel@...r.kernel.org, Andi Kleen <ak@...ux.intel.com>
Subject: [PATCH 07/12] perf, tools, script: Resolve variable names for registers

From: Andi Kleen <ak@...ux.intel.com>

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@...ux.intel.com>
---
 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

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ