mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-27 03:51:15 +08:00
ac51afb51c
Eli mentioned [1] that given that we use US English spelling in our documentation, we should use "behavior" instead of "behaviour". In wikipedia-common-misspellings.txt there's a rule: ... behavour->behavior, behaviour ... which leaves this as a choice. Add an overriding rule to hardcode the choice to common-misspellings.txt: ... behavour->behavior ... and add a rule to rewrite behaviour into behavior: ... behaviour->behavior ... and re-run spellcheck.sh on gdb*. Tested on x86_64-linux. [1] https://sourceware.org/pipermail/gdb-patches/2024-November/213371.html
381 lines
13 KiB
Plaintext
381 lines
13 KiB
Plaintext
# Copyright 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 checks GDB's ability to use build-ids when setting up file backed
|
|
# mappings as part of reading a core-file.
|
|
#
|
|
# A core-file contains a list of the files that were mapped into the process
|
|
# at the time of the core-file creation. If the file was mapped read-only
|
|
# then the file contents will not be present in the core-file, but instead GDB
|
|
# is expected to open the mapped file and read the contents from there if
|
|
# needed. And this is what GDB does.
|
|
#
|
|
# GDB (via the BFD library) will also spot if a mapped looks like a valid ELF
|
|
# and contains a build-id, this build-id is passed back to GDB so that GDB can
|
|
# validate the on-disk file it finds matches the file that was mapped when the
|
|
# core-file was created.
|
|
#
|
|
# In addition, if the on-disk file is found to have a non-matching build-id
|
|
# then GDB can use debuginfod to (try) and download a suitable file.
|
|
#
|
|
# This test is about checking that this file backed mapping mechanism works
|
|
# correctly; that GDB will spot when the build-ids fail to match and will
|
|
# refuse to load an incorrect file. Additionally we check that the correct
|
|
# file can be downloaded from debuginfod.
|
|
#
|
|
# The test is rather contrived though. Instead of relying on having a shared
|
|
# library mapped at the time of crash we mmap a shared library into the
|
|
# process and then check this mapping within the test.
|
|
#
|
|
# The problem with using a normal shared library load for this test is that
|
|
# the shared library list is processed as part of a separate step when opening
|
|
# the core file. Right now this separate step doesn't check the build-ids
|
|
# correctly, so GDB will potentially open the wrong shared library file. The
|
|
# sections of this incorrect shared library are then added to GDB's list of
|
|
# target sections, and are used to satisfy memory reads, which can give the
|
|
# wrong results.
|
|
#
|
|
# This obviously needs fixing, but is a separate problem from the one being
|
|
# tested here, so this test deliberately checks the mapping using a file that
|
|
# is mmaped rather than loaded as a shared library, as such the file is in the
|
|
# core-files list of mapped files, but is not in the shared library list.
|
|
#
|
|
# Despite this test living in the gdb.debuginfod/ directory, only the last
|
|
# part of this test actually uses debuginfod, everything up to that point is
|
|
# pretty generic.
|
|
|
|
require {!is_remote host}
|
|
require {!is_remote target}
|
|
|
|
load_lib debuginfod-support.exp
|
|
|
|
require allow_shlib_tests
|
|
|
|
standard_testfile -1.c -2.c -3.c
|
|
|
|
# Compile an executable that loads the shared library as an actual
|
|
# shared library, then use GDB to figure out the offset of the
|
|
# variable 'library_ptr' within the library.
|
|
set library_filename [standard_output_file "libfoo.so"]
|
|
set binfile2 [standard_output_file "library_loader"]
|
|
|
|
if {[prepare_for_testing_full "build exec which loads the shared library" \
|
|
[list $library_filename \
|
|
{ debug shlib build-id \
|
|
additional_flags=-DPOINTER_VALUE=0x12345678 } \
|
|
$srcfile2 {}] \
|
|
[list $binfile2 [list debug shlib=$library_filename ] \
|
|
$srcfile { debug }]] != 0} {
|
|
return
|
|
}
|
|
|
|
if {![runto_main]} {
|
|
return
|
|
}
|
|
|
|
if { [is_address_zero_readable] } {
|
|
return
|
|
}
|
|
|
|
set ptr_address [get_hexadecimal_valueof "&library_ptr" "unknown"]
|
|
|
|
set ptr_offset "unknown"
|
|
gdb_test_multiple "info proc mappings" "" {
|
|
-re "^($hex)\\s+($hex)\\s+$hex\\s+($hex)\[^\r\n\]+$library_filename\\s*\r\n" {
|
|
set low_addr $expect_out(1,string)
|
|
set high_addr $expect_out(2,string)
|
|
set file_offset $expect_out(3,string)
|
|
|
|
if {[expr $ptr_address >= $low_addr] && [expr $ptr_address < $high_addr]} {
|
|
set mapping_offset [expr $ptr_address - $low_addr]
|
|
set ptr_offset [format 0x%x [expr $file_offset + $mapping_offset]]
|
|
}
|
|
|
|
exp_continue
|
|
}
|
|
|
|
-re "^$gdb_prompt $" {
|
|
}
|
|
|
|
-re "(^\[^\r\n\]*)\r\n" {
|
|
set tmp $expect_out(1,string)
|
|
exp_continue
|
|
}
|
|
}
|
|
|
|
gdb_assert { $ptr_offset ne "unknown" } \
|
|
"found pointer offset"
|
|
|
|
set ptr_size [get_integer_valueof "sizeof (library_ptr)" "unknown"]
|
|
set ptr_format_char ""
|
|
if { $ptr_size == 2 } {
|
|
set ptr_format_char "b"
|
|
} elseif { $ptr_size == 4 } {
|
|
set ptr_format_char "w"
|
|
} elseif { $ptr_size == 8 } {
|
|
set ptr_format_char "g"
|
|
}
|
|
if { $ptr_format_char eq "" } {
|
|
untested "could not figure out size of library_ptr variable"
|
|
return
|
|
}
|
|
|
|
# Helper proc to read a value from inferior memory. Reads at address held in
|
|
# global PTR_ADDRESS, and use PTR_FORMAT_CHAR for the size of the read.
|
|
proc read_ptr_value { } {
|
|
set value ""
|
|
gdb_test_multiple "x/1${::ptr_format_char}x ${::ptr_address}" "" {
|
|
-re -wrap "^${::hex}(?:\\s+<\[^>\]+>)?:\\s+($::hex)" {
|
|
set value $expect_out(1,string)
|
|
}
|
|
-re -wrap "^${::hex}(?:\\s+<\[^>\]+>)?:\\s+Cannot access memory at address ${::hex}" {
|
|
set value "unavailable"
|
|
}
|
|
}
|
|
return $value
|
|
}
|
|
|
|
set ptr_expected_value [read_ptr_value]
|
|
if { $ptr_expected_value eq "" } {
|
|
untested "could not find expected value for library_ptr"
|
|
return
|
|
}
|
|
|
|
# Now compile a second executable. This one doesn't load the shared
|
|
# library as an actual shared library, but instead mmaps the library
|
|
# into the executable.
|
|
#
|
|
# Load this executable within GDB and confirm that we can use the
|
|
# offset we calculated previously to view the value of 'library_ptr'.
|
|
set opts [list debug additional_flags=-DSHLIB_FILENAME=\"$library_filename\"]
|
|
if {[prepare_for_testing "prepare second executable" $binfile \
|
|
$srcfile3 $opts] != 0} {
|
|
return
|
|
}
|
|
|
|
if {![runto_main]} {
|
|
return
|
|
}
|
|
|
|
gdb_breakpoint [gdb_get_line_number "Undefined behavior here" $srcfile3]
|
|
gdb_continue_to_breakpoint "run to breakpoint"
|
|
|
|
set library_base_address \
|
|
[get_hexadecimal_valueof "library_base_address" "unknown"]
|
|
set ptr_address [format 0x%x [expr $library_base_address + $ptr_offset]]
|
|
|
|
set ptr_value [read_ptr_value]
|
|
gdb_assert { $ptr_value == $ptr_expected_value } \
|
|
"check value of pointer variable"
|
|
|
|
# Now rerun the second executable outside of GDB. The executable should crash
|
|
# and generate a corefile.
|
|
set corefile [core_find $binfile]
|
|
if {$corefile eq ""} {
|
|
untested "could not generate core file"
|
|
return
|
|
}
|
|
|
|
# Load a core file from the global COREFILE. Use TESTNAME as the name
|
|
# of the test.
|
|
#
|
|
# If LINE_RE is not the empty string then this is a regexp for a line
|
|
# that we expect to see in the output when loading the core file, if
|
|
# the line is not present then this test will fail.
|
|
#
|
|
# Any lines beginning with 'warning: ' will cause this test to fail.
|
|
#
|
|
# A couple of other standard lines that are produced when loading a
|
|
# core file are also checked for, just to make sure the core file
|
|
# loading has progressed as expected.
|
|
proc load_core_file { testname { line_re "" } } {
|
|
set code {}
|
|
|
|
if { $line_re ne "" } {
|
|
append code {
|
|
-re "^$line_re\r\n" {
|
|
set saw_expected_line true
|
|
exp_continue
|
|
}
|
|
}
|
|
set saw_expected_line false
|
|
} else {
|
|
set saw_expected_line true
|
|
}
|
|
|
|
set saw_unknown_warning false
|
|
set saw_generated_by_line false
|
|
set saw_prog_terminated_line false
|
|
|
|
append code {
|
|
-re "^warning: \[^\r\n\]+\r\n" {
|
|
set saw_unknown_warning true
|
|
exp_continue
|
|
}
|
|
|
|
-re "^Core was generated by \[^\r\n\]+\r\n" {
|
|
set saw_generated_by_line true
|
|
exp_continue
|
|
}
|
|
|
|
-re "^Program terminated with signal SIGSEGV, Segmentation fault\\.\r\n" {
|
|
set saw_prog_terminated_line true
|
|
exp_continue
|
|
}
|
|
|
|
-re "^$::gdb_prompt $" {
|
|
gdb_assert {$saw_generated_by_line \
|
|
&& $saw_prog_terminated_line \
|
|
&& $saw_expected_line \
|
|
&& !$saw_unknown_warning} \
|
|
$gdb_test_name
|
|
}
|
|
|
|
-re "^\[^\r\n\]*\r\n" {
|
|
exp_continue
|
|
}
|
|
}
|
|
|
|
set res [catch { return [gdb_test_multiple "core-file $::corefile" \
|
|
"$testname" $code] } string]
|
|
|
|
if {$res == 1} {
|
|
global errorInfo errorCode
|
|
return -code error -errorinfo $errorInfo -errorcode $errorCode $string
|
|
} elseif {$res == 2} {
|
|
return $string
|
|
} else {
|
|
# We expect RES to be 2 (TCL_RETURN) or 1 (TCL_ERROR). If we get
|
|
# here then somehow the 'catch' above finished without hitting
|
|
# either of those cases, which is .... weird.
|
|
perror "unexepcted return value, code = $res, value = $string"
|
|
return -1
|
|
}
|
|
}
|
|
|
|
# And now restart GDB, load the core-file and check that the library shows as
|
|
# being mapped in, and that we can still read the library_ptr value from
|
|
# memory.
|
|
clean_restart $binfile
|
|
|
|
load_core_file "load core file"
|
|
|
|
set library_base_address [get_hexadecimal_valueof "library_base_address" \
|
|
"unknown" "get library_base_address in core-file"]
|
|
set ptr_address [format 0x%x [expr $library_base_address + $ptr_offset]]
|
|
|
|
set ptr_value [read_ptr_value]
|
|
gdb_assert { $ptr_value == $ptr_expected_value } \
|
|
"check value of pointer variable from core-file"
|
|
|
|
# Now move the shared library file away and restart GDB. This time when we
|
|
# load the core-file we should see a warning that GDB has failed to map in the
|
|
# library file. An attempt to read the variable from the library file should
|
|
# fail / give a warning.
|
|
set library_backup_filename [standard_output_file "libfoo.so.backup"]
|
|
remote_exec build "mv \"$library_filename\" \"$library_backup_filename\""
|
|
|
|
clean_restart $binfile
|
|
|
|
load_core_file "load corefile with library file missing" \
|
|
"warning: Can't open file [string_to_regexp $library_filename] during file-backed mapping note processing"
|
|
|
|
set ptr_value [read_ptr_value]
|
|
gdb_assert { $ptr_value eq "unavailable" } \
|
|
"check value of pointer is unavailable with library file missing"
|
|
|
|
# Now symlink the .build-id/xx/xxx...xxx filename within the debug
|
|
# directory to library we just moved aside. Restart GDB and setup the
|
|
# debug-file-directory before loading the core file.
|
|
#
|
|
# GDB should lookup the file to map via the build-id link in the
|
|
# .build-id/ directory.
|
|
set debugdir [standard_output_file "debugdir"]
|
|
set build_id_filename \
|
|
$debugdir/[build_id_debug_filename_get $library_backup_filename ""]
|
|
|
|
remote_exec build "mkdir -p [file dirname $build_id_filename]"
|
|
remote_exec build "ln -sf $library_backup_filename $build_id_filename"
|
|
|
|
clean_restart $binfile
|
|
|
|
gdb_test_no_output "set debug-file-directory $debugdir" \
|
|
"set debug-file-directory"
|
|
|
|
load_core_file "load corefile, lookup in debug-file-directory"
|
|
|
|
set ptr_value [read_ptr_value]
|
|
gdb_assert { $ptr_value == $ptr_expected_value } \
|
|
"check value of pointer variable from core-file, lookup in debug-file-directory"
|
|
|
|
# Build a new version of the shared library, keep the library the same size,
|
|
# but change the contents so the build-id changes. Then restart GDB and load
|
|
# the core-file again. GDB should spot that the build-id for the shared
|
|
# library is not as expected, and should refuse to map in the shared library.
|
|
if {[build_executable "build second version of shared library" \
|
|
$library_filename $srcfile2 \
|
|
{ debug shlib build-id \
|
|
additional_flags=-DPOINTER_VALUE=0x11223344 }] != 0} {
|
|
return
|
|
}
|
|
|
|
clean_restart $binfile
|
|
|
|
load_core_file "load corefile with wrong library in place" \
|
|
"warning: File [string_to_regexp $library_filename] doesn't match build-id from core-file during file-backed mapping processing"
|
|
|
|
set ptr_value [read_ptr_value]
|
|
gdb_assert { $ptr_value eq "unavailable" } \
|
|
"check value of pointer is unavailable with wrong library in place"
|
|
|
|
# Setup a debuginfod server which can serve the original shared library file.
|
|
# Then restart GDB and load the core-file. GDB should download the original
|
|
# shared library from debuginfod and use that to provide the file backed
|
|
# mapping.
|
|
if {![allow_debuginfod_tests]} {
|
|
untested "skippig debuginfod parts of this test"
|
|
return
|
|
}
|
|
|
|
set server_dir [standard_output_file "debuginfod.server"]
|
|
file mkdir $server_dir
|
|
file rename -force $library_backup_filename $server_dir
|
|
|
|
prepare_for_debuginfod cache db
|
|
|
|
set url [start_debuginfod $db $server_dir]
|
|
if { $url eq "" } {
|
|
unresolved "failed to start debuginfod server"
|
|
return
|
|
}
|
|
|
|
with_debuginfod_env $cache {
|
|
setenv DEBUGINFOD_URLS $url
|
|
|
|
clean_restart
|
|
gdb_test_no_output "set debuginfod enabled on" \
|
|
"enabled debuginfod for initial test"
|
|
gdb_load $binfile
|
|
|
|
load_core_file "load corefile, download library from debuginfod" \
|
|
"Downloading\[^\r\n\]* file [string_to_regexp $library_filename]\\.\\.\\."
|
|
|
|
set ptr_value [read_ptr_value]
|
|
gdb_assert { $ptr_value == $ptr_expected_value } \
|
|
"check value of pointer variable after downloading library file"
|
|
}
|
|
|
|
stop_debuginfod
|