mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-12-15 04:31:49 +08:00
4f24b90aba
No address update before a DW_LNS_copy might mean an incorrect dwarf assembly test-case. Try to catch such incorrect dwarf assembly test-cases by: - requiring an explicit address update for each DW_LNS_copy, and - handling the cases where an update is indeed not needed, by adding "DW_LNS_advance_pc 0". Tested on aarch64-linux.
372 lines
12 KiB
Plaintext
372 lines
12 KiB
Plaintext
# Copyright 2021-2024 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.
|
|
require allow_shlib_tests
|
|
|
|
load_lib dwarf.exp
|
|
|
|
# This test can only be run on targets which support DWARF-2 and use gas.
|
|
require dwarf2_support
|
|
|
|
# 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;
|
|
# The output of Dwarf::assemble will be placed in $lib_basename.S
|
|
# which will be ${srcfile3} after the execution of standard_testfile.
|
|
|
|
standard_testfile $main_basename.c $lib_basename.c $lib_basename.S
|
|
include_file locexpr-data-member-location.h
|
|
|
|
set libsrc "${::srcdir}/${::subdir}/${::srcfile2}"
|
|
set lib_so [standard_output_file ${lib_basename}.so]
|
|
set asm_file [standard_output_file ${::srcfile3}]
|
|
|
|
# Compile the shared library for the first GDB session. Note that debugging
|
|
# symbols will be present for this compilation, because we want to print some
|
|
# type information.
|
|
if {[gdb_compile_shlib $libsrc $lib_so \
|
|
{debug}] != ""} {
|
|
untested "failed to compile shared library"
|
|
return
|
|
}
|
|
|
|
# Compile the main program for use with the shared object. Note we're using
|
|
# debug, such that "finish out of foo" prints:
|
|
# Value returned is $1 = (class B *) $hex <g_>
|
|
# instead of:
|
|
# Value returned is $1 = (B *) $hex <g_>
|
|
# Note that this compilation is used for all GDB sessions.
|
|
set exec_options [list debug shlib=$lib_so]
|
|
if [prepare_for_testing "failed to prepare" ${testfile} \
|
|
${::srcfile} $exec_options] {
|
|
return -1
|
|
}
|
|
|
|
### First GDB session.
|
|
|
|
with_test_prefix "first session" {
|
|
# Do whatever is necessary to make sure that the shared library is
|
|
# loaded for remote targets.
|
|
gdb_load_shlib ${lib_so}
|
|
|
|
# Run to foo to make sure foo refers to the function, and not foo@PLT.
|
|
if {![runto foo qualified]} {
|
|
return
|
|
}
|
|
|
|
with_shared_gdb {
|
|
|
|
set session_options $exec_options
|
|
|
|
# Rather than start a new session, declare the current session the
|
|
# shared one. Otherwise, get_func_info would compile an executable
|
|
# in a temp dir, which means -Wl,-rpath,\\\$ORIGIN no longer finds
|
|
# the shared lib.
|
|
share_gdb ${srcdir}/${subdir}/$srcfile $session_options
|
|
|
|
get_func_info foo $session_options
|
|
get_func_info bar $session_options
|
|
|
|
# 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]
|
|
|
|
# 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]
|
|
}
|
|
}
|
|
|
|
if { $long_size == -1 || $addr_size == -1 \
|
|
|| $struct_A_size == -1 || $struct_B_size == -1} {
|
|
perror "Can't determine type sizes"
|
|
return
|
|
}
|
|
|
|
# Create the DWARF.
|
|
Dwarf::assemble ${asm_file} {
|
|
declare_labels L
|
|
global foo_start foo_end
|
|
global bar_start bar_end
|
|
global libsrc
|
|
|
|
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 "${::srcfile2}" 1
|
|
|
|
# Generate a line table program.
|
|
program {
|
|
DW_LNE_set_address $foo_start
|
|
line [gdb_get_line_number "foo prologue" $libsrc]
|
|
DW_LNS_copy
|
|
|
|
DW_LNE_set_address foo_label
|
|
line [gdb_get_line_number "foo return" $libsrc]
|
|
DW_LNS_copy
|
|
|
|
DW_LNS_advance_pc 0
|
|
line [gdb_get_line_number "foo end" $libsrc]
|
|
DW_LNS_copy
|
|
|
|
DW_LNE_set_address $foo_end
|
|
DW_LNE_end_sequence
|
|
|
|
|
|
DW_LNE_set_address $bar_start
|
|
line [gdb_get_line_number "bar prologue" $libsrc]
|
|
DW_LNS_copy
|
|
|
|
DW_LNE_set_address bar_label
|
|
line [gdb_get_line_number "bar return" $libsrc]
|
|
DW_LNS_copy
|
|
|
|
DW_LNS_advance_pc 0
|
|
line [gdb_get_line_number "bar end" $libsrc]
|
|
DW_LNS_copy
|
|
|
|
DW_LNE_set_address $bar_end
|
|
DW_LNE_end_sequence
|
|
}
|
|
}
|
|
|
|
aranges {} cu_label {
|
|
# This 0,0 entry tests that the .debug_aranges reader can
|
|
# handle an apparent terminator before the end of the ranges.
|
|
arange {} 0 0
|
|
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 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 \
|
|
{nodebug}] != ""} {
|
|
untested "failed to compile shared library"
|
|
return
|
|
}
|
|
|
|
### Second GDB session.
|
|
|
|
with_test_prefix "second session" {
|
|
clean_restart $binfile
|
|
|
|
# Again, do whatever is necessary to make sure that the shared library is
|
|
# loaded for remote targets.
|
|
gdb_load_shlib ${lib_so}
|
|
|
|
if {![runto_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> = {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"
|
|
}
|
|
|
|
### Third GDB session.
|
|
|
|
with_test_prefix "third session" {
|
|
# 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
|
|
}
|