perf probe: Support basic dwarf-based operations on uprobe events
authorMasami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
Thu, 26 Dec 2013 05:41:53 +0000 (05:41 +0000)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Thu, 26 Dec 2013 14:22:01 +0000 (11:22 -0300)
Support basic dwarf(debuginfo) based operations for uprobe events.  With
this change, perf probe can analyze debuginfo of user application binary
to set up new uprobe event.

This allows perf-probe --add(with local variables, line numbers) and
--line works with -x option.  (Actually, --vars has already accepted -x
option)

For example, the following command shows the probe-able lines of a given
user space function. Something that so far was only available in the
'perf probe' tool for kernel space functions:

  # ./perf probe -x perf --line map__load
  <map__load@/home/fedora/ksrc/linux-2.6/tools/perf/util/map.c:0>
        0  int map__load(struct map *map, symbol_filter_t filter)
        1  {
        2         const char *name = map->dso->long_name;
                  int nr;

        5         if (dso__loaded(map->dso, map->type))
        6                 return 0;

        8         nr = dso__load(map->dso, map, filter);
        9         if (nr < 0) {
       10                 if (map->dso->has_build_id) {

And this shows the available variables at the given line of the
function.

  # ./perf probe -x perf --vars map__load:8
  Available variables at map__load:8
          @<map__load+96>
                  char*   name
                  struct map*     map
                  symbol_filter_t filter
          @<map__find_symbol+112>
                  char*   name
                  symbol_filter_t filter
          @<map__find_symbol_by_name+136>
                  char*   name
                  symbol_filter_t filter
          @<map_groups__find_symbol_by_name+176>
                  char*   name
                  struct map*     map
                  symbol_filter_t filter

And lastly, we can now define probe(s) with all available
variables on the given line:

  # ./perf probe -x perf --add 'map__load:8 $vars'

  Added new events:
    probe_perf:map__load (on map__load:8 with $vars)
    probe_perf:map__load_1 (on map__load:8 with $vars)
    probe_perf:map__load_2 (on map__load:8 with $vars)
    probe_perf:map__load_3 (on map__load:8 with $vars)

  You can now use it in all perf tools, such as:

          perf record -e probe_perf:map__load_3 -aR sleep 1

  Changes from previous version:
   - Add examples in the patch description.
   - Use .text section start address and dwarf symbol address
     for calculating the offset of given symbol, instead of
     searching the symbol in symtab again.
     With this change, we can safely handle multiple local
     function instances (e.g. scnprintf in perf).

Signed-off-by: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: David A. Long <dave.long@linaro.org>
Cc: Ingo Molnar <mingo@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Srikar Dronamraju <srikar@linux.vnet.ibm.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: systemtap@sourceware.org
Cc: yrl.pp-manager.tt@hitachi.com
Link: http://lkml.kernel.org/r/20131226054152.22364.47021.stgit@kbuild-fedora.novalocal
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
tools/perf/builtin-probe.c
tools/perf/util/probe-event.c
tools/perf/util/probe-event.h
tools/perf/util/probe-finder.c

index 1792a3f1f4ce26d26ee03782a7572f4579ae1ff8..43ff33d0007b1edbe9e9db1ea37e6c6c1bfefbfb 100644 (file)
@@ -424,7 +424,7 @@ int cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused)
        }
 
 #ifdef HAVE_DWARF_SUPPORT
-       if (params.show_lines && !params.uprobes) {
+       if (params.show_lines) {
                if (params.mod_events) {
                        pr_err("  Error: Don't use --line with"
                               " --add/--del.\n");
index 68013b91377c89de3c3c8331a5b0dbba57bac0da..72b56aef105ea321b4aa3d0859a7c93e28cd1930 100644 (file)
@@ -172,6 +172,52 @@ const char *kernel_get_module_path(const char *module)
        return (dso) ? dso->long_name : NULL;
 }
 
+/* Copied from unwind.c */
+static Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep,
+                                   GElf_Shdr *shp, const char *name)
+{
+       Elf_Scn *sec = NULL;
+
+       while ((sec = elf_nextscn(elf, sec)) != NULL) {
+               char *str;
+
+               gelf_getshdr(sec, shp);
+               str = elf_strptr(elf, ep->e_shstrndx, shp->sh_name);
+               if (!strcmp(name, str))
+                       break;
+       }
+
+       return sec;
+}
+
+static int get_text_start_address(const char *exec, unsigned long *address)
+{
+       Elf *elf;
+       GElf_Ehdr ehdr;
+       GElf_Shdr shdr;
+       int fd, ret = -ENOENT;
+
+       fd = open(exec, O_RDONLY);
+       if (fd < 0)
+               return -errno;
+
+       elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL);
+       if (elf == NULL)
+               return -EINVAL;
+
+       if (gelf_getehdr(elf, &ehdr) == NULL)
+               goto out;
+
+       if (!elf_section_by_name(elf, &ehdr, &shdr, ".text"))
+               goto out;
+
+       *address = shdr.sh_addr - shdr.sh_offset;
+       ret = 0;
+out:
+       elf_end(elf);
+       return ret;
+}
+
 static int init_user_exec(void)
 {
        int ret = 0;
@@ -186,6 +232,37 @@ static int init_user_exec(void)
        return ret;
 }
 
+static int convert_exec_to_group(const char *exec, char **result)
+{
+       char *ptr1, *ptr2, *exec_copy;
+       char buf[64];
+       int ret;
+
+       exec_copy = strdup(exec);
+       if (!exec_copy)
+               return -ENOMEM;
+
+       ptr1 = basename(exec_copy);
+       if (!ptr1) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       ptr2 = strpbrk(ptr1, "-._");
+       if (ptr2)
+               *ptr2 = '\0';
+       ret = e_snprintf(buf, 64, "%s_%s", PERFPROBE_GROUP, ptr1);
+       if (ret < 0)
+               goto out;
+
+       *result = strdup(buf);
+       ret = *result ? 0 : -ENOMEM;
+
+out:
+       free(exec_copy);
+       return ret;
+}
+
 static int convert_to_perf_probe_point(struct probe_trace_point *tp,
                                        struct perf_probe_point *pp)
 {
@@ -261,6 +338,40 @@ static int kprobe_convert_to_perf_probe(struct probe_trace_point *tp,
        return 0;
 }
 
+static int add_exec_to_probe_trace_events(struct probe_trace_event *tevs,
+                                         int ntevs, const char *exec)
+{
+       int i, ret = 0;
+       unsigned long offset, stext = 0;
+       char buf[32];
+
+       if (!exec)
+               return 0;
+
+       ret = get_text_start_address(exec, &stext);
+       if (ret < 0)
+               return ret;
+
+       for (i = 0; i < ntevs && ret >= 0; i++) {
+               offset = tevs[i].point.address - stext;
+               offset += tevs[i].point.offset;
+               tevs[i].point.offset = 0;
+               free(tevs[i].point.symbol);
+               ret = e_snprintf(buf, 32, "0x%lx", offset);
+               if (ret < 0)
+                       break;
+               tevs[i].point.module = strdup(exec);
+               tevs[i].point.symbol = strdup(buf);
+               if (!tevs[i].point.symbol || !tevs[i].point.module) {
+                       ret = -ENOMEM;
+                       break;
+               }
+               tevs[i].uprobes = true;
+       }
+
+       return ret;
+}
+
 static int add_module_to_probe_trace_events(struct probe_trace_event *tevs,
                                            int ntevs, const char *module)
 {
@@ -305,15 +416,6 @@ static int try_to_find_probe_trace_events(struct perf_probe_event *pev,
        struct debuginfo *dinfo;
        int ntevs, ret = 0;
 
-       if (pev->uprobes) {
-               if (need_dwarf) {
-                       pr_warning("Debuginfo-analysis is not yet supported"
-                                       " with -x/--exec option.\n");
-                       return -ENOSYS;
-               }
-               return convert_name_to_addr(pev, target);
-       }
-
        dinfo = open_debuginfo(target);
 
        if (!dinfo) {
@@ -332,9 +434,14 @@ static int try_to_find_probe_trace_events(struct perf_probe_event *pev,
 
        if (ntevs > 0) {        /* Succeeded to find trace events */
                pr_debug("find %d probe_trace_events.\n", ntevs);
-               if (target)
-                       ret = add_module_to_probe_trace_events(*tevs, ntevs,
-                                                              target);
+               if (target) {
+                       if (pev->uprobes)
+                               ret = add_exec_to_probe_trace_events(*tevs,
+                                                ntevs, target);
+                       else
+                               ret = add_module_to_probe_trace_events(*tevs,
+                                                ntevs, target);
+               }
                return ret < 0 ? ret : ntevs;
        }
 
@@ -654,9 +761,6 @@ static int try_to_find_probe_trace_events(struct perf_probe_event *pev,
                return -ENOSYS;
        }
 
-       if (pev->uprobes)
-               return convert_name_to_addr(pev, target);
-
        return 0;
 }
 
@@ -1913,14 +2017,29 @@ static int convert_to_probe_trace_events(struct perf_probe_event *pev,
                                          int max_tevs, const char *target)
 {
        struct symbol *sym;
-       int ret = 0, i;
+       int ret, i;
        struct probe_trace_event *tev;
 
+       if (pev->uprobes && !pev->group) {
+               /* Replace group name if not given */
+               ret = convert_exec_to_group(target, &pev->group);
+               if (ret != 0) {
+                       pr_warning("Failed to make a group name.\n");
+                       return ret;
+               }
+       }
+
        /* Convert perf_probe_event with debuginfo */
        ret = try_to_find_probe_trace_events(pev, tevs, max_tevs, target);
        if (ret != 0)
                return ret;     /* Found in debuginfo or got an error */
 
+       if (pev->uprobes) {
+               ret = convert_name_to_addr(pev, target);
+               if (ret < 0)
+                       return ret;
+       }
+
        /* Allocate trace event buffer */
        tev = *tevs = zalloc(sizeof(struct probe_trace_event));
        if (tev == NULL)
index f9f3de8b4220b9c4989016f212192cb48cd83aeb..d481c46e079677c149c0a125333c3b978208bd87 100644 (file)
@@ -12,6 +12,7 @@ struct probe_trace_point {
        char            *symbol;        /* Base symbol */
        char            *module;        /* Module name */
        unsigned long   offset;         /* Offset from symbol */
+       unsigned long   address;        /* Actual address of the trace point */
        bool            retprobe;       /* Return probe flag */
 };
 
index ffb657ffd327b1779b328f4546f4d45c0784e243..7db7e05ccb89f6b7e697e00bcd3f8b15e9bee10a 100644 (file)
@@ -729,6 +729,7 @@ static int convert_to_trace_point(Dwarf_Die *sp_die, Dwfl_Module *mod,
                return -ENOENT;
        }
        tp->offset = (unsigned long)(paddr - sym.st_value);
+       tp->address = (unsigned long)paddr;
        tp->symbol = strdup(symbol);
        if (!tp->symbol)
                return -ENOMEM;