| # 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 <http://www.gnu.org/licenses/>. |
| |
| # 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 { label cu_label } { |
| 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} |
| } |
| } |
| |
| aranges {} cu_label { |
| arange {} $foo_start $foo_end |
| arange {} $bar_start $bar_end |
| } |
| } |
| |
| # 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 { |
| 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> = {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 |