* [PATCH 0/2] Python testcases to check DWARF output @ 2017-07-26 16:01 Pierre-Marie de Rodat 2017-07-26 16:01 ` [PATCH 1/2] Introduce testsuite support to run Python tests Pierre-Marie de Rodat ` (4 more replies) 0 siblings, 5 replies; 27+ messages in thread From: Pierre-Marie de Rodat @ 2017-07-26 16:01 UTC (permalink / raw) To: gcc-patches Hello, At the last GNU Cauldron, Richard Biener and I talked about DWARF output testing. Except for guality tests, which are disabled on several targets, the only way tests check the DWARF is scanning the annotated assembly (-dA), making it hard to write reliable tests. For instance, checking the number of times DW_AT_location is present in order to check that some specific variable is assigned one is fuzzy. Depending on the target and on the evolution of the compiler, the number of output variables, or which one is assigned a location can vary legitimately but still make the test fail. On my side, I already had written an out-of-tree testsuite for the DWARF features I added for Ada. This testsuite uses a DWARF parser in order to perform checks on a tree: <https://github.com/pmderodat/dwarf-ada-testsuite/>. I had to update it a couple of times, for instance when a change created a DW_TAG_const_type DIE or removed one somewhere in a type tree, but thatâs very rare. I would say that Iâm satisfied with the checks I could express, but I donât remember I ever caught a regression with them, so I have no representative experience to share in this area. Maybe DWARF back-end developpers do a too good job. ;-) Anyway, Richard and I discussed about doing something similar in-tree, and here is a candidate set of patches to achieve that: * The first patch installs DejaGNU scripts to run a Python interpreter in testcases. * The second one installs other DejaGNU scripts to detect DWARF dumping tools, plus a small Python library to parse and pattern match DIEs and their attributes. It also adds several C and Ada tests as examples; these are inspired by existing homonym tests based on assembly scanning. For now, this supports only platforms where objdump is available for the current target, but extending it to other tools, such as otool on Darwin should be doable. I would appreciate feedback about the idea and the implementation I propose. This is the first time I do more in the testsuite than just adding new tests, so thank you in advance for you patience in reviewing these. :-) I tested these patches on x86_64-linux. ^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH 1/2] Introduce testsuite support to run Python tests 2017-07-26 16:01 [PATCH 0/2] Python testcases to check DWARF output Pierre-Marie de Rodat @ 2017-07-26 16:01 ` Pierre-Marie de Rodat 2017-07-26 16:25 ` David Malcolm 2017-07-27 8:50 ` Matthias Klose 2017-07-26 16:01 ` [PATCH 2/2] Introduce Python testcases to check DWARF output Pierre-Marie de Rodat ` (3 subsequent siblings) 4 siblings, 2 replies; 27+ messages in thread From: Pierre-Marie de Rodat @ 2017-07-26 16:01 UTC (permalink / raw) To: gcc-patches; +Cc: Pierre-Marie de Rodat gcc/testsuite/ * lib/gcc-python.exp: New test library. * python/testutils.py: New Python helper. --- gcc/testsuite/lib/gcc-python.exp | 95 +++++++++++++++++++++++++++++++++++++++ gcc/testsuite/python/testutils.py | 45 +++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 gcc/testsuite/lib/gcc-python.exp create mode 100644 gcc/testsuite/python/testutils.py diff --git a/gcc/testsuite/lib/gcc-python.exp b/gcc/testsuite/lib/gcc-python.exp new file mode 100644 index 00000000000..30cf74a87ac --- /dev/null +++ b/gcc/testsuite/lib/gcc-python.exp @@ -0,0 +1,95 @@ +# 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 a Python interpreter + +load_lib "remote.exp" + +# Return whether a working Python interpreter is available. + +proc check-python-available { args } { + set result [local_exec "python -c print(\"Hello\")" "" "" 300] + + set status [lindex $result 0] + set output [string trim [lindex $result 1]] + + if { $status != 0 || $output != "Hello" } { + return 0 + } else { + return 1 + } +} + +# Run the SCRIPT_PY Python script. Add one PASSing (FAILing) test per output +# line that starts with "PASS: " ("FAIL: "). Also fail for any other output +# line and for non-zero exit status code. +# +# The Python script can access Python modules and packages in the +# $srcdir/python directory. + +proc python-test { script_py } { + global srcdir + + set testname testname-for-summary + + # This assumes that we are three frames down from dg-test, and that + # it still stores the filename of the testcase in a local variable "name". + # A cleaner solution would require a new DejaGnu release. + upvar 2 prog src_file + + set asm_file "[file rootname [file tail $src_file]].o" + set script_py_path "[file dirname $src_file]/$script_py" + + set old_pythonpath [getenv "PYTHONPATH"] + set support_dir "$srcdir/python" + if { $old_pythonpath == "" } { + setenv "PYTHONPATH" $support_dir + } else { + setenv "PYTHONPATH" "$support_dir:$PYTHONPATH" + } + + set commandline "python $script_py_path $asm_file" + set timeout 300 + + verbose -log "Executing: $commandline (timeout = $timeout)" 2 + set result [local_exec $commandline "" "" $timeout] + + set status [lindex $result 0] + set output [lindex $result 1] + + if { $status != 0 } { + fail [concat "$testname: $script_py stopped with non-zero status" \ + " code ($status)"] + } + + foreach line [split $output "\n"] { + if { $line == "" } { + continue + } + if { [regexp "^PASS: (.*)" $line dummy message] } { + pass "$testname/$script_py: $message" + continue + } + if { [regexp "^FAIL: (.*)" $line dummy message] } { + fail "$testname/$script_py: $message" + continue + } + + fail "$testname/$script_py: spurious output: $line" + } + + setenv "PYTHONPATH" $old_pythonpath +} diff --git a/gcc/testsuite/python/testutils.py b/gcc/testsuite/python/testutils.py new file mode 100644 index 00000000000..503105ad9d0 --- /dev/null +++ b/gcc/testsuite/python/testutils.py @@ -0,0 +1,45 @@ +# 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 drive a testcase + +def print_pass(message): + """Emit a PASS message. + + :param str message: Message to emit. + """ + print('PASS: {}'.format(message)) + + +def print_fail(message): + """Emit a FAIL message. + + :param str message: Message to emit. + """ + print('FAIL: {}'.format(message)) + + +def check(predicate, message): + """ + If `predicate` is True, emit a PASS message, otherwise emit a FAIL one. + + :param bool predicate: Whether the test should pass. + :param str message: Message to emit. + """ + if predicate: + print_pass(message) + else: + print_fail(message) -- 2.13.0 ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 1/2] Introduce testsuite support to run Python tests 2017-07-26 16:01 ` [PATCH 1/2] Introduce testsuite support to run Python tests Pierre-Marie de Rodat @ 2017-07-26 16:25 ` David Malcolm 2017-07-26 16:35 ` Pierre-Marie de Rodat 2017-07-27 8:50 ` Matthias Klose 1 sibling, 1 reply; 27+ messages in thread From: David Malcolm @ 2017-07-26 16:25 UTC (permalink / raw) To: Pierre-Marie de Rodat, gcc-patches On Wed, 2017-07-26 at 18:00 +0200, Pierre-Marie de Rodat wrote: [...snip...] > diff --git a/gcc/testsuite/python/testutils.py > b/gcc/testsuite/python/testutils.py > new file mode 100644 > index 00000000000..503105ad9d0 > --- /dev/null > +++ b/gcc/testsuite/python/testutils.py > @@ -0,0 +1,45 @@ > +# 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 drive a testcase > + > +def print_pass(message): > + """Emit a PASS message. > + > + :param str message: Message to emit. > + """ > + print('PASS: {}'.format(message)) str.format was introduced in Python 2.6, so presumably the minimum python 2 version here is at least 2.6+; for Python 3 I believe it was present in Python 3.0 onwards. > + > +def print_fail(message): > + """Emit a FAIL message. > + > + :param str message: Message to emit. > + """ > + print('FAIL: {}'.format(message)) > + > + > +def check(predicate, message): > + """ > + If `predicate` is True, emit a PASS message, otherwise emit a > FAIL one. A very nitpicky nitpick: this comment should be spelled as "is true" (lowercase), rather than "is True" since the requirement is that predicate's "truth value" is true, rather than predicate *is* the boolean "True" singleton; e.g. if someone passes in an int as predicate, its nonzero-ness would be used, rather than always being false (since no int *is* the boolean singleton "True"). > + > + :param bool predicate: Whether the test should pass. > + :param str message: Message to emit. > + """ > + if predicate: > + print_pass(message) > + else: > + print_fail(message) ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 1/2] Introduce testsuite support to run Python tests 2017-07-26 16:25 ` David Malcolm @ 2017-07-26 16:35 ` Pierre-Marie de Rodat 2017-07-26 16:48 ` David Malcolm 0 siblings, 1 reply; 27+ messages in thread From: Pierre-Marie de Rodat @ 2017-07-26 16:35 UTC (permalink / raw) To: David Malcolm, gcc-patches On 07/26/2017 06:25 PM, David Malcolm wrote: > str.format was introduced in Python 2.6, so presumably the minimum > python 2 version here is at least 2.6+; for Python 3 I believe it was > present in Python 3.0 onwards. Hm⦠Python 2.6 is fairly old: last binary release was ages ago, last source release was in 2013. Do you think itâs worth supporting it? >> +def check(predicate, message): >> + """ >> + If `predicate` is True, emit a PASS message, otherwise emit a >> FAIL one. > > A very nitpicky nitpick: this comment should be spelled as "is true" > (lowercase), rather than "is True" since the requirement is that > predicate's "truth value" is true, rather than predicate *is* the > boolean "True" singleton; e.g. if someone passes in an int as > predicate, its nonzero-ness would be used, rather than always being > false (since no int *is* the boolean singleton "True"). I agree with you: I updated the patch on my machine. Thank you! -- Pierre-Marie de Rodat ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 1/2] Introduce testsuite support to run Python tests 2017-07-26 16:35 ` Pierre-Marie de Rodat @ 2017-07-26 16:48 ` David Malcolm 2017-07-27 8:49 ` Pierre-Marie de Rodat 2017-08-02 18:43 ` Jeff Law 0 siblings, 2 replies; 27+ messages in thread From: David Malcolm @ 2017-07-26 16:48 UTC (permalink / raw) To: Pierre-Marie de Rodat, gcc-patches On Wed, 2017-07-26 at 18:35 +0200, Pierre-Marie de Rodat wrote: > On 07/26/2017 06:25 PM, David Malcolm wrote: > > str.format was introduced in Python 2.6, so presumably the minimum > > python 2 version here is at least 2.6+; for Python 3 I believe it > > was > > present in Python 3.0 onwards. > > Hm⦠Python 2.6 is fairly old: last binary release was ages ago, last > source release was in 2013. Do you think itâs worth supporting it? IIRC RHEL 6 has Python 2.6 as its /usr/bin/python (but Python 2.7 is available as a "software collection" add-on). I don't know if gcc as a project would want to support 2.6+ or simply 2.7 for Python 2. > > > +def check(predicate, message): > > > + """ > > > + If `predicate` is True, emit a PASS message, otherwise emit > > > a > > > FAIL one. > > > > A very nitpicky nitpick: this comment should be spelled as "is > > true" > > (lowercase), rather than "is True" since the requirement is that > > predicate's "truth value" is true, rather than predicate *is* the > > boolean "True" singleton; e.g. if someone passes in an int as > > predicate, its nonzero-ness would be used, rather than always being > > false (since no int *is* the boolean singleton "True"). > > I agree with you: I updated the patch on my machine. Thank you! Thanks. I saw at least one other instance of this in the other patch; I'll look over it again. Dave ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 1/2] Introduce testsuite support to run Python tests 2017-07-26 16:48 ` David Malcolm @ 2017-07-27 8:49 ` Pierre-Marie de Rodat 2017-07-27 13:40 ` David Malcolm 2017-08-02 18:43 ` Jeff Law 1 sibling, 1 reply; 27+ messages in thread From: Pierre-Marie de Rodat @ 2017-07-27 8:49 UTC (permalink / raw) To: David Malcolm, gcc-patches On 07/26/2017 06:48 PM, David Malcolm wrote: > IIRC RHEL 6 has Python 2.6 as its /usr/bin/python (but Python 2.7 is > available as a "software collection" add-on). > > I don't know if gcc as a project would want to support 2.6+ or simply > 2.7 for Python 2. I donât know neither: letâs wait for further feedback, then. If needed Iâll turn all the .format into % operations. -- Pierre-Marie de Rodat ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 1/2] Introduce testsuite support to run Python tests 2017-07-27 8:49 ` Pierre-Marie de Rodat @ 2017-07-27 13:40 ` David Malcolm 0 siblings, 0 replies; 27+ messages in thread From: David Malcolm @ 2017-07-27 13:40 UTC (permalink / raw) To: Pierre-Marie de Rodat, gcc-patches On Thu, 2017-07-27 at 10:49 +0200, Pierre-Marie de Rodat wrote: > On 07/26/2017 06:48 PM, David Malcolm wrote: > > IIRC RHEL 6 has Python 2.6 as its /usr/bin/python (but Python 2.7 > > is > > available as a "software collection" add-on). > > > > I don't know if gcc as a project would want to support 2.6+ or > > simply > > 2.7 for Python 2. > > I donât know neither: letâs wait for further feedback, then. If > needed > Iâll turn all the .format into % operations. Note that str.format was introduced in Python 2.6, so even if we do support that old version, str.format is still good; no need to rewrite those ops. (sorry, I used to maintain Python for RHEL, so I'm perhaps over -sensitive to this kind of thing). Dave ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 1/2] Introduce testsuite support to run Python tests 2017-07-26 16:48 ` David Malcolm 2017-07-27 8:49 ` Pierre-Marie de Rodat @ 2017-08-02 18:43 ` Jeff Law 2017-08-03 8:27 ` Pierre-Marie de Rodat 1 sibling, 1 reply; 27+ messages in thread From: Jeff Law @ 2017-08-02 18:43 UTC (permalink / raw) To: David Malcolm, Pierre-Marie de Rodat, gcc-patches On 07/26/2017 10:48 AM, David Malcolm wrote: > On Wed, 2017-07-26 at 18:35 +0200, Pierre-Marie de Rodat wrote: >> On 07/26/2017 06:25 PM, David Malcolm wrote: >>> str.format was introduced in Python 2.6, so presumably the minimum >>> python 2 version here is at least 2.6+; for Python 3 I believe it >>> was >>> present in Python 3.0 onwards. >> >> Hm⦠Python 2.6 is fairly old: last binary release was ages ago, last >> source release was in 2013. Do you think itâs worth supporting it? > > IIRC RHEL 6 has Python 2.6 as its /usr/bin/python (but Python 2.7 is > available as a "software collection" add-on). Given the age of RHEL 6, I wouldn't get too hung up on on supporting it for this stuff. Hell, it's EOL in just a few short years. > > I don't know if gcc as a project would want to support 2.6+ or simply > 2.7 for Python 2. If it was trivial, then let's support 2.6. But if it's nontrivial, I'd support stepping to something more modern. Jeff ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 1/2] Introduce testsuite support to run Python tests 2017-08-02 18:43 ` Jeff Law @ 2017-08-03 8:27 ` Pierre-Marie de Rodat 0 siblings, 0 replies; 27+ messages in thread From: Pierre-Marie de Rodat @ 2017-08-03 8:27 UTC (permalink / raw) To: Jeff Law, David Malcolm, gcc-patches On 08/02/2017 08:43 PM, Jeff Law wrote: > If it was trivial, then let's support 2.6. But if it's nontrivial, I'd > support stepping to something more modern. It is trivial. Iâve done it locally. :-) -- Pierre-Marie de Rodat ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 1/2] Introduce testsuite support to run Python tests 2017-07-26 16:01 ` [PATCH 1/2] Introduce testsuite support to run Python tests Pierre-Marie de Rodat 2017-07-26 16:25 ` David Malcolm @ 2017-07-27 8:50 ` Matthias Klose 2017-07-27 10:09 ` Pierre-Marie de Rodat 1 sibling, 1 reply; 27+ messages in thread From: Matthias Klose @ 2017-07-27 8:50 UTC (permalink / raw) To: Pierre-Marie de Rodat, gcc-patches you are unconditionally hard coding python as the interpreter, which on most distributions points to 2.7. Please check python3 as well and make that the preferred interpreter if available. python 2.7 is now EOL'd for 2020. Matthias On 26.07.2017 18:00, Pierre-Marie de Rodat wrote: > gcc/testsuite/ > > * lib/gcc-python.exp: New test library. > * python/testutils.py: New Python helper. > --- > gcc/testsuite/lib/gcc-python.exp | 95 +++++++++++++++++++++++++++++++++++++++ > gcc/testsuite/python/testutils.py | 45 +++++++++++++++++++ > 2 files changed, 140 insertions(+) > create mode 100644 gcc/testsuite/lib/gcc-python.exp > create mode 100644 gcc/testsuite/python/testutils.py > > diff --git a/gcc/testsuite/lib/gcc-python.exp b/gcc/testsuite/lib/gcc-python.exp > new file mode 100644 > index 00000000000..30cf74a87ac > --- /dev/null > +++ b/gcc/testsuite/lib/gcc-python.exp > @@ -0,0 +1,95 @@ > +# 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 a Python interpreter > + > +load_lib "remote.exp" > + > +# Return whether a working Python interpreter is available. > + > +proc check-python-available { args } { > + set result [local_exec "python -c print(\"Hello\")" "" "" 300] > + > + set status [lindex $result 0] > + set output [string trim [lindex $result 1]] > + > + if { $status != 0 || $output != "Hello" } { > + return 0 > + } else { > + return 1 > + } > +} > + > +# Run the SCRIPT_PY Python script. Add one PASSing (FAILing) test per output > +# line that starts with "PASS: " ("FAIL: "). Also fail for any other output > +# line and for non-zero exit status code. > +# > +# The Python script can access Python modules and packages in the > +# $srcdir/python directory. > + > +proc python-test { script_py } { > + global srcdir > + > + set testname testname-for-summary > + > + # This assumes that we are three frames down from dg-test, and that > + # it still stores the filename of the testcase in a local variable "name". > + # A cleaner solution would require a new DejaGnu release. > + upvar 2 prog src_file > + > + set asm_file "[file rootname [file tail $src_file]].o" > + set script_py_path "[file dirname $src_file]/$script_py" > + > + set old_pythonpath [getenv "PYTHONPATH"] > + set support_dir "$srcdir/python" > + if { $old_pythonpath == "" } { > + setenv "PYTHONPATH" $support_dir > + } else { > + setenv "PYTHONPATH" "$support_dir:$PYTHONPATH" > + } > + > + set commandline "python $script_py_path $asm_file" > + set timeout 300 > + > + verbose -log "Executing: $commandline (timeout = $timeout)" 2 > + set result [local_exec $commandline "" "" $timeout] > + > + set status [lindex $result 0] > + set output [lindex $result 1] > + > + if { $status != 0 } { > + fail [concat "$testname: $script_py stopped with non-zero status" \ > + " code ($status)"] > + } > + > + foreach line [split $output "\n"] { > + if { $line == "" } { > + continue > + } > + if { [regexp "^PASS: (.*)" $line dummy message] } { > + pass "$testname/$script_py: $message" > + continue > + } > + if { [regexp "^FAIL: (.*)" $line dummy message] } { > + fail "$testname/$script_py: $message" > + continue > + } > + > + fail "$testname/$script_py: spurious output: $line" > + } > + > + setenv "PYTHONPATH" $old_pythonpath > +} > diff --git a/gcc/testsuite/python/testutils.py b/gcc/testsuite/python/testutils.py > new file mode 100644 > index 00000000000..503105ad9d0 > --- /dev/null > +++ b/gcc/testsuite/python/testutils.py > @@ -0,0 +1,45 @@ > +# 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 drive a testcase > + > +def print_pass(message): > + """Emit a PASS message. > + > + :param str message: Message to emit. > + """ > + print('PASS: {}'.format(message)) > + > + > +def print_fail(message): > + """Emit a FAIL message. > + > + :param str message: Message to emit. > + """ > + print('FAIL: {}'.format(message)) > + > + > +def check(predicate, message): > + """ > + If `predicate` is True, emit a PASS message, otherwise emit a FAIL one. > + > + :param bool predicate: Whether the test should pass. > + :param str message: Message to emit. > + """ > + if predicate: > + print_pass(message) > + else: > + print_fail(message) > ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 1/2] Introduce testsuite support to run Python tests 2017-07-27 8:50 ` Matthias Klose @ 2017-07-27 10:09 ` Pierre-Marie de Rodat 0 siblings, 0 replies; 27+ messages in thread From: Pierre-Marie de Rodat @ 2017-07-27 10:09 UTC (permalink / raw) To: Matthias Klose, gcc-patches On 07/27/2017 10:50 AM, Matthias Klose wrote: > you are unconditionally hard coding python as the interpreter, which on most > distributions points to 2.7. Please check python3 as well and make that the > preferred interpreter if available. python 2.7 is now EOL'd for 2020. Understood, thank you. Iâll do this for the next patchset Iâll submit. -- Pierre-Marie de Rodat ^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH 2/2] Introduce Python testcases to check DWARF output 2017-07-26 16:01 [PATCH 0/2] Python testcases to check DWARF output Pierre-Marie de Rodat 2017-07-26 16:01 ` [PATCH 1/2] Introduce testsuite support to run Python tests Pierre-Marie de Rodat @ 2017-07-26 16:01 ` Pierre-Marie de Rodat 2017-07-26 17:10 ` David Malcolm 2017-07-27 8:36 ` Richard Biener 2017-07-26 16:16 ` [PATCH 0/2] " David Malcolm ` (2 subsequent siblings) 4 siblings, 2 replies; 27+ messages in thread From: Pierre-Marie de Rodat @ 2017-07-26 16:01 UTC (permalink / raw) To: gcc-patches; +Cc: Pierre-Marie de Rodat 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 ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 2/2] Introduce Python testcases to check DWARF output 2017-07-26 16:01 ` [PATCH 2/2] Introduce Python testcases to check DWARF output Pierre-Marie de Rodat @ 2017-07-26 17:10 ` David Malcolm 2017-07-27 8:59 ` Pierre-Marie de Rodat 2017-07-27 8:36 ` Richard Biener 1 sibling, 1 reply; 27+ messages in thread From: David Malcolm @ 2017-07-26 17:10 UTC (permalink / raw) To: Pierre-Marie de Rodat, gcc-patches On Wed, 2017-07-26 at 18:00 +0200, Pierre-Marie de Rodat wrote: [...] > 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 [...] > +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 "is True" -> "is true" [...] > --- /dev/null > +++ b/gcc/testsuite/python/dwarfutils/data.py > + > + 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 "True" -> "true" in two places [...] > + 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 "True" -> "true", I suppose [...] > +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 "succeded" -> "succeeded" > + > + def capture(self, name): > + """Return what has been captured by the `name` capture. > + > + This is valid iff the match succeded. here again. [...] > 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')) Aha! Python 2 and Python 3. Presumably this all runs with LANG=C so that there's no danger of any non-ASCII bytes? (bytes.decode('ascii' will raise a UnicodeDecodeError if any byte >=128). > 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 [...] There's a fair amount of non-trivial parsing going on here. I wonder if it would be helpful to add a "unittest" suite for the parsing? (e.g. to have some precanned fragments of objdump output as strings, and to verify that they're parsed as expected). Note that I'm not a reviewer for the testsuite, so this is just a suggestion. Hope this is constructive Dave ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 2/2] Introduce Python testcases to check DWARF output 2017-07-26 17:10 ` David Malcolm @ 2017-07-27 8:59 ` Pierre-Marie de Rodat 0 siblings, 0 replies; 27+ messages in thread From: Pierre-Marie de Rodat @ 2017-07-27 8:59 UTC (permalink / raw) To: David Malcolm, gcc-patches On 07/26/2017 07:09 PM, David Malcolm wrote: >> + If `single_cu` is True, make sure there is exactly one >> compilation unit and > > "is True" -> "is true" Fixed. >> + :param bool or_error: When True, if `single` is True and no >> attribute > > "True" -> "true" in two places Fixed. >> + :param None|(DIE) -> bool predicate: If provided, function >> that filters >> + out DIEs when it returns False. You did not suggested, but I replaced âFalseâ with âfalseâ to be consistent. ;-) >> + :param bool single: If True, look for a single DIE and raise >> a > > "True" -> "true", I suppose Fixed. >> + If left to None, the match succeded. Otherwise, must be set > > > "succeded" -> "succeeded" Fixed. >> + This is valid iff the match succeded. > > here again. Likewise. >> + 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')) > > Aha! Python 2 and Python 3. > > > Presumably this all runs with LANG=C so that there's no danger of any > non-ASCII bytes? (bytes.decode('ascii' will raise a UnicodeDecodeError > if any byte >=128). Iâm not sure about the interaction with the locale. What I thought was: Iâve never seen non-ASCII strings in DWARF, nor in objdumpâs output. I know itâs theorically possible: if that happens in the future (like some language allows non-ASCII identifier and yield non-ASCII names in DWARF), weâll only have this function to fix. > There's a fair amount of non-trivial parsing going on here. > I wonder if it would be helpful to add a "unittest" suite for the > parsing? > (e.g. to have some precanned fragments of objdump output as strings, > and to verify that they're parsed as expected). > > Note that I'm not a reviewer for the testsuite, so this is just a > suggestion. Thatâs a good idea. Actually I think it will be very easy to write such tests *and* to assess Python code coverage for them. Iâll do this if this proposal is in good way to be accepted. > Hope this is constructive It totally was: thank you very much! -- Pierre-Marie de Rodat ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 2/2] Introduce Python testcases to check DWARF output 2017-07-26 16:01 ` [PATCH 2/2] Introduce Python testcases to check DWARF output Pierre-Marie de Rodat 2017-07-26 17:10 ` David Malcolm @ 2017-07-27 8:36 ` Richard Biener 2017-07-27 10:09 ` Pierre-Marie de Rodat 1 sibling, 1 reply; 27+ messages in thread From: Richard Biener @ 2017-07-27 8:36 UTC (permalink / raw) To: Pierre-Marie de Rodat; +Cc: GCC Patches On Wed, Jul 26, 2017 at 6:00 PM, Pierre-Marie de Rodat <derodat@adacore.com> wrote: > 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. Just some random thoughts. Given that gdb can decode dwarf and we rely on gdb for guality and gdb has python scripting can we somehow walk its dwarf tree from within a python script? That is, not need the dwarf decoding or objdump requirement? On IRC I suggested to use pre-existing python DWARF decoders which we might be able to import into the tree. We'd still need them to handle non-ELF object formats or somehow extract DWARF from other containers to an ELF file (objcopy to the rescue...). That said, not needing to write a DWARF / object file decoder would be nice. I see your testcases have associated .py files. There are a few existing "simple" dwarf testcases that would benefit from being able to embed matching into the testcase source file itself? Thus have TCL autogenerate a .py file for the testing from, say /* { dg-final { scan-dwarf { "Matcher('DW_TAG_member', 'i', attrs={'DW_AT_type': Capture('s0_i_type')})" } } } */ do you think that's feasible or doesn't it make much sense because it would essentially match anywhere? Or we'd end up with a gazillion of scan-dwarf variants? I think a separate .py for checking is required anyway for the more complex cases. > 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 > ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 2/2] Introduce Python testcases to check DWARF output 2017-07-27 8:36 ` Richard Biener @ 2017-07-27 10:09 ` Pierre-Marie de Rodat 0 siblings, 0 replies; 27+ messages in thread From: Pierre-Marie de Rodat @ 2017-07-27 10:09 UTC (permalink / raw) To: Richard Biener; +Cc: GCC Patches On 07/27/2017 10:36 AM, Richard Biener wrote: > Given that gdb can decode dwarf and we rely on gdb for guality and > gdb has python scripting can we somehow walk its dwarf tree from > within a python script? That is, not need the dwarf decoding or > objdump requirement? Iâm quite familiar with GDBâs Python scripting API and unfortunately, no, it does not provide any access to raw debugging information: <https://sourceware.org/gdb/onlinedocs/gdb/Python-API.html>. All we have is access to ~source-level entities such as variables, functions and types (and âobjfilesâ themselves, but we canât do anything interesting with them), so there is no way other way than testing dynamic behavior, i.e. checking that variables are properly read/decoded, etc. which is what we already do in guality tests. > On IRC I suggested to use pre-existing python DWARF decoders > which we might be able to import into the tree. We'd still need them > to handle non-ELF object formats or somehow extract DWARF from > other containers to an ELF file (objcopy to the rescue...). > > That said, not needing to write a DWARF / object file decoder > would be nice. Yes. On IRC, I mentionned pyelftools (https://github.com/eliben/pyelftools/), which knows about ELF and DWARF, and that, I think, we could plug on some PE/XCOFF/⦠extractor to parse embedded DWARF. In any case, I feel it would not be simpler than what I sent. Of course Iâm still open to suggestions. :-) > I see your testcases have associated .py files. There are a few > existing "simple" dwarf testcases that would benefit from being > able to embed matching into the testcase source file itself? Thus > have TCL autogenerate a .py file for the testing from, say > > /* { dg-final { scan-dwarf { "Matcher('DW_TAG_member', 'i', > attrs={'DW_AT_type': Capture('s0_i_type')})" } } } */ > > do you think that's feasible or doesn't it make much sense because > it would essentially match anywhere? Or we'd end up with a > gazillion of scan-dwarf variants? I think this is a good idea! If it is technically possible to have such multi-line statements in comments, I think this would be easy. Iâll prepare the engine for the next patchset version and Iâll try to find existing tests that could be re-written this way. As long as the pattern isnât too generic, I think it would makes sense: for instance if the input source has only one structure field called âiâ, then the above pattern will make it possible to match its type precisely. > I think a separate .py for checking is required anyway for the more > complex cases. I think so as well, for instance for the tests I sent so far. -- Pierre-Marie de Rodat ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 0/2] Python testcases to check DWARF output 2017-07-26 16:01 [PATCH 0/2] Python testcases to check DWARF output Pierre-Marie de Rodat 2017-07-26 16:01 ` [PATCH 1/2] Introduce testsuite support to run Python tests Pierre-Marie de Rodat 2017-07-26 16:01 ` [PATCH 2/2] Introduce Python testcases to check DWARF output Pierre-Marie de Rodat @ 2017-07-26 16:16 ` David Malcolm 2017-07-26 16:26 ` Pierre-Marie de Rodat 2017-07-26 21:25 ` Mike Stump 2017-08-02 15:43 ` Jeff Law 4 siblings, 1 reply; 27+ messages in thread From: David Malcolm @ 2017-07-26 16:16 UTC (permalink / raw) To: Pierre-Marie de Rodat, gcc-patches On Wed, 2017-07-26 at 18:00 +0200, Pierre-Marie de Rodat wrote: > Hello, > > At the last GNU Cauldron, Richard Biener and I talked about DWARF > output > testing. Except for guality tests, which are disabled on several > targets, the only way tests check the DWARF is scanning the annotated > assembly (-dA), making it hard to write reliable tests. > > For instance, checking the number of times DW_AT_location is present > in > order to check that some specific variable is assigned one is fuzzy. > Depending on the target and on the evolution of the compiler, the > number > of output variables, or which one is assigned a location can vary > legitimately but still make the test fail. > > On my side, I already had written an out-of-tree testsuite for the > DWARF > features I added for Ada. This testsuite uses a DWARF parser in order > to > perform checks on a tree: > <https://github.com/pmderodat/dwarf-ada-testsuite/>. I had to update > it > a couple of times, for instance when a change created a > DW_TAG_const_type DIE or removed one somewhere in a type tree, but > thatâs very rare. I would say that Iâm satisfied with the checks I > could > express, but I donât remember I ever caught a regression with them, > so I > have no representative experience to share in this area. Maybe DWARF > back-end developpers do a too good job. ;-) > > Anyway, Richard and I discussed about doing something similar in > -tree, > and here is a candidate set of patches to achieve that: > > * The first patch installs DejaGNU scripts to run a Python > interpreter > in testcases. > > * The second one installs other DejaGNU scripts to detect DWARF > dumping tools, plus a small Python library to parse and pattern > match DIEs and their attributes. It also adds several C and Ada > tests as examples; these are inspired by existing homonym tests > based on assembly scanning. > > For now, this supports only platforms where objdump is available for > the > current target, but extending it to other tools, such as otool on > Darwin > should be doable. > > I would appreciate feedback about the idea and the implementation I > propose. This is the first time I do more in the testsuite than just > adding new tests, so thank you in advance for you patience in > reviewing > these. :-) (FWIW I'm a big fan of Python, so am happy to see this proposal) > I tested these patches on x86_64-linux. Which version of Python did you test against? As far as I can see you've coded this using the common subset of Python 2 and Python 3; it's worth spelling out what the assumptions are in this regard (and what the minimum versions are). Dave ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 0/2] Python testcases to check DWARF output 2017-07-26 16:16 ` [PATCH 0/2] " David Malcolm @ 2017-07-26 16:26 ` Pierre-Marie de Rodat 0 siblings, 0 replies; 27+ messages in thread From: Pierre-Marie de Rodat @ 2017-07-26 16:26 UTC (permalink / raw) To: David Malcolm, gcc-patches On 07/26/2017 06:15 PM, David Malcolm wrote: > (FWIW I'm a big fan of Python, so am happy to see this proposal) Me too. :-) > Which version of Python did you test against? As far as I can see > you've coded this using the common subset of Python 2 and Python 3; > it's worth spelling out what the assumptions are in this regard (and > what the minimum versions are). I tested with both Python 2 and Python 3, as âpythonâ can be each one depending on the system (itâs Python 3 on my Linux box). I agree I should explicitely state that sources must be compatible with both. Maybe in lib/gcc-python.exp? -- Pierre-Marie de Rodat ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 0/2] Python testcases to check DWARF output 2017-07-26 16:01 [PATCH 0/2] Python testcases to check DWARF output Pierre-Marie de Rodat ` (2 preceding siblings ...) 2017-07-26 16:16 ` [PATCH 0/2] " David Malcolm @ 2017-07-26 21:25 ` Mike Stump 2017-07-27 7:52 ` Richard Biener 2017-08-02 15:43 ` Jeff Law 4 siblings, 1 reply; 27+ messages in thread From: Mike Stump @ 2017-07-26 21:25 UTC (permalink / raw) To: Pierre-Marie de Rodat; +Cc: gcc-patches [-- Attachment #1: Type: text/plain, Size: 762 bytes --] On Jul 26, 2017, at 9:00 AM, Pierre-Marie de Rodat <derodat@adacore.com> wrote: > At the last GNU Cauldron, Richard Biener and I talked about DWARF output > testing. Except for guality tests, which are disabled on several > targets, the only way tests check the DWARF is scanning the annotated > assembly (-dA), making it hard to write reliable tests. > Anyway, Richard and I discussed about doing something similar in-tree, > and here is a candidate set of patches to achieve that I'm fine with the direction if a reviewer wants to go in that direction. I wish python didn't have a built-in speed penalty, that's the only downside I don't like about it. Aside from that, even switching all of the testsuite to be python based isn't a terrible idea. [-- Attachment #2: smime.p7s --] [-- Type: application/pkcs7-signature, Size: 1578 bytes --] ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 0/2] Python testcases to check DWARF output 2017-07-26 21:25 ` Mike Stump @ 2017-07-27 7:52 ` Richard Biener 2017-07-27 9:09 ` Pierre-Marie de Rodat 2017-08-02 15:44 ` Jeff Law 0 siblings, 2 replies; 27+ messages in thread From: Richard Biener @ 2017-07-27 7:52 UTC (permalink / raw) To: Mike Stump; +Cc: Pierre-Marie de Rodat, GCC Patches On Wed, Jul 26, 2017 at 11:25 PM, Mike Stump <mikestump@comcast.net> wrote: > On Jul 26, 2017, at 9:00 AM, Pierre-Marie de Rodat <derodat@adacore.com> wrote: >> At the last GNU Cauldron, Richard Biener and I talked about DWARF output >> testing. Except for guality tests, which are disabled on several >> targets, the only way tests check the DWARF is scanning the annotated >> assembly (-dA), making it hard to write reliable tests. > >> Anyway, Richard and I discussed about doing something similar in-tree, >> and here is a candidate set of patches to achieve that > > I'm fine with the direction if a reviewer wants to go in that direction. I wish python didn't have a built-in speed penalty, that's the only downside I don't like about it. Aside from that, even switching all of the testsuite to be python based isn't a terrible idea. But is it worse than TCL? Richard. ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 0/2] Python testcases to check DWARF output 2017-07-27 7:52 ` Richard Biener @ 2017-07-27 9:09 ` Pierre-Marie de Rodat 2017-08-03 22:23 ` Mike Stump 2017-08-02 15:44 ` Jeff Law 1 sibling, 1 reply; 27+ messages in thread From: Pierre-Marie de Rodat @ 2017-07-27 9:09 UTC (permalink / raw) To: Richard Biener, Mike Stump; +Cc: GCC Patches Thank you for your feedback. On 07/27/2017 09:52 AM, Richard Biener wrote: >> I'm fine with the direction if a reviewer wants to go in that >> direction. I wish python didn't have a built-in speed penalty, >> that's the only downside I don't like about it. Aside from that, >> even switching all of the testsuite to be python based isn't a >> terrible idea. > > But is it worse than TCL? Good point. Actually for Python there are ways to make it faster. If we can somehow manage to have a limited set of Python interpreter instances (instead of one per test), we could use pypy, which is very good I heard to make long running instances fast. As to switch all of the testsuite to Python, I donât have an educated opinion on this. I just want to say that, here Iâm using Python to pattern match DIEs, but if needed we could perfectly use it to do other complex tasks. This is why I kept the DWARF-specific stuff (gcc-dwarf.exp and the dwarfutils Python package, from second commit) separate from just Python interpreter handling (gcc-python.exp, from first commit). Note that having a Python only testsuite would make it easier to have only one Python instance for all the testsuite run, so it would, in theory, make it easier to get a fast execution. ⦠now, Iâm not familiar with DejaGNU but I have the feeling that it does a lot with respect to the handling of a great variety of targets/remote/etc. combinations. Re-writing it (and making sure it works!) sounds like a huuuge task. Iâll let experts in this area comment. :-) -- Pierre-Marie de Rodat ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 0/2] Python testcases to check DWARF output 2017-07-27 9:09 ` Pierre-Marie de Rodat @ 2017-08-03 22:23 ` Mike Stump 2017-08-06 14:35 ` Iain Buclaw 0 siblings, 1 reply; 27+ messages in thread From: Mike Stump @ 2017-08-03 22:23 UTC (permalink / raw) To: Pierre-Marie de Rodat; +Cc: Richard Biener, GCC Patches On Jul 27, 2017, at 2:07 AM, Pierre-Marie de Rodat <derodat@adacore.com> wrote: > On 07/27/2017 09:52 AM, Richard Biener wrote: >>> I'm fine with the direction if a reviewer wants to go in that >>> direction. I wish python didn't have a built-in speed penalty, >>> that's the only downside I don't like about it. Aside from that, >>> even switching all of the testsuite to be python based isn't a >>> terrible idea. >> But is it worse than TCL? python is likely 2x faster than tcl, if you have one instance per testsuite run. The problem is, for the work required, it's cheaper to do the work once to cut over to a new language. I'd rather switch to some other language that can come closer to the speed of compiled C code, yet in the scripting family. I don't have a pointer to a better solution than python at this time. I'd not be opposed to switching to python, it should be faster, just as safe, a bit easier for new people to code in, and more people who know it and use it. I think python is funner to code in than tcl. Cutting the entire testsuite over at once, might well be more than any one person can contribute, but, we could cut over subtrees, as people donate time; if people want to go in that direction. This can't be a 1 person decision, but rather a consensus building type decision. What do others think? > Good point. Actually for Python there are ways to make it faster. If we can somehow manage to have a limited set of Python interpreter instances (instead of one per test), we could use pypy, which is very good I heard to make long running instances fast. Yes. One instance would help ensure the performance is good. I don't have a firm grasp of startup time to know just how critical it is. Also, I don't have a good grasp on memory pressures that python would create, say, compared to how we use tcl. ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 0/2] Python testcases to check DWARF output 2017-08-03 22:23 ` Mike Stump @ 2017-08-06 14:35 ` Iain Buclaw 0 siblings, 0 replies; 27+ messages in thread From: Iain Buclaw @ 2017-08-06 14:35 UTC (permalink / raw) To: Mike Stump; +Cc: Pierre-Marie de Rodat, Richard Biener, GCC Patches On 4 August 2017 at 00:23, Mike Stump <mikestump@comcast.net> wrote: > On Jul 27, 2017, at 2:07 AM, Pierre-Marie de Rodat <derodat@adacore.com> wrote: >> On 07/27/2017 09:52 AM, Richard Biener wrote: >>>> I'm fine with the direction if a reviewer wants to go in that >>>> direction. I wish python didn't have a built-in speed penalty, >>>> that's the only downside I don't like about it. Aside from that, >>>> even switching all of the testsuite to be python based isn't a >>>> terrible idea. >>> But is it worse than TCL? > > python is likely 2x faster than tcl, if you have one instance per testsuite run. The problem is, for the work required, it's cheaper to do the work once to cut over to a new language. I'd rather switch to some other language that can come closer to the speed of compiled C code, yet in the scripting family. I don't have a pointer to a better solution than python at this time. I'd not be opposed to switching to python, it should be faster, just as safe, a bit easier for new people to code in, and more people who know it and use it. I think python is funner to code in than tcl. Cutting the entire testsuite over at once, might well be more than any one person can contribute, but, we could cut over subtrees, as people donate time; if people want to go in that direction. This can't be a 1 person decision, but rather a consensus building type decision. What do others think? > Sounds good to me. Having recently done quite a bit of writing in tcl for the D testsuite recently, there are certainly many gotchas that I ran into. Not sure whether you would rather take an existing testing framework, or write one internally, but I can imagine something that makes heavy use of python decorators for the simple cases. ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 0/2] Python testcases to check DWARF output 2017-07-27 7:52 ` Richard Biener 2017-07-27 9:09 ` Pierre-Marie de Rodat @ 2017-08-02 15:44 ` Jeff Law 1 sibling, 0 replies; 27+ messages in thread From: Jeff Law @ 2017-08-02 15:44 UTC (permalink / raw) To: Richard Biener, Mike Stump; +Cc: Pierre-Marie de Rodat, GCC Patches On 07/27/2017 01:52 AM, Richard Biener wrote: > On Wed, Jul 26, 2017 at 11:25 PM, Mike Stump <mikestump@comcast.net> wrote: >> On Jul 26, 2017, at 9:00 AM, Pierre-Marie de Rodat <derodat@adacore.com> wrote: >>> At the last GNU Cauldron, Richard Biener and I talked about DWARF output >>> testing. Except for guality tests, which are disabled on several >>> targets, the only way tests check the DWARF is scanning the annotated >>> assembly (-dA), making it hard to write reliable tests. >> >>> Anyway, Richard and I discussed about doing something similar in-tree, >>> and here is a candidate set of patches to achieve that >> >> I'm fine with the direction if a reviewer wants to go in that direction. I wish python didn't have a built-in speed penalty, that's the only downside I don't like about it. Aside from that, even switching all of the testsuite to be python based isn't a terrible idea. > > But is it worse than TCL? I don't think Python is worse than TCL. Few things would have that property. And I think we're a lot more likely to be able to find folks that can hack Python as needed vs hacking TCL. jeff ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 0/2] Python testcases to check DWARF output 2017-07-26 16:01 [PATCH 0/2] Python testcases to check DWARF output Pierre-Marie de Rodat ` (3 preceding siblings ...) 2017-07-26 21:25 ` Mike Stump @ 2017-08-02 15:43 ` Jeff Law 2017-08-03 8:27 ` Pierre-Marie de Rodat 4 siblings, 1 reply; 27+ messages in thread From: Jeff Law @ 2017-08-02 15:43 UTC (permalink / raw) To: Pierre-Marie de Rodat, gcc-patches On 07/26/2017 10:00 AM, Pierre-Marie de Rodat wrote: > Hello, > > At the last GNU Cauldron, Richard Biener and I talked about DWARF output > testing. Except for guality tests, which are disabled on several > targets, the only way tests check the DWARF is scanning the annotated > assembly (-dA), making it hard to write reliable tests. > > For instance, checking the number of times DW_AT_location is present in > order to check that some specific variable is assigned one is fuzzy. > Depending on the target and on the evolution of the compiler, the number > of output variables, or which one is assigned a location can vary > legitimately but still make the test fail. > > On my side, I already had written an out-of-tree testsuite for the DWARF > features I added for Ada. This testsuite uses a DWARF parser in order to > perform checks on a tree: > <https://github.com/pmderodat/dwarf-ada-testsuite/>. I had to update it > a couple of times, for instance when a change created a > DW_TAG_const_type DIE or removed one somewhere in a type tree, but > thatâs very rare. I would say that Iâm satisfied with the checks I could > express, but I donât remember I ever caught a regression with them, so I > have no representative experience to share in this area. Maybe DWARF > back-end developpers do a too good job. ;-) > > Anyway, Richard and I discussed about doing something similar in-tree, > and here is a candidate set of patches to achieve that: > > * The first patch installs DejaGNU scripts to run a Python interpreter > in testcases. > > * The second one installs other DejaGNU scripts to detect DWARF > dumping tools, plus a small Python library to parse and pattern > match DIEs and their attributes. It also adds several C and Ada > tests as examples; these are inspired by existing homonym tests > based on assembly scanning. > > For now, this supports only platforms where objdump is available for the > current target, but extending it to other tools, such as otool on Darwin > should be doable. > > I would appreciate feedback about the idea and the implementation I > propose. This is the first time I do more in the testsuite than just > adding new tests, so thank you in advance for you patience in reviewing > these. :-) > > I tested these patches on x86_64-linux. I hate to throw in a wrench at this point, but has anyone looked at dwgrep from Petr Machata? He's not doing much with it anymore, but it might provide enough of a dwarf scanning framework to be useful for testing purposes. jeff ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 0/2] Python testcases to check DWARF output 2017-08-02 15:43 ` Jeff Law @ 2017-08-03 8:27 ` Pierre-Marie de Rodat 2017-08-03 16:13 ` Jeff Law 0 siblings, 1 reply; 27+ messages in thread From: Pierre-Marie de Rodat @ 2017-08-03 8:27 UTC (permalink / raw) To: Jeff Law, gcc-patches On 08/02/2017 05:43 PM, Jeff Law wrote: > I hate to throw in a wrench at this point, but has anyone looked at > dwgrep from Petr Machata? He's not doing much with it anymore, but it > might provide enough of a dwarf scanning framework to be useful for > testing purposes. Sure, no problem: I first started talking publicly about this one week ago, so itâs definitely not too late to mention alternatives. ;-) I learned about dwgrep two years ago and forgot about it, so thank you for the idea. I started to have a look at it, and for now I donât think itâs a good match in this context: 1. itâs an ELF only tool; 2. it must be built, requiring external dependencies: cmake and elfutils; 3. in order to use it, one must learn a dedicated post-fix language (Zwerg) For 1. I think this is a true problem, as it means for instance that we could not test DWARF on Windows and Darwin setups. Unless we add PE and Mach-O handling in dwgrep of course, but that does not sound easy and will bring other external dependencies. For 3. I feel that, for someone who is comfortable with Python, it will be easier to deal with a Python library (the dwarfutils in my patch) than having to learn yet another DSL. I think thatâs precisely why some people would like to have a Python test framework rather than a TCL one. Working with a âusualâ imperative language looks easier than with postfix expressions. Smaller cognitive load. Actually I see another problem: pattern will have to vary depending on the target platform (for instance 32/64bit or depending on the DWARF version). Of course we could duplicate whole patterns in testcases to take this into account, but thatâs like code duplication: I think we should be able to include small âX if 32bit else Yâ in patterns, and I donât think we can do that with Zwerg (no way to pass something like environment variables). Of course, I have written a âcompetitorâ tool: I guess my judgment is biased. :-) So other opinions are welcome! -- Pierre-Marie de Rodat ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 0/2] Python testcases to check DWARF output 2017-08-03 8:27 ` Pierre-Marie de Rodat @ 2017-08-03 16:13 ` Jeff Law 0 siblings, 0 replies; 27+ messages in thread From: Jeff Law @ 2017-08-03 16:13 UTC (permalink / raw) To: Pierre-Marie de Rodat, gcc-patches On 08/03/2017 02:27 AM, Pierre-Marie de Rodat wrote: > On 08/02/2017 05:43 PM, Jeff Law wrote: >> I hate to throw in a wrench at this point, but has anyone looked at >> dwgrep from Petr Machata? He's not doing much with it anymore, but it >> might provide enough of a dwarf scanning framework to be useful for >> testing purposes. > > Sure, no problem: I first started talking publicly about this one week > ago, so itâs definitely not too late to mention alternatives. ;-) I > learned about dwgrep two years ago and forgot about it, so thank you for > the idea. I started to have a look at it, and for now I donât think itâs > a good match in this context: > > 1. itâs an ELF only tool; > 2. it must be built, requiring external dependencies: cmake and > elfutils; > 3. in order to use it, one must learn a dedicated post-fix language > (Zwerg) > > For 1. I think this is a true problem, as it means for instance that we > could not test DWARF on Windows and Darwin setups. Unless we add PE and > Mach-O handling in dwgrep of course, but that does not sound easy and > will bring other external dependencies. > > For 3. I feel that, for someone who is comfortable with Python, it will > be easier to deal with a Python library (the dwarfutils in my patch) > than having to learn yet another DSL. I think thatâs precisely why some > people would like to have a Python test framework rather than a TCL one. > Working with a âusualâ imperative language looks easier than with > postfix expressions. Smaller cognitive load. > > Actually I see another problem: pattern will have to vary depending on > the target platform (for instance 32/64bit or depending on the DWARF > version). Of course we could duplicate whole patterns in testcases to > take this into account, but thatâs like code duplication: I think we > should be able to include small âX if 32bit else Yâ in patterns, and I > donât think we can do that with Zwerg (no way to pass something like > environment variables). > > Of course, I have written a âcompetitorâ tool: I guess my judgment is > biased. :-) So other opinions are welcome! Thanks. I just wanted to raise it as a possibility. It looks like it's not a good fit here, so let's not let it be a distraction. jeff ^ permalink raw reply [flat|nested] 27+ messages in thread
end of thread, other threads:[~2017-08-06 14:35 UTC | newest] Thread overview: 27+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2017-07-26 16:01 [PATCH 0/2] Python testcases to check DWARF output Pierre-Marie de Rodat 2017-07-26 16:01 ` [PATCH 1/2] Introduce testsuite support to run Python tests Pierre-Marie de Rodat 2017-07-26 16:25 ` David Malcolm 2017-07-26 16:35 ` Pierre-Marie de Rodat 2017-07-26 16:48 ` David Malcolm 2017-07-27 8:49 ` Pierre-Marie de Rodat 2017-07-27 13:40 ` David Malcolm 2017-08-02 18:43 ` Jeff Law 2017-08-03 8:27 ` Pierre-Marie de Rodat 2017-07-27 8:50 ` Matthias Klose 2017-07-27 10:09 ` Pierre-Marie de Rodat 2017-07-26 16:01 ` [PATCH 2/2] Introduce Python testcases to check DWARF output Pierre-Marie de Rodat 2017-07-26 17:10 ` David Malcolm 2017-07-27 8:59 ` Pierre-Marie de Rodat 2017-07-27 8:36 ` Richard Biener 2017-07-27 10:09 ` Pierre-Marie de Rodat 2017-07-26 16:16 ` [PATCH 0/2] " David Malcolm 2017-07-26 16:26 ` Pierre-Marie de Rodat 2017-07-26 21:25 ` Mike Stump 2017-07-27 7:52 ` Richard Biener 2017-07-27 9:09 ` Pierre-Marie de Rodat 2017-08-03 22:23 ` Mike Stump 2017-08-06 14:35 ` Iain Buclaw 2017-08-02 15:44 ` Jeff Law 2017-08-02 15:43 ` Jeff Law 2017-08-03 8:27 ` Pierre-Marie de Rodat 2017-08-03 16:13 ` Jeff Law
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).