]> git.ipfire.org Git - thirdparty/elfutils.git/commitdiff
libdw: Make dwarf_getfuncs find all (defining) DW_TAG_subprogram DIEs.
authorMark Wielaard <mjw@redhat.com>
Fri, 20 Sep 2013 13:50:42 +0000 (09:50 -0400)
committerMark Wielaard <mjw@redhat.com>
Tue, 24 Sep 2013 08:28:14 +0000 (10:28 +0200)
dwarf_getfuncs used to return only the DW_TAG_subprogram DIEs that were
direct children of the given CU. This is normally how GCC outputs the
subprogram DIEs. But not always. For nested functions the subprogram DIE
is placed under the code construct DIE where it is nested. Other compilers
might output the defining subprogram DIE of a C++ class function under
the DW_TAG_namespace DIE where it was defined. Both such constructs seem
allowed by the DWARF specification. So just searching the CU DIE children
was wrong.

To find all (defining) subprogram DIEs in a CU dwarf_getfuncs should
use __libdw_visit_scopes to walk the tree for all DIEs that can contain
subprograms as children. The only tricky part is making sure the offset
returned and used when the callback returns DWARF_CB_ABORT is correct
and the search continues at the right spot in the CU DIE tree. This
operation now needs to rewalk the whole tree.

Two new testcases were added that fail without this patch. And the
allfcts test was tweaked so that it always returns DWARF_CB_ABORT
from its callback to make sure the offset handling is correct.

Signed-off-by: Mark Wielaard <mjw@redhat.com>
libdw/ChangeLog
libdw/dwarf_getfuncs.c
libdw/libdw.h
tests/ChangeLog
tests/Makefile.am
tests/allfcts.c
tests/run-allfcts.sh
tests/testfile_class_func.bz2 [new file with mode: 0755]
tests/testfile_nested_funcs.bz2 [new file with mode: 0755]

index 1a8519489e856be835d1dde079053dc6c5fabec2..8e1dd92cb7f1411148ed7a2199e69477bc8a68fc 100644 (file)
@@ -1,3 +1,10 @@
+2013-09-20  Mark Wielaard  <mjw@redhat.com>
+
+       * dwarf_getfuncs.c (visitor_info): New struct.
+       (tree_visitor): New function.
+       (dwarf_getfuncs): Use __libdw_visit_scopes with tree_visitor.
+       * libdw.h (dwarf_getfuncs): Expand function documentation.
+
 2013-09-12  Mark Wielaard  <mjw@redhat.com>
 
        * fde.c (intern_fde): Free fde and set libdw errno when start
index afc5b6ebfc025f207e1c45b4ba700bd563e899a0..87e0341aa99b8fe8ead7f9ae6a49579bc72009b2 100644 (file)
@@ -1,5 +1,5 @@
 /* Get function information.
-   Copyright (C) 2005 Red Hat, Inc.
+   Copyright (C) 2005, 2013 Red Hat, Inc.
    This file is part of elfutils.
    Written by Ulrich Drepper <drepper@redhat.com>, 2005.
 
 #include "libdwP.h"
 
 
+struct visitor_info
+{
+  /* The user callback of dwarf_getfuncs.  */
+  int (*callback) (Dwarf_Die *, void *);
+
+  /* The user arg value to dwarf_getfuncs.  */
+  void *arg;
+
+  /* The DIE offset where to (re)start the search.  Zero for all.  */
+  Dwarf_Off start_offset;
+
+  /* Last subprogram DIE offset seen.  */
+  Dwarf_Off last_offset;
+
+  /* The CU only contains C functions.  Allows pruning of most subtrees.  */
+  bool c_cu;
+};
+
+static int
+tree_visitor (unsigned int depth __attribute__ ((unused)),
+             struct Dwarf_Die_Chain *chain, void *arg)
+{
+  struct visitor_info *const v = arg;
+  Dwarf_Die *die = &chain->die;
+  Dwarf_Off start_offset = v->start_offset;
+  Dwarf_Off die_offset = INTUSE(dwarf_dieoffset) (die);
+
+  /* Pure C CUs can only contain defining subprogram DIEs as direct
+     children of the CU DIE or as nested function inside a normal C
+     code constructs.  */
+  int tag = INTUSE(dwarf_tag) (die);
+  if (v->c_cu
+      && tag != DW_TAG_subprogram
+      && tag != DW_TAG_lexical_block
+      && tag != DW_TAG_inlined_subroutine)
+    {
+      chain->prune = true;
+      return DWARF_CB_OK;
+    }
+
+  /* Skip all DIEs till we found the (re)start offset.  */
+  if (start_offset != 0)
+    {
+      if (die_offset == start_offset)
+       v->start_offset = 0;
+      return DWARF_CB_OK;
+    }
+
+  /* If this isn't a (defining) subprogram entity, skip DIE.  */
+  if (tag != DW_TAG_subprogram
+      || INTUSE(dwarf_hasattr) (die, DW_AT_declaration))
+    return DWARF_CB_OK;
+
+  v->last_offset = die_offset;
+  return (*v->callback) (die, v->arg);
+}
+
 ptrdiff_t
 dwarf_getfuncs (Dwarf_Die *cudie, int (*callback) (Dwarf_Die *, void *),
                void *arg, ptrdiff_t offset)
@@ -43,31 +100,18 @@ dwarf_getfuncs (Dwarf_Die *cudie, int (*callback) (Dwarf_Die *, void *),
                || INTUSE(dwarf_tag) (cudie) != DW_TAG_compile_unit))
     return -1;
 
-  Dwarf_Die die_mem;
-  Dwarf_Die *die;
+  int lang = INTUSE(dwarf_srclang) (cudie);
+  bool c_cu = (lang == DW_LANG_C89
+              || lang == DW_LANG_C
+              || lang == DW_LANG_C99);
 
-  int res;
-  if (offset == 0)
-    res = INTUSE(dwarf_child) (cudie, &die_mem);
-  else
-    {
-      die = INTUSE(dwarf_offdie) (cudie->cu->dbg, offset, &die_mem);
-      res = INTUSE(dwarf_siblingof) (die, &die_mem);
-    }
-  die = res != 0 ? NULL : &die_mem;
+  struct visitor_info v = { callback, arg, offset, 0, c_cu };
+  struct Dwarf_Die_Chain chain = { .die = CUDIE (cudie->cu),
+                                  .parent = NULL };
+  int res = __libdw_visit_scopes (0, &chain, &tree_visitor, NULL, &v);
 
-  while (die != NULL)
-    {
-      if (INTUSE(dwarf_tag) (die) == DW_TAG_subprogram)
-       {
-         if (callback (die, arg) != DWARF_CB_OK)
-           return INTUSE(dwarf_dieoffset) (die);
-       }
-
-      if (INTUSE(dwarf_siblingof) (die, &die_mem) != 0)
-       break;
-    }
-
-  /* That's all.  */
-  return 0;
+  if (res == DWARF_CB_ABORT)
+    return v.last_offset;
+  else
+    return res;
 }
index d1cd1775c371d644d1bd3d876d2f2555292f185f..0d94c526c027ecd528b20c2ddd47c3505edc1d1d 100644 (file)
@@ -747,7 +747,16 @@ extern Dwarf_Arange *dwarf_getarange_addr (Dwarf_Aranges *aranges,
 
 
 
-/* Get functions in CUDIE.  */
+/* Get functions in CUDIE.  The given callback will be called for all
+   defining DW_TAG_subprograms in the CU DIE tree.  If the callback
+   returns DWARF_CB_ABORT the return value can be used as offset argument
+   to resume the function to find all remaining functions (this is not
+   really recommended, since it needs to rewalk the CU DIE tree first till
+   that offset is found again).  If the callback returns DWARF_CB_OK
+   dwarf_getfuncs will not return but keep calling the callback for each
+   function DIE it finds.  Pass zero for offset on the first call to walk
+   the full CU DIE tree.  If no more functions can be found and the callback
+   returned DWARF_CB_OK then the function returns zero.  */
 extern ptrdiff_t dwarf_getfuncs (Dwarf_Die *cudie,
                                 int (*callback) (Dwarf_Die *, void *),
                                 void *arg, ptrdiff_t offset);
index 9ea285f778eb92cbc7ac8e4afc064e9e425385af..34cffd42e645b25d255778cd762dc43ea31e7f42 100644 (file)
@@ -1,3 +1,13 @@
+2013-09-20  Mark Wielaard  <mjw@redhat.com>
+
+       * allfcts.c (cb): Return DWARF_CB_ABORT.
+       (main): Iterate over all offsets returned by dwarf_getfuncs.
+       * run-allfcts.sh: Add nested_funcs and class_func testcases.
+       * testfile_nested_funcs.bz2: New test file.
+       * testfile_class_func.bz2: Likewise.
+       * Makefile.am (EXTRA_DIST): Add testfile_class_func.bz2 and
+       testfile_nested_funcs.bz2.
+
 2013-08-30  Mark Wielaard  <mjw@redhat.com>
 
        * Makefile.am (check_PROGRAMS): Add varlocs.
index e06d914d5365cc3431962da36b5b832d1b1fe6c9..58db6c361c92924d562067e1ba596e706bb77a8a 100644 (file)
@@ -119,6 +119,7 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh \
             testfile5.bz2 testfile6.bz2 testfile7.bz2 testfile8.bz2 \
             testfile9.bz2 testfile10.bz2 testfile11.bz2 testfile12.bz2 \
             testfile13.bz2 run-strip-test3.sh run-allfcts.sh \
+            testfile_class_func.bz2 testfile_nested_funcs.bz2 \
             run-line2addr.sh run-elflint-test.sh testfile14.bz2 \
             run-strip-test4.sh run-strip-test5.sh run-strip-test6.sh \
             run-strip-test7.sh run-strip-test8.sh run-strip-groups.sh \
index f14b49355a0a6064ab4fda5a996adf7b96725c31..7803722f5ee9b1959050d7da8c231bad209d9c55 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2005 Red Hat, Inc.
+/* Copyright (C) 2005, 2013 Red Hat, Inc.
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -34,7 +34,7 @@ cb (Dwarf_Die *func, void *arg __attribute__ ((unused)))
 
   printf ("%s:%d:%s\n", file, line, fct);
 
-  return DWARF_CB_OK;
+  return DWARF_CB_ABORT;
 }
 
 
@@ -57,7 +57,13 @@ main (int argc, char *argv[])
              Dwarf_Die die_mem;
              Dwarf_Die *die = dwarf_offdie (dbg, off + cuhl, &die_mem);
 
-             (void) dwarf_getfuncs (die, cb, NULL, 0);
+             /* Explicitly stop in the callback and then resume each time.  */
+             ptrdiff_t doff = 0;
+             do
+               {
+                 doff = dwarf_getfuncs (die, cb, NULL, doff);
+               }
+             while (doff > 0);
 
              off = noff;
            }
index 30f7dd4d42b007a0f77ee9abfa6b85e5c6c4ae31..6eaf13c8b11167899db04d5bcf7409207c601be1 100755 (executable)
@@ -1,5 +1,5 @@
 #! /bin/sh
-# Copyright (C) 2005 Red Hat, Inc.
+# Copyright (C) 2005, 2013 Red Hat, Inc.
 # This file is part of elfutils.
 # Written by Ulrich Drepper <drepper@redhat.com>, 2005.
 #
@@ -37,4 +37,58 @@ testrun_compare ${abs_builddir}/allfcts testfile testfile2 testfile8 <<\EOF
 /home/drepper/gnu/elfutils/build/src/../../src/strip.c:313:handle_elf
 EOF
 
+# = nested_funcs.c =
+#
+# static int
+# foo (int x)
+# {
+#   int bar (int y)
+#   {
+#     return x - y;
+#   }
+# 
+#   return bar (x * 2);
+# }
+#
+# int
+# main (int argc, char ** argv)
+# {
+#   return foo (argc);
+# }
+#
+# gcc -g -o nested_funcs nested_funcs.c
+
+# = class_func.cxx =
+#
+# namespace foobar
+# {
+#   class Foo
+#   {
+#   public:
+#     int bar(int x);
+#   };
+#
+#   int Foo::bar(int x) { return x - 42; }
+# };
+#
+# int
+# main (int argc, char **argv)
+# {
+#   foobar::Foo foo;
+#
+#   return foo.bar (42);
+# }
+#
+# clang++ -g -o class_func class_func.cxx
+
+testfiles testfile_nested_funcs testfile_class_func
+
+testrun_compare ${abs_builddir}/allfcts testfile_nested_funcs testfile_class_func <<\EOF
+/home/mark/src/tests/nested/nested_funcs.c:2:foo
+/home/mark/src/tests/nested/nested_funcs.c:4:bar
+/home/mark/src/tests/nested/nested_funcs.c:13:main
+/home/mark/src/tests/nested/class_func.cxx:6:bar
+/home/mark/src/tests/nested/class_func.cxx:13:main
+EOF
+
 exit 0
diff --git a/tests/testfile_class_func.bz2 b/tests/testfile_class_func.bz2
new file mode 100755 (executable)
index 0000000..e40dcf2
Binary files /dev/null and b/tests/testfile_class_func.bz2 differ
diff --git a/tests/testfile_nested_funcs.bz2 b/tests/testfile_nested_funcs.bz2
new file mode 100755 (executable)
index 0000000..d36b603
Binary files /dev/null and b/tests/testfile_nested_funcs.bz2 differ