--- /dev/null
+# 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
+# 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/>.
+
+# Tools which use the BFD library will output error messages of the
+# form "BFD: some messsage" when a problem with the file upon which it
+# operating is found. E.g. an actual message (modulo some shortening
+# of the pathname) from this test is:
+#
+# BFD: bfd-errors-lib.so: invalid string offset 1154 >= 154 for section `.dynstr'
+#
+# For some problems with executable files or libraries, BFD will
+# attempt to output many identical messages. Code has been added to
+# GDB to suppress messages which are identical to earlier messages
+# that have already been printed.
+#
+# This test makes sure that (all but the first) identical BFD messages
+# are suppresssed and also that differing messages are output at least
+# once.
+#
+# To accomplish this, a shared object with at least four symbols is
+# created. The .dynsym section is extracted and offsets which should
+# refer to strings in the .dynstr section are changed to be
+# larger than the size of the .dynstr section. Only two (different)
+# offsets are used; thus BFD will attempt to output at least two pairs
+# of identical messages. (And it would do this too if not intercepted
+# by the hook placed by GDB.) After modifying the extracted section,
+# the mangled section is placed back into the shared object.
+#
+# This test then loads the shared library's symbol table (and other
+# debug info) using the 'add-symbol-file' command. While doing this,
+# the test observes and records the BFD errors that were output.
+# Finally, data collected while adding the shared library symbols are
+# examined to make sure that identical messages were suppressed while
+# also making sure that at least two messages have been printed.
+
+# This test can't be run on targets lacking shared library support
+# or for non-ELF targets.
+if { [skip_shlib_tests] || ![is_elf_target] } {
+ return 0
+}
+
+# Library file names and flags:
+set lib_basename ${::gdb_test_file_name}-lib
+set srcfile_lib ${srcdir}/${subdir}/${lib_basename}.c
+set binfile_lib [standard_output_file ${lib_basename}.so]
+set lib_flags debug
+
+# Compile shared library:
+if { [gdb_compile_shlib ${srcfile_lib} ${binfile_lib} $lib_flags] != "" } {
+ untested "failed to compile"
+ return -1
+}
+
+# Open the shared library and determine some basic facts. The key
+# things that we need to learn are 1) whether the solib is 32-bit or
+# 64-bit ELF file, and 2) the endianness.
+set solib_fp [open ${binfile_lib} r]
+fconfigure $solib_fp -translation binary
+
+# Read and check EI_MAG to verify that it's really an ELF file.
+set data [read $solib_fp 4]
+if { ![string equal $data "\x7fELF"] } {
+ close $solib_fp
+ untested "shared library is not an ELF file"
+ return -1
+}
+
+# Read EI_CLASS for ELF32 versus ELF64.
+set data [read $solib_fp 1]
+set is_elf64 [string equal $data "\x02"]
+
+# Read EI_DATA to determine data encoding (byte order).
+set data [read $solib_fp 1]
+set is_big_endian [string equal $data "\x02"]
+
+close $solib_fp
+
+set objcopy_program [gdb_find_objcopy]
+
+# Extract the .dynsym and .dynstr section from the shared object.
+if { [catch "exec $objcopy_program \
+ --dump-section .dynsym=${binfile_lib}.dynsym \
+ --dump-section .dynstr=${binfile_lib}.dynstr \
+ ${binfile_lib}" output] } {
+ untested "failed objcopy dump-section"
+ verbose -log "objcopy output: $output"
+ return -1
+}
+
+# Determine length of .dynstr. We'll use the length for creating invalid
+# offsets into .dynstr.
+set dynstr_len [file size ${binfile_lib}.dynstr]
+
+# Open the file containing .dynsym and determine its length. In this
+# case, we want to know the length in order to compute the total number
+# of symbols that it contains. We also leave the file open for a
+# while so that we can write invalid offsets to it.
+set dynsym_fp [open ${binfile_lib}.dynsym r+]
+fconfigure $dynsym_fp -translation binary
+set dynsym_len [string length [read $dynsym_fp]]
+
+# SZ is the size of the Elf32_Sym / Elf64_Sym struct. OFF is the
+# offset into the file. CNT is one greater than the number of symbols
+# left to mangle. Note that, in the loop below, the first symbol is
+# skipped. This is intentional since the first symbol is defined by
+# the ELF specification to be the undefined symbol.
+set off 0
+if { $is_elf64 } {
+ set sz 24
+} else {
+ set sz 16
+}
+set cnt [expr $dynsym_len / $sz]
+
+# Create 32-bit patterns (bad offsets) to write into the st_name area.
+if { $is_big_endian } {
+ set pat(0) [binary format I [expr $dynstr_len + 1000]]
+ set pat(1) [binary format I [expr $dynstr_len + 2000]]
+} else {
+ set pat(0) [binary format i [expr $dynstr_len + 1000]]
+ set pat(1) [binary format i [expr $dynstr_len + 2000]]
+}
+
+# Mangle st_name for the symbols following the first (STN_UNDEF) entry.
+while { [incr cnt -1] > 0 } {
+ seek $dynsym_fp [incr off $sz]
+ puts $dynsym_fp $pat([expr $cnt % 2])
+}
+
+close $dynsym_fp
+
+# Replace .dynsym section in shared object with the mangled version.
+if { [catch "exec $objcopy_program \
+ --update-section .dynsym=${binfile_lib}.dynsym \
+ ${binfile_lib}" output] } {
+ untested "failed objcopy update-section"
+ verbose -log "objcopy output: $output"
+ return -1
+}
+
+clean_restart
+
+# Count number of distinct BFD error messages via 'bfd_error_count'
+# array while using add-symbol-file to "load" the shared library.
+gdb_test_multiple "add-symbol-file -readnow $binfile_lib" \
+ "load library with add-symbol-file" {
+ -re "add symbol table from file.*\(y or n\)" {
+ send_gdb "y\n" answer
+ exp_continue
+ }
+ -re "(BFD:\[^\r\n\]*)\[\r\n\]+" {
+ incr bfd_error_count($expect_out(1,string))
+ exp_continue
+ }
+ -re "Expanding full symbols from.*$gdb_prompt $" {
+ pass $gdb_test_name
+ }
+}
+
+# Examine counts recorded in the 'bfd_error_count' array to see if any
+# message was printed multiple times.
+set more_than_one 0
+foreach index [array names bfd_error_count] {
+ if { $bfd_error_count($index) > 1 } {
+ incr more_than_one
+ }
+}
+gdb_assert { $more_than_one == 0 } "consolidated bfd errors"
+
+# There should have been at least two distinct BFD errors printed.
+gdb_assert { [array size bfd_error_count] >= 2 } "print all unique bfd errors"
+
+gdb_exit
+return 0