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