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