perf unwind: Fix looking up dwarf unwind stack info
authorMatija Glavinic Pecotic <matija.glavinic-pecotic.ext@nokia.com>
Tue, 17 Jan 2017 14:50:35 +0000 (15:50 +0100)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Wed, 18 Jan 2017 15:29:52 +0000 (12:29 -0300)
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>
tools/perf/util/dso.c
tools/perf/util/unwind-libunwind-local.c

index d2c6cdd9d42b72a194d913bdc1840d5ff72bb281..28d41e709128f51414817ad21a16ea636c007709 100644 (file)
@@ -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[] = {
@@ -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;
index 6fec84dff3f777ac51f4f7201e2fb97d849d2aaf..bfb9b7987692ee583104c54f7756c43ca13cf52e 100644 (file)
@@ -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,
@@ -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;