For now, this supports only platforms that have an objdump available for
the corresponding target. There are several things that would be nico to
have in the future:

  * add support for more DWARF dumping tools, such as otool on Darwin;

  * have a DWARF location expression decoder, to be able to parse and
    pattern match expressions that objdump does not decode itself;

  * complete the set of decoders for DIE attributes.

gcc/testsuite/

        * lib/gcc-dwarf.exp: New helper files.
        * python/dwarfutils/__init__.py,
        python/dwarfutils/data.py,
        python/dwarfutils/helpers.py,
        python/dwarfutils/objdump.py: New Python helpers.
        * gcc.dg/debug/dwarf2-py/dwarf2-py.exp,
        gnat.dg/dwarf/dwarf.exp: New test drivers.
        * gcc.dg/debug/dwarf2-py/sso.c,
        gcc.dg/debug/dwarf2-py/sso.py,
        gcc.dg/debug/dwarf2-py/var2.c,
        gcc.dg/debug/dwarf2-py/var2.py,
        gnat.dg/dwarf/debug9.adb,
        gnat.dg/dwarf/debug9.py,
        gnat.dg/dwarf/debug11.adb,
        gnat.dg/dwarf/debug11.py,
        gnat.dg/dwarf/debug12.adb,
        gnat.dg/dwarf/debug12.ads,
        gnat.dg/dwarf/debug12.py: New tests.
---
 gcc/testsuite/gcc.dg/debug/dwarf2-py/dwarf2-py.exp |  52 ++
 gcc/testsuite/gcc.dg/debug/dwarf2-py/sso.c         |  19 +
 gcc/testsuite/gcc.dg/debug/dwarf2-py/sso.py        |  52 ++
 gcc/testsuite/gcc.dg/debug/dwarf2-py/var2.c        |  13 +
 gcc/testsuite/gcc.dg/debug/dwarf2-py/var2.py       |  11 +
 gcc/testsuite/gnat.dg/dg.exp                       |   1 +
 gcc/testsuite/gnat.dg/dwarf/debug11.adb            |  19 +
 gcc/testsuite/gnat.dg/dwarf/debug11.py             |  51 ++
 gcc/testsuite/gnat.dg/dwarf/debug12.adb            |  10 +
 gcc/testsuite/gnat.dg/dwarf/debug12.ads            |   8 +
 gcc/testsuite/gnat.dg/dwarf/debug12.py             |   9 +
 gcc/testsuite/gnat.dg/dwarf/debug9.adb             |  45 ++
 gcc/testsuite/gnat.dg/dwarf/debug9.py              |  22 +
 gcc/testsuite/gnat.dg/dwarf/dwarf.exp              |  39 ++
 gcc/testsuite/lib/gcc-dwarf.exp                    |  41 ++
 gcc/testsuite/python/dwarfutils/__init__.py        |  70 +++
 gcc/testsuite/python/dwarfutils/data.py            | 597 +++++++++++++++++++++
 gcc/testsuite/python/dwarfutils/helpers.py         |  11 +
 gcc/testsuite/python/dwarfutils/objdump.py         | 338 ++++++++++++
 19 files changed, 1408 insertions(+)
 create mode 100644 gcc/testsuite/gcc.dg/debug/dwarf2-py/dwarf2-py.exp
 create mode 100644 gcc/testsuite/gcc.dg/debug/dwarf2-py/sso.c
 create mode 100644 gcc/testsuite/gcc.dg/debug/dwarf2-py/sso.py
 create mode 100644 gcc/testsuite/gcc.dg/debug/dwarf2-py/var2.c
 create mode 100644 gcc/testsuite/gcc.dg/debug/dwarf2-py/var2.py
 create mode 100644 gcc/testsuite/gnat.dg/dwarf/debug11.adb
 create mode 100644 gcc/testsuite/gnat.dg/dwarf/debug11.py
 create mode 100644 gcc/testsuite/gnat.dg/dwarf/debug12.adb
 create mode 100644 gcc/testsuite/gnat.dg/dwarf/debug12.ads
 create mode 100644 gcc/testsuite/gnat.dg/dwarf/debug12.py
 create mode 100644 gcc/testsuite/gnat.dg/dwarf/debug9.adb
 create mode 100644 gcc/testsuite/gnat.dg/dwarf/debug9.py
 create mode 100644 gcc/testsuite/gnat.dg/dwarf/dwarf.exp
 create mode 100644 gcc/testsuite/lib/gcc-dwarf.exp
 create mode 100644 gcc/testsuite/python/dwarfutils/__init__.py
 create mode 100644 gcc/testsuite/python/dwarfutils/data.py
 create mode 100644 gcc/testsuite/python/dwarfutils/helpers.py
 create mode 100644 gcc/testsuite/python/dwarfutils/objdump.py

diff --git a/gcc/testsuite/gcc.dg/debug/dwarf2-py/dwarf2-py.exp 
b/gcc/testsuite/gcc.dg/debug/dwarf2-py/dwarf2-py.exp
new file mode 100644
index 00000000000..5c49bc81a55
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/debug/dwarf2-py/dwarf2-py.exp
@@ -0,0 +1,52 @@
+# Copyright (C) 2017 Free Software Foundation, Inc.
+
+# This program 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.
+# 
+# This program 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 GCC; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.
+
+# Testsuite driver for testcases that check the DWARF output with Python
+# scripts.
+
+load_lib gcc-dg.exp
+load_lib gcc-python.exp
+load_lib gcc-dwarf.exp
+
+# This series of tests require a working Python interpreter and a supported
+# host tool to dump DWARF.
+if { ![check-python-available] || ![detect-dwarf-dump-tool] } {
+    return
+}
+
+# If a testcase doesn't have special options, use these.
+global DEFAULT_CFLAGS
+if ![info exists DEFAULT_CFLAGS] then {
+    set DEFAULT_CFLAGS " -ansi -pedantic-errors -gdwarf"
+}
+
+# Initialize `dg'.
+dg-init
+
+# Main loop.
+if {[check-python-available]} {
+    set comp_output [gcc_target_compile \
+       "$srcdir/$subdir/../trivial.c" "trivial.S" assembly \
+       "additional_flags=-gdwarf"]
+    if { ! [string match "*: target system does not support the * debug 
format*" \
+       $comp_output] } {
+       remove-build-file "trivial.S"
+       dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.\[cS\] ] ] "" 
$DEFAULT_CFLAGS
+    }
+}
+
+# All done.
+dg-finish
diff --git a/gcc/testsuite/gcc.dg/debug/dwarf2-py/sso.c 
b/gcc/testsuite/gcc.dg/debug/dwarf2-py/sso.c
new file mode 100644
index 00000000000..f7429a58179
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/debug/dwarf2-py/sso.c
@@ -0,0 +1,19 @@
+/* { dg-do assemble } */
+/* { dg-options "-gdwarf-3" } */
+/* { dg-final { python-test sso.py } } */
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define REVERSE_SSO __attribute__((scalar_storage_order("big-endian")));
+#else
+#define REVERSE_SSO __attribute__((scalar_storage_order("little-endian")));
+#endif
+
+struct S0 { int i; };
+
+struct S1 { int i; struct S0 s; } REVERSE_SSO;
+
+struct S2 { int a[4]; struct S0 s; } REVERSE_SSO;
+
+struct S0 s0;
+struct S1 s1;
+struct S2 s2;
diff --git a/gcc/testsuite/gcc.dg/debug/dwarf2-py/sso.py 
b/gcc/testsuite/gcc.dg/debug/dwarf2-py/sso.py
new file mode 100644
index 00000000000..0c95abfe2b8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/debug/dwarf2-py/sso.py
@@ -0,0 +1,52 @@
+import dwarfutils
+from dwarfutils.data import Capture, DIE, Matcher
+from testutils import check
+
+
+cu = dwarfutils.parse_dwarf()
+s0 = cu.find(tag='DW_TAG_structure_type', name='S0')
+s1 = cu.find(tag='DW_TAG_structure_type', name='S1')
+s2 = cu.find(tag='DW_TAG_structure_type', name='S2')
+
+# Check the DIE structure of these structure types
+m0 = s0.tree_check(Matcher(
+    'DW_TAG_structure_type', 'S0',
+    children=[Matcher('DW_TAG_member', 'i',
+                      attrs={'DW_AT_type': Capture('s0_i_type')})]
+))
+m1 = s1.tree_check(Matcher(
+    'DW_TAG_structure_type', 'S1',
+    children=[
+        Matcher('DW_TAG_member', 'i',
+                attrs={'DW_AT_type': Capture('s1_i_type')}),
+        Matcher('DW_TAG_member', 's', attrs={'DW_AT_type': s0}),
+    ]
+))
+m2 = s2.tree_check(Matcher(
+    'DW_TAG_structure_type', 'S2',
+    children=[
+        Matcher('DW_TAG_member', 'a',
+                attrs={'DW_AT_type': Capture('s2_a_type')}),
+        Matcher('DW_TAG_member', 's', attrs={'DW_AT_type': s0}),
+    ]
+))
+
+# Now check that their scalar members have expected types
+s0_i_type = m0.capture('s0_i_type').value
+s1_i_type = m1.capture('s1_i_type').value
+s2_a_type = m2.capture('s2_a_type').value
+
+# S0.i must not have a DW_AT_endianity attribute.  S1.i must have one.
+s0_i_type.tree_check(Matcher('DW_TAG_base_type',
+                             attrs={'DW_AT_endianity': None}))
+s1_i_type.tree_check(Matcher('DW_TAG_base_type',
+                             attrs={'DW_AT_endianity': True}))
+
+# So does the integer type that S2.a contains.
+ma = s2_a_type.tree_check(Matcher(
+    'DW_TAG_array_type',
+    attrs={'DW_AT_type': Capture('element_type')}
+))
+element_type = ma.capture('element_type').value
+check(element_type == s1_i_type,
+      'check element type of S2.a is type of S1.i')
diff --git a/gcc/testsuite/gcc.dg/debug/dwarf2-py/var2.c 
b/gcc/testsuite/gcc.dg/debug/dwarf2-py/var2.c
new file mode 100644
index 00000000000..e77adc0eaf5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/debug/dwarf2-py/var2.c
@@ -0,0 +1,13 @@
+/* PR 23190 */
+/* { dg-do assemble } */
+/* { dg-options "-O2 -gdwarf" } */
+/* { dg-final { python-test var2.py } } */
+
+static int foo;
+int bar;
+int main(void)
+{
+   foo += 3;
+   bar *= 5;
+   return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/debug/dwarf2-py/var2.py 
b/gcc/testsuite/gcc.dg/debug/dwarf2-py/var2.py
new file mode 100644
index 00000000000..9a9b2c4a4ca
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/debug/dwarf2-py/var2.py
@@ -0,0 +1,11 @@
+import dwarfutils
+from dwarfutils.data import Capture, DIE, Matcher
+from testutils import check
+
+
+cu = dwarfutils.parse_dwarf()
+foo = cu.find(tag='DW_TAG_variable', name='foo')
+bar = cu.find(tag='DW_TAG_variable', name='bar')
+
+foo.check_attr('DW_AT_location', [('DW_OP_addr', '0')])
+bar.check_attr('DW_AT_location', [('DW_OP_addr', '0')])
diff --git a/gcc/testsuite/gnat.dg/dg.exp b/gcc/testsuite/gnat.dg/dg.exp
index 228c71e85bb..dff86600957 100644
--- a/gcc/testsuite/gnat.dg/dg.exp
+++ b/gcc/testsuite/gnat.dg/dg.exp
@@ -18,6 +18,7 @@
 
 # Load support procs.
 load_lib gnat-dg.exp
+load_lib gcc-python.exp
 
 # If a testcase doesn't have special options, use these.
 global DEFAULT_CFLAGS
diff --git a/gcc/testsuite/gnat.dg/dwarf/debug11.adb 
b/gcc/testsuite/gnat.dg/dwarf/debug11.adb
new file mode 100644
index 00000000000..a87470925f1
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/dwarf/debug11.adb
@@ -0,0 +1,19 @@
+--  { dg-options "-cargs -O0 -g -dA -fgnat-encodings=minimal -margs" }
+--  { dg-do assemble }
+--  { dg-final { python-test debug11.py } }
+
+with Ada.Text_IO;
+
+procedure Debug11 is
+   type Rec_Type (C : Character) is record
+      case C is
+         when 'Z' .. Character'Val (128) => I : Integer;
+         when others                     => null;
+      end case;
+   end record;
+   --  R : Rec_Type := ('Z', 2);
+   R : Rec_Type ('Z');
+begin
+   R.I := 0;
+   Ada.Text_IO.Put_Line ("" & R.C);
+end Debug11;
diff --git a/gcc/testsuite/gnat.dg/dwarf/debug11.py 
b/gcc/testsuite/gnat.dg/dwarf/debug11.py
new file mode 100644
index 00000000000..26c3fdfeeda
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/dwarf/debug11.py
@@ -0,0 +1,51 @@
+import dwarfutils
+from dwarfutils.data import Capture, DIE, Matcher
+from testutils import check, print_pass
+
+
+cu = dwarfutils.parse_dwarf()
+rec_type = cu.find(tag='DW_TAG_structure_type', name='debug11__rec_type')
+
+check(rec_type.parent.matches(tag='DW_TAG_subprogram', name='debug11'),
+      'check that rec_type appears in the expected context')
+
+# Check that rec_type has the expected DIE structure
+m = rec_type.tree_check(Matcher(
+    'DW_TAG_structure_type', 'debug11__rec_type',
+    children=[
+        Matcher('DW_TAG_member', 'c', capture='c'),
+        Matcher(
+            'DW_TAG_variant_part',
+            attrs={'DW_AT_discr': Capture('discr')},
+            children=[
+                Matcher(
+                    'DW_TAG_variant',
+                    attrs={'DW_AT_discr_list': Capture('discr_list'),
+                           'DW_AT_discr_value': None},
+                    children=[
+                        Matcher('DW_TAG_member', 'i'),
+                    ]
+                ),
+                Matcher(
+                    'DW_TAG_variant',
+                    attrs={'DW_AT_discr_list': None,
+                           'DW_AT_discr_value': None},
+                    children=[]
+                )
+            ]
+        )
+    ]
+))
+
+# Check that DW_AT_discr refers to the expected DW_TAG_member
+c = m.capture('c')
+discr = m.capture('discr')
+check(c == discr.value, 'check that discriminant is {}'.format(discr.value))
+
+# Check that DW_AT_discr_list has the expected content: the C discriminant must
+# be properly described as unsigned, hence the 0x5a ('Z') and 0x80 0x01 (128)
+# values in the DW_AT_discr_list attribute. If it was described as signed, we
+# would have instead 90 and -128.
+discr_list = m.capture('discr_list')
+check(discr_list.value == [0x1, 0x5a, 0x80, 0x1],
+      'check discriminant list')
diff --git a/gcc/testsuite/gnat.dg/dwarf/debug12.adb 
b/gcc/testsuite/gnat.dg/dwarf/debug12.adb
new file mode 100644
index 00000000000..1fa9f27aa9b
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/dwarf/debug12.adb
@@ -0,0 +1,10 @@
+--  { dg-options "-cargs -gdwarf-4 -margs" }
+--  { dg-do assemble }
+--  { dg-final { python-test debug12.py } }
+
+package body Debug12 is
+   function Get_A2 return Boolean is
+   begin
+      return A2;
+   end Get_A2;
+end Debug12;
diff --git a/gcc/testsuite/gnat.dg/dwarf/debug12.ads 
b/gcc/testsuite/gnat.dg/dwarf/debug12.ads
new file mode 100644
index 00000000000..dbc5896cc73
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/dwarf/debug12.ads
@@ -0,0 +1,8 @@
+package Debug12 is
+   type Bit_Array is array (Positive range <>) of Boolean
+      with Pack;
+   A  : Bit_Array := (1 .. 10 => False);
+   A2 : Boolean renames A (2);
+
+   function Get_A2 return Boolean;
+end Debug12;
diff --git a/gcc/testsuite/gnat.dg/dwarf/debug12.py 
b/gcc/testsuite/gnat.dg/dwarf/debug12.py
new file mode 100644
index 00000000000..41e589b2ff1
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/dwarf/debug12.py
@@ -0,0 +1,9 @@
+import dwarfutils
+from dwarfutils.data import Capture, DIE, Matcher
+from testutils import check
+
+
+cu = dwarfutils.parse_dwarf()
+
+a2 = cu.find(tag='DW_TAG_variable', name='debug12__a2___XR_debug12__a___XEXS2')
+a2.check_attr('DW_AT_location', [('DW_OP_const1s', '-1')])
diff --git a/gcc/testsuite/gnat.dg/dwarf/debug9.adb 
b/gcc/testsuite/gnat.dg/dwarf/debug9.adb
new file mode 100644
index 00000000000..9ed66b55cdf
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/dwarf/debug9.adb
@@ -0,0 +1,45 @@
+--  { dg-options "-cargs -g -fgnat-encodings=minimal -dA -margs" }
+--  { dg-do assemble }
+--  { dg-final { python-test debug9.py } }
+
+procedure Debug9 is
+   type Array_Type is array (Natural range <>) of Integer;
+   type Record_Type (L1, L2 : Natural) is record
+      I1 : Integer;
+      A1 : Array_Type (1 .. L1);
+      I2 : Integer;
+      A2 : Array_Type (1 .. L2);
+      I3 : Integer;
+   end record;
+
+   function Get (L1, L2 : Natural) return Record_Type is
+      Result : Record_Type (L1, L2);
+   begin
+      Result.I1 := 1;
+      for I in Result.A1'Range loop
+         Result.A1 (I) := I;
+      end loop;
+      Result.I2 := 2;
+      for I in Result.A2'Range loop
+         Result.A2 (I) := I;
+      end loop;
+      Result.I3 := 3;
+      return Result;
+   end Get;
+
+   R1 : Record_Type := Get (0, 0);
+   R2 : Record_Type := Get (1, 0);
+   R3 : Record_Type := Get (0, 1);
+   R4 : Record_Type := Get (2, 2);
+
+   procedure Process (R : Record_Type) is
+   begin
+      null;
+   end Process;
+
+begin
+   Process (R1);
+   Process (R2);
+   Process (R3);
+   Process (R4);
+end Debug9;
diff --git a/gcc/testsuite/gnat.dg/dwarf/debug9.py 
b/gcc/testsuite/gnat.dg/dwarf/debug9.py
new file mode 100644
index 00000000000..560f69d4ec7
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/dwarf/debug9.py
@@ -0,0 +1,22 @@
+import dwarfutils
+from dwarfutils.data import Capture, DIE, Matcher
+from testutils import check, print_pass, print_fail
+
+
+cu = dwarfutils.parse_dwarf()
+cu_die = cu.root
+
+# Check that array and structure types are not declared as compilation
+# unit-level types.
+types = cu.find(
+    predicate=lambda die: die.tag in ('DW_TAG_structure_type ',
+                                      'DW_TAG_array_type'),
+    single=False
+)
+
+global_types = [t for t in types if t.parent == cu_die]
+check(not global_types, 'check composite types are not global')
+if global_types:
+    print('Global types:')
+    for t in global_types:
+        print('  {}'.format(t))
diff --git a/gcc/testsuite/gnat.dg/dwarf/dwarf.exp 
b/gcc/testsuite/gnat.dg/dwarf/dwarf.exp
new file mode 100644
index 00000000000..cbf21a9829a
--- /dev/null
+++ b/gcc/testsuite/gnat.dg/dwarf/dwarf.exp
@@ -0,0 +1,39 @@
+# Copyright (C) 2017 Free Software Foundation, Inc.
+
+# This program 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.
+# 
+# This program 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 GCC; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.
+
+# Testsuite driver for testcases that check the DWARF output with Python
+# scripts.
+
+load_lib gnat-dg.exp
+load_lib gcc-python.exp
+load_lib gcc-dwarf.exp
+
+# This series of tests require a working Python interpreter and a supported
+# host tool to dump DWARF.
+if { ![check-python-available] || ![detect-dwarf-dump-tool] } {
+    return
+}
+
+# Initialize `dg'.
+dg-init
+
+# Main loop.
+if {[check-python-available]} {
+    dg-runtest [lsort [glob $srcdir/$subdir/*.adb]] "" ""
+}
+
+# All done.
+dg-finish
diff --git a/gcc/testsuite/lib/gcc-dwarf.exp b/gcc/testsuite/lib/gcc-dwarf.exp
new file mode 100644
index 00000000000..5e0e6117e16
--- /dev/null
+++ b/gcc/testsuite/lib/gcc-dwarf.exp
@@ -0,0 +1,41 @@
+# Copyright (C) 2017 Free Software Foundation, Inc.
+
+# This program 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.
+# 
+# This program 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 GCC; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.
+
+# Helpers to run tools to dump DWARF
+
+load_lib "remote.exp"
+
+# Look for a tool that we can use to dump DWARF. If nothing is found, return 0.
+#
+# If one is found, return 1, set the DWARF_DUMP_TOOL_KIND environment variable
+# to contain the class of tool detected (e.g. objdump) and set the
+# DWARF_DUMP_TOOL to the name of the tool program (e.g. arm-eabi-objdump).
+
+proc detect-dwarf-dump-tool { args } {
+
+    # Look for an objdump corresponding to the current target
+    set objdump [transform objdump]
+    set result [local_exec "which $objdump" "" "" 300]
+    set status [lindex $result 0]
+
+    if { $status == 0 } {
+       setenv DWARF_DUMP_TOOL_KIND objdump
+       setenv DWARF_DUMP_TOOL $objdump
+       return 1
+    }
+
+    return 0
+}
diff --git a/gcc/testsuite/python/dwarfutils/__init__.py 
b/gcc/testsuite/python/dwarfutils/__init__.py
new file mode 100644
index 00000000000..246fbbd15be
--- /dev/null
+++ b/gcc/testsuite/python/dwarfutils/__init__.py
@@ -0,0 +1,70 @@
+# Copyright (C) 2017 Free Software Foundation, Inc.
+
+# This program 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.
+#
+# This program 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 GCC; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.
+
+# Helpers to parse and check DWARF in object files.
+#
+# The purpose of these is to make it easy to write "smart" tests on DWARF
+# information: pattern matching on DIEs and their attributes, check links
+# between DIEs, etc. Doing these checks using abstract representations of DIEs
+# is far easier than scanning the generated assembly!
+
+import os
+import sys
+
+import dwarfutils.objdump
+
+
+# Fetch the DWARF parsing function that correspond to the DWARF dump tool to
+# use.
+DWARF_DUMP_TOOL_KIND = os.environ['DWARF_DUMP_TOOL_KIND']
+DWARF_DUMP_TOOL = os.environ['DWARF_DUMP_TOOL']
+
+dwarf_parsers = {
+    'objdump': dwarfutils.objdump.parse_dwarf,
+}
+try:
+    dwarf_parser = dwarf_parsers[DWARF_DUMP_TOOL_KIND]
+except KeyError:
+    raise RuntimeError('Unhandled DWARF dump tool: {}'.format(
+        DWARF_DUMP_TOOL_KIND
+    ))
+
+
+def parse_dwarf(object_file=None, single_cu=True):
+    """
+    Fetch and decode DWARF compilation units in `object_file`.
+
+    If `single_cu` is True, make sure there is exactly one compilation unit and
+    return it. Otherwise, return compilation units as a list.
+
+    :param str|None object_file: Name of the object file to process. If left to
+        None, `sys.argv[1]` is used instead.
+
+    :rtype: dwarfutils.data.CompilationUnit
+           |list[dwarfutils.data.CompilationUnit]
+    """
+    if object_file is None:
+        object_file = sys.argv[1]
+    result = dwarf_parser(object_file)
+
+    if single_cu:
+        if not result:
+            return None
+        if len(result) > 1:
+            raise ValueError('Multiple compilation units found')
+        return result[0]
+    else:
+        return result
diff --git a/gcc/testsuite/python/dwarfutils/data.py 
b/gcc/testsuite/python/dwarfutils/data.py
new file mode 100644
index 00000000000..6b91d5bd779
--- /dev/null
+++ b/gcc/testsuite/python/dwarfutils/data.py
@@ -0,0 +1,597 @@
+# Copyright (C) 2017 Free Software Foundation, Inc.
+
+# This program 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.
+#
+# This program 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 GCC; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.
+
+# Data structures to represent DWARF compilation units, DIEs and attributes,
+# and helpers to perform various checks on them.
+
+from testutils import check
+
+
+class Abbrev(object):
+    """DWARF abbreviation entry."""
+
+    def __init__(self, number, tag, has_children):
+        """
+        :param int number: Abbreviation number, which is 1-based, as in the
+            DWARF standard.
+        :param str|int tag: Tag name or, if unknown, tag number.
+        :param bool has_children: Whether DIEs will have children.
+        """
+        self.number = number
+        self.tag = tag
+        self.has_children = has_children
+        self.attributes = []
+
+    def add_attribute(self, name, form):
+        """
+        :param str|int name: Attribute name or, if unknown, attribute number.
+        :param str form: Form for this attribute.
+        """
+        self.attributes.append((name, form))
+
+
+class CompilationUnit(object):
+    """DWARF compilation unit."""
+
+    def __init__(self, offset, length, is_32bit, version, abbrevs,
+                 pointer_size):
+        """
+        :param int offset: Offset of this compilation unit in the .debug_info
+            section.
+        :param int length: Value of the length field for this compilation unit.
+        :param bool is_32bit: Whether this compilation unit is encoded in the
+            32-bit format. If not, it must be the 64-bit one.
+        :param int version: DWARF version used by this compilation unit.
+        :param list[Abbrev] abbrevs: List of abbreviations for this compilation
+            unit.
+        :param int pointer_size: Size of pointers for this architecture.
+        """
+        self.offset = offset
+        self.length = length
+        self.is_32bit = is_32bit
+        self.version = version
+        self.abbrevs = abbrevs
+        self.pointer_size = pointer_size
+
+        self.root = None
+        self.offset_to_die = {}
+
+    def set_root(self, die):
+        assert self.root is None, ('Trying to create the root DIE of a'
+                                   ' compilation unit that already has one')
+        self.root = die
+
+    def find(self, *args, **kwargs):
+        return self.root.find(*args, **kwargs)
+
+
+class DIE(object):
+    """DWARF information entry."""
+
+    def __init__(self, cu, level, offset, abbrev_number):
+        """
+        :param CompilationUnit cu: Compilation unit this DIE belongs to.
+        :param int level: Depth for this DIE.
+        :param int offset: Offset of this DIE in the .debug_info section.
+        :param int abbrev_number: Abbreviation number for this DIE.
+        """
+        self.cu = cu
+        self.cu.offset_to_die[offset] = self
+
+        self.level = level
+        self.offset = offset
+        self.abbrev_number = abbrev_number
+
+        self.parent = None
+        self.attributes = []
+        self.children = []
+
+    @property
+    def abbrev(self):
+        """Abbreviation for this DIE.
+
+        :rtype: Abbrev
+        """
+        # The abbreviation number is 1-based, but list indexes are 0-based
+        return self.cu.abbrevs[self.abbrev_number - 1]
+
+    @property
+    def tag(self):
+        """Tag for this DIE.
+
+        :rtype: str|int
+        """
+        return self.abbrev.tag
+
+    @property
+    def has_children(self):
+        return self.abbrev.has_children
+
+    def get_attr(self, name, single=True, or_error=True):
+        """Look for an attribute in this DIE.
+
+        :param str|int name: Attribute name, or number if name is unknown.
+        :param bool single: If true, this will raise a KeyError for
+            zero/multiple matches and return an Attribute instance when found.
+            Otherwise, return a potentially empty list of attributes.
+        :param bool or_error: When True, if `single` is True and no attribute
+            is found, return None instead of raising a KeyError.
+        :rtype: Attribute|list[Attribute]
+        """
+        result = [a for a in self.attributes if a.name == name]
+
+        if single:
+            if not result:
+                if or_error:
+                    raise KeyError('No {} attribute in {}'.format(name, self))
+                else:
+                    return None
+            if len(result) > 1:
+                raise KeyError('Multiple {} attributes in {}'.format(name,
+                                                                     self))
+            return result[0]
+        else:
+            return result
+
+    def check_attr(self, name, value):
+        """Check for the presence/value of an attribute.
+
+        :param str|int name: Attribute name, or number if name is unknown.
+        :param value: If None, check that the attribute is not present.
+            Otherwise, check that the attribute exists and that its value
+            matches `value`.
+        """
+        m = MatchResult()
+        Matcher._match_attr(self, name, value, m)
+        check(
+            m.succeeded,
+            m.mismatch_reason or 'check attribute {} of {}'.format(name, self)
+        )
+
+    def get_child(self, child_index):
+        """Get a DIE child.
+
+        :param int child_index: Index of the child to fetch (zero-based index).
+        :rtype: DIE
+        """
+        return self.children[child_index]
+
+    @property
+    def name(self):
+        """Return the name (DW_AT_name) for this DIE, if any.
+
+        :rtype: str|None
+        """
+        name = self.get_attr('DW_AT_name', or_error=False)
+        return name.value if name is not None else None
+
+    def __str__(self):
+        tag = (self.tag if isinstance(self.tag, str) else
+               'DIE {}'.format(self.tag))
+        name = self.name
+        fmt = '{tag} "{name}"' if name else '{tag}'
+        return fmt.format(tag=tag, name=name)
+
+    def __repr__(self):
+        return '<{} at {:#x}>'.format(self, self.offset)
+
+    def matches(self, tag=None, name=None):
+        """Return whether this DIE matches expectations.
+
+        :rtype: bool
+        """
+        return ((tag is None or self.tag == tag) and
+                (name is None or self.name == name))
+
+    def tree_matches(self, matcher):
+        """Match this DIE against the given match object.
+
+        :param Matcher matcher: Match object used to check the structure of
+            this DIE.
+        :rtype: MatchResult
+        """
+        return matcher.matches(self)
+
+    def tree_check(self, matcher):
+        """Like `tree_matches`, but also check that the DIE matches."""
+        m = self.tree_matches(matcher)
+        check(
+            m.succeeded,
+            m.mismatch_reason or 'check structure of {}'.format(self)
+        )
+        return m
+
+    def find(self, predicate=None, tag=None, name=None, recursive=True,
+             single=True):
+        """Look for a DIE that satisfies the given expectations.
+
+        :param None|(DIE) -> bool predicate: If provided, function that filters
+            out DIEs when it returns False.
+        :param str|int|None tag: If provided, filter out DIEs whose tag does
+            not match.
+        :param str|None name: If provided, filter out DIEs whose name (see
+            the `name` property) does not match.
+        :param bool recursive: If True, perform the search recursively in
+            self's children.
+        :param bool single: If True, look for a single DIE and raise a
+            ValueError if none or several DIEs are found. Otherwise, return a
+            potentially empty list of DIEs.
+
+        :rtype: DIE|list[DIE]
+        """
+        def p(die):
+            return ((predicate is None or predicate(die)) and
+                    die.matches(tag, name))
+        result = self._find(p, recursive)
+
+        if single:
+            if not result:
+                raise ValueError('No matching DIE found')
+            if len(result) > 1:
+                raise ValueError('Multiple matching DIEs found')
+            return result[0]
+        else:
+            return result
+
+    def _find(self, predicate, recursive):
+        result = []
+
+        if predicate(self):
+            result.append(self)
+
+        for c in self.children:
+            if not recursive:
+                if predicate(c):
+                    result.append(c)
+            else:
+                result.extend(c._find(predicate, recursive))
+
+        return result
+
+    def next_attribute_form(self, name):
+        """Return the form of the next attribute this DIE requires.
+
+        Used during DIE tree construction.
+
+        :param str name: Expected name for this attribute. The abbreviation
+            will confirm it.
+        :rtype: str
+        """
+        assert len(self.attributes) < len(self.abbrev.attributes)
+        expected_name, form = self.abbrev.attributes[len(self.attributes)]
+        assert name == expected_name, (
+            'Attribute desynchronization in {}'.format(self)
+        )
+        return form
+
+    def add_attribute(self, name, form, offset, value):
+        """Add an attribute to this DIE.
+
+        Used during DIE tree construction. See Attribute's constructor for the
+        meaning of arguments.
+        """
+        self.attributes.append(Attribute(self, name, form, offset, value))
+
+    def add_child(self, child):
+        """Add a DIE child to this DIE.
+
+        Used during DIE tree construction.
+
+        :param DIE child: DIE to append.
+        """
+        assert self.has_children
+        assert child.parent is None
+        child.parent = self
+        self.children.append(child)
+
+
+class Attribute(object):
+    """DIE attribute."""
+
+    def __init__(self, die, name, form, offset, value):
+        """
+        :param DIE die: DIE that will own this attribute.
+        :param str|int name: Attribute name, or attribute number if unknown.
+        :param str form: Attribute form.
+        :param int offset: Offset of this attribute in the .debug_info section.
+        :param value: Decoded value for this attribute. If it's a Defer
+            instance, decoding will happen the first time the "value" property
+            is evaluated.
+        """
+        self.die = die
+        self.name = name
+        self.form = form
+        self.offset = offset
+
+        if isinstance(value, Defer):
+            self._value = None
+            self._value_getter = value
+        else:
+            self._value = value
+            self._value_getter = None
+            self._refine_value()
+
+    @property
+    def value(self):
+        if self._value_getter:
+            self._value = self._value_getter.get()
+            self._value_getter = None
+            self._refine_value()
+        return self._value
+
+    def _refine_value(self):
+        # If we hold a location expression, bind it to this attribute
+        if isinstance(self._value, Exprloc):
+            self._value.attribute = self
+
+    def __repr__(self):
+        label = (self.name if isinstance(self.name, str) else
+                 'Attribute {}'.format(self.name))
+        return '<{} at {:#x}>'.format(label, self.offset)
+
+
+class Exprloc(object):
+    """DWARF location expression."""
+
+    def __init__(self, byte_list, operations):
+        """
+        :param list[int] byte_list: List of bytes that encode this expression.
+        :param list[(str, ...)] operations: List of operations this expression
+            contains. Each expression is a tuple whose first element is the
+            opcode name (DW_OP_...) and whose other elements are operands.
+        """
+        self.attribute = None
+        self.byte_list = byte_list
+        self.operations = operations
+
+    @property
+    def die(self):
+        return self.attribute.die
+
+    @staticmethod
+    def format_operation(operation):
+        opcode = operation[0]
+        operands = operation[1:]
+        return '{}: {}'.format(opcode, ' '.join(operands))
+
+    def matches(self, operations):
+        """Match this list of operations to `operations`.
+
+        :param list[(str, ...)] operations: List of operations to match.
+        :rtype: bool
+        """
+        return self.operations == operations
+
+    def __repr__(self):
+        return '{} ({})'.format(
+            ' '.join(hex(b) for b in self.byte_list),
+            '; '.join(self.format_operation(op) for op in self.operations)
+        )
+
+
+class Defer(object):
+    """Helper to defer a computation."""
+
+    def __init__(self, func):
+        """
+        :param () -> T func: Callback to perform the computation.
+        """
+        self.func = func
+
+    def get(self):
+        """
+        :rtype: T
+        """
+        return self.func()
+
+
+class Matcher(object):
+    """Specification for DIE tree pattern matching."""
+
+    def __init__(self, tag=None, name=None, attrs=None, children=None,
+                 capture=None):
+        """
+        :param None|str tag: If provided, name of the tag that DIEs must match.
+        :param None|str name: If provided, name that DIEs must match (see the
+            DIE.name property).
+        :param attrs: If provided, dictionary that specifies attribute
+            expectations. Keys are attribute names. Values can be:
+
+              * None, so that attribute must be undefined in the DIE;
+              * a value, so that attribute must be defined and the value must
+                match;
+              * a Capture instance, so that the attribute value (or None, if
+                undefined) is captured.
+
+        :param None | list[DIE|Capture] children: If provided, list of DIEs
+            that children must match. Capture instances match any DIE and
+            captures it.
+
+        :param str|None capture: If provided, capture the DIE to match with the
+            given name.
+        """
+        self.tag = tag
+        self.name = name
+        self.attrs = attrs
+        self.children = children
+        self.capture_name = capture
+
+    def matches(self, die):
+        """Pattern match the given DIE.
+
+        :param DIE die: DIE to match.
+        :rtype: MatchResult
+        """
+        result = MatchResult()
+        self._match_die(die, result)
+        return result
+
+    def _match_die(self, die, result):
+        """Helper for the "matches" method.
+
+        Return whether DIE could be matched. If not, a message to describe why
+        is recorded in `result`.
+
+        :param DIE die: DIE to match.
+        :param MatchResult result: Holder for the result of the match.
+        :rtype: bool
+        """
+
+        # If asked to, check the DIE tag
+        if self.tag is not None and self.tag != die.tag:
+            result.mismatch_reason = '{} is expected to be a {}'.format(
+                die, self.tag
+            )
+            return False
+
+        # If asked to, check the DIE name
+        if self.name is not None and self.name != die.name:
+            result.mismatch_reason = (
+                '{} is expected to be called "{}"'.format(self.name,
+                                                          die.name)
+            )
+            return False
+
+        # Check attribute expectations
+        if self.attrs:
+            for n, v in self.attrs.items():
+                if not self._match_attr(die, n, v, result):
+                    return False
+
+        # Check children expectations
+        if self.children is not None:
+
+            # The number of children must match
+            if len(self.children) != len(die.children):
+                result.mismatch_reason = (
+                    '{} has {} children, {} expected'.format(
+                        die, len(die.children), len(self.children)
+                    )
+                )
+                return False
+
+            # Then each child must match the corresponding child matcher
+            for matcher_child, die_child in zip(self.children,
+                                                die.children):
+                # Capture instances matches anything and captures it
+                if isinstance(matcher_child, Capture):
+                    result.dict[matcher_child.name] = die_child
+
+                elif not matcher_child._match_die(die_child, result):
+                    return False
+
+        # Capture the input DIE if asked to
+        if self.capture_name:
+            result.dict[self.capture_name] = die
+
+        # If no check failed, the DIE matches the pattern
+        return True
+
+    @staticmethod
+    def _match_attr(die, attr_name, attr_value, result):
+        """Helper for the "matches" method.
+
+        Return whether the `attr_name` attribute in DIE matches the
+        `attr_value` expectation. If not, a message to describe why is recorded
+        in `result`.
+
+        :param DIE die: DIE that contain the attribute to match.
+        :param str attr_name: Attribute name.
+        :param attr_value: Attribute expectation. See attrs's description in
+            Match.__init__ docstring for possible values.
+        """
+        attr = die.get_attr(attr_name, or_error=False)
+
+        if attr_value is None:
+            # The attribute is expected not to be defined
+            if attr is None:
+                return True
+
+            result.mismatch_reason = (
+                '{} has a {} attribute, none expected'.format(
+                    die, attr_name
+                )
+            )
+            return False
+
+        # Capture instances matches anything and capture it
+        if isinstance(attr_value, Capture):
+            result.dict[attr_value.name] = attr
+            return True
+
+        # If we reach this point, the attribute is supposed to be defined:
+        # check it is.
+        if attr is None:
+            result.mismatch_reason = (
+                '{} is missing a {} attribute'.format(die, attr_name)
+            )
+            return False
+
+        # Check the value of the attribute matches
+        if isinstance(attr.value, Exprloc):
+            is_matching = attr.value.matches(attr_value)
+        else:
+            is_matching = attr.value == attr_value
+        if not is_matching:
+            result.mismatch_reason = (
+                '{}: {} is {}, expected to be {}'.format(
+                    die, attr_name, attr.value, attr_value
+                )
+            )
+            return False
+
+        # If no check failed, the attribute matches the pattern
+        return True
+
+
+class Capture(object):
+    """Placeholder in Matcher tree patterns.
+
+    This is used to capture specific elements during pattern matching.
+    """
+    def __init__(self, name):
+        """
+        :param str name: Capture name.
+        """
+        self.name = name
+
+
+class MatchResult(object):
+    """Holder for the result of a DIE tree pattern match."""
+
+    def __init__(self):
+        self.dict = {}
+
+        self.mismatch_reason = None
+        """
+        If left to None, the match succeded. Otherwise, must be set to a string
+        that describes why the match failed.
+
+        :type: None|str
+        """
+
+    @property
+    def succeeded(self):
+        return self.mismatch_reason is None
+
+    def capture(self, name):
+        """Return what has been captured by the `name` capture.
+
+        This is valid iff the match succeded.
+
+        :param str name: Capture name:
+        """
+        return self.dict[name]
diff --git a/gcc/testsuite/python/dwarfutils/helpers.py 
b/gcc/testsuite/python/dwarfutils/helpers.py
new file mode 100644
index 00000000000..f5e77896ae6
--- /dev/null
+++ b/gcc/testsuite/python/dwarfutils/helpers.py
@@ -0,0 +1,11 @@
+import sys
+
+
+def as_ascii(str_or_byte):
+    """
+    Python 2/3 compatibility helper.
+
+    In Python 2, just return the input. In Python 3, decode the input as ASCII.
+    """
+    return (str_or_byte if sys.version_info.major < 3 else
+            str_or_byte.decode('ascii'))
diff --git a/gcc/testsuite/python/dwarfutils/objdump.py 
b/gcc/testsuite/python/dwarfutils/objdump.py
new file mode 100644
index 00000000000..52cfc06c03b
--- /dev/null
+++ b/gcc/testsuite/python/dwarfutils/objdump.py
@@ -0,0 +1,338 @@
+# Copyright (C) 2017 Free Software Foundation, Inc.
+
+# This program 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.
+#
+# This program 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 GCC; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.
+
+# objdump-based DWARF parser
+
+# TODO: for now, this assumes that there is only one compilation unit per
+# object file. This should be implemented later if needed.
+
+import re
+import subprocess
+
+import dwarfutils
+from dwarfutils.data import Abbrev, CompilationUnit, Defer, DIE, Exprloc
+from dwarfutils.helpers import as_ascii
+
+
+abbrev_tag_re = re.compile(r'\s+(?P<number>\d+)'
+                           r'\s+(?P<tag>DW_TAG_[a-zA-Z0-9_]+)'
+                           r'\s+\[(?P<has_children>.*)\]')
+attr_re = re.compile(r'\s+(?P<attr>DW_AT(_[a-zA-Z0-9_]+| value: \d+))'
+                     r'\s+(?P<form>DW_FORM(_[a-zA-Z0-9_]+| value: \d+))')
+
+compilation_unit_re = re.compile(r'\s+Compilation Unit @ offset'
+                                 r' (?P<offset>0x[0-9a-f]+):')
+compilation_unit_attr_re = re.compile(r'\s+(?P<name>[A-Z][a-zA-Z ]*):'
+                                      r'\s+(?P<value>.*)')
+die_re = re.compile(r'\s+<(?P<level>\d+)>'
+                    r'<(?P<offset>[0-9a-f]+)>:'
+                    r' Abbrev Number: (?P<abbrev_number>\d+)'
+                    r'( \((?P<tag>DW_TAG_[a-zA-Z0-9_]+)\))?')
+die_attr_re = re.compile(r'\s+<(?P<offset>[0-9a-f]+)>'
+                         r'\s+(?P<attr>DW_AT_[a-zA-Z0-9_]+)'
+                         r'\s*: (?P<value>.*)')
+
+indirect_string_re = re.compile(r'\(indirect string, offset: 0x[0-9a-f]+\):'
+                                r' (?P<value>.*)')
+language_re = re.compile(r'(?P<number>\d+)\s+\((?P<name>.*)\)')
+block_re = re.compile(r'\d+ byte block: (?P<value>[0-9a-f ]+)')
+loc_expr_re = re.compile(r'\d+ byte block:'
+                         r' (?P<bytes>[0-9a-f ]+)'
+                         r'\s+\((?P<expr>.*)\)')
+
+
+def parse_dwarf(object_file):
+    """
+    Implementation of dwarfutils.parse_dwarf for objdump.
+
+    Run objdump on `object_file` and parse the list compilation units it
+    contains.
+
+    :param str object_file: Name of the object file to process.
+    :rtype: list[CompilationUnit]
+    """
+    abbrevs = parse_abbrevs(object_file)
+
+    lines = [as_ascii(line).rstrip()
+             for line in subprocess.check_output(
+                 [dwarfutils.DWARF_DUMP_TOOL, '--dwarf=info', object_file]
+             ).splitlines()
+             if line.strip()]
+    i = [0]
+    def next_line():
+        if i[0] >= len(lines):
+            return None
+        i[0] += 1
+        return lines[i[0] - 1]
+
+    result = []
+    die_stack = []
+    last_die = None
+
+    while True:
+        line = next_line()
+        if line is None:
+            break
+
+        # Try to match the beginning of a compilation unit
+        m = compilation_unit_re.match(line)
+        if m:
+            offset = int(m.group('offset'), 16)
+
+            attrs = {}
+            while True:
+                m = compilation_unit_attr_re.match(next_line())
+                if not m:
+                    i[0] -= 1
+                    break
+                attrs[m.group('name')] = m.group('value')
+
+            length, is_32bit = attrs['Length'].split()
+            length = int(length, 16)
+            is_32bit = is_32bit == '(32-bit)'
+
+            version = int(attrs['Version'])
+            abbrev_offset = int(attrs['Abbrev Offset'], 16)
+            pointer_size = int(attrs['Pointer Size'])
+
+            assert abbrev_offset == 0, ('Multiple compilations unit are not'
+                                        ' handled for now')
+            abbrevs_sublist = list(abbrevs)
+
+            result.append(CompilationUnit(offset, length, is_32bit, version,
+                                          abbrevs_sublist, pointer_size))
+            continue
+
+        # Try to match the beginning of a DIE
+        m = die_re.match(line)
+        if m:
+            assert result, 'Invalid DIE: missing containing compilation unit'
+            cu = result[-1]
+
+            level = int(m.group('level'))
+            offset = int(m.group('offset'), 16)
+            abbrev_number = int(m.group('abbrev_number'))
+            tag = m.group('tag')
+
+            assert level == len(die_stack)
+
+            # The end of child list is represented as a special DIE with
+            # abbreviation number 0.
+            if tag is None:
+                assert abbrev_number == 0
+                die_stack.pop()
+                continue
+
+            die = DIE(cu, level, offset, abbrev_number)
+            last_die = die
+            assert die.tag == tag, 'Unexpected tag for {}: got {}'.format(
+                die, tag
+            )
+            if die_stack:
+                die_stack[-1].add_child(die)
+            else:
+                cu.set_root(die)
+            if die.has_children:
+                die_stack.append(die)
+            continue
+
+        # Try to match an attribute
+        m = die_attr_re.match(line)
+        if m:
+            assert die_stack, 'Invalid attribute: missing containing DIE'
+            die = last_die
+
+            offset = int(m.group('offset'), 16)
+            name = m.group('attr')
+            value = m.group('value')
+
+            form = die.next_attribute_form(name)
+            try:
+                value_decoder = value_decoders[form]
+            except KeyError:
+                pass
+            else:
+                try:
+                    value = value_decoder(die, name, form, offset, value)
+                except ValueError:
+                    print('Error while decoding {} ({}) at {:#x}: {}'.format(
+                        name, form, offset, value
+                    ))
+                    raise
+            die.add_attribute(name, form, offset, value)
+            continue
+
+        # Otherwise, we must be processing "header" text before the dump
+        # itself: just discard it.
+        assert not result, 'Unhandled output: ' + line
+
+    return result
+
+
+def parse_abbrevs(object_file):
+    """
+    Run objdump on `object_file` and parse the list of abbreviations it
+    contains.
+
+    :param str object_file: Name of the object file to process.
+    :rtype: list[Abbrev]
+    """
+    result = []
+
+    for line in subprocess.check_output(
+        [dwarfutils.DWARF_DUMP_TOOL, '--dwarf=abbrev', object_file]
+    ).splitlines():
+        line = as_ascii(line).rstrip()
+        if not line:
+            continue
+
+        # Try to match a new abbrevation
+        m = abbrev_tag_re.match(line)
+        if m:
+            number = int(m.group('number'))
+            tag = m.group('tag')
+            has_children = m.group('has_children')
+            assert has_children in ('has children', 'no children')
+            has_children = has_children == 'has children'
+
+            result.append(Abbrev(number, tag, has_children))
+            continue
+
+        # Try to match an attribute
+        m = attr_re.match(line)
+        if m:
+            assert result, 'Invalid attribute: missing containing abbreviation'
+            name = m.group('attr')
+            form = m.group('form')
+
+            # When objdump finds unknown abbreviation numbers or unknown form
+            # numbers, it cannot turn them into names.
+            if name.startswith('DW_AT value'):
+                name = int(name.split()[-1])
+            if form.startswith('DW_FORM value'):
+                form = int(form.split()[-1])
+
+            # The (0, 0) couple marks the end of the attribute list
+            if name != 0 or form != 0:
+                result[-1].add_attribute(name, form)
+            continue
+
+        # Otherwise, we must be processing "header" text before the dump
+        # itself: just discard it.
+        assert not result, 'Unhandled output: ' + line
+
+    return result
+
+
+# Decoders for attribute values
+
+def _decode_flag_present(die, name, form, offset, value):
+    return True
+
+
+def _decode_flag(die, name, form, offset, value):
+    return bool(int(value))
+
+
+def _decode_data(die, name, form, offset, value):
+    if name == 'DW_AT_language':
+        m = language_re.match(value)
+        assert m, 'Unhandled language value: {}'.format(value)
+        return m.group('name')
+
+    elif name == 'DW_AT_encoding':
+        m = language_re.match(value)
+        assert m, 'Unhandled encoding value: {}'.format(value)
+        return m.group('name')
+
+    return int(value, 16) if value.startswith('0x') else int(value)
+
+
+def _decode_ref(die, name, form, offset, value):
+    assert value[0] == '<' and value[-1] == '>'
+    offset = int(value[1:-1], 16)
+    return Defer(lambda: die.cu.offset_to_die[offset])
+
+
+def _decode_indirect_string(die, name, form, offset, value):
+    m = indirect_string_re.match(value)
+    assert m, 'Unhandled indirect string: ' + value
+    return m.group('value')
+
+
+def _decode_block(die, name, form, offset, value, no_exprloc=False):
+    if (
+        not no_exprloc and
+        name in ('DW_AT_location', 'DW_AT_data_member_location')
+    ):
+        return _decode_exprloc(die, name, form, offset, value, )
+
+    m = block_re.match(value)
+    assert m, 'Unhandled block value: {}'.format(value)
+    return [int(b, 16) for b in m.group('value').split()]
+
+
+def _decode_exprloc(die, name, form, offset, value):
+    m = loc_expr_re.match(value)
+    if not m:
+        # Even though they have the expected DW_FORM_exploc form, objdump does
+        # not decode some location expressions such as DW_AT_byte_size. In this
+        # case, return a dummy block decoding instead.
+        # TODO: implement raw bytes parsing into expressions instead.
+        return _decode_block(die, name, form, offset, value, no_exprloc=True)
+
+    byte_list = [int(b, 16) for b in m.group('bytes').split()]
+
+    expr = m.group('expr')
+    operations = []
+    for op in expr.split('; '):
+        chunks = op.split(': ', 1)
+        assert len(chunks) <= 2, (
+            'Unhandled DWARF expression operation: {}'.format(op)
+        )
+        opcode = chunks[0]
+        operands = chunks[1].split() if len(chunks) == 2 else []
+        operations.append((opcode, ) + tuple(operands))
+
+    return Exprloc(byte_list, operations)
+
+
+value_decoders = {
+    'DW_FORM_flag_present': _decode_flag_present,
+    'DW_FORM_flag': _decode_flag,
+
+    'DW_FORM_data1': _decode_data,
+    'DW_FORM_data2': _decode_data,
+    'DW_FORM_data4': _decode_data,
+    'DW_FORM_data8': _decode_data,
+    'DW_FORM_sdata': _decode_data,
+    'DW_FORM_udata': _decode_data,
+
+    'DW_FORM_ref4': _decode_ref,
+    'DW_FORM_ref8': _decode_ref,
+
+    'DW_FORM_strp': _decode_indirect_string,
+
+    'DW_FORM_block': _decode_block,
+    'DW_FORM_block1': _decode_block,
+    'DW_FORM_block2': _decode_block,
+    'DW_FORM_block4': _decode_block,
+    'DW_FORM_block8': _decode_block,
+    'DW_FORM_block8': _decode_block,
+    'DW_FORM_exprloc': _decode_exprloc,
+
+    # TODO: handle all existing forms
+}
-- 
2.13.0

Reply via email to