libdw: Handle GNU DebugFission split ranges.
authorMark Wielaard <mark@klomp.org>
Fri, 18 May 2018 22:46:02 +0000 (00:46 +0200)
committerMark Wielaard <mark@klomp.org>
Tue, 22 May 2018 22:31:22 +0000 (00:31 +0200)
GNU DebugFission split dwarf handles DW_FORM_sec_offset specially for
attributes that point to ranges. The .debug_ranges section is not in
the .dwo file, but in the main/skeleton object file. The sec_offset is
not relocated (in the ELF file), but is an offset against the skeleton
DIE DW_AT_GNU_ranges_base attribute. dwarf_formudata is changed so it
still looks like a normal offset ptr into the .debug_ranges section.
dwarf_ranges is adapted to look for the .debug_ranges in the main object
file. dwarf_highpc and dwarf_lowpc now handle the highpc and lowpc
attributes being inherited for the split unit DIE from the skeleton.

A new testcase is added to iterate over all ranges in a split GNU
DebugFission file.

Signed-off-by: Mark Wielaard <mark@klomp.org>
13 files changed:
libdw/ChangeLog
libdw/dwarf_formudata.c
libdw/dwarf_highpc.c
libdw/dwarf_lowpc.c
libdw/dwarf_ranges.c
libdw/libdwP.h
tests/ChangeLog
tests/Makefile.am
tests/all-dwarf-ranges.c [new file with mode: 0644]
tests/run-all-dwarf-ranges.sh [new file with mode: 0755]
tests/testfile-ranges-hello.dwo.bz2 [new file with mode: 0644]
tests/testfile-ranges-world.dwo.bz2 [new file with mode: 0644]
tests/testfilesplitranges4.debug.bz2 [new file with mode: 0755]

index ab9675e..4db0f5c 100644 (file)
@@ -1,5 +1,17 @@
 2018-05-18  Mark Wielaard  <mark@klomp.org>
 
+       * dwarf_formudata.c (__libdw_formptr): Handle the special case
+       of IDX_debug_ranges for DW_UT_split_compile with version < 5.
+       * dwarf_highpc.c (dwarf_highpc): Use dwarf_lowpc, check for
+       split compile cudie.
+       * dwarf_lowpc.c (dwarf_lowpc): Check for split compile cudie.
+       * dwarf_ranges.c (dwarf_ranges): Switch cu and sectiondata for
+       split compile units.
+       * libdwP.h (struct Dwarf_CU): Add ranges_base field.
+       (__libdw_cu_ranges_base): New static inline function.
+
+2018-05-18  Mark Wielaard  <mark@klomp.org>
+
        * libdw_findcu.c (__libdw_intern_next_unit): Init files to NULL.
        * dwarf_getsrclines.c (dwarf_getsrclines): Handle split units by
        taking the line table from the skeleton.
index 19d34f8..316ad86 100644 (file)
@@ -43,6 +43,17 @@ __libdw_formptr (Dwarf_Attribute *attr, int sec_index,
     return NULL;
 
   const Elf_Data *d = attr->cu->dbg->sectiondata[sec_index];
+  Dwarf_CU *skel = NULL; /* See below, needed for GNU DebugFission.  */
+  if (unlikely (d == NULL
+               && sec_index == IDX_debug_ranges
+               && attr->cu->version < 5
+               && attr->cu->unit_type == DW_UT_split_compile))
+    {
+      skel = __libdw_find_split_unit (attr->cu);
+      if (skel != NULL)
+       d = skel->dbg->sectiondata[IDX_debug_ranges];
+    }
+
   if (unlikely (d == NULL))
     {
       __libdw_seterrno (err_nodata);
@@ -52,10 +63,41 @@ __libdw_formptr (Dwarf_Attribute *attr, int sec_index,
   Dwarf_Word offset;
   if (attr->form == DW_FORM_sec_offset)
     {
-      if (__libdw_read_offset (attr->cu->dbg, attr->cu->dbg,
-                              cu_sec_idx (attr->cu), attr->valp,
-                              attr->cu->offset_size, &offset, sec_index, 0))
-       return NULL;
+      /* GNU DebugFission is slightly odd.  It uses DW_FORM_sec_offset
+        in split units, but they are really (unrelocated) offsets
+        from the skeleton DW_AT_GNU_ranges_base (which is only used
+        for the split unit, not the skeleton ranges itself, see also
+        DW_AT_rnglists_base, which is used in DWARF5 for both, but
+        points to the offsets index).  So it isn't really a formptr,
+        but an offset + base calculation.  */
+      if (unlikely (skel != NULL))
+       {
+         Elf_Data *data = attr->cu->dbg->sectiondata[cu_sec_idx (attr->cu)];
+         const unsigned char *datap = attr->valp;
+         size_t size = attr->cu->offset_size;
+         if (unlikely (data == NULL
+                       || datap < (const unsigned char *) data->d_buf
+                       || data->d_size < size
+                       || ((size_t) (datap
+                                     - (const unsigned char *) data->d_buf)
+                           > data->d_size - size)))
+           goto invalid;
+
+         if (size == 4)
+           offset = read_4ubyte_unaligned (attr->cu->dbg, datap);
+         else
+           offset = read_8ubyte_unaligned (attr->cu->dbg, datap);
+
+         offset += __libdw_cu_ranges_base (skel);
+       }
+      else
+       {
+         if (__libdw_read_offset (attr->cu->dbg, attr->cu->dbg,
+                                  cu_sec_idx (attr->cu), attr->valp,
+                                  attr->cu->offset_size, &offset,
+                                  sec_index, 0))
+           return NULL;
+       }
     }
   else if (attr->cu->version > 3)
     goto invalid;
index 2070254..1baffa7 100644 (file)
@@ -1,7 +1,6 @@
 /* Return high PC attribute of DIE.
-   Copyright (C) 2003, 2005, 2012 Red Hat, Inc.
+   Copyright (C) 2003, 2005, 2012, 2018 Red Hat, Inc.
    This file is part of elfutils.
-   Written by Ulrich Drepper <drepper@redhat.com>, 2003.
 
    This file is free software; you can redistribute it and/or modify
    it under the terms of either
@@ -39,8 +38,14 @@ int
 dwarf_highpc (Dwarf_Die *die, Dwarf_Addr *return_addr)
 {
   Dwarf_Attribute attr_high_mem;
-  Dwarf_Attribute *attr_high = INTUSE(dwarf_attr) (die, DW_AT_high_pc,
-                                                  &attr_high_mem);
+  Dwarf_Attribute *attr_high;
+  /* Split compile DIEs inherit high_pc from their skeleton DIE.  */
+  if (is_cudie (die) && die->cu->unit_type == DW_UT_split_compile)
+    attr_high = INTUSE(dwarf_attr_integrate) (die, DW_AT_high_pc,
+                                             &attr_high_mem);
+  else
+    attr_high = INTUSE(dwarf_attr) (die, DW_AT_high_pc, &attr_high_mem);
+
   if (attr_high == NULL)
     return -1;
 
@@ -48,10 +53,7 @@ dwarf_highpc (Dwarf_Die *die, Dwarf_Addr *return_addr)
     return INTUSE(dwarf_formaddr) (attr_high, return_addr);
 
   /* DWARF 4 allows high_pc to be a constant offset from low_pc. */
-  Dwarf_Attribute attr_low_mem;
-  if (INTUSE(dwarf_formaddr) (INTUSE(dwarf_attr) (die, DW_AT_low_pc,
-                                                 &attr_low_mem),
-                             return_addr) == 0)
+  if (INTUSE(dwarf_lowpc) (die, return_addr) == 0)
     {
       Dwarf_Word uval;
       if (INTUSE(dwarf_formudata) (attr_high, &uval) == 0)
index b3be2b0..4d743a7 100644 (file)
@@ -1,7 +1,6 @@
 /* Return low PC attribute of DIE.
-   Copyright (C) 2003, 2005 Red Hat, Inc.
+   Copyright (C) 2003, 2005, 2018 Red Hat, Inc.
    This file is part of elfutils.
-   Written by Ulrich Drepper <drepper@redhat.com>, 2003.
 
    This file is free software; you can redistribute it and/or modify
    it under the terms of either
 int
 dwarf_lowpc (Dwarf_Die *die, Dwarf_Addr *return_addr)
 {
-  Dwarf_Attribute attr_mem;
-
-  return INTUSE(dwarf_formaddr) (INTUSE(dwarf_attr) (die, DW_AT_low_pc,
-                                                    &attr_mem),
-                                return_addr);
+  Dwarf_Attribute attr_mem, *attr;
+  /* Split compile DIEs inherit low_pc from their skeleton DIE.  */
+  if (is_cudie (die) && die->cu->unit_type == DW_UT_split_compile)
+    attr = INTUSE(dwarf_attr_integrate) (die, DW_AT_low_pc, &attr_mem);
+  else
+    attr = INTUSE(dwarf_attr) (die, DW_AT_low_pc, &attr_mem);
+  return INTUSE(dwarf_formaddr) (attr, return_addr);
 }
 INTDEF(dwarf_lowpc)
index dbcfa2d..b0450cf 100644 (file)
@@ -122,7 +122,17 @@ dwarf_ranges (Dwarf_Die *die, ptrdiff_t offset, Dwarf_Addr *basep,
 
   /* We have to look for a noncontiguous range.  */
   size_t secidx = IDX_debug_ranges;
-  const Elf_Data *d = die->cu->dbg->sectiondata[secidx];
+  Dwarf_CU *cu = die->cu;
+  const Elf_Data *d = cu->dbg->sectiondata[secidx];
+  if (d == NULL && cu->unit_type == DW_UT_split_compile)
+    {
+      Dwarf_CU *skel = __libdw_find_split_unit (cu);
+      if (skel != NULL)
+       {
+         cu = skel;
+         d = cu->dbg->sectiondata[secidx];
+       }
+    }
 
   const unsigned char *readp;
   const unsigned char *readendp;
@@ -131,6 +141,10 @@ dwarf_ranges (Dwarf_Die *die, ptrdiff_t offset, Dwarf_Addr *basep,
       Dwarf_Attribute attr_mem;
       Dwarf_Attribute *attr = INTUSE(dwarf_attr) (die, DW_AT_ranges,
                                                  &attr_mem);
+      if (attr == NULL
+         && is_cudie (die)
+         && die->cu->unit_type == DW_UT_split_compile)
+       attr = INTUSE(dwarf_attr_integrate) (die, DW_AT_ranges, &attr_mem);
       if (attr == NULL)
        /* No PC attributes in this DIE at all, so an empty range list.  */
        return 0;
@@ -144,7 +158,7 @@ dwarf_ranges (Dwarf_Die *die, ptrdiff_t offset, Dwarf_Addr *basep,
     }
   else
     {
-      if (__libdw_offset_in_section (die->cu->dbg,
+      if (__libdw_offset_in_section (cu->dbg,
                                     secidx, offset, 1))
        return -1;
     }
@@ -156,9 +170,9 @@ dwarf_ranges (Dwarf_Die *die, ptrdiff_t offset, Dwarf_Addr *basep,
   Dwarf_Addr end;
 
  next:
-  switch (__libdw_read_begin_end_pair_inc (die->cu->dbg, secidx,
+  switch (__libdw_read_begin_end_pair_inc (cu->dbg, secidx,
                                           &readp, readendp,
-                                          die->cu->address_size,
+                                          cu->address_size,
                                           &begin, &end, basep))
     {
     case 0:
index f739005..2b5b5ea 100644 (file)
@@ -352,6 +352,11 @@ struct Dwarf_CU
      Don't access directly, call __libdw_cu_str_off_base.  */
   Dwarf_Off str_off_base;
 
+  /* The offset into the .debug_ranges section to use for GNU
+     DebugFission split units.  Don't access directly, call
+     __libdw_cu_ranges_base.  */
+  Dwarf_Off ranges_base;
+
   /* Memory boundaries of this CU.  */
   void *startp;
   void *endp;
@@ -1055,6 +1060,27 @@ static inline Dwarf_Off __libdw_cu_str_off_base (Dwarf_CU *cu)
 }
 
 
+static inline Dwarf_Off
+__libdw_cu_ranges_base (Dwarf_CU *cu)
+{
+  if (cu->ranges_base == (Dwarf_Off) -1)
+    {
+      Dwarf_Off offset = 0;
+      Dwarf_Die cu_die = CUDIE(cu);
+      Dwarf_Attribute attr;
+      if (dwarf_attr (&cu_die, DW_AT_GNU_ranges_base, &attr) != NULL)
+       {
+         Dwarf_Word off;
+         if (dwarf_formudata (&attr, &off) == 0)
+           offset = off;
+       }
+      cu->ranges_base = offset;
+    }
+
+  return cu->ranges_base;
+}
+
+
 /* Helper function to set debugdir field in Dwarf, used from dwarf_begin_elf
    and libdwfl process_file.  */
 char * __libdw_debugdir (int fd);
index 0370fbc..a021a01 100644 (file)
@@ -1,5 +1,19 @@
 2018-05-18  Mark Wielaard  <mark@klomp.org>
 
+       * tests/Makefiles.am (check_PROGRAMS): Add all-dwarf-ranges.
+       (TESTS): Add run-all-dwarf-ranges.sh.
+       (EXTRA_DIST): Add run-all-dwarf-ranges.sh,
+       testfilesplitranges4.debug.bz2, testfile-ranges-hello.dwo.bz2
+       and testfile-ranges-world.dwo.bz2.
+       (all_dwarf_ranges_LDADD): New variable.
+       * all-dwarf-ranges.c: New test program.
+       * run-all-dwarf-ranges: New test runner.
+       * testfile-ranges-hello.dwo.bz2: New test file.
+       * testfile-ranges-world.dwo.bz2: Likewise.
+       * testfilesplitranges4.debug.bz2: Likewise.
+
+2018-05-18  Mark Wielaard  <mark@klomp.org>
+
        * run-get-files.sh: Add testcases for testfile-splitdwarf-4,
        testfile-hello4.dwo, testfile-world4.dwo and testfile-splitdwarf-5,
        testfile-hello5.dwo, testfile-world5.dwo.
index 05a5441..08d8464 100644 (file)
@@ -56,7 +56,8 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \
                  elfshphehdr elfstrmerge dwelfgnucompressed elfgetchdr \
                  elfgetzdata elfputzdata zstrptr emptyfile vendorelf \
                  fillfile dwarf_default_lower_bound dwarf-die-addr-die \
-                 get-units-invalid get-units-split attr-integrate-skel
+                 get-units-invalid get-units-split attr-integrate-skel \
+                 all-dwarf-ranges
 
 asm_TESTS = asm-tst1 asm-tst2 asm-tst3 asm-tst4 asm-tst5 \
            asm-tst6 asm-tst7 asm-tst8 asm-tst9
@@ -141,7 +142,8 @@ TESTS = run-arextract.sh run-arsymtest.sh run-ar.sh newfile test-nlist \
        emptyfile vendorelf fillfile dwarf_default_lower_bound \
        run-dwarf-die-addr-die.sh \
        run-get-units-invalid.sh run-get-units-split.sh \
-       run-attr-integrate-skel.sh
+       run-attr-integrate-skel.sh \
+       run-all-dwarf-ranges.sh
 
 if !BIARCH
 export ELFUTILS_DISABLE_BIARCH = 1
@@ -366,7 +368,9 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \
             testfile-hello4.dwo.bz2 testfile-hello5.dwo.bz2 \
             testfile-splitdwarf-4.bz2 testfile-splitdwarf-5.bz2 \
             testfile-world5.dwo.bz2 testfile-world4.dwo.bz2 \
-            run-attr-integrate-skel.sh
+            run-attr-integrate-skel.sh \
+            run-all-dwarf-ranges.sh testfilesplitranges4.debug.bz2 \
+            testfile-ranges-hello.dwo.bz2 testfile-ranges-world.dwo.bz2
 
 if USE_VALGRIND
 valgrind_cmd='valgrind -q --leak-check=full --error-exitcode=1'
@@ -528,6 +532,7 @@ dwarf_die_addr_die_LDADD = $(libdw)
 get_units_invalid_LDADD = $(libdw)
 get_units_split_LDADD = $(libdw)
 attr_integrate_skel_LDADD = $(libdw)
+all_dwarf_ranges_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/all-dwarf-ranges.c b/tests/all-dwarf-ranges.c
new file mode 100644 (file)
index 0000000..4331a05
--- /dev/null
@@ -0,0 +1,90 @@
+/* Test program for dwarf_ranges
+   Copyright (C) 2015, 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>
+
+static void
+ranges_die (Dwarf_Die *die)
+{
+  Dwarf_Addr base, start, end;
+  int ranges = dwarf_ranges (die, 0, &base, &start, &end);
+  if (ranges < 0)
+    puts (dwarf_errmsg (-1));
+  else if (ranges > 0)
+    {
+      printf ("die: %s (%x)\n", dwarf_diename (die) ?: "<unknown>",
+             dwarf_tag (die));
+      for (ptrdiff_t off = 0;
+          (off = dwarf_ranges (die, off, &base, &start, &end)); )
+       if (off == -1)
+         {
+           puts (dwarf_errmsg (-1));
+           break;
+         }
+       else
+         printf (" %"PRIx64"..%"PRIx64"\n", start, end);
+      printf ("\n");
+    }
+}
+
+static void
+walk_tree (Dwarf_Die *dwarf_die)
+{
+  Dwarf_Die die = *dwarf_die;
+  do
+    {
+      Dwarf_Die child;
+      ranges_die (&die);
+      if (dwarf_child (&die, &child) == 0)
+       walk_tree (&child);
+    }
+  while (dwarf_siblingof (&die, &die) == 0);
+}
+
+int
+main (int argc, char *argv[])
+{
+  assert (argc >= 2);
+  const char *name = argv[1];
+
+  int fd = open (name, O_RDONLY);
+  Dwarf *dbg = dwarf_begin (fd, DWARF_C_READ);
+
+  Dwarf_CU *cu = NULL;
+  Dwarf_Die cudie, subdie;
+  uint8_t unit_type;
+  while (dwarf_get_units (dbg, cu, &cu, NULL,
+                         &unit_type, &cudie, &subdie) == 0)
+    {
+      Dwarf_Die die = (unit_type == DW_UT_skeleton
+                      ? subdie : cudie);
+      walk_tree (&die);
+    }
+  dwarf_end (dbg);
+
+  return 0;
+}
diff --git a/tests/run-all-dwarf-ranges.sh b/tests/run-all-dwarf-ranges.sh
new file mode 100755 (executable)
index 0000000..0bd641b
--- /dev/null
@@ -0,0 +1,49 @@
+#! /bin/sh
+# 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/>.
+
+. $srcdir/test-subr.sh
+
+# See run-dwarf-ranges.sh
+# Compiled with:
+# gcc -c -O2 -o testfile-ranges-hello.o -gsplit-dwarf -gdwarf-4 hello.c
+# gcc -c -O2 -o testfile-ranges-world.o -gsplit-dwarf -gdwarf-4 world.c
+# gcc -o testfilesplitranges4 -O2 \
+#        testfile-ranges-hello.o testfile-ranges-world.o
+# eu-strip -f testfilesplitranges4.debug testfilesplitranges4
+
+testfiles testfilesplitranges4.debug
+testfiles testfile-ranges-hello.dwo testfile-ranges-world.dwo
+
+testrun_compare ${abs_builddir}/all-dwarf-ranges testfilesplitranges4.debug <<\EOF
+die: hello.c (11)
+ 4004e0..4004ff
+ 4003e0..4003f7
+
+die: world.c (11)
+ 400500..400567
+
+die: happy (1d)
+ 8009e0..8009ff
+ 8008e0..8008f7
+
+die: sad (1d)
+ 400530..400534
+ 400535..40053f
+
+EOF
+
+exit 0
diff --git a/tests/testfile-ranges-hello.dwo.bz2 b/tests/testfile-ranges-hello.dwo.bz2
new file mode 100644 (file)
index 0000000..fac2467
Binary files /dev/null and b/tests/testfile-ranges-hello.dwo.bz2 differ
diff --git a/tests/testfile-ranges-world.dwo.bz2 b/tests/testfile-ranges-world.dwo.bz2
new file mode 100644 (file)
index 0000000..27ad06d
Binary files /dev/null and b/tests/testfile-ranges-world.dwo.bz2 differ
diff --git a/tests/testfilesplitranges4.debug.bz2 b/tests/testfilesplitranges4.debug.bz2
new file mode 100755 (executable)
index 0000000..df8e3ac
Binary files /dev/null and b/tests/testfilesplitranges4.debug.bz2 differ