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 <raviti...@lbl.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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to