Commit 9b118aca authored by Masami Hiramatsu's avatar Masami Hiramatsu Committed by Arnaldo Carvalho de Melo

perf probe: Fix to handle aliased symbols in glibc

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: default avatarArnaldo Carvalho de Melo <acme@kernel.org>
Signed-off-by: default avatarMasami 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.localdomainSigned-off-by: default avatarArnaldo Carvalho de Melo <acme@redhat.com>
parent 4a6b362f
...@@ -178,6 +178,25 @@ static struct map *kernel_get_module_map(const char *module) ...@@ -178,6 +178,25 @@ static struct map *kernel_get_module_map(const char *module)
return NULL; 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) static struct dso *kernel_get_module_dso(const char *module)
{ {
struct dso *dso; struct dso *dso;
...@@ -249,6 +268,13 @@ static int convert_exec_to_group(const char *exec, char **result) ...@@ -249,6 +268,13 @@ static int convert_exec_to_group(const char *exec, char **result)
return ret; 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) static void clear_probe_trace_events(struct probe_trace_event *tevs, int ntevs)
{ {
int i; int i;
...@@ -258,6 +284,74 @@ static void clear_probe_trace_events(struct probe_trace_event *tevs, int ntevs) ...@@ -258,6 +284,74 @@ static void clear_probe_trace_events(struct probe_trace_event *tevs, int ntevs)
} }
#ifdef HAVE_DWARF_SUPPORT #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 */ /* Open new debuginfo of given module */
static struct debuginfo *open_debuginfo(const char *module, bool silent) 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, ...@@ -466,6 +560,7 @@ static int try_to_find_probe_trace_events(struct perf_probe_event *pev,
int max_tevs, const char *target) int max_tevs, const char *target)
{ {
bool need_dwarf = perf_probe_event_need_dwarf(pev); bool need_dwarf = perf_probe_event_need_dwarf(pev);
struct perf_probe_point tmp;
struct debuginfo *dinfo; struct debuginfo *dinfo;
int ntevs, ret = 0; int ntevs, ret = 0;
...@@ -482,6 +577,20 @@ static int try_to_find_probe_trace_events(struct perf_probe_event *pev, ...@@ -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 */ /* Searching trace events corresponding to a probe event */
ntevs = debuginfo__find_trace_events(dinfo, pev, tevs, max_tevs); 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); debuginfo__delete(dinfo);
if (ntevs > 0) { /* Succeeded to find trace events */ 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) ...@@ -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, static int show_available_vars_at(struct debuginfo *dinfo,
struct perf_probe_event *pev, struct perf_probe_event *pev,
int max_vls, struct strfilter *_filter, int max_vls, struct strfilter *_filter,
bool externs) bool externs, const char *target)
{ {
char *buf; char *buf;
int ret, i, nvars; int ret, i, nvars;
struct str_node *node; struct str_node *node;
struct variable_list *vls = NULL, *vl; struct variable_list *vls = NULL, *vl;
struct perf_probe_point tmp;
const char *var; const char *var;
buf = synthesize_perf_probe_point(&pev->point); buf = synthesize_perf_probe_point(&pev->point);
...@@ -734,6 +844,15 @@ static int show_available_vars_at(struct debuginfo *dinfo, ...@@ -734,6 +844,15 @@ static int show_available_vars_at(struct debuginfo *dinfo,
ret = debuginfo__find_available_vars_at(dinfo, pev, &vls, ret = debuginfo__find_available_vars_at(dinfo, pev, &vls,
max_vls, externs); 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) {
if (ret == 0 || ret == -ENOENT) { if (ret == 0 || ret == -ENOENT) {
pr_err("Failed to find the address of %s\n", buf); 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, ...@@ -796,7 +915,7 @@ int show_available_vars(struct perf_probe_event *pevs, int npevs,
for (i = 0; i < npevs && ret >= 0; i++) for (i = 0; i < npevs && ret >= 0; i++)
ret = show_available_vars_at(dinfo, &pevs[i], max_vls, _filter, ret = show_available_vars_at(dinfo, &pevs[i], max_vls, _filter,
externs); externs, module);
debuginfo__delete(dinfo); debuginfo__delete(dinfo);
out: out:
...@@ -1742,15 +1861,12 @@ static int convert_to_perf_probe_event(struct probe_trace_event *tev, ...@@ -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) void clear_perf_probe_event(struct perf_probe_event *pev)
{ {
struct perf_probe_point *pp = &pev->point;
struct perf_probe_arg_field *field, *next; struct perf_probe_arg_field *field, *next;
int i; int i;
free(pev->event); free(pev->event);
free(pev->group); free(pev->group);
free(pp->file); clear_perf_probe_point(&pev->point);
free(pp->function);
free(pp->lazy_line);
for (i = 0; i < pev->nargs; i++) { for (i = 0; i < pev->nargs; i++) {
free(pev->args[i].name); free(pev->args[i].name);
...@@ -2367,11 +2483,7 @@ static int find_probe_trace_events_from_map(struct perf_probe_event *pev, ...@@ -2367,11 +2483,7 @@ static int find_probe_trace_events_from_map(struct perf_probe_event *pev,
int num_matched_functions; int num_matched_functions;
int ret, i; int ret, i;
/* Init maps of given executable or kernel */ map = get_target_map(target, pev->uprobes);
if (pev->uprobes)
map = dso__new_map(target);
else
map = kernel_get_module_map(target);
if (!map) { if (!map) {
ret = -EINVAL; ret = -EINVAL;
goto out; goto out;
...@@ -2464,11 +2576,7 @@ static int find_probe_trace_events_from_map(struct perf_probe_event *pev, ...@@ -2464,11 +2576,7 @@ static int find_probe_trace_events_from_map(struct perf_probe_event *pev,
} }
out: out:
if (map && pev->uprobes) { put_target_map(map, pev->uprobes);
/* Only when using uprobe(exec) map needs to be released */
dso__delete(map->dso);
map__delete(map);
}
return ret; return ret;
nomem_out: nomem_out:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment