mirror of
https://github.com/espressif/binutils-gdb.git
synced 2025-12-19 01:19:41 +08:00
This updates the copyright headers to include 2025. I did this by running gdb/copyright.py and then manually modifying a few files as noted by the script. Approved-By: Eli Zaretskii <eliz@gnu.org>
431 lines
15 KiB
Plaintext
431 lines
15 KiB
Plaintext
# This testcase is part of GDB, the GNU debugger.
|
|
#
|
|
# Copyright 2021-2025 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/>.
|
|
#
|
|
#
|
|
# Test shared libraries loaded into different namespaces with dlmopen().
|
|
#
|
|
# We test that GDB shows the correct number of instances of the libraries
|
|
# the test loaded while unloading them one-by-one.
|
|
|
|
require allow_dlmopen_tests
|
|
|
|
# Don't use 'dlmopen.c' as the source file name, glibc also has a file
|
|
# with that name. Within our tests, we set the source directory search
|
|
# path order to:
|
|
#
|
|
# (1) the test source directory,
|
|
# (2) the compilation directory, and then
|
|
# (3) the current working directory.
|
|
#
|
|
# Because (1) is first when we try to place a breakpoint on
|
|
# 'dlmopen.c', if the test source file has that name, then GDB will
|
|
# find both the test source file, and the source file from glibc.
|
|
#
|
|
# We could work around this by making (2) first in the source
|
|
# directory list, but that only works when the glibc source is
|
|
# installed. If it isn't then GDB will try the compilation directory,
|
|
# fail to find the source, then try the test source directory, get a
|
|
# hit, and so still confuse the two files.
|
|
#
|
|
# You might think the problem can be solved by specifying the absolute
|
|
# path to the source file. This doesn't work because the glibc file
|
|
# has its filename recorded as just "dlmopen.c", as such GDB has to
|
|
# figure out an absolute path to the file (if possible). The absolute
|
|
# path is figured out based on where GDB can find a matching file in
|
|
# the source directory list, and because of the confusion above, GDB
|
|
# will usually think the test 'dlmopen.c' and the glibc 'dlmopen.c'
|
|
# are actually the same file.
|
|
#
|
|
# The conclusion is that it is just easier to rename the test source
|
|
# file to avoid conflicts with glibc.
|
|
|
|
standard_testfile -main.c -lib.c -lib-dep.c
|
|
|
|
set basename_lib dlmopen-lib
|
|
set srcfile_lib $srcfile2
|
|
set binfile_lib1 [standard_output_file $basename_lib.1.so]
|
|
set binfile_lib2 [standard_output_file $basename_lib.2.so]
|
|
set srcfile_lib_dep $srcfile3
|
|
set binfile_lib_dep [standard_output_file $basename_lib-dep.so]
|
|
|
|
if { [build_executable "build shlib dep" $binfile_lib_dep $srcfile_lib_dep \
|
|
{debug shlib}] == -1 } {
|
|
return
|
|
}
|
|
|
|
if { [build_executable "build shlib" $binfile_lib1 $srcfile_lib \
|
|
[list debug shlib_load shlib libs=$binfile_lib_dep]] == -1 } {
|
|
return
|
|
}
|
|
|
|
if { [build_executable "build shlib" $binfile_lib2 $srcfile_lib \
|
|
[list debug shlib_load shlib libs=$binfile_lib_dep]] == -1 } {
|
|
return
|
|
}
|
|
|
|
if { [build_executable "failed to build" $testfile $srcfile \
|
|
[list additional_flags=-DDSO1_NAME=\"$binfile_lib1\" \
|
|
additional_flags=-DDSO2_NAME=\"$binfile_lib2\" \
|
|
shlib_load debug]] } {
|
|
return
|
|
}
|
|
|
|
# Some locations needed by the tests.
|
|
set bp_inc [gdb_get_line_number "bp.inc" $srcfile_lib]
|
|
set bp_main [gdb_get_line_number "bp.main" $srcfile]
|
|
|
|
# Figure out the file name for the dynamic linker.
|
|
set dyln_name [section_get $binfile .interp]
|
|
if { $dyln_name eq "" } {
|
|
unsupported "couldn't find dynamic linker name"
|
|
return
|
|
}
|
|
|
|
# Return true if FILENAME is the dynamic linker. Otherwise return false.
|
|
proc is_dyln { filename } {
|
|
return [expr {$filename eq $::dyln_name}]
|
|
}
|
|
|
|
# Check that 'info shared' show NUM occurrences of DSO.
|
|
proc check_dso_count { dso num } {
|
|
global gdb_prompt hex
|
|
|
|
set count 0
|
|
gdb_test_multiple "info shared" "info shared" {
|
|
-re "$hex $hex \(\[\[$::decimal\]\]\\s+\)\?Yes \[^\r\n\]*$dso\r\n" {
|
|
# use longer form so debug remote does not interfere
|
|
set count [expr $count + 1]
|
|
exp_continue
|
|
}
|
|
-re "$gdb_prompt " {
|
|
verbose -log "library: $dso, expected: $num, found: $count"
|
|
gdb_assert {$count == $num} "$gdb_test_name"
|
|
}
|
|
}
|
|
}
|
|
|
|
# The DSO part of the test. We run it once per DSO call.
|
|
proc test_dlmopen_one { ndso1 ndso2 exp_glob } {
|
|
global srcfile_lib srcfile_lib basename_lib bp_inc
|
|
|
|
# Try to reach the breakpoint in the dynamically loaded library.
|
|
gdb_continue_to_breakpoint "cont to bp.inc" \
|
|
".*$srcfile_lib:$bp_inc\r\n.*"
|
|
|
|
# We opened all DSOs initially and close them one by one.
|
|
with_test_prefix "dso 1" { check_dso_count $basename_lib.1.so $ndso1 }
|
|
with_test_prefix "dso 2" { check_dso_count $basename_lib.2.so $ndso2 }
|
|
|
|
# This might help debugging.
|
|
gdb_test "info breakpoints" ".*"
|
|
gdb_test "print \$pc" ".*"
|
|
|
|
# We expect different instances of GDB_DLMOPEN_GLOB per DSO.
|
|
gdb_test "print amount" "= $exp_glob"
|
|
gdb_test "print gdb_dlmopen_glob" "= $exp_glob"
|
|
|
|
# Modify that DSO's instance, which should leave the others intact.
|
|
gdb_test "print &gdb_dlmopen_glob" "= .*"
|
|
gdb_test "print gdb_dlmopen_glob = -1" "= -1"
|
|
}
|
|
|
|
# The actual test. We run it twice.
|
|
proc test_dlmopen {} {
|
|
global srcfile basename_lib bp_main
|
|
|
|
# Note that when loading dlmopen-lib.1.so and dlmopen-lib.2.so into
|
|
# the same namespace, dlmopen-lib-dep.so is loaded only once, so in
|
|
# this case, the changes to gdb_dlmopen_glob inside test_dlmopen_one
|
|
# will actually be visible.
|
|
#
|
|
# Hence, we supply the expected value of this variable as argument to
|
|
# test_dlmopen_one.
|
|
with_test_prefix "dlmopen 1" { test_dlmopen_one 3 1 1 }
|
|
with_test_prefix "dlmopen 2" { test_dlmopen_one 2 1 1 }
|
|
with_test_prefix "dlmopen 3" { test_dlmopen_one 1 1 1 }
|
|
with_test_prefix "dlmopen 4" { test_dlmopen_one 0 1 -1 }
|
|
|
|
with_test_prefix "main" {
|
|
# Try to reach the breakpoint in the dynamically loaded library.
|
|
gdb_continue_to_breakpoint "cont to bp.main" \
|
|
".*$srcfile:$bp_main\r\n.*"
|
|
|
|
# The library should not be listed.
|
|
with_test_prefix "dso 1" { check_dso_count $basename_lib.1.so 0 }
|
|
with_test_prefix "dso 2" { check_dso_count $basename_lib.2.so 0 }
|
|
}
|
|
}
|
|
|
|
# Setup for calling 'test_dlmopen', this is the version of the test
|
|
# that doesn't use 'attach'.
|
|
proc test_dlmopen_no_attach {} {
|
|
clean_restart $::binfile
|
|
|
|
if { ![runto_main] } {
|
|
return
|
|
}
|
|
|
|
# Remove the pause. We only need it for the attach test.
|
|
gdb_test "print wait_for_gdb = 0" "\\\$1 = 0"
|
|
|
|
# Break in the to-be-loaded library and at the end of main.
|
|
delete_breakpoints
|
|
gdb_breakpoint $::srcfile_lib:$::bp_inc allow-pending
|
|
gdb_breakpoint $::srcfile:$::bp_main
|
|
|
|
test_dlmopen
|
|
}
|
|
|
|
# Setup for calling 'test_dlmopen', this is the version of the test
|
|
# that does use 'attach'.
|
|
proc test_dlmopen_with_attach {} {
|
|
if { ![can_spawn_for_attach] } {
|
|
unsupported "cannot run attach tests"
|
|
return
|
|
}
|
|
|
|
clean_restart $::binfile
|
|
|
|
# Start the test program.
|
|
set test_spawn_id [spawn_wait_for_attach $::binfile]
|
|
set testpid [spawn_id_get_pid $test_spawn_id]
|
|
|
|
# Attach.
|
|
if { ![gdb_attach $testpid] } {
|
|
return
|
|
}
|
|
|
|
with_test_prefix "attach" {
|
|
# Remove the pause. We no longer need it.
|
|
gdb_test "print wait_for_gdb = 0" "\\\$1 = 0"
|
|
|
|
# Set the same breakpoints again. This time, however, we do not allow the
|
|
# breakpoint to be pending since the library has already been loaded.
|
|
gdb_breakpoint $::srcfile_lib:$::bp_inc
|
|
gdb_breakpoint $::srcfile:$::bp_main
|
|
|
|
test_dlmopen
|
|
}
|
|
}
|
|
|
|
# Run 'info sharedlibrary' and count the number of mappings that look
|
|
# like they might be the dynamic linker. This will only work for
|
|
# Linux right now.
|
|
proc get_dyld_info {} {
|
|
if { ![istarget *-linux*] } {
|
|
return [list 0 ""]
|
|
}
|
|
|
|
set dyld_count 0
|
|
set dyld_start_addr ""
|
|
gdb_test_multiple "info sharedlibrary" "" {
|
|
-re "From\\s+To\\s+\(NS\\s+\)?Syms\\s+Read\\s+Shared Object Library\r\n" {
|
|
exp_continue
|
|
}
|
|
-re "^($::hex)\\s+$::hex\\s+\(\#$::decimal\\s+\)?\[^/\]+(/\[^\r\n\]+)\r\n" {
|
|
set addr $expect_out(1,string)
|
|
set lib $expect_out(3,string)
|
|
|
|
if { [is_dyln $lib] } {
|
|
# This looks like it might be the dynamic linker.
|
|
incr dyld_count
|
|
if { $dyld_start_addr eq "" } {
|
|
set dyld_start_addr $addr
|
|
} elseif { $dyld_start_addr ne $addr } {
|
|
set dyld_start_addr "MULTIPLE"
|
|
}
|
|
}
|
|
|
|
exp_continue
|
|
}
|
|
-re "\\(\\*\\): Shared library is missing debugging information\\.\r\n" {
|
|
exp_continue
|
|
}
|
|
-re "^$::gdb_prompt $" {
|
|
}
|
|
}
|
|
|
|
if { $dyld_start_addr eq "MULTIPLE" } {
|
|
set dyld_start_addr ""
|
|
}
|
|
|
|
return [list $dyld_count $dyld_start_addr]
|
|
}
|
|
|
|
# The inferior for this test causes the dynamic linker to be appear
|
|
# multiple times in the inferior's shared library list, but (at least
|
|
# with glibc), the dynamic linker is really only mapped in once. That
|
|
# is, each of the dynamic linker instances that appear in the 'info
|
|
# sharedlibrary' output, will have the same address range.
|
|
#
|
|
# This test creates a user breakpoint in the dynamic linker, and then
|
|
# runs over the dlcose calls, which unmap all but one of the dynamic
|
|
# linker instances.
|
|
#
|
|
# The expectation is that the user breakpoint in the dynamic linker
|
|
# should still be active. Older versions of GDB had a bug where the
|
|
# breakpoint would become pending.
|
|
proc_with_prefix test_solib_unmap_events { } {
|
|
|
|
# This test relies on finding the dynamic linker library, and is
|
|
# currently written assuming Linux.
|
|
if { ![istarget *-linux*] } {
|
|
unsupport "cannot find dynamic linker library on this target"
|
|
return
|
|
}
|
|
|
|
clean_restart $::binfile
|
|
|
|
if { ![runto_main] } {
|
|
return
|
|
}
|
|
|
|
# Check that before any of our dlopen/dlmopen calls, we can find a
|
|
# single copy of the dynamic linker in the shared library list.
|
|
set dyld_info [get_dyld_info]
|
|
set dyld_count [lindex $dyld_info 0]
|
|
if { $dyld_count != 1 } {
|
|
unsupported "initial dyld state appears strange"
|
|
return
|
|
}
|
|
|
|
# Continue the inferior until all solib are loaded.
|
|
set alarm_lineno [gdb_get_line_number "alarm" $::srcfile]
|
|
gdb_breakpoint ${::srcfile}:${alarm_lineno}
|
|
gdb_continue_to_breakpoint "all solib are now loaded"
|
|
|
|
# Check that we have multiple copies of dynamic linker loaded, and
|
|
# that the dynamic linker is only loaded at a single address.
|
|
set dyld_info [get_dyld_info]
|
|
set dyld_count [lindex $dyld_info 0]
|
|
set dyld_start_addr [lindex $dyld_info 1]
|
|
|
|
# If we didn't find a suitable dynamic linker address, or we
|
|
# didn't find multiple copies of the dynamic linker, then
|
|
# something has gone wrong with the test setup.
|
|
if { $dyld_count < 2 } {
|
|
unsupported "multiple copies of the dynamic linker not found"
|
|
return
|
|
}
|
|
if { $dyld_start_addr eq "" } {
|
|
unsupported "unable to find suitable dynamic linker start address"
|
|
return
|
|
}
|
|
|
|
# Check the address we found is (likely) writable.
|
|
gdb_test_multiple "x/1i $dyld_start_addr" "check b/p address" {
|
|
-re -wrap "Cannot access memory at address \[^\r\n\]+" {
|
|
unsupported "dynamic linker address is not accessible"
|
|
return
|
|
}
|
|
-re -wrap "" {
|
|
}
|
|
}
|
|
|
|
# Create a breakpoint within the dynamic linker. It is pretty unlikely
|
|
# that this breakpoint will ever be hit, but just in case it is, make it
|
|
# conditional, with a condition that will never be true. All we really
|
|
# care about for this test is whether the breakpoint will be made
|
|
# pending or not (it should not).
|
|
gdb_test "break *$dyld_start_addr if (0)" \
|
|
"Breakpoint $::decimal at $::hex\[^\r\n\]+" \
|
|
"create breakpoint within dynamic linker"
|
|
set bpnum [get_integer_valueof "\$bpnum" INVALID "get bpnum"]
|
|
|
|
# Now continue until the 'bp.main' location, this will unload some
|
|
# copies, but not all copies, of the dynamic linker.
|
|
gdb_test "print wait_for_gdb = 0" " = 0"
|
|
set bp_main [gdb_get_line_number "bp.main" $::srcfile]
|
|
|
|
gdb_breakpoint $::srcfile:$bp_main
|
|
gdb_continue_to_breakpoint "stop at bp.main"
|
|
|
|
# At one point, GDB would incorrectly mark the breakpoints in the
|
|
# dynamic linker as pending when some instances of the library were
|
|
# unloaded, despite there really only being one copy of the dynamic
|
|
# linker actually loaded into the inferior's address space.
|
|
gdb_test_multiple "info breakpoints $bpnum" "check b/p status" {
|
|
-re -wrap "$bpnum\\s+breakpoint\\s+keep\\s+y\\s+<PENDING>\\s+\\*$::hex\\s*\r\n\\s+stop only if \\(0\\)" {
|
|
fail $gdb_test_name
|
|
}
|
|
|
|
-re -wrap "$bpnum\\s+breakpoint\\s+keep\\s+y\\s+$::hex\\s*\[^\r\n\]+\r\n\\s+stop only if \\(0\\)" {
|
|
pass $gdb_test_name
|
|
}
|
|
}
|
|
|
|
# With all the dlclose calls now complete, we should be back to a
|
|
# single copy of the dynamic linker.
|
|
set dyld_info [get_dyld_info]
|
|
set dyld_count [lindex $dyld_info 0]
|
|
gdb_assert { $dyld_count == 1 } \
|
|
"one dynamic linker found after dlclose calls"
|
|
}
|
|
|
|
# Check that we can 'next' over the dlclose calls without GDB giving any
|
|
# warnings or errors.
|
|
proc_with_prefix test_next_over_dlclose {} {
|
|
clean_restart $::binfile
|
|
|
|
if { ![runto_main] } {
|
|
return
|
|
}
|
|
|
|
set dlclose_lineno [gdb_get_line_number "dlclose" $::srcfile]
|
|
gdb_breakpoint $::srcfile:$dlclose_lineno
|
|
gdb_breakpoint $::srcfile:$::bp_main
|
|
|
|
# Remove the pause. We no longer need it.
|
|
gdb_test "print wait_for_gdb = 0" "\\\$1 = 0"
|
|
|
|
set loc_re [multi_line \
|
|
"\[^\r\n\]+/[string_to_regexp $::srcfile]:$dlclose_lineno" \
|
|
"$dlclose_lineno\\s+dlclose \[^\r\n\]+"]
|
|
|
|
# This loop mirrors the loop in dlmopen.c where the inferior performs
|
|
# four calls to dlclose. Here we continue to the dlclose, and then use
|
|
# 'next' to step over the call. As part of the 'next' GDB will insert
|
|
# breakpoints to catch longjmps into the multiple copies of libc which
|
|
# have been loaded. Each dlclose call will cause a copy of libc to be
|
|
# unloaded, which should disable the longjmp breakpoint that GDB
|
|
# previously inserted.
|
|
#
|
|
# At one point a bug in GDB meant that we failed to correctly disable
|
|
# the longjmp breakpoints and GDB would try to remove the breakpoint
|
|
# from a library after it had been unloaded, which would trigger a
|
|
# warning.
|
|
for { set i 0 } { $i < 4 } { incr i } {
|
|
gdb_continue_to_breakpoint "continue to dlclose $i" $loc_re
|
|
gdb_test "next" "^$::decimal\\s+for \\(dl = 0;\[^\r\n\]+\\)" \
|
|
"next over dlclose $i"
|
|
}
|
|
|
|
# Just to confirm that we are where we think we are, continue to the
|
|
# final 'return' line in main. If this isn't hit then we likely went
|
|
# wrong earlier.
|
|
gdb_continue_to_breakpoint "continue to final return" \
|
|
[multi_line \
|
|
"\[^\r\n\]+/[string_to_regexp $::srcfile]:$::bp_main" \
|
|
"$::bp_main\\s+return 0;\[^\r\n\]+"]
|
|
}
|
|
|
|
# Run the actual tests.
|
|
test_dlmopen_no_attach
|
|
test_dlmopen_with_attach
|
|
test_solib_unmap_events
|
|
test_next_over_dlclose
|