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-next>] [day] [month] [year] [list]
Date:	Tue, 26 Mar 2013 12:14:49 -0700
From:	Roberto Vitillo <ravitillo@....gov>
To:	linux-kernel@...r.kernel.org
Cc:	a.p.zijlstra@...llo.nl, paulus@...ba.org, mingo@...hat.com,
	acme@...stprotocols.net, namhyung@...nel.org
Subject: [PATCH v2] perf: add callgrind conversion tool

The proposed patch adds the convert tool to perf which allows to convert a
perf.data file to a set of callgrind data files which can subsequently be
displayed with kcachegrind.

Note that the code may trigger the following bug in libbfd:
http://sourceware.org/bugzilla/show_bug.cgi?id=15106

Signed-off-by: Roberto Agostino Vitillo <ravitillo@....gov>
---
 tools/perf/Documentation/perf-convert.txt |  74 ++++
 tools/perf/Makefile                       |  12 +
 tools/perf/builtin-convert.c              | 652 ++++++++++++++++++++++++++++++
 tools/perf/builtin.h                      |   1 +
 tools/perf/command-list.txt               |   1 +
 tools/perf/perf.c                         |   3 +
 tools/perf/util/a2l.c                     | 147 +++++++
 tools/perf/util/a2l.h                     |   9 +
 8 files changed, 899 insertions(+)
 create mode 100644 tools/perf/Documentation/perf-convert.txt
 create mode 100644 tools/perf/builtin-convert.c
 create mode 100644 tools/perf/util/a2l.c
 create mode 100644 tools/perf/util/a2l.h

diff --git a/tools/perf/Documentation/perf-convert.txt
b/tools/perf/Documentation/perf-convert.txt
new file mode 100644
index 0000000..fa75933
--- /dev/null
+++ b/tools/perf/Documentation/perf-convert.txt
@@ -0,0 +1,74 @@
+perf-convert(1)
+================
+
+NAME
+----
+perf-convert - Convert perf.data (created by perf record) to a set of
callgrind
+data files.
+
+SYNOPSIS
+--------
+[verse]
+'perf convert' [-i <file> | --input=file] [-p <name> | --prefix=name]
+
+DESCRIPTION
+-----------
+This command converts the input file to a set of callgrind data files
which can
+be subsequently displayed with kcachegrind. A distinct callgrind data file is
+generated for each recorded event.
+
+OPTIONS
+-------
+-i::
+--input=::
+        Input file name. (default: perf.data unless stdin is a fifo)
+
+-p::
+--prefix=::
+        Filename prefix of the generated callgrind files. (default: callgrind_)
+
+-d::
+--dsos=<dso[,dso...]>::
+        Only consider symbols in these dsos.
+
+-f::
+--force::
+        Don't complain, do it.
+
+-k::
+--vmlinux=<file>::
+        vmlinux pathname.
+
+-m::
+--modules::
+        Load module symbols. WARNING: use only with -k and LIVE kernel.
+
+-C::
+--cpu:: Only report samples for the list of CPUs provided. Multiple CPUs can
+	be provided as a comma-separated list with no space: 0,1. Ranges of
+	CPUs are specified with -: 0-2. Default is to report samples on all
+	CPUs.
+
+--symfs=<directory>::
+        Look for files with symbols relative to this directory.
+
+EXAMPLE
+-------
+	Given a perf.data file with a certain set of events, that has
+	been previously generated by perf record, e.g.:
+
+	perf record -g -e cycles -e instructions ...
+	
+	The convert tool generates a callgrind file for each of the collected
+	events, i.e. cycles and instructions:
+	
+	perf convert
+
+	The generated callgrind files are named callgrind_cycles and
+	callgrind_instructions. To display callgrind_instructions:
+
+	kcachegrind callgrind_instructions
+
+SEE ALSO
+--------
+linkperf:perf-record[1], linkperf:perf-report[1]
diff --git a/tools/perf/Makefile b/tools/perf/Makefile
index bb74c79..5c47df3 100644
--- a/tools/perf/Makefile
+++ b/tools/perf/Makefile
@@ -808,16 +808,19 @@ else
 		has_bfd := $(call try-cc,$(SOURCE_BFD),$(FLAGS_BFD),libbfd)
 		ifeq ($(has_bfd),y)
 			EXTLIBS += -lbfd
+			bfd_available = y
 		else
 			FLAGS_BFD_IBERTY=$(FLAGS_BFD) -liberty
 			has_bfd_iberty := $(call try-cc,$(SOURCE_BFD),$(FLAGS_BFD_IBERTY),liberty)
 			ifeq ($(has_bfd_iberty),y)
 				EXTLIBS += -lbfd -liberty
+				bfd_available = y
 			else
 				FLAGS_BFD_IBERTY_Z=$(FLAGS_BFD_IBERTY) -lz
 				has_bfd_iberty_z := $(call try-cc,$(SOURCE_BFD),$(FLAGS_BFD_IBERTY_Z),libz)
 				ifeq ($(has_bfd_iberty_z),y)
 					EXTLIBS += -lbfd -liberty -lz
+					bfd_available = y
 				else
 					FLAGS_CPLUS_DEMANGLE=$(ALL_CFLAGS) $(ALL_LDFLAGS) $(EXTLIBS) -liberty
 					has_cplus_demangle := $(call
try-cc,$(SOURCE_CPLUS_DEMANGLE),$(FLAGS_CPLUS_DEMANGLE),demangle)
@@ -834,6 +837,15 @@ else
 	endif
 endif

+ifeq ($(bfd_available),y)
+	LIB_H += util/a2l.h
+	LIB_OBJS += $(OUTPUT)util/a2l.o
+	BUILTIN_OBJS += $(OUTPUT)builtin-convert.o
+	BASIC_CFLAGS += -DLIBBFD_SUPPORT
+else
+	msg := $(warning No bfd.h/libbfd found, install
binutils-dev[el]/zlib-static to enable the convert tool)
+endif
+
 ifeq ($(NO_PERF_REGS),0)
 	ifeq ($(ARCH),x86)
 		LIB_H += arch/x86/include/perf_regs.h
diff --git a/tools/perf/builtin-convert.c b/tools/perf/builtin-convert.c
new file mode 100644
index 0000000..2582221
--- /dev/null
+++ b/tools/perf/builtin-convert.c
@@ -0,0 +1,652 @@
+/*
+ * builtin-convert.c
+ *
+ * Builtin convert command: Convert a perf.data input file
+ * to a set of callgrind profile data files.
+ */
+
+#include <linux/list.h>
+#include <linux/rbtree.h>
+#include <linux/bitmap.h>
+
+#include "util/util.h"
+#include "util/cache.h"
+#include "util/symbol.h"
+#include "util/evlist.h"
+#include "util/evsel.h"
+#include "util/annotate.h"
+#include "util/event.h"
+#include "util/parse-options.h"
+#include "util/parse-events.h"
+#include "util/thread.h"
+#include "util/session.h"
+#include "util/tool.h"
+#include "util/a2l.h"
+
+#include "builtin.h"
+#include "perf.h"
+
+struct perf_convert {
+	struct perf_tool tool;
+	char const *input_name;
+	char const *output_prefix;
+	bool	   force;
+	const char *cpu_list;
+	DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS);
+};
+
+struct stats {
+	u64 hits;
+	bool has_callees;
+};
+
+struct graph_node {
+	const char *filename;
+	struct rb_node rb_node;
+	struct map *map;
+	struct symbol *sym;
+	struct rb_root callees;
+	struct stats stats[0];
+};
+
+struct callee {
+	struct rb_node rb_node;
+	struct map *map;
+	struct symbol *sym;
+	u64 hits[0];
+};
+
+static const char *last_source_name;
+static unsigned nr_events;
+static unsigned last_line;
+static u64 last_off;
+static FILE **output_files;
+static struct rb_root graph_root;
+
+static inline int64_t cmp_null(void *l, void *r)
+{
+	if (!l && !r)
+		return 0;
+	else if (!l)
+		return -1;
+	else
+		return 1;
+}
+
+static int64_t map_cmp(struct map *map_l, struct map *map_r)
+{
+	struct dso *dso_l = map_l ? map_l->dso : NULL;
+	struct dso *dso_r = map_r ? map_r->dso : NULL;
+	const char *dso_name_l, *dso_name_r;
+
+	if (!dso_l || !dso_r)
+		return cmp_null(dso_l, dso_r);
+
+	if (verbose) {
+		dso_name_l = dso_l->long_name;
+		dso_name_r = dso_r->long_name;
+	} else {
+		dso_name_l = dso_l->short_name;
+		dso_name_r = dso_r->short_name;
+	}
+
+	return strcmp(dso_name_l, dso_name_r);
+}
+
+static int64_t sym_cmp(struct symbol *sym_l, struct symbol *sym_r)
+{
+	u64 ip_l, ip_r;
+
+	if (!sym_l || !sym_r)
+		return cmp_null(sym_l, sym_r);
+
+	if (sym_l == sym_r)
+		return 0;
+
+	ip_l = sym_l->start;
+	ip_r = sym_r->start;
+
+	return (int64_t)(ip_r - ip_l);
+}
+
+static inline int64_t map_sym_cmp(struct map *map_l, struct symbol *sym_l,
+				  struct map *map_r, struct symbol *sym_r)
+{
+	int64_t cmp = map_cmp(map_l, map_r);
+
+	if (!cmp)
+		return sym_cmp(sym_l, sym_r);
+	else
+		return cmp;
+}
+
+static struct graph_node *add_graph_node(struct map *map, struct symbol *sym)
+{
+	struct rb_node **rb_node = &(&graph_root)->rb_node, *parent = NULL;
+	struct graph_node *node;
+	int64_t cmp;
+
+	while (*rb_node) {
+		parent = *rb_node;
+		node = rb_entry(parent, struct graph_node, rb_node);
+		cmp = map_sym_cmp(map, sym, node->map, node->sym);
+
+		if (cmp < 0)
+			rb_node = &(*rb_node)->rb_left;
+		else if (cmp > 0)
+			rb_node = &(*rb_node)->rb_right;
+		else {
+			if (map != node->map)
+				node->map = map;
+
+			if (map)
+				map->referenced = true;
+
+			return node;
+		}
+	}
+
+	node = zalloc(sizeof(*node) + nr_events * sizeof(node->stats[0]));
+	if (node) {
+		node->map = map;
+		node->sym = sym;
+		node->filename = "";
+		node->callees = RB_ROOT;
+
+		if (map)
+			map->referenced = true;
+
+		rb_link_node(&node->rb_node, parent, rb_node);
+		rb_insert_color(&node->rb_node, &graph_root);
+	}
+
+	return node;
+}
+
+static struct graph_node *get_graph_node(struct map *map, struct symbol *sym)
+{
+	struct rb_node *rb_node = (&graph_root)->rb_node;
+	struct graph_node *node;
+	int64_t cmp;
+
+	while (rb_node) {
+		node = rb_entry(rb_node, struct graph_node, rb_node);
+		cmp = map_sym_cmp(map, sym, node->map, node->sym);
+
+		if (cmp < 0)
+			rb_node = rb_node->rb_left;
+		else if (cmp > 0)
+			rb_node = rb_node->rb_right;
+		else
+			return node;
+	}
+
+	return NULL;
+}
+
+static int graph_node__add_callee(struct graph_node *caller, struct map *map,
+				  struct symbol *sym, int idx)
+{
+	struct rb_node **rb_node = &caller->callees.rb_node, *parent = NULL;
+	struct callee *callee;
+	int64_t cmp;
+
+	while (*rb_node) {
+		parent = *rb_node;
+		callee = rb_entry(parent, struct callee, rb_node);
+		cmp = map_sym_cmp(map, sym, callee->map, callee->sym);
+
+		if (cmp < 0)
+			rb_node = &(*rb_node)->rb_left;
+		else if (cmp > 0)
+			rb_node = &(*rb_node)->rb_right;
+		else{
+			callee->hits[idx]++;
+			caller->stats[idx].has_callees = true;
+
+			if (map != callee->map)
+				callee->map = map;
+
+			if (map)
+				map->referenced = true;
+
+			return 0;
+		}
+	}
+
+	callee = zalloc(sizeof(*callee) + nr_events * sizeof(callee->hits[0]));
+	if (callee) {
+		callee->map = map;
+		callee->sym = sym;
+		callee->hits[idx] = 1;
+		caller->stats[idx].has_callees = true;
+
+		if (map)
+			map->referenced = true;
+
+		rb_link_node(&callee->rb_node, parent, rb_node);
+		rb_insert_color(&callee->rb_node, &caller->callees);
+
+		return 0;
+	} else
+		return -ENOMEM;
+}
+
+static int add_callchain_to_callgraph(int idx)
+{
+	struct callchain_cursor_node *caller, *callee;
+	struct graph_node *node;
+	int err;
+
+	callchain_cursor_commit(&callchain_cursor);
+	callee = callchain_cursor_current(&callchain_cursor);
+
+	if (!callee)
+		return 0;
+
+	while (true) {
+		callchain_cursor_advance(&callchain_cursor);
+		caller = callchain_cursor_current(&callchain_cursor);
+
+		if (!caller)
+			break;
+
+		node = add_graph_node(caller->map, caller->sym);
+		if (!node)
+			return -ENOMEM;
+
+		err = graph_node__add_callee(node, callee->map, callee->sym, idx);
+		if (err)
+			return err;
+
+		callee = caller;
+	}
+
+	return 0;
+}
+
+static int accumulate_sample(struct perf_evsel *evsel, struct
perf_sample *sample,
+			     struct addr_location *al, struct machine *machine)
+{
+	struct symbol *parent = NULL;
+	struct graph_node *node;
+	int err;
+
+	if (sample->callchain) {
+		err = machine__resolve_callchain(machine, evsel, al->thread,
+						 sample, &parent);
+
+		if (err)
+			return err;
+	}
+
+	node = add_graph_node(al->map, al->sym);
+	if (!node)
+		return -ENOMEM;
+
+	err = add_callchain_to_callgraph(evsel->idx);
+	if (err)
+		return err;
+
+	node->stats[evsel->idx].hits++;
+
+	if (node->sym != NULL) {
+		struct annotation *notes = symbol__annotation(node->sym);
+		if (notes->src == NULL && symbol__alloc_hist(node->sym) < 0)
+			return -ENOMEM;
+
+		err = symbol__inc_addr_samples(node->sym, node->map, evsel->idx,
+					       al->addr);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int process_sample_event(struct perf_tool *tool,
+				union perf_event *event,
+				struct perf_sample *sample,
+				struct perf_evsel *evsel,
+				struct machine *machine)
+{
+	struct perf_convert *cnv = container_of(tool, struct perf_convert, tool);
+	struct addr_location al;
+	int err;
+
+	if (perf_event__preprocess_sample(event, machine, &al, sample,
+					  symbol__annotate_init) < 0) {
+		pr_warning("problem processing %d event, skipping it.\n",
+			   event->header.type);
+		return -1;
+	}
+
+	if (cnv->cpu_list && !test_bit(sample->cpu, cnv->cpu_bitmap))
+		return 0;
+
+	if (!al.filtered) {
+		err = accumulate_sample(evsel, sample, &al, machine);
+		if (err) {
+			pr_warning("problem incrementing symbol count, skipping event\n");
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static inline void print_header(const char *evname, int idx)
+{
+	fprintf(output_files[idx], "positions: instr line\nevents: %s\n", evname);
+}
+
+static void print_function_header(struct graph_node *node, u64 offset, int idx)
+{
+	FILE *output = output_files[idx];
+	const char *filename;
+	struct map *map = node->map;
+	struct symbol *sym = node->sym;
+	struct annotation *notes = symbol__annotation(sym);
+	u64 function_start = map__rip_2objdump(map, sym->start);
+	u64 address = function_start + offset;
+	int ret;
+
+	filename = "";
+	addr2line(function_start, &filename, &last_line);
+
+	/* Cache filename to speedup the callgraph generation */
+	node->filename = strdup(filename);
+
+	fprintf(output, "ob=%s\n", map->dso->long_name);
+	fprintf(output, "fl=%s\n", filename);
+	fprintf(output, "fn=%s\n", sym->name);
+	fprintf(output, "0 0\n");
+
+	ret = addr2line(address, &last_source_name, &last_line);
+	if (ret && strcmp(filename, last_source_name))
+		fprintf(output, "fl=%s\n", last_source_name);
+
+	fprintf(output, "%#" PRIx64 " %u %" PRIu64 "\n", address, last_line,
+		annotation__histogram(notes, idx)->addr[offset]);
+
+	last_off = offset;
+}
+
+static inline bool event_has_samples(struct annotation *notes, u64
offset, int idx)
+{
+	return annotation__histogram(notes, idx)->addr[offset];
+}
+
+static void print_function_tail(struct graph_node *node, u64 offset, int idx)
+{
+	int ret;
+	unsigned line;
+	const char *filename = NULL;
+	FILE *output = output_files[idx];
+	struct map *map = node->map;
+	struct symbol *sym = node->sym;
+	struct annotation *notes = symbol__annotation(sym);
+
+	ret = addr2line(map__rip_2objdump(map, sym->start) + offset,
+			&filename, &line);
+	if (ret && strcmp(filename, last_source_name)) {
+		fprintf(output, "fl=%s\n", filename);
+		last_source_name = filename;
+	}
+
+	if (ret)
+		fprintf(output, "+%" PRIu64 " %+d", offset - last_off,
+			(int)(line - last_line));
+	else{
+		fprintf(output, "+%" PRIu64 " 0", offset - last_off);
+		line = 0;
+	}
+
+	fprintf(output, " %" PRIu64 "\n",
+		annotation__histogram(notes, idx)->addr[offset]);
+
+	last_off = offset;
+	last_line = line;
+}
+
+static void print_function_summary(struct graph_node *node, int idx)
+{
+	FILE *output = output_files[idx];
+
+	fprintf(output, "ob=%s\n", node->map && node->map->dso ?
+		node->map->dso->long_name : "");
+
+	/* Without the empty fl declaration kcachegrind would apply the last
+	 * valid fl declaration in the file*/
+	fprintf(output, "fl=\n");
+	fprintf(output, "fn=%s\n", node->sym ? node->sym->name : "");
+	fprintf(output, "0 0 %" PRIu64, node->stats[idx].hits);
+	fprintf(output, "\n");
+}
+
+static void print_function(struct graph_node *node, int idx)
+{
+	struct annotation *notes;
+	struct map *map;
+	struct symbol *sym;
+	u64 sym_len, i;
+
+	if (!node->stats[idx].hits)
+		return;
+
+	map = node->map;
+	sym = node->sym;
+
+	if (!map || !sym || addr2line_init(map->dso->long_name)) {
+		print_function_summary(node, idx);
+		return;
+	}
+
+	notes = symbol__annotation(sym);
+	sym_len = sym->end - sym->start;
+
+	for (i = 0; i < sym_len; i++) {
+		if (event_has_samples(notes, i, idx)) {
+			print_function_header(node, i, idx);
+			break;
+		}
+	}
+
+	for (++i; i < sym_len; i++) {
+		if (event_has_samples(notes, i, idx))
+			print_function_tail(node, i, idx);
+	}
+}
+
+static void print_functions(void){
+	struct rb_node *rb_node;
+	struct graph_node *node;
+	u64 i = 0;
+
+	for (rb_node = rb_first(&graph_root); rb_node; rb_node = rb_next(rb_node)) {
+		node = rb_entry(rb_node, struct graph_node, rb_node);
+
+		for (i = 0; i < nr_events; i++)
+			print_function(node, i);
+	}
+}
+
+static void print_callee(struct callee *callee, int idx)
+{
+	FILE *output = output_files[idx];
+	struct graph_node *callee_node;
+
+	if (!callee->hits[idx])
+		return;
+
+	if (callee->sym) {
+		callee_node = get_graph_node(callee->map, callee->sym);
+		fprintf(output, "cob=%s\ncfl=%s\ncfn=%s\n",
+			callee->map->dso->long_name, callee_node->filename,
+			callee->sym->name);
+	} else
+		fprintf(output, "cob=%s\ncfl=\ncfn=\n", callee->map ?
+			callee->map->dso->long_name : "");
+
+	fprintf(output, "calls=%" PRIu64 "\n0 0 %" PRIu64 "\n",
+		callee->hits[idx], callee->hits[idx]);
+
+}
+
+static void print_caller(struct graph_node *node, int idx)
+{
+	FILE *output = output_files[idx];
+	struct callee *callee;
+	struct rb_node *rb_node;
+
+	if (!node->stats[idx].has_callees)
+		return;
+
+	if (node->sym)
+		fprintf(output, "ob=%s\nfl=%s\nfn=%s\n",
+				node->map->dso->long_name,
+				node->filename, node->sym->name);
+	else
+		fprintf(output, "ob=%s\nfl=\nfn=\n",
+			node->map ? node->map->dso->long_name : "");
+
+	for (rb_node = rb_first(&node->callees); rb_node; rb_node =
rb_next(rb_node)) {
+		callee = rb_entry(rb_node, struct callee, rb_node);
+		print_callee(callee, idx);
+	}
+}
+
+static void print_calls(void)
+{
+	struct rb_node *rb_node;
+	struct graph_node *node;
+	u64 i = 0;
+
+	for (rb_node = rb_first(&graph_root); rb_node; rb_node = rb_next(rb_node)) {
+		node = rb_entry(rb_node, struct graph_node, rb_node);
+
+		for (i = 0; i < nr_events; i++)
+			print_caller(node, i);
+	}
+}
+
+static int __cmd_convert(struct perf_convert *cnv)
+{
+	int ret;
+	unsigned i = 0;
+	struct perf_session *session;
+	struct perf_evsel *pos;
+	char output_filename[100];
+
+	session = perf_session__new(cnv->input_name, O_RDONLY,
+				    cnv->force, false, &cnv->tool);
+	if (session == NULL)
+		return -ENOMEM;
+
+	nr_events = session->evlist->nr_entries;
+
+	if (cnv->cpu_list) {
+		ret = perf_session__cpu_bitmap(session, cnv->cpu_list,
+					       cnv->cpu_bitmap);
+		if (ret)
+			goto out_delete;
+	}
+
+	ret = perf_session__process_events(session, &cnv->tool);
+	if (ret)
+		goto out_delete;
+
+	output_files = malloc(sizeof(*output_files)*nr_events);
+	list_for_each_entry(pos, &session->evlist->entries, node) {
+		const char *evname = perf_evsel__name(pos);
+
+		snprintf(output_filename, sizeof(output_filename), "%s%s",
+			 cnv->output_prefix, evname);
+		output_files[i] = fopen(output_filename, "w");
+
+		if (!output_files[i]) {
+			fprintf(stderr, "Cannot open %s for output\n",
+				output_filename);
+			return -1;
+		}
+
+		print_header(evname, i++);
+	}
+
+	print_functions();
+	print_calls();
+
+	for (i = 0; i < nr_events; i++)
+		fclose(output_files[i]);
+
+out_delete:
+	/*
+	 * Speed up the exit process, for large files this can
+	 * take quite a while.
+	 *
+	 * XXX Enable this when using valgrind or if we ever
+	 * librarize this command.
+	 *
+	 * Also experiment with obstacks to see how much speed
+	 * up we'll get here.
+	 *
+	 * perf_session__delete(session);
+	 */
+	return ret;
+}
+
+static const char * const convert_usage[] = {
+	"perf convert [<options>]",
+	NULL
+};
+
+int cmd_convert(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+	struct perf_convert convert = {
+		.tool = {
+			.sample	= process_sample_event,
+			.mmap	= perf_event__process_mmap,
+			.comm	= perf_event__process_comm,
+			.exit	= perf_event__process_exit,
+			.fork	= perf_event__process_fork,
+			.ordered_samples = true,
+			.ordering_requires_timestamps = true,
+		},
+		.output_prefix = "callgrind_"
+	};
+	const struct option options[] = {
+	OPT_STRING('i', "input", &convert.input_name, "file",
+		    "input file name"),
+	OPT_STRING('p', "prefix", &convert.output_prefix, "prefix", "filename "
+		   "prefix of the generated callgrind files, default is 'callgrind_'"),
+	OPT_STRING('d', "dsos", &symbol_conf.dso_list_str, "dso[,dso...]",
+		   "only consider symbols in these dsos"),
+	OPT_BOOLEAN('f', "force", &convert.force, "don't complain, do it"),
+	OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name,
+		   "file", "vmlinux pathname"),
+	OPT_BOOLEAN('m', "modules", &symbol_conf.use_modules,
+		    "load module symbols - WARNING: use only with -k and LIVE kernel"),
+	OPT_STRING('C', "cpu", &convert.cpu_list, "cpu", "list of cpus to profile"),
+	OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory",
+		   "Look for files with symbols relative to this directory"),
+	OPT_END()
+	};
+
+	argc = parse_options(argc, argv, options, convert_usage, 0);
+
+	symbol_conf.priv_size = sizeof(struct annotation);
+	symbol_conf.try_vmlinux_path = true;
+	symbol_conf.use_callchain = true;
+
+	if (callchain_register_param(&callchain_param) < 0) {
+		fprintf(stderr, "Can't register callchain params\n");
+		return -1;
+	}
+
+	if (symbol__init() < 0)
+		return -1;
+
+	graph_root = RB_ROOT;
+
+	return __cmd_convert(&convert);
+}
diff --git a/tools/perf/builtin.h b/tools/perf/builtin.h
index 08143bd..e4f7d5a 100644
--- a/tools/perf/builtin.h
+++ b/tools/perf/builtin.h
@@ -36,6 +36,7 @@ extern int cmd_kvm(int argc, const char **argv,
const char *prefix);
 extern int cmd_test(int argc, const char **argv, const char *prefix);
 extern int cmd_trace(int argc, const char **argv, const char *prefix);
 extern int cmd_inject(int argc, const char **argv, const char *prefix);
+extern int cmd_convert(int argc, const char **argv, const char *prefix);

 extern int find_scripts(char **scripts_array, char **scripts_path_array);
 #endif
diff --git a/tools/perf/command-list.txt b/tools/perf/command-list.txt
index 3e86bbd..74e792c 100644
--- a/tools/perf/command-list.txt
+++ b/tools/perf/command-list.txt
@@ -7,6 +7,7 @@ perf-archive			mainporcelain common
 perf-bench			mainporcelain common
 perf-buildid-cache		mainporcelain common
 perf-buildid-list		mainporcelain common
+perf-convert			mainporcelain common
 perf-diff			mainporcelain common
 perf-evlist			mainporcelain common
 perf-inject			mainporcelain common
diff --git a/tools/perf/perf.c b/tools/perf/perf.c
index 095b882..01104f1 100644
--- a/tools/perf/perf.c
+++ b/tools/perf/perf.c
@@ -60,6 +60,9 @@ static struct cmd_struct commands[] = {
 	{ "trace",	cmd_trace,	0 },
 #endif
 	{ "inject",	cmd_inject,	0 },
+#ifdef LIBBFD_SUPPORT
+	{ "convert",	cmd_convert,	0 },
+#endif
 };

 struct pager_config {
diff --git a/tools/perf/util/a2l.c b/tools/perf/util/a2l.c
new file mode 100644
index 0000000..3df6b7e
--- /dev/null
+++ b/tools/perf/util/a2l.c
@@ -0,0 +1,147 @@
+/* based on addr2line */
+
+#define PACKAGE "perf"
+
+#include <linux/kernel.h>
+
+#include <bfd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "a2l.h"
+
+static const char *filename;
+static const char *functionname;
+static const char *last_opened_file;
+static unsigned int line;
+static asymbol **syms;
+static bfd_vma pc;
+static bfd_boolean found;
+static bfd *abfd;
+
+static void bfd_nonfatal(const char *string)
+{
+	const char *errmsg;
+
+	errmsg = bfd_errmsg(bfd_get_error());
+	fflush(stdout);
+	if (string)
+		pr_warning("%s: %s\n", string, errmsg);
+	else
+		pr_warning("%s\n", errmsg);
+}
+
+static int bfd_fatal(const char *string)
+{
+	bfd_nonfatal(string);
+	return -1;
+}
+
+static int slurp_symtab(void)
+{
+	long storage;
+	long symcount;
+	bfd_boolean dynamic = FALSE;
+
+	if ((bfd_get_file_flags(abfd) & HAS_SYMS) == 0)
+		return bfd_fatal(bfd_get_filename(abfd));
+
+	storage = bfd_get_symtab_upper_bound(abfd);
+	if (storage == 0) {
+		storage = bfd_get_dynamic_symtab_upper_bound(abfd);
+		dynamic = TRUE;
+	}
+	if (storage < 0)
+		return bfd_fatal(bfd_get_filename(abfd));
+
+	syms = (asymbol **) malloc(storage);
+	if (dynamic)
+		symcount = bfd_canonicalize_dynamic_symtab(abfd, syms);
+	else
+		symcount = bfd_canonicalize_symtab(abfd, syms);
+
+	if (symcount < 0)
+		return bfd_fatal(bfd_get_filename(abfd));
+
+	return 0;
+}
+
+static void find_address_in_section(bfd *mybfd, asection *section,
+				    void *data ATTRIBUTE_UNUSED)
+{
+	bfd_vma vma;
+	bfd_size_type size;
+	(void)mybfd;
+
+	if (found)
+		return;
+
+	if ((bfd_get_section_flags(abfd, section) & SEC_ALLOC) == 0)
+		return;
+
+	vma = bfd_get_section_vma(abfd, section);
+	if (pc < vma)
+		return;
+
+	size = bfd_get_section_size(section);
+	if (pc >= vma + size)
+		return;
+
+	found = bfd_find_nearest_line(abfd, section, syms, pc - vma,
+			&filename, &functionname, &line);
+}
+
+int addr2line_init(const char *file_name)
+{
+	if (last_opened_file && !strcmp(last_opened_file, file_name))
+		return 0;
+	else
+		addr2line_cleanup();
+
+	abfd = bfd_openr(file_name, NULL);
+	if (abfd == NULL)
+		return -1;
+
+	if (!bfd_check_format(abfd, bfd_object))
+		return bfd_fatal(bfd_get_filename(abfd));
+
+	last_opened_file = file_name;
+	return slurp_symtab();
+
+}
+
+void addr2line_cleanup(void)
+{
+	if (syms != NULL) {
+		free(syms);
+		syms = NULL;
+	}
+
+	if (abfd)
+		bfd_close(abfd);
+
+	line = found = 0;
+	last_opened_file = NULL;
+	abfd = 0;
+}
+
+int addr2line_inline(const char **file, unsigned *line_nr)
+{
+	return bfd_find_inliner_info(abfd, file, &functionname, line_nr);
+}
+
+int addr2line(unsigned long addr, const char **file, unsigned *line_nr)
+{
+	found = 0;
+	pc = addr;
+	bfd_map_over_sections(abfd, find_address_in_section, NULL);
+
+	if (found) {
+		*file = filename ? filename : "";
+		*line_nr = line;
+		return found;
+	}
+
+	return 0;
+}
diff --git a/tools/perf/util/a2l.h b/tools/perf/util/a2l.h
new file mode 100644
index 0000000..b248429
--- /dev/null
+++ b/tools/perf/util/a2l.h
@@ -0,0 +1,9 @@
+#ifndef __A2L_H_
+#define __A2L_H_
+
+int addr2line_init(const char *file_name);
+int addr2line(unsigned long addr, const char **file, unsigned *line_nr);
+int addr2line_inline(const char **file, unsigned *line_nr);
+void addr2line_cleanup(void);
+
+#endif
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ