| # Copyright 2024-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/>. */ |
| |
| # This test exercises GDB's ability to validate build-ids when loading |
| # shared libraries for a core file. |
| # |
| # The test creates two "versions" of a shared library, sets up a |
| # symlink to point to one version of the library, and creates a core file. |
| # |
| # We then try re-loading the core file and executable and check that |
| # GDB is able to correctly load the shared library. To confuse things |
| # we retarget the library symlink at the other version of the library. |
| # |
| # After that we repeat the test, but this time deleting the symlink |
| # completely. |
| # |
| # Then we remove the version of the library completely, at this point |
| # we do expect GDB to give a warning about being unable to load the library. |
| # |
| # And finally, we setup debuginfod and have it serve the missing |
| # library file, GDB should correctly download the library file. |
| # |
| # 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. |
| |
| load_lib debuginfod-support.exp |
| |
| require allow_shlib_tests |
| require {istarget "*-linux*"} |
| require {!is_remote host} |
| require {!using_fission} |
| |
| standard_testfile -1.c -2.c |
| |
| # Build two similar, but slightly different versions of the shared |
| # library. Both libraries have DT_SONAME set to the generic |
| # libfoo.so, we'll create a symlink with that name later. |
| set library_1_filename [standard_output_file "libfoo_1.so"] |
| set library_2_filename [standard_output_file "libfoo_2.so"] |
| |
| # The generic name for the library. |
| set library_filename [standard_output_file "libfoo.so"] |
| |
| # When compiling a shared library the -Wl,-soname,NAME option is |
| # automatically added based on the final name of the library. We want |
| # to compile libfoo_1.so, but set the soname to libfoo.so. To achieve |
| # this we first compile into libfoo.so, and then rename the library to |
| # libfoo_1.so. |
| if {[build_executable "build libfoo_1.so" $library_filename \ |
| $srcfile \ |
| { debug shlib build-id \ |
| additional_flags=-DLIB_VERSION=1 }] == -1} { |
| return |
| } |
| remote_exec build "mv ${library_filename} ${library_1_filename}" |
| |
| # See the comment above, but this time we rename to libfoo_2.so. |
| if {[build_executable "build libfoo_2.so" $library_filename \ |
| $srcfile \ |
| { debug shlib build-id \ |
| additional_flags=-DLIB_VERSION=2 }] == -1} { |
| return |
| } |
| remote_exec build "mv ${library_filename} ${library_2_filename}" |
| |
| # Create libfoo.so symlink to the libfoo_1.so library. If this |
| # symlink creation fails then we assume we can't create symlinks on |
| # this host. If this succeeds then later symlink creation is required |
| # to succeed, and will trigger an FAIL if it doesn't. |
| set status \ |
| [remote_exec build \ |
| "ln -sf ${library_1_filename} ${library_filename}"] |
| if {[lindex $status 0] != 0} { |
| unsupported "host does not support symbolic links" |
| return |
| } |
| |
| # Build the executable. This links against libfoo.so, which is |
| # pointing at libfoo_1.so. Just to confuse things even more, this |
| # executable uses dlopen to load libfoo_2.so. Weird! |
| if { [build_executable "build executable" ${binfile} ${srcfile2} \ |
| [list debug shlib=${library_filename} shlib_load]] == -1 } { |
| return |
| } |
| |
| # If the board file is automatically splitting the debug information |
| # into a separate file (e.g. the cc-with-gnu-debuglink.exp board) then |
| # this test isn't going to work. |
| clean_restart |
| gdb_file_cmd $binfile |
| if {$gdb_file_cmd_debug_info ne "debug"} { |
| unsupported "failed to find debug information" |
| return |
| } |
| if {[regexp "${testfile}.debug" $gdb_file_cmd_msg]} { |
| unsupported "debug information has been split to a separate file" |
| return |
| } |
| |
| # Run BINFILE which will generate a corefile. |
| set corefile [core_find $binfile] |
| if {$corefile eq ""} { |
| untested "could not generate core file" |
| return |
| } |
| |
| # Helper proc to load global BINFILE and then load global COREFILE. |
| # |
| # If EXPECT_WARNING is true then we require a warning about being |
| # unable to load the shared library symbols, otherwise, EXPECT_WARNING |
| # is false and we require no warning. |
| # |
| # If EXPECT_DOWNLOAD is true then we require a line indicating that |
| # the shared library is being downloaded from debuginfod, otherwise |
| # the shared library should not be downloaded. |
| # |
| # If DEBUGDIR is not the empty string then 'debug-file-directory' is |
| # set to the value of DEBUGDIR. |
| proc load_exec_and_core_file { expect_warning expect_download testname \ |
| {debugdir ""} } { |
| with_test_prefix $testname { |
| clean_restart $::testfile |
| |
| if { $debugdir ne "" } { |
| gdb_test_no_output "set debug-file-directory $debugdir" \ |
| "set debug directory" |
| } |
| |
| set saw_warning false |
| set saw_download false |
| set saw_generated false |
| set saw_terminated false |
| |
| gdb_test_multiple "core-file $::corefile" "load core file" { |
| -re "^Core was generated by \[^\r\n\]+\r\n" { |
| set saw_generated true |
| exp_continue |
| } |
| -re "^Program terminated with signal \[^\r\n\]+\r\n" { |
| set saw_terminated true |
| exp_continue |
| } |
| -re "^warning: Can't open file \[^\r\n\]+ during file-backed mapping note processing\r\n" { |
| # Ignore warnings from the file backed mapping phase. |
| exp_continue |
| } |
| -re "^warning: Could not load shared library symbols for \[^\r\n\]+/libfoo\\.so\\.\r\n" { |
| set saw_warning true |
| exp_continue |
| } |
| -re "^Downloading\[^\r\n\]*file \[^\r\n\]+/libfoo_1\\.so\\.\\.\\.\r\n" { |
| set saw_download true |
| exp_continue |
| } |
| -re "^$::gdb_prompt $" { |
| gdb_assert { $saw_generated && $saw_terminated \ |
| && $saw_warning == $expect_warning \ |
| && $saw_download == $expect_download } \ |
| $gdb_test_name |
| } |
| -re "^\[^\r\n\]*\r\n" { |
| exp_continue |
| } |
| } |
| |
| # If we don't expect a warning then debug symbols from the |
| # shared library should be available. Confirm we can read a |
| # variable from the shared library. If we do expect a warning |
| # then the shared library debug symbols have not loaded, and |
| # the library variable should not be available. |
| if { !$expect_warning } { |
| gdb_test "print/x library_1_var" " = 0x12345678" \ |
| "check library_1_var can be read" |
| } else { |
| gdb_test "print/x library_1_var" \ |
| "^No symbol \"library_1_var\" in current context\\." \ |
| "check library_1_var cannot be read" |
| } |
| } |
| } |
| |
| # Initial test, just load the executable and core file. At this point |
| # everything should load fine as everything is where we expect to find |
| # it. |
| load_exec_and_core_file false false \ |
| "load core file, all libraries as expected" |
| |
| # Update libfoo.so symlink to point at the second library then reload |
| # the core file. GDB should spot that the symlink points to the wrong |
| # file, but should be able to figure out the correct file to load as |
| # the right file will be in the mapped file list. |
| set status [remote_exec build \ |
| "ln -sf ${library_2_filename} ${library_filename}"] |
| gdb_assert { [lindex $status 0] == 0 } \ |
| "update library symlink to point to the wrong file" |
| |
| load_exec_and_core_file false false \ |
| "load core file, symlink points to wrong file" |
| |
| # Remove libfoo.so symlink and reload the core file. As in the |
| # previous test GDB should be able to figure out the correct file to |
| # load as the correct file will still appear in the mapped file list. |
| set status [remote_exec build "rm -f ${library_filename}"] |
| gdb_assert { [lindex $status 0] == 0 } "remove library symlink" |
| |
| load_exec_and_core_file false false \ |
| "load core file, symlink removed" |
| |
| # Remove LIBRARY_1_FILENAME. We'll now see a warning that the mapped |
| # file can't be loaded (we ignore that warning), and we'll see a |
| # warning that the shared library can't be loaded. |
| set library_1_backup_filename ${library_1_filename}.backup |
| set status \ |
| [remote_exec build \ |
| "mv ${library_1_filename} ${library_1_backup_filename}"] |
| gdb_assert { [lindex $status 0] == 0 } \ |
| "remove libfoo_1.so" |
| |
| load_exec_and_core_file true false \ |
| "load core file, libfoo_1.so removed" |
| |
| # Symlink the .build-id/xx/xxx...xxx filename within the debug |
| # directory to LIBRARY_1_BACKUP_FILENAME, now when we restart GDB it |
| # should find the missing library within the debug directory. |
| set debugdir [standard_output_file "debugdir"] |
| set build_id_filename \ |
| $debugdir/[build_id_debug_filename_get $library_1_backup_filename ""] |
| set status \ |
| [remote_exec build \ |
| "mkdir -p [file dirname $build_id_filename]"] |
| gdb_assert { [lindex $status 0] == 0 } \ |
| "create sub-directory within the debug directory" |
| set status \ |
| [remote_exec build \ |
| "ln -sf $library_1_backup_filename $build_id_filename"] |
| gdb_assert { [lindex $status 0] == 0 } \ |
| "create symlink within the debug directory " |
| |
| load_exec_and_core_file false false \ |
| "load core file, find libfoo_1.so through debug-file-directory" \ |
| $debugdir |
| |
| # Setup a debuginfod server which can serve the original shared |
| # library file. |
| 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_1_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 |
| |
| save_vars { GDBFLAGS } { |
| append GDBFLAGS " -ex \"set debuginfod enabled on\"" |
| |
| # Reload the executable and core file. GDB should download |
| # the file libfoo_1.so using debuginfod during the mapped file |
| # phase, but should then reuse that download during the shared |
| # library phase. |
| load_exec_and_core_file false true \ |
| "load core file, use debuginfod" |
| } |
| } |
| |
| stop_debuginfod |