perf probe: Fix to handle aliased symbols in glibc
authorMasami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
Fri, 6 Mar 2015 07:31:20 +0000 (16:31 +0900)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Thu, 12 Mar 2015 15:39:52 +0000 (12:39 -0300)
Fix perf probe to handle aliased symbols correctly in glibc.  In the
glibc, several symbols are defined as an alias of __libc_XXX, e.g.
malloc is an alias of __libc_malloc.

In such cases, dwarf has no subroutine instances of the alias functions
(e.g. no "malloc" instance), but the map has that symbol and its
address.

Thus, if we search the alieased symbol in debuginfo, we always fail to
find it, but it is in the map.

To solve this problem, this fails back to address-based alternative
search, which searches the symbol in the map, translates its address to
alternative (correct) function name by using debuginfo, and retry to
find the alternative function point from debuginfo.

This adds fail-back process to --vars, --lines and --add options. So,
now you can use those on malloc@libc :)

Without this patch;
  -----
  # ./perf probe -x /usr/lib64/libc-2.17.so -V malloc
  Failed to find the address of malloc
    Error: Failed to show vars.
  # ./perf probe -x /usr/lib64/libc-2.17.so -a "malloc bytes"
  Probe point 'malloc' not found in debuginfo.
    Error: Failed to add events.
  -----

With this patch;
  -----
  # ./perf probe -x /usr/lib64/libc-2.17.so -V malloc
  Available variables at malloc
          @<__libc_malloc+0>
                  size_t  bytes
  # ./perf probe -x /usr/lib64/libc-2.17.so -a "malloc bytes"
  Added new event:
    probe_libc:malloc    (on malloc in /usr/lib64/libc-2.17.so with bytes)

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

          perf record -e probe_libc:malloc -aR sleep 1
  -----

Reported-by: Arnaldo Carvalho de Melo <acme@kernel.org>
Signed-off-by: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
Cc: David Ahern <dsahern@gmail.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Naohiro Aota <naota@elisp.net>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: http://lkml.kernel.org/r/20150306073120.6904.13779.stgit@localhost.localdomain
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
tools/perf/util/probe-event.c

index 1c570c2..b8f4578 100644 (file)
@@ -178,6 +178,25 @@ static struct map *kernel_get_module_map(const char *module)
        return NULL;
 }
 
+static struct map *get_target_map(const char *target, bool user)
+{
+       /* Init maps of given executable or kernel */
+       if (user)
+               return dso__new_map(target);
+       else
+               return kernel_get_module_map(target);
+}
+
+static void put_target_map(struct map *map, bool user)
+{
+       if (map && user) {
+               /* Only the user map needs to be released */
+               dso__delete(map->dso);
+               map__delete(map);
+       }
+}
+
+
 static struct dso *kernel_get_module_dso(const char *module)
 {
        struct dso *dso;
@@ -249,6 +268,13 @@ out:
        return ret;
 }
 
+static void clear_perf_probe_point(struct perf_probe_point *pp)
+{
+       free(pp->file);
+       free(pp->function);
+       free(pp->lazy_line);
+}
+
 static void clear_probe_trace_events(struct probe_trace_event *tevs, int ntevs)
 {
        int i;
@@ -258,6 +284,74 @@ static void clear_probe_trace_events(struct probe_trace_event *tevs, int ntevs)
 }
 
 #ifdef HAVE_DWARF_SUPPORT
+/*
+ * Some binaries like glibc have special symbols which are on the symbol
+ * table, but not in the debuginfo. If we can find the address of the
+ * symbol from map, we can translate the address back to the probe point.
+ */
+static int find_alternative_probe_point(struct debuginfo *dinfo,
+                                       struct perf_probe_point *pp,
+                                       struct perf_probe_point *result,
+                                       const char *target, bool uprobes)
+{
+       struct map *map = NULL;
+       struct symbol *sym;
+       u64 address = 0;
+       int ret = -ENOENT;
+
+       /* This can work only for function-name based one */
+       if (!pp->function || pp->file)
+               return -ENOTSUP;
+
+       map = get_target_map(target, uprobes);
+       if (!map)
+               return -EINVAL;
+
+       /* Find the address of given function */
+       map__for_each_symbol_by_name(map, pp->function, sym) {
+               if (sym->binding == STB_GLOBAL || sym->binding == STB_LOCAL) {
+                       address = sym->start;
+                       break;
+               }
+       }
+       if (!address) {
+               ret = -ENOENT;
+               goto out;
+       }
+       pr_debug("Symbol %s address found : %lx\n", pp->function, address);
+
+       ret = debuginfo__find_probe_point(dinfo, (unsigned long)address,
+                                         result);
+       if (ret <= 0)
+               ret = (!ret) ? -ENOENT : ret;
+       else {
+               result->offset += pp->offset;
+               result->line += pp->line;
+               ret = 0;
+       }
+
+out:
+       put_target_map(map, uprobes);
+       return ret;
+
+}
+
+static int get_alternative_probe_event(struct debuginfo *dinfo,
+                                      struct perf_probe_event *pev,
+                                      struct perf_probe_point *tmp,
+                                      const char *target)
+{
+       int ret;
+
+       memcpy(tmp, &pev->point, sizeof(*tmp));
+       memset(&pev->point, 0, sizeof(pev->point));
+       ret = find_alternative_probe_point(dinfo, tmp, &pev->point,
+                                          target, pev->uprobes);
+       if (ret < 0)
+               memcpy(&pev->point, tmp, sizeof(*tmp));
+
+       return ret;
+}
 
 /* Open new debuginfo of given module */
 static struct debuginfo *open_debuginfo(const char *module, bool silent)
@@ -466,6 +560,7 @@ static int try_to_find_probe_trace_events(struct perf_probe_event *pev,
                                          int max_tevs, const char *target)
 {
        bool need_dwarf = perf_probe_event_need_dwarf(pev);
+       struct perf_probe_point tmp;
        struct debuginfo *dinfo;
        int ntevs, ret = 0;
 
@@ -482,6 +577,20 @@ static int try_to_find_probe_trace_events(struct perf_probe_event *pev,
        /* Searching trace events corresponding to a probe event */
        ntevs = debuginfo__find_trace_events(dinfo, pev, tevs, max_tevs);
 
+       if (ntevs == 0) {  /* Not found, retry with an alternative */
+               ret = get_alternative_probe_event(dinfo, pev, &tmp, target);
+               if (!ret) {
+                       ntevs = debuginfo__find_trace_events(dinfo, pev,
+                                                            tevs, max_tevs);
+                       /*
+                        * Write back to the original probe_event for
+                        * setting appropriate (user given) event name
+                        */
+                       clear_perf_probe_point(&pev->point);
+                       memcpy(&pev->point, &tmp, sizeof(tmp));
+               }
+       }
+
        debuginfo__delete(dinfo);
 
        if (ntevs > 0) {        /* Succeeded to find trace events */
@@ -719,12 +828,13 @@ int show_line_range(struct line_range *lr, const char *module, bool user)
 static int show_available_vars_at(struct debuginfo *dinfo,
                                  struct perf_probe_event *pev,
                                  int max_vls, struct strfilter *_filter,
-                                 bool externs)
+                                 bool externs, const char *target)
 {
        char *buf;
        int ret, i, nvars;
        struct str_node *node;
        struct variable_list *vls = NULL, *vl;
+       struct perf_probe_point tmp;
        const char *var;
 
        buf = synthesize_perf_probe_point(&pev->point);
@@ -734,6 +844,15 @@ static int show_available_vars_at(struct debuginfo *dinfo,
 
        ret = debuginfo__find_available_vars_at(dinfo, pev, &vls,
                                                max_vls, externs);
+       if (!ret) {  /* Not found, retry with an alternative */
+               ret = get_alternative_probe_event(dinfo, pev, &tmp, target);
+               if (!ret) {
+                       ret = debuginfo__find_available_vars_at(dinfo, pev,
+                                               &vls, max_vls, externs);
+                       /* Release the old probe_point */
+                       clear_perf_probe_point(&tmp);
+               }
+       }
        if (ret <= 0) {
                if (ret == 0 || ret == -ENOENT) {
                        pr_err("Failed to find the address of %s\n", buf);
@@ -796,7 +915,7 @@ int show_available_vars(struct perf_probe_event *pevs, int npevs,
 
        for (i = 0; i < npevs && ret >= 0; i++)
                ret = show_available_vars_at(dinfo, &pevs[i], max_vls, _filter,
-                                            externs);
+                                            externs, module);
 
        debuginfo__delete(dinfo);
 out:
@@ -1742,15 +1861,12 @@ static int convert_to_perf_probe_event(struct probe_trace_event *tev,
 
 void clear_perf_probe_event(struct perf_probe_event *pev)
 {
-       struct perf_probe_point *pp = &pev->point;
        struct perf_probe_arg_field *field, *next;
        int i;
 
        free(pev->event);
        free(pev->group);
-       free(pp->file);
-       free(pp->function);
-       free(pp->lazy_line);
+       clear_perf_probe_point(&pev->point);
 
        for (i = 0; i < pev->nargs; i++) {
                free(pev->args[i].name);
@@ -2367,11 +2483,7 @@ static int find_probe_trace_events_from_map(struct perf_probe_event *pev,
        int num_matched_functions;
        int ret, i;
 
-       /* Init maps of given executable or kernel */
-       if (pev->uprobes)
-               map = dso__new_map(target);
-       else
-               map = kernel_get_module_map(target);
+       map = get_target_map(target, pev->uprobes);
        if (!map) {
                ret = -EINVAL;
                goto out;
@@ -2464,11 +2576,7 @@ static int find_probe_trace_events_from_map(struct perf_probe_event *pev,
        }
 
 out:
-       if (map && pev->uprobes) {
-               /* Only when using uprobe(exec) map needs to be released */
-               dso__delete(map->dso);
-               map__delete(map);
-       }
+       put_target_map(map, pev->uprobes);
        return ret;
 
 nomem_out: