diff --git a/gdb/testsuite/gdb.dwarf2/locexpr-data-member-location-lib.c b/gdb/testsuite/gdb.dwarf2/locexpr-data-member-location-lib.c new file mode 100644 index 000000000000..2cfd1c613a4b --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/locexpr-data-member-location-lib.c @@ -0,0 +1,48 @@ +/* Copyright (C) 2021 Free Software Foundation, Inc. + + This file is part of GDB. + + 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 this program. If not, see . */ + +#include "locexpr-data-member-location.h" + +struct A g_A = {3, 4}; +struct B g_B = { {8, 9}, 10, 11 }; + +B * +foo () +{ /* foo prologue */ + asm ("foo_label: .globl foo_label"); + return &g_B; /* foo return */ +} /* foo end */ + +B * +bar (B *v) +{ /* bar prologue */ + asm ("bar_label: .globl bar_label"); + return v; /* bar return */ +} /* bar end */ + +/* Some of the DWARF assembler procs (e.g. function_range) compile + this file, expecting it to be a complete program with a main() + function. When IS_SHAREDLIB is NOT defined, we have main() as + defined below. */ + +#ifndef IS_SHAREDLIB +int +main () +{ + B *b = foo (); +} +#endif diff --git a/gdb/testsuite/gdb.dwarf2/locexpr-data-member-location-main.c b/gdb/testsuite/gdb.dwarf2/locexpr-data-member-location-main.c new file mode 100644 index 000000000000..62845edcdf75 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/locexpr-data-member-location-main.c @@ -0,0 +1,27 @@ +/* Copyright (C) 2021 Free Software Foundation, Inc. + + This file is part of GDB. + + 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 this program. If not, see . */ + +#include "locexpr-data-member-location.h" + +int +main (void) +{ + B *v1; + v1 = bar (foo ()); + + return 0; +} diff --git a/gdb/testsuite/gdb.dwarf2/locexpr-data-member-location.exp b/gdb/testsuite/gdb.dwarf2/locexpr-data-member-location.exp new file mode 100644 index 000000000000..1f70e6a34bd0 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/locexpr-data-member-location.exp @@ -0,0 +1,349 @@ +# Copyright 2021 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 this program. If not, see . + +# This test case uses the DWARF assembler to reproduce the problem +# described by PR28030. The bug turned out to be that +# FIELD_LOC_KIND_DWARF_BLOCK was not handled when recursively copying +# a value's type when preserving the value history during the freeing +# up of objfiles associated with a shared object. (Yes, figuring out +# how to make this happen in a concise test case turned out to be +# challenging.) +# +# The following elements proved to be necessary for reproducing the +# problem: +# +# 1) A location expression needed to be used with +# DW_AT_data_member_location rather than a simple offset. +# Moreover, this location expression needed to use opcodes +# which GDB's DWARF reader could not convert to a simple +# offset. (Note, however, that GDB could probably be improved +# to handle the opcodes chosen for this test; if decode_locdesc() +# in dwarf2/read.c is ever updated to handle both DW_OP_pick and +# DW_OP_drop, then this test could end up passing even if +# the bug it's intended to test has not been fixed.) +# +# 2) The debug info containing the above DWARF info needed +# to be associated with a shared object since the problem +# occurred while GDB was preserving values during the +# purging of shared objects. +# +# 3) After performing some simple gdb commands, the program is +# run again. In the course of running the objfile destructor +# associated with the shared object, values are preserved +# along with their types. As noted earlier, it was during +# the recursive type copy that the bug was observed. +# +# Therefore, due to #2 above, this test case creates debug info +# which is then used by a shared object. + +# This test can't be run on targets lacking shared library support. +if [skip_shlib_tests] { + return 0 +} + +load_lib dwarf.exp + +# This test can only be run on targets which support DWARF-2 and use gas. +if ![dwarf2_support] { + return 0 +} + +# gdb_test_file_name is the name of this file without the .exp +# extension. Use it to form basenames for the main program +# and shared object. +set main_basename ${::gdb_test_file_name}-main +set lib_basename ${::gdb_test_file_name}-lib + +# We're generating DWARF assembly for the shared object; therefore, +# the source file for the library / shared object must be listed first +# (in the standard_testfile invocation) since ${srcfile} is used by +# get_func_info (for determining the start, end, and length of a +# function). +# +# The output of Dwarf::assemble will be placed in $lib_basename.S +# which will be ${srcfile3} after the execution of standard_testfile. + +standard_testfile $lib_basename.c $main_basename.c $lib_basename.S + +set libsrc "${::srcdir}/${::subdir}/${::srcfile}" +set lib_so [standard_output_file ${lib_basename}.so] +set asm_file [standard_output_file ${::srcfile3}] + +# We need to know the size of some types in order to write some of the +# debugging info that we're about to generate. For that, we ask GDB +# by debugging the shared object associated with this test case. + +# Compile the shared library: -DIS_SHAREDLIB prevents main() from +# being defined. Note that debugging symbols will be present for +# this compilation. +if {[gdb_compile_shlib $libsrc $lib_so \ + {additional_flags=-DIS_SHAREDLIB debug}] != ""} { + untested "failed to compile shared library" + return +} + +# Start a fresh GDB and load the shared library. +clean_restart $lib_so + +# Using our running GDB session, determine sizes of several types. +set long_size [get_sizeof "long" -1] +set addr_size [get_sizeof "void *" -1] +set struct_A_size [get_sizeof "g_A" -1] +set struct_B_size [get_sizeof "g_B" -1] + +if { $long_size == -1 || $addr_size == -1 \ + || $struct_A_size == -1 || $struct_B_size == -1} { + perror "Can't determine type sizes" + return +} + +# Retrieve struct offset of MBR in struct TP +proc get_offsetof { tp mbr } { + return [get_integer_valueof "&((${tp} *) 0)->${mbr}" -1] +} + +# Use running GDB session to get struct offsets +set A_a [get_offsetof A a] +set A_x [get_offsetof A x] +set B_a [get_offsetof B a] +set B_b [get_offsetof B b] +set B_x2 [get_offsetof B x2] + +# Create the DWARF. +Dwarf::assemble ${asm_file} { + declare_labels L + + # Find start, end, and length of functions foo and bar. + # These calls to get_func_info will create and set variables + # foo_start, bar_start, foo_end, bar_end, foo_len, and + # bar_len. + # + # In order to get the right answers, get_func_info (and, + # underneath, function_range) should use the same compiler flags + # as those used to make a shared object. For any targets that get + # this far, -fpic is probably correct. + # + # Also, it should be noted that IS_SHAREDLIB is NOT defined as one + # of the additional flags. Not defining IS_SHAREDLIB will cause a + # main() to be defined for the compilation of the shared library + # source file which happens as a result of using get_func_info; + # this is currently required in order to this facility. + set flags {additional_flags=-fpic debug} + get_func_info foo $flags + get_func_info bar $flags + + cu {} { + DW_TAG_compile_unit { + {DW_AT_language @DW_LANG_C_plus_plus} + {name ${::srcfile}} + {stmt_list $L DW_FORM_sec_offset} + } { + declare_labels int_label class_A_label class_B_label \ + B_ptr_label + + int_label: DW_TAG_base_type { + {DW_AT_byte_size ${::long_size} DW_FORM_udata} + {DW_AT_encoding @DW_ATE_signed} + {DW_AT_name "int"} + } + + class_A_label: DW_TAG_class_type { + {DW_AT_name "A"} + {DW_AT_byte_size ${::struct_A_size} DW_FORM_sdata} + } { + DW_TAG_member { + {DW_AT_name "a"} + {DW_AT_type :$int_label} + {DW_AT_data_member_location ${::A_a} DW_FORM_udata} + } + DW_TAG_member { + {DW_AT_name "x"} + {DW_AT_type :$int_label} + {DW_AT_data_member_location ${::A_x} DW_FORM_udata} + } + } + + class_B_label: DW_TAG_class_type { + {DW_AT_name "B"} + {DW_AT_byte_size ${::struct_B_size} DW_FORM_sdata} + } { + # While there are easier / better ways to specify an + # offset used by DW_AT_data_member_location than that + # used below, we need a location expression here in + # order to reproduce the bug. Moreover, this location + # expression needs to use opcodes that aren't handled + # by decode_locdesc() in dwarf2/read.c; if we use + # opcodes that _are_ handled by that function, the + # location expression will be converted into a simple + # offset - which will then (again) not reproduce the + # bug. At the time that this test was written, + # neither DW_OP_pick nor DW_OP_drop were being handled + # by decode_locdesc(); this is why those opcodes were + # chosen. + DW_TAG_inheritance { + {DW_AT_type :$class_A_label} + {DW_AT_data_member_location { + DW_OP_constu ${::B_a} + DW_OP_plus + DW_OP_pick 0 + DW_OP_drop} SPECIAL_expr} + {DW_AT_accessibility 1 DW_FORM_data1} + } + DW_TAG_member { + {DW_AT_name "b"} + {DW_AT_type :$int_label} + {DW_AT_data_member_location ${::B_b} DW_FORM_udata} + } + DW_TAG_member { + {DW_AT_name "x2"} + {DW_AT_type :$int_label} + {DW_AT_data_member_location ${::B_x2} DW_FORM_udata} + } + } + + B_ptr_label: DW_TAG_pointer_type { + {DW_AT_type :$class_B_label} + {DW_AT_byte_size ${::addr_size} DW_FORM_sdata} + } + + DW_TAG_variable { + {DW_AT_name "g_A"} + {DW_AT_type :$class_A_label} + {DW_AT_external 1 flag} + {DW_AT_location {DW_OP_addr [gdb_target_symbol "g_A"]} \ + SPECIAL_expr} + } + + DW_TAG_variable { + {DW_AT_name "g_B"} + {DW_AT_type :$class_B_label} + {DW_AT_external 1 flag} + {DW_AT_location {DW_OP_addr [gdb_target_symbol "g_B"]} \ + SPECIAL_expr} + } + + # We can't use MACRO_AT for the definitions of foo and bar + # because it doesn't provide a way to pass the appropriate + # flags. Therefore, we list the name, low_pc, and high_pc + # explicitly. + DW_TAG_subprogram { + {DW_AT_name foo} + {DW_AT_low_pc $foo_start DW_FORM_addr} + {DW_AT_high_pc $foo_end DW_FORM_addr} + {DW_AT_type :${B_ptr_label}} + {DW_AT_external 1 flag} + } + + DW_TAG_subprogram { + {DW_AT_name bar} + {DW_AT_low_pc $bar_start DW_FORM_addr} + {DW_AT_high_pc $bar_end DW_FORM_addr} + {DW_AT_type :${B_ptr_label}} + {DW_AT_external 1 flag} + } { + DW_TAG_formal_parameter { + {DW_AT_name v} + {DW_AT_type :${B_ptr_label}} + } + } + } + } + + lines {version 2} L { + include_dir "${::srcdir}/${::subdir}" + file_name "${::srcfile}" 1 + + # Generate a line table program. + program { + {DW_LNE_set_address $foo_start} + {line [gdb_get_line_number "foo prologue"]} + {DW_LNS_copy} + {DW_LNE_set_address foo_label} + {line [gdb_get_line_number "foo return"]} + {DW_LNS_copy} + {line [gdb_get_line_number "foo end"]} + {DW_LNS_copy} + {DW_LNE_set_address $foo_end} + {DW_LNS_advance_line 1} + {DW_LNS_copy} + {DW_LNE_end_sequence} + + {DW_LNE_set_address $bar_start} + {line [gdb_get_line_number "bar prologue"]} + {DW_LNS_copy} + {DW_LNE_set_address bar_label} + {line [gdb_get_line_number "bar return"]} + {DW_LNS_copy} + {line [gdb_get_line_number "bar end"]} + {DW_LNS_copy} + {DW_LNE_set_address $bar_end} + {DW_LNS_advance_line 1} + {DW_LNS_copy} + {DW_LNE_end_sequence} + } + } +} + +# Compile the shared object again, but this time include / use the +# DWARF info that we've created above. Note that (again) +# -DIS_SHAREDLIB is used to prevent inclusion of main() in the shared +# object. Also note the use of the "nodebug" option. Any debugging +# information that we need will be provided by the DWARF info created +# above. +if {[gdb_compile_shlib [list $libsrc $asm_file] $lib_so \ + {additional_flags=-DIS_SHAREDLIB nodebug}] != ""} { + untested "failed to compile shared library" + return +} + +# Compile the main program for use with the shared object. +if [prepare_for_testing "failed to prepare" ${testfile} \ + ${::srcfile2} [list debug shlib=$lib_so]] { + return -1 +} + +# Do whatever is necessary to make sure that the shared library is +# loaded for remote targets. +gdb_load_shlib ${lib_so} + +if ![runto_main] then { + fail "can't run to main" + return +} + +# Step into foo so that we can finish out of it. +gdb_test "step" "foo .. at .* foo end.*" "step into foo" + +# Finishing out of foo will create a value that will later need to +# be preserved when restarting the program. +gdb_test "finish" "= \\(class B \\*\\) ${::hex} .*" "finish out of foo" + +# Dereferencing and printing the return value isn't necessary +# for reproducing the bug, but we should make sure that the +# return value is what we expect it to be. +gdb_test "p *$" { = { = {a = 8, x = 9}, b = 10, x2 = 11}} \ + "dereference return value" + +# The original PR28030 reproducer stepped back into the shared object, +# so we'll do the same here: +gdb_test "step" "bar \\(.*" "step into bar" + +# We don't want a clean restart here since that will be too clean. +# The original reproducer for PR28030 set a breakpoint in the shared +# library and then restarted via "run". The command below does roughly +# the same thing. It's at this step that an internal error would +# occur for PR28030. The "message" argument tells runto to turn on +# the printing of PASSes while runto is doing its job. +runto "bar" message diff --git a/gdb/testsuite/gdb.dwarf2/locexpr-data-member-location.h b/gdb/testsuite/gdb.dwarf2/locexpr-data-member-location.h new file mode 100644 index 000000000000..81cd2b38e859 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/locexpr-data-member-location.h @@ -0,0 +1,30 @@ +/* Copyright (C) 2021 Free Software Foundation, Inc. + + This file is part of GDB. + + 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 this program. If not, see . */ + +typedef struct A { + long a; + long x; +} A; + +typedef struct B { + A a; + long b; + long x2; +} B; + +extern B *foo (); +extern B *bar (B *v);