[PATCH] libdw: Add dwarf_die_addr_die function.

Mark Wielaard mark@klomp.org
Thu Jan 25 15:07:00 GMT 2018


Currently storing a lot of Dwarf_Dies might be inefficient since it
costs a lot of memory since the sizeof (Dwarf_Die) == 32 bytes on 64bit
arches. You can try storing just the Dwarf_Off from dwarf_dieoffset.
Which is just 8 bytes. But then you have to keep track of whether to
call dwarf_dieoffset, if the Dwarf_Die came from the main .debug_info,
or call dwarf_dieoffset_types, if it came from .debug_types. And you'll
have to keep track of whether it came from the main Dwarf or the alt
Dwarf (dwz multi file). With DWARF5 or GNU DebugFission split-dwarf
you will also need to know which split Dwarf file the original DIE
came from.

A Dwarf_Die consists of an addr pointer where the actual DIE data
comes from, a CU pointer that provides context (and has a pointer
to the Dwarf file the Die is associated with) and a (cached)
Dwarf_Abbrev pointer that is initialized when the Dwarf_Die is
first read and describes how to interpret the DIE data.

libdw already keeps track of the data pointers (sections) of a
Dwarf file and given an offset it can already reconstruct the
other Dwarf_Die fields. So this patch introduces dwarf_die_addr_die.
Given a Dwarf_Die addr dwarf_die_addr_die returns a (reconstructed)
Dwarf_Die, or NULL if the given addr didn't come from a valid
Dwarf_Die. In particular it will make sure that the correct Dwarf_CU
pointer is set for the Dwarf_Die, the Dwarf_Abbrev pointer will not
be set up yet (it will only be once the Dwarf_Die is used to read
attributes, children or siblings).

This functions can be used to keep a reference to a Dwarf_Die which
you want to refer to later. The addr, and the result of this function,
is only valid while the associated Dwarf is valid.

Since libdw already had to lookup the Dwarf_CU given an offset, this
function is as efficient as dwarf_dieoffset (or dwarf_dieoffset_types)
without having to know the original origin of the Dwarf_Die. It will
search both the .debug_info and .debug_types data sections from both
the main Dwarf or the alt Dwarf file. Once split dwarf support is added
it will also look in any split dwarf .dwo (or the .dwp) file.

The only limitation, compared to using a Dwarf_Off and dwarf_dieoffset,
is that it only works during runtime while the main Dwarf object is
valid (till dwarf_end has been called on it).

Signed-off-by: Mark Wielaard <mark@klomp.org>
---
 libdw/ChangeLog                 |   9 +++
 libdw/Makefile.am               |   3 +-
 libdw/dwarf_die_addr_die.c      |  58 ++++++++++++++
 libdw/libdw.h                   |  12 +++
 libdw/libdw.map                 |   5 ++
 libdw/libdwP.h                  |   4 +
 libdw/libdw_findcu.c            |  35 +++++++-
 tests/ChangeLog                 |   9 +++
 tests/Makefile.am               |   9 ++-
 tests/dwarf-die-addr-die.c      | 172 ++++++++++++++++++++++++++++++++++++++++
 tests/run-dwarf-die-addr-die.sh |  38 +++++++++
 11 files changed, 349 insertions(+), 5 deletions(-)
 create mode 100644 libdw/dwarf_die_addr_die.c
 create mode 100644 tests/dwarf-die-addr-die.c
 create mode 100755 tests/run-dwarf-die-addr-die.sh

diff --git a/libdw/ChangeLog b/libdw/ChangeLog
index cb5e61a..37edcd7 100644
--- a/libdw/ChangeLog
+++ b/libdw/ChangeLog
@@ -1,3 +1,12 @@
+2018-01-25  Mark Wielaard  <mark@klomp.org>
+
+	* Makefile.am (libdw_a_SOURCES): Add dwarf_die_addr_die.c.
+	* dwarf_die_addr_die.c: New file.
+	* libdw.h (dwarf_die_addr_die): New function declaration.
+	* libdw.map (ELFUTILS_0.171): New section with dwarf_die_addr_die.
+	* libdwP.h (__libdw_findcu_addr): New internal function declaration.
+	* libdw_findcu.c (__libdw_findcu_addr): New internal function.
+
 2018-01-22  Mark Wielaard  <mark@klomp.org>
 
 	* Makefile.am (AM_CPPFLAGS): Add -I libdwelf.
diff --git a/libdw/Makefile.am b/libdw/Makefile.am
index 8545b5b..b1da440 100644
--- a/libdw/Makefile.am
+++ b/libdw/Makefile.am
@@ -89,7 +89,8 @@ libdw_a_SOURCES = dwarf_begin.c dwarf_begin_elf.c dwarf_end.c dwarf_getelf.c \
 		  dwarf_aggregate_size.c dwarf_getlocation_implicit_pointer.c \
 		  dwarf_getlocation_die.c dwarf_getlocation_attr.c \
 		  dwarf_getalt.c dwarf_setalt.c dwarf_cu_getdwarf.c \
-		  dwarf_cu_die.c dwarf_peel_type.c dwarf_default_lower_bound.c
+		  dwarf_cu_die.c dwarf_peel_type.c dwarf_default_lower_bound.c \
+		  dwarf_die_addr_die.c
 
 if MAINTAINER_MODE
 BUILT_SOURCES = $(srcdir)/known-dwarf.h
diff --git a/libdw/dwarf_die_addr_die.c b/libdw/dwarf_die_addr_die.c
new file mode 100644
index 0000000..02d63b7
--- /dev/null
+++ b/libdw/dwarf_die_addr_die.c
@@ -0,0 +1,58 @@
+/* Return offset of DIE.
+   Copyright (C) 2018 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of either
+
+     * the GNU Lesser General Public License as published by the Free
+       Software Foundation; either version 3 of the License, or (at
+       your option) any later version
+
+   or
+
+     * the GNU General Public License as published by the Free
+       Software Foundation; either version 2 of the License, or (at
+       your option) any later version
+
+   or both in parallel, as here.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received copies of the GNU General Public License and
+   the GNU Lesser General Public License along with this program.  If
+   not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <dwarf.h>
+#include "libdwP.h"
+
+
+Dwarf_Die *
+dwarf_die_addr_die (Dwarf *dbg, void *addr, Dwarf_Die *result)
+{
+  if (dbg == NULL)
+    return NULL;
+
+  Dwarf_CU *cu = __libdw_findcu_addr (dbg, addr);
+
+  if (cu == NULL)
+    {
+      Dwarf *alt = INTUSE (dwarf_getalt) (dbg);
+      if (alt != NULL)
+	cu = __libdw_findcu_addr (alt, addr);
+    }
+
+  if (cu == NULL)
+    return NULL;
+
+  *result = (Dwarf_Die) { .addr = addr, .cu = cu };
+
+  return result;
+}
diff --git a/libdw/libdw.h b/libdw/libdw.h
index 1dcc815..d85d285 100644
--- a/libdw/libdw.h
+++ b/libdw/libdw.h
@@ -350,6 +350,18 @@ extern Dwarf_Die *dwarf_diecu (Dwarf_Die *die, Dwarf_Die *result,
 			       uint8_t *address_sizep, uint8_t *offset_sizep)
      __nonnull_attribute__ (2);
 
+/* Given a Dwarf_Die addr returns a (reconstructed) Dwarf_Die, or NULL
+   if the given addr didn't come from a valid Dwarf_Die.  In particular
+   it will make sure that the correct Dwarf_CU pointer is set for the
+   Dwarf_Die, the Dwarf_Abbrev pointer will not be set up yet (it will
+   only be once the Dwarf_Die is used to read attributes, children or
+   siblings).  This functions can be used to keep a reference to a
+   Dwarf_Die which you want to refer to later.  The addr, and the result
+   of this function, is only valid while the associated Dwarf is valid.  */
+extern Dwarf_Die *dwarf_die_addr_die (Dwarf *dbg, void *addr,
+				      Dwarf_Die *result)
+     __nonnull_attribute__ (3);
+
 /* Return the CU DIE and the header info associated with a Dwarf_Die
    or Dwarf_Attribute.  A Dwarf_Die or a Dwarf_Attribute is associated
    with a particular Dwarf_CU handle.  This function returns the CU or
diff --git a/libdw/libdw.map b/libdw/libdw.map
index 1430705..cdc63ce 100644
--- a/libdw/libdw.map
+++ b/libdw/libdw.map
@@ -344,3 +344,8 @@ ELFUTILS_0.170 {
     dwarf_default_lower_bound;
     dwarf_line_file;
 } ELFUTILS_0.167;
+
+ELFUTILS_0.171 {
+  global:
+    dwarf_die_addr_die;
+} ELFUTILS_0.170;
diff --git a/libdw/libdwP.h b/libdw/libdwP.h
index 0681aa1..a38dcfb 100644
--- a/libdw/libdwP.h
+++ b/libdw/libdwP.h
@@ -449,6 +449,10 @@ extern struct Dwarf_CU *__libdw_intern_next_unit (Dwarf *dbg, bool debug_types)
 extern struct Dwarf_CU *__libdw_findcu (Dwarf *dbg, Dwarf_Off offset, bool tu)
      __nonnull_attribute__ (1) internal_function;
 
+/* Find CU for given DIE address.  */
+extern struct Dwarf_CU *__libdw_findcu_addr (Dwarf *dbg, void *addr)
+     __nonnull_attribute__ (1) internal_function;
+
 /* Get abbreviation with given code.  */
 extern Dwarf_Abbrev *__libdw_findabbrev (struct Dwarf_CU *cu,
 					 unsigned int code)
diff --git a/libdw/libdw_findcu.c b/libdw/libdw_findcu.c
index 4e025e2..3ec1ce5 100644
--- a/libdw/libdw_findcu.c
+++ b/libdw/libdw_findcu.c
@@ -1,5 +1,5 @@
 /* Find CU for given offset.
-   Copyright (C) 2003-2010, 2014 Red Hat, Inc.
+   Copyright (C) 2003-2010, 2014, 2018 Red Hat, Inc.
    This file is part of elfutils.
    Written by Ulrich Drepper <drepper@redhat.com>, 2003.
 
@@ -167,3 +167,36 @@ __libdw_findcu (Dwarf *dbg, Dwarf_Off start, bool debug_types)
     }
   /* NOTREACHED */
 }
+
+struct Dwarf_CU *
+internal_function
+__libdw_findcu_addr (Dwarf *dbg, void *addr)
+{
+  void **tree;
+  Dwarf_Off start;
+  if (addr >= dbg->sectiondata[IDX_debug_info]->d_buf
+      && addr < (dbg->sectiondata[IDX_debug_info]->d_buf
+		 + dbg->sectiondata[IDX_debug_info]->d_size))
+    {
+      tree = &dbg->cu_tree;
+      start = addr - dbg->sectiondata[IDX_debug_info]->d_buf;
+    }
+  else if (dbg->sectiondata[IDX_debug_types] != NULL
+	   && addr >= dbg->sectiondata[IDX_debug_types]->d_buf
+	   && addr < (dbg->sectiondata[IDX_debug_types]->d_buf
+		      + dbg->sectiondata[IDX_debug_types]->d_size))
+    {
+      tree = &dbg->tu_tree;
+      start = addr - dbg->sectiondata[IDX_debug_types]->d_buf;
+    }
+  else
+    return NULL;
+
+  struct Dwarf_CU fake = { .start = start, .end = 0 };
+  struct Dwarf_CU **found = tfind (&fake, tree, findcu_cb);
+
+  if (found != NULL)
+    return *found;
+
+  return NULL;
+}
diff --git a/tests/ChangeLog b/tests/ChangeLog
index 89c531b..571a821 100644
--- a/tests/ChangeLog
+++ b/tests/ChangeLog
@@ -1,3 +1,12 @@
+2018-01-25  Mark Wielaard  <mark@klomp.org>
+
+	* Makefile.am (check_PROGRAMS): Add dwarf-die-addr-die.
+	(TESTS): Add run-dwarf-die-addr-die.sh.
+	(EXTRA_DIST): Likewise.
+	(dwarf_die_addr_die_LDADD): New variable.
+	* dwarf-die-addr-die.c: New file.
+	* run-dwarf-die-addr-die.sh: New test.
+
 2018-01-22  Mark Wielaard  <mark@klomp.org>
 
 	* allfcts.c (setup_alt): Print warning when alt file couldn't be
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 1fce447..fe6c8ec 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -55,7 +55,7 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \
 		  getsrc_die strptr newdata elfstrtab dwfl-proc-attach \
 		  elfshphehdr elfstrmerge dwelfgnucompressed elfgetchdr \
 		  elfgetzdata elfputzdata zstrptr emptyfile vendorelf \
-		  fillfile dwarf_default_lower_bound
+		  fillfile dwarf_default_lower_bound dwarf-die-addr-die
 
 asm_TESTS = asm-tst1 asm-tst2 asm-tst3 asm-tst4 asm-tst5 \
 	    asm-tst6 asm-tst7 asm-tst8 asm-tst9
@@ -137,7 +137,8 @@ TESTS = run-arextract.sh run-arsymtest.sh run-ar.sh newfile test-nlist \
 	run-elfgetzdata.sh run-elfputzdata.sh run-zstrptr.sh \
 	run-compress-test.sh \
 	run-readelf-zdebug.sh run-readelf-zdebug-rel.sh \
-	emptyfile vendorelf fillfile dwarf_default_lower_bound
+	emptyfile vendorelf fillfile dwarf_default_lower_bound \
+	run-dwarf-die-addr-die.sh
 
 if !BIARCH
 export ELFUTILS_DISABLE_BIARCH = 1
@@ -352,7 +353,8 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \
 	     run-zstrptr.sh run-compress-test.sh \
 	     run-disasm-bpf.sh \
 	     testfile-bpf-dis1.expect.bz2 testfile-bpf-dis1.o.bz2 \
-	     testfile-m68k-core.bz2 testfile-m68k.bz2 testfile-m68k-s.bz2
+	     testfile-m68k-core.bz2 testfile-m68k.bz2 testfile-m68k-s.bz2 \
+	     run-dwarf-die-addr-die.sh
 
 if USE_VALGRIND
 valgrind_cmd='valgrind -q --leak-check=full --error-exitcode=1'
@@ -510,6 +512,7 @@ emptyfile_LDADD = $(libelf)
 vendorelf_LDADD = $(libelf)
 fillfile_LDADD = $(libelf)
 dwarf_default_lower_bound_LDADD = $(libdw)
+dwarf_die_addr_die_LDADD = $(libdw)
 
 # We want to test the libelf header against the system elf.h header.
 # Don't include any -I CPPFLAGS.
diff --git a/tests/dwarf-die-addr-die.c b/tests/dwarf-die-addr-die.c
new file mode 100644
index 0000000..b4f6dbc
--- /dev/null
+++ b/tests/dwarf-die-addr-die.c
@@ -0,0 +1,172 @@
+/* Test program for dwarf_die_addr_die.
+   Copyright (C) 2018 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include ELFUTILS_HEADER(dw)
+#include <dwarf.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <inttypes.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+/* The main Dwarf file.  */
+static Dwarf *dwarf;
+
+int
+check_die (Dwarf_Die *die)
+{
+  if (dwarf_tag (die) == DW_TAG_invalid)
+    {
+      printf ("Invalid die\n");
+      return -1;
+    }
+
+  int res = 0;
+  void *addr = die->addr;
+  Dwarf_Die die2;
+  if (dwarf_die_addr_die (dwarf, addr, &die2) == NULL)
+    {
+      printf ("Bad die addr die at offset %" PRIx64 "\n",
+	      dwarf_dieoffset (die));
+      res = -1;
+    }
+
+  if (dwarf_tag (die) != dwarf_tag (&die2))
+    {
+      printf ("Tags differ for die at offset %" PRIx64 "\n",
+	      dwarf_dieoffset (die));
+      res = -1;
+    }
+
+  if (dwarf_cuoffset (die) != dwarf_cuoffset (&die2))
+    {
+      printf ("CU offsets differ for die at offset %" PRIx64 "\n",
+	      dwarf_dieoffset (die));
+      res = -1;
+    }
+
+  Dwarf_Die child;
+  if (dwarf_child (die, &child) == 0)
+    res |= check_die (&child);
+
+  Dwarf_Die sibling;
+  if (dwarf_siblingof (die, &sibling) == 0)
+    res |= check_die (&sibling);
+
+  return res;
+}
+
+int
+check_dbg (Dwarf *dbg)
+{
+  int res = 0;
+  Dwarf_Off off = 0;
+  Dwarf_Off old_off = 0;
+  size_t hsize;
+  Dwarf_Off abbrev;
+  uint8_t addresssize;
+  uint8_t offsetsize;
+  while (dwarf_nextcu (dbg, off, &off, &hsize, &abbrev, &addresssize,
+                       &offsetsize) == 0)
+    {
+      Dwarf_Die die;
+      if (dwarf_offdie (dbg, old_off + hsize, &die) != NULL)
+	{
+	  printf ("checking CU at %" PRIx64 "\n", old_off);
+	  res |= check_die (&die);
+	}
+
+      old_off = off;
+    }
+
+  // Same for type...
+  Dwarf_Half version;
+  uint64_t typesig;
+  Dwarf_Off typeoff;
+  off = 0;
+  old_off = 0;
+  while (dwarf_next_unit (dbg, off, &off, &hsize, &version, &abbrev,
+			  &addresssize, &offsetsize, &typesig, &typeoff) == 0)
+    {
+      Dwarf_Die die;
+      if (dwarf_offdie_types (dbg, old_off + hsize, &die) != NULL)
+	{
+	  printf ("checking TU at %" PRIx64 "\n", old_off);
+	  res |= check_die (&die);
+	}
+
+      // We should have seen this already, but double check...
+      if (dwarf_offdie_types (dbg, old_off + typeoff, &die) != NULL)
+	{
+	  printf ("checking Type DIE at %" PRIx64 "\n",
+		  old_off + hsize + typeoff);
+	  res |= check_die (&die);
+	}
+
+      old_off = off;
+    }
+
+  Dwarf *alt = dwarf_getalt (dbg);
+  if (alt != NULL)
+    {
+      printf ("checking alt debug\n");
+      res |= check_dbg (alt);
+    }
+
+  return res;
+}
+
+int
+main (int argc, char *argv[])
+{
+  if (argc < 2)
+    {
+      printf ("No file given.\n");
+      return -1;
+    }
+
+  const char *name = argv[1];
+  int fd = open (name, O_RDONLY);
+  if (fd < 0)
+    {
+      printf ("Cannnot open '%s': %s\n", name, strerror (errno));
+      return -1;
+    }
+
+  dwarf = dwarf_begin (fd, DWARF_C_READ);
+  if (dwarf == NULL)
+    {
+      printf ("Not a Dwarf file '%s': %s\n", name, dwarf_errmsg (-1));
+      close (fd);
+      return -1;
+    }
+
+  printf ("checking %s\n", name);
+  int res = check_dbg (dwarf);
+
+  dwarf_end (dwarf);
+  close (fd);
+
+  return res;
+}
diff --git a/tests/run-dwarf-die-addr-die.sh b/tests/run-dwarf-die-addr-die.sh
new file mode 100755
index 0000000..16fe7b0
--- /dev/null
+++ b/tests/run-dwarf-die-addr-die.sh
@@ -0,0 +1,38 @@
+#! /bin/sh
+# Copyright (C) 2012, 2015 Red Hat, Inc.
+# This file is part of elfutils.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# elfutils is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+. $srcdir/test-subr.sh
+
+# See run-typeiter.sh
+testfiles testfile-debug-types
+
+testrun ${abs_builddir}/dwarf-die-addr-die testfile-debug-types
+
+# see run-readelf-dwz-multi.sh
+testfiles testfile_multi_main testfile_multi.dwz
+
+testrun ${abs_builddir}/dwarf-die-addr-die testfile_multi_main
+
+# see tests/run-dwflsyms.sh
+testfiles testfilebazdbgppc64.debug
+
+testrun ${abs_builddir}/dwarf-die-addr-die testfilebazdbgppc64.debug
+
+# Self test
+testrun_on_self ${abs_builddir}/dwarf-die-addr-die
+
+exit 0
-- 
1.8.3.1



More information about the Elfutils-devel mailing list