Skip to content

Commit

Permalink
perf unwind: Fix looking up dwarf unwind stack info
Browse files Browse the repository at this point in the history
Using perf with call graph method dwarf fails to provide backtrace
support for stripped binary even though .gnu_debuglink points to *.dbg
flavor with properly populated debug symbols.

Problem is reproduced on ARM (v7, v8), kernels 3.14.y, 4.4.y and
4.10.rc3.  Perf is configured with libunwind, and unwind dwarf support
[1]. Test code (stress_bt.c) can be found on [2].

Running (explicitly disable other unwinding methods):

  $ gcc -g -o stress_bt -fomit-frame-pointer -fno-unwind-tables \
	-fno-asynchronous-unwind-tables stress_bt.c
  $ perf record -N --call-graph dwarf ./stress_bt
  $ perf report

results in properly generated call graph. Stripping the binary and running
it results with missing call graph. Expected result is to have call graph:

  $ gcc -g -o stress_bt -fomit-frame-pointer -fno-unwind-tables \
	  -fno-asynchronous-unwind-tables stress_bt.c
  $ objcopy --only-keep-debug stress_bt stress_bt.dbg
  $ objcopy --strip-debug stress_bt
  $ objcopy --add-gnu-debuglink=stress_bt.dbg stress_bt
  $ perf record -N --call-graph dwarf ./stress_bt
  $ perf report

Problem is that perf doesn't try to read symbols pointed by gnu
debuglink.  Patch adds checking, and reading of the symbols from
debuglink and symsrc.  Order of the check is to first check within dso,
then check whether symsrc is defined and try to read from it. Finally,
debuglink is checked. Default locations of debug files are discussed in
[3] and [4]. Comments on RFC are on [5].

[1] https://wiki.linaro.org/LEG/Engineering/TOOLS/perf-callstack-unwinding
[2] [1]#Backtrace_stress_application
[3] https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
[4] https://sourceware.org/binutils/docs/binutils/objcopy.html
[5] https://lkml.org/lkml/2016/8/22/473

Signed-off-by: Matija Glavinic Pecotic <matija.glavinic-pecotic.ext@nokia.com>
Acked-by: Jiri Olsa <jolsa@kernel.org>
Cc: Alexander Sverdlin <alexander.sverdlin@nokia.com>
Cc: Masami Hiramatsu <mhiramat@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Link: http://lkml.kernel.org/r/d309d40a-463f-482b-68e1-1465326efdc1@nokia.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
  • Loading branch information
Matija Glavinic Pecotic authored and Arnaldo Carvalho de Melo committed Jan 18, 2017
1 parent 9f6f941 commit 9343e45
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 16 deletions.
48 changes: 37 additions & 11 deletions tools/perf/util/dso.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
#include "debug.h"
#include "vdso.h"

static const char * const debuglink_paths[] = {
"%.0s%s",
"%s/%s",
"%s/.debug/%s",
"/usr/lib/debug%s/%s"
};

char dso__symtab_origin(const struct dso *dso)
{
static const char origin[] = {
Expand Down Expand Up @@ -44,24 +51,43 @@ int dso__read_binary_type_filename(const struct dso *dso,
size_t len;

switch (type) {
case DSO_BINARY_TYPE__DEBUGLINK: {
char *debuglink;
case DSO_BINARY_TYPE__DEBUGLINK:
{
const char *last_slash;
char dso_dir[PATH_MAX];
char symfile[PATH_MAX];
unsigned int i;

len = __symbol__join_symfs(filename, size, dso->long_name);
debuglink = filename + len;
while (debuglink != filename && *debuglink != '/')
debuglink--;
if (*debuglink == '/')
debuglink++;
last_slash = filename + len;
while (last_slash != filename && *last_slash != '/')
last_slash--;

ret = -1;
if (!is_regular_file(filename))
strncpy(dso_dir, filename, last_slash - filename);
dso_dir[last_slash-filename] = '\0';

if (!is_regular_file(filename)) {
ret = -1;
break;
}

ret = filename__read_debuglink(filename, symfile, PATH_MAX);
if (ret)
break;

ret = filename__read_debuglink(filename, debuglink,
size - (debuglink - filename));
/* Check predefined locations where debug file might reside */
ret = -1;
for (i = 0; i < ARRAY_SIZE(debuglink_paths); i++) {
snprintf(filename, size,
debuglink_paths[i], dso_dir, symfile);
if (is_regular_file(filename)) {
ret = 0;
break;
}
}

break;
}
case DSO_BINARY_TYPE__BUILD_ID_CACHE:
if (dso__build_id_filename(dso, filename, size) == NULL)
ret = -1;
Expand Down
54 changes: 49 additions & 5 deletions tools/perf/util/unwind-libunwind-local.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "util.h"
#include "debug.h"
#include "asm/bug.h"
#include "dso.h"

extern int
UNW_OBJ(dwarf_search_unwind_table) (unw_addr_space_t as,
Expand Down Expand Up @@ -297,15 +298,58 @@ static int read_unwind_spec_debug_frame(struct dso *dso,
int fd;
u64 ofs = dso->data.debug_frame_offset;

/* debug_frame can reside in:
* - dso
* - debug pointed by symsrc_filename
* - gnu_debuglink, which doesn't necessary
* has to be pointed by symsrc_filename
*/
if (ofs == 0) {
fd = dso__data_get_fd(dso, machine);
if (fd < 0)
return -EINVAL;
if (fd >= 0) {
ofs = elf_section_offset(fd, ".debug_frame");
dso__data_put_fd(dso);
}

if (ofs <= 0) {
fd = open(dso->symsrc_filename, O_RDONLY);
if (fd >= 0) {
ofs = elf_section_offset(fd, ".debug_frame");
close(fd);
}
}

if (ofs <= 0) {
char *debuglink = malloc(PATH_MAX);
int ret = 0;

ret = dso__read_binary_type_filename(
dso, DSO_BINARY_TYPE__DEBUGLINK,
machine->root_dir, debuglink, PATH_MAX);
if (!ret) {
fd = open(debuglink, O_RDONLY);
if (fd >= 0) {
ofs = elf_section_offset(fd,
".debug_frame");
close(fd);
}
}
if (ofs > 0) {
if (dso->symsrc_filename != NULL) {
pr_warning(
"%s: overwrite symsrc(%s,%s)\n",
__func__,
dso->symsrc_filename,
debuglink);
free(dso->symsrc_filename);
}
dso->symsrc_filename = debuglink;
} else {
free(debuglink);
}
}

/* Check the .debug_frame section for unwinding info */
ofs = elf_section_offset(fd, ".debug_frame");
dso->data.debug_frame_offset = ofs;
dso__data_put_fd(dso);
}

*offset = ofs;
Expand Down

0 comments on commit 9343e45

Please sign in to comment.