[6/6] gdb/testsuite: rewrite gdb.cp/call-method-register.exp with dwarf assembler

Message ID b186b6cf44fa7fc279a6f05a2f0c295f0ccd5da4.1668184173.git.aburgess@redhat.com
State New
Headers
Series The DWARF assembler and Clang |

Commit Message

Andrew Burgess Nov. 11, 2022, 4:36 p.m. UTC
  Convert the gdb.cp/call-method-register.exp test to make use of the
DWARF assembler.

The existing gdb.cp/call-method-register.exp test relies on a GCC
extension - forcing a local variable into a particular named register.

This means that the test will only work with Clang, and, as we have to
name the register into which the variable will be placed, will only
work for those targets where we've selected a suitable register,
currently this is x86-64, i386, and ppc64.

By switching to the DWARF assembler, the test will work with gcc and
clang, and should work on most, if not all, architectures.

The test creates a small structure, something that can fit within a
register, and then tries to call a method on the structure from within
GDB.  This should fail because GDB can't take the address of the in
register structure (for the `this` pointer).

As the test is for a failure case, then we don't really care _which_
register the structure is in, and I take advantage of this for the
DWARF assembler test, I just declare that the variable is in
DW_OP_reg0, whatever that might be.  I've tested the new test on
x86-64, ppc, aarch64, and risc-v, and the test runs, and passes on all
these architectures, which is already more than we used to cover.

Additionally, on x86-64, I've tested with Clang and gcc, and the test
runs and passed with both compilers.
---
 gdb/testsuite/gdb.cp/call-method-register.cc  |  49 +-------
 gdb/testsuite/gdb.cp/call-method-register.exp | 108 +++++++++++++-----
 2 files changed, 81 insertions(+), 76 deletions(-)
  

Comments

Lancelot SIX Nov. 16, 2022, 11:59 a.m. UTC | #1
Hi Andrew


On Fri, Nov 11, 2022 at 04:36:25PM +0000, Andrew Burgess via Gdb-patches wrote:
> Convert the gdb.cp/call-method-register.exp test to make use of the
> DWARF assembler.
> 
> The existing gdb.cp/call-method-register.exp test relies on a GCC
> extension - forcing a local variable into a particular named register.
> 
> This means that the test will only work with Clang, and, as we have to
> name the register into which the variable will be placed, will only
> work for those targets where we've selected a suitable register,
> currently this is x86-64, i386, and ppc64.
> 
> By switching to the DWARF assembler, the test will work with gcc and
> clang, and should work on most, if not all, architectures.
> 
> The test creates a small structure, something that can fit within a
> register, and then tries to call a method on the structure from within
> GDB.  This should fail because GDB can't take the address of the in
> register structure (for the `this` pointer).
> 
> As the test is for a failure case, then we don't really care _which_
> register the structure is in, and I take advantage of this for the
> DWARF assembler test, I just declare that the variable is in
> DW_OP_reg0, whatever that might be.  I've tested the new test on
> x86-64, ppc, aarch64, and risc-v, and the test runs, and passes on all
> these architectures, which is already more than we used to cover.
> 
> Additionally, on x86-64, I've tested with Clang and gcc, and the test
> runs and passed with both compilers.
> ---
>  gdb/testsuite/gdb.cp/call-method-register.cc  |  49 +-------
>  gdb/testsuite/gdb.cp/call-method-register.exp | 108 +++++++++++++-----
>  2 files changed, 81 insertions(+), 76 deletions(-)
> 
> diff --git a/gdb/testsuite/gdb.cp/call-method-register.cc b/gdb/testsuite/gdb.cp/call-method-register.cc
> index d60fee03701..91cbf13ebca 100644
> --- a/gdb/testsuite/gdb.cp/call-method-register.cc
> +++ b/gdb/testsuite/gdb.cp/call-method-register.cc
> @@ -1,6 +1,6 @@
>  /* This testcase is part of GDB, the GNU debugger.
>  
> -   Copyright 1993-2022 Free Software Foundation, Inc.
> +   Copyright 2022 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
> @@ -15,54 +15,9 @@
>     You should have received a copy of the GNU General Public License
>     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
>  
> -#if defined __x86_64__
> -# define ASM_REG "rax"
> -#elif defined __i386__
> -# define ASM_REG "eax"
> -#elif defined __powerpc64__
> -# define ASM_REG "r9"
> -#else
> -# error "port me"
> -#endif
> -
> -/* A class small enough that it fits in a register.  */
> -struct small
> -{
> -  int x;
> -  int method ();
> -};
> -
> -int
> -small::method ()
> -{
> -  return x + 5;
> -}
> -
> -int
> -register_class ()
> -{
> -  /* Given the use of the GNU register-asm local variables extension,
> -     the compiler puts this variable in a register.  This means that
> -     GDB can't call any methods for this variable, which is what we
> -     want to test.  */
> -  register small v asm (ASM_REG);
> -
> -  int i;
> -
> -  /* Perform a computation sufficiently complicated that optimizing
> -     compilers won't optimize out the variable.  If some compiler
> -     constant-folds this whole loop, maybe using a parameter to this
> -     function here would help.  */
> -  v.x = 0;
> -  for (i = 0; i < 13; ++i)
> -    v.x += i;
> -  --v.x; /* v.x is now 77 */
> -  return v.x + 5; /* set breakpoint here */
> -}
> -
>  int
>  main ()
>  {
> -  register_class ();
> +  asm ("main_label: .globl main_label");
>    return 0;
>  }
> diff --git a/gdb/testsuite/gdb.cp/call-method-register.exp b/gdb/testsuite/gdb.cp/call-method-register.exp
> index a98b11e4c11..a86ea1e44f9 100644
> --- a/gdb/testsuite/gdb.cp/call-method-register.exp
> +++ b/gdb/testsuite/gdb.cp/call-method-register.exp
> @@ -19,43 +19,93 @@
>  if { [skip_cplus_tests] } { continue }
>  
>  load_lib "cp-support.exp"
> +load_lib dwarf.exp
>  
> -standard_testfile .cc
> +standard_testfile .cc -dw.S
>  
> -if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug c++}]} {
> +if {[prepare_for_testing "failed to prepare" $testfile $srcfile \
> +	 {debug c++}]} {
>      return -1
>  }
>  
> -if {![test_compiler_info gcc-*-* c++]} {
> -    untested "test relies on a gcc extension"
> -    return
> -}
> +set asm_file [standard_output_file $srcfile2]
> +Dwarf::assemble $asm_file {
> +
> +    set main_result \
> +	[function_range main ${::srcdir}/${::subdir}/${::srcfile}]
> +    set main_start [lindex $main_result 0]
> +    set main_length [lindex $main_result 1]
> +
> +    cu {} {
> +	compile_unit {
> +	    {DW_AT_language @DW_LANG_C_plus_plus}
> +	    {DW_AT_name	    $::srcfile}
> +	    {DW_AT_comp_dir /tmp}
> +	} {
> +	    declare_labels int_type_label struct_type_label \
> +		struct_ptr_type_label
> +	    set ptr_size [get_sizeof "void *" 96]
> +
> +	    DW_TAG_subprogram {
> +		{name main}
> +		{low_pc $main_start addr}
> +		{high_pc $main_length data8}
> +		{DW_AT_type :$int_type_label}
> +	    }
> +
> +	    int_type_label: DW_TAG_base_type {
> +		{DW_AT_byte_size 4 DW_FORM_sdata}
> +		{DW_AT_encoding @DW_ATE_signed}
> +		{DW_AT_name int}
> +	    }
> +
> +	    struct_type_label: DW_TAG_structure_type {
> +		{DW_AT_byte_size 4 DW_FORM_sdata}
> +		{DW_AT_name small}
> +	    } {
> +		member {
> +		    {name xxx}
> +		    {type :$int_type_label}
> +		    {data_member_location 0 data1}
> +		}
> +		subprogram {
> +		    {name yyy}
> +		    {type :$int_type_label}
> +		    {data_member_location 0 data1}

I do not think a DW_AT_data_member_location makes sense for a
DW_TAG_subprogram.  I do not see it in the Dwarf5's Appendix A
(Attributes by Tag Value) in the list of attributed valid for
subprogram.

On x86_64 the test is still passing wich gcc-11 and Clang-15 if I remove
this line.

> +		} {
> +		    formal_parameter {
> +			{type :$struct_ptr_type_label}
> +			{artificial 1 flag_present}
> +		    }
> +		}
> +	    }
>  
> -proc test_call_register_class {} {
> -    global gdb_prompt
> +	    struct_ptr_type_label: DW_TAG_pointer_type {
> +		{DW_AT_byte_size $ptr_size DW_FORM_data1}
> +		{type :$struct_type_label}
> +	    }
>  
> -    if ![runto_main] {
> -	return
> +	    DW_TAG_variable {
> +		{DW_AT_name global_var}
> +		{DW_AT_type :$struct_type_label}
> +		{DW_AT_location {
> +		    DW_OP_reg0
> +		} SPECIAL_expr}
> +		{external 1 flag}
> +	    }
> +	}
>      }
> +}
>  
> -    set bp_location [gdb_get_line_number "set breakpoint here"]
> -    gdb_breakpoint $bp_location
> -    gdb_continue_to_breakpoint "break here"
> -
> -    # This class is so small that an instance of it can fit in a register.
> -    # When gdb tries to call a method, it gets embarrassed about taking
> -    # the address of a register.
> -    #
> -    # That message is a PASS, not an XFAIL, because gdb prints an
> -    # informative message and declines to do something impossible.
> -    #
> -    # The method call actually succeeds if the compiler allocates very
> -    # small classes in memory instead of registers.  If that happens,
> -    # it's a FAIL, because the testcase is written in a form such that
> -    # it should not happen.
> -    gdb_test "print v.method ()" \
> -	"Address requested for identifier \"v\" which is in register .*" \
> -	"call method on register local"
> +if { [prepare_for_testing "failed to prepare" ${testfile} \
> +	  [list $srcfile $asm_file] {nodebug}] } {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    return -1
>  }
>  
> -test_call_register_class
> +gdb_test "print global_var.yyy ()" \
> +    "Address requested for identifier \"global_var\" which is in register .*" \
> +    "call method on register local"
> -- 
> 2.25.4
> 

Other than this, and for what it is worth the series seems reasonable to
me.

Patch 5 (fix gdb.debuginfod/fetch_src_and_symbols.exp with Clang) got me
curious on what the line table looks like.  I do not have a Clang-9
handy to reproduce the problem.  Anyway, your patch is fine to work
around the issue.

So with one change in this patch, feel free to apply my RB tag on the
series:

Reviewed-By: Lancelot SIX <lancelot.six@amd.com>

Best,
Lancelot.
  

Patch

diff --git a/gdb/testsuite/gdb.cp/call-method-register.cc b/gdb/testsuite/gdb.cp/call-method-register.cc
index d60fee03701..91cbf13ebca 100644
--- a/gdb/testsuite/gdb.cp/call-method-register.cc
+++ b/gdb/testsuite/gdb.cp/call-method-register.cc
@@ -1,6 +1,6 @@ 
 /* This testcase is part of GDB, the GNU debugger.
 
-   Copyright 1993-2022 Free Software Foundation, Inc.
+   Copyright 2022 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
@@ -15,54 +15,9 @@ 
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
-#if defined __x86_64__
-# define ASM_REG "rax"
-#elif defined __i386__
-# define ASM_REG "eax"
-#elif defined __powerpc64__
-# define ASM_REG "r9"
-#else
-# error "port me"
-#endif
-
-/* A class small enough that it fits in a register.  */
-struct small
-{
-  int x;
-  int method ();
-};
-
-int
-small::method ()
-{
-  return x + 5;
-}
-
-int
-register_class ()
-{
-  /* Given the use of the GNU register-asm local variables extension,
-     the compiler puts this variable in a register.  This means that
-     GDB can't call any methods for this variable, which is what we
-     want to test.  */
-  register small v asm (ASM_REG);
-
-  int i;
-
-  /* Perform a computation sufficiently complicated that optimizing
-     compilers won't optimize out the variable.  If some compiler
-     constant-folds this whole loop, maybe using a parameter to this
-     function here would help.  */
-  v.x = 0;
-  for (i = 0; i < 13; ++i)
-    v.x += i;
-  --v.x; /* v.x is now 77 */
-  return v.x + 5; /* set breakpoint here */
-}
-
 int
 main ()
 {
-  register_class ();
+  asm ("main_label: .globl main_label");
   return 0;
 }
diff --git a/gdb/testsuite/gdb.cp/call-method-register.exp b/gdb/testsuite/gdb.cp/call-method-register.exp
index a98b11e4c11..a86ea1e44f9 100644
--- a/gdb/testsuite/gdb.cp/call-method-register.exp
+++ b/gdb/testsuite/gdb.cp/call-method-register.exp
@@ -19,43 +19,93 @@ 
 if { [skip_cplus_tests] } { continue }
 
 load_lib "cp-support.exp"
+load_lib dwarf.exp
 
-standard_testfile .cc
+standard_testfile .cc -dw.S
 
-if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug c++}]} {
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile \
+	 {debug c++}]} {
     return -1
 }
 
-if {![test_compiler_info gcc-*-* c++]} {
-    untested "test relies on a gcc extension"
-    return
-}
+set asm_file [standard_output_file $srcfile2]
+Dwarf::assemble $asm_file {
+
+    set main_result \
+	[function_range main ${::srcdir}/${::subdir}/${::srcfile}]
+    set main_start [lindex $main_result 0]
+    set main_length [lindex $main_result 1]
+
+    cu {} {
+	compile_unit {
+	    {DW_AT_language @DW_LANG_C_plus_plus}
+	    {DW_AT_name	    $::srcfile}
+	    {DW_AT_comp_dir /tmp}
+	} {
+	    declare_labels int_type_label struct_type_label \
+		struct_ptr_type_label
+	    set ptr_size [get_sizeof "void *" 96]
+
+	    DW_TAG_subprogram {
+		{name main}
+		{low_pc $main_start addr}
+		{high_pc $main_length data8}
+		{DW_AT_type :$int_type_label}
+	    }
+
+	    int_type_label: DW_TAG_base_type {
+		{DW_AT_byte_size 4 DW_FORM_sdata}
+		{DW_AT_encoding @DW_ATE_signed}
+		{DW_AT_name int}
+	    }
+
+	    struct_type_label: DW_TAG_structure_type {
+		{DW_AT_byte_size 4 DW_FORM_sdata}
+		{DW_AT_name small}
+	    } {
+		member {
+		    {name xxx}
+		    {type :$int_type_label}
+		    {data_member_location 0 data1}
+		}
+		subprogram {
+		    {name yyy}
+		    {type :$int_type_label}
+		    {data_member_location 0 data1}
+		} {
+		    formal_parameter {
+			{type :$struct_ptr_type_label}
+			{artificial 1 flag_present}
+		    }
+		}
+	    }
 
-proc test_call_register_class {} {
-    global gdb_prompt
+	    struct_ptr_type_label: DW_TAG_pointer_type {
+		{DW_AT_byte_size $ptr_size DW_FORM_data1}
+		{type :$struct_type_label}
+	    }
 
-    if ![runto_main] {
-	return
+	    DW_TAG_variable {
+		{DW_AT_name global_var}
+		{DW_AT_type :$struct_type_label}
+		{DW_AT_location {
+		    DW_OP_reg0
+		} SPECIAL_expr}
+		{external 1 flag}
+	    }
+	}
     }
+}
 
-    set bp_location [gdb_get_line_number "set breakpoint here"]
-    gdb_breakpoint $bp_location
-    gdb_continue_to_breakpoint "break here"
-
-    # This class is so small that an instance of it can fit in a register.
-    # When gdb tries to call a method, it gets embarrassed about taking
-    # the address of a register.
-    #
-    # That message is a PASS, not an XFAIL, because gdb prints an
-    # informative message and declines to do something impossible.
-    #
-    # The method call actually succeeds if the compiler allocates very
-    # small classes in memory instead of registers.  If that happens,
-    # it's a FAIL, because the testcase is written in a form such that
-    # it should not happen.
-    gdb_test "print v.method ()" \
-	"Address requested for identifier \"v\" which is in register .*" \
-	"call method on register local"
+if { [prepare_for_testing "failed to prepare" ${testfile} \
+	  [list $srcfile $asm_file] {nodebug}] } {
+    return -1
+}
+
+if ![runto_main] {
+    return -1
 }
 
-test_call_register_class
+gdb_test "print global_var.yyy ()" \
+    "Address requested for identifier \"global_var\" which is in register .*" \
+    "call method on register local"