Home | History | Annotate | Line # | Download | only in gdb.debuginfod
      1 # Copyright 2024 Free Software Foundation, Inc.
      2 #
      3 # This program is free software; you can redistribute it and/or modify
      4 # it under the terms of the GNU General Public License as published by
      5 # the Free Software Foundation; either version 3 of the License, or
      6 # (at your option) any later version.
      7 #
      8 # This program is distributed in the hope that it will be useful,
      9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     11 # GNU General Public License for more details.
     12 #
     13 # You should have received a copy of the GNU General Public License
     14 # along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
     15 
     16 # This test checks GDB's ability to use build-ids when setting up file backed
     17 # mappings as part of reading a core-file.
     18 #
     19 # A core-file contains a list of the files that were mapped into the process
     20 # at the time of the core-file creation.  If the file was mapped read-only
     21 # then the file contents will not be present in the core-file, but instead GDB
     22 # is expected to open the mapped file and read the contents from there if
     23 # needed.  And this is what GDB does.
     24 #
     25 # GDB (via the BFD library) will also spot if a mapped looks like a valid ELF
     26 # and contains a build-id, this build-id is passed back to GDB so that GDB can
     27 # validate the on-disk file it finds matches the file that was mapped when the
     28 # core-file was created.
     29 #
     30 # In addition, if the on-disk file is found to have a non-matching build-id
     31 # then GDB can use debuginfod to (try) and download a suitable file.
     32 #
     33 # This test is about checking that this file backed mapping mechanism works
     34 # correctly; that GDB will spot when the build-ids fail to match and will
     35 # refuse to load an incorrect file.  Additionally we check that the correct
     36 # file can be downloaded from debuginfod.
     37 #
     38 # The test is rather contrived though.  Instead of relying on having a shared
     39 # library mapped at the time of crash we mmap a shared library into the
     40 # process and then check this mapping within the test.
     41 #
     42 # The problem with using a normal shared library load for this test is that
     43 # the shared library list is processed as part of a separate step when opening
     44 # the core file.  Right now this separate step doesn't check the build-ids
     45 # correctly, so GDB will potentially open the wrong shared library file.  The
     46 # sections of this incorrect shared library are then added to GDB's list of
     47 # target sections, and are used to satisfy memory reads, which can give the
     48 # wrong results.
     49 #
     50 # This obviously needs fixing, but is a separate problem from the one being
     51 # tested here, so this test deliberately checks the mapping using a file that
     52 # is mmaped rather than loaded as a shared library, as such the file is in the
     53 # core-files list of mapped files, but is not in the shared library list.
     54 #
     55 # Despite this test living in the gdb.debuginfod/ directory, only the last
     56 # part of this test actually uses debuginfod, everything up to that point is
     57 # pretty generic.
     58 
     59 require {!is_remote host}
     60 require {!is_remote target}
     61 
     62 load_lib debuginfod-support.exp
     63 
     64 require allow_shlib_tests
     65 
     66 standard_testfile -1.c -2.c -3.c
     67 
     68 # Compile an executable that loads the shared library as an actual
     69 # shared library, then use GDB to figure out the offset of the
     70 # variable 'library_ptr' within the library.
     71 set library_filename [standard_output_file "libfoo.so"]
     72 set binfile2 [standard_output_file "library_loader"]
     73 
     74 if {[prepare_for_testing_full "build exec which loads the shared library" \
     75 	 [list $library_filename \
     76 	      { debug shlib build-id \
     77 		    additional_flags=-DPOINTER_VALUE=0x12345678 } \
     78 	      $srcfile2 {}] \
     79 	 [list $binfile2 [list debug shlib=$library_filename ] \
     80 	      $srcfile { debug }]] != 0} {
     81     return
     82 }
     83 
     84 if {![runto_main]} {
     85     return
     86 }
     87 
     88 if { [is_address_zero_readable] } {
     89     return
     90 }
     91 
     92 set ptr_address [get_hexadecimal_valueof "&library_ptr" "unknown"]
     93 
     94 set ptr_offset "unknown"
     95 gdb_test_multiple "info proc mappings" "" {
     96     -re "^($hex)\\s+($hex)\\s+$hex\\s+($hex)\[^\r\n\]+$library_filename\\s*\r\n" {
     97 	set low_addr $expect_out(1,string)
     98 	set high_addr $expect_out(2,string)
     99 	set file_offset $expect_out(3,string)
    100 
    101 	if {[expr $ptr_address >= $low_addr] && [expr $ptr_address < $high_addr]} {
    102 	    set mapping_offset [expr $ptr_address - $low_addr]
    103 	    set ptr_offset [format 0x%x [expr $file_offset + $mapping_offset]]
    104 	}
    105 
    106 	exp_continue
    107     }
    108 
    109     -re "^$gdb_prompt $" {
    110     }
    111 
    112     -re "(^\[^\r\n\]*)\r\n" {
    113 	set tmp $expect_out(1,string)
    114 	exp_continue
    115     }
    116 }
    117 
    118 gdb_assert { $ptr_offset ne "unknown" } \
    119     "found pointer offset"
    120 
    121 set ptr_size [get_integer_valueof "sizeof (library_ptr)" "unknown"]
    122 set ptr_format_char ""
    123 if { $ptr_size == 2 } {
    124     set ptr_format_char "b"
    125 } elseif { $ptr_size == 4 } {
    126     set ptr_format_char "w"
    127 } elseif { $ptr_size == 8 } {
    128     set ptr_format_char "g"
    129 }
    130 if { $ptr_format_char eq "" } {
    131     untested "could not figure out size of library_ptr variable"
    132     return
    133 }
    134 
    135 # Helper proc to read a value from inferior memory.  Reads at address held in
    136 # global PTR_ADDRESS, and use PTR_FORMAT_CHAR for the size of the read.
    137 proc read_ptr_value { } {
    138     set value ""
    139     gdb_test_multiple "x/1${::ptr_format_char}x ${::ptr_address}" "" {
    140 	-re -wrap "^${::hex}(?:\\s+<\[^>\]+>)?:\\s+($::hex)" {
    141 	    set value $expect_out(1,string)
    142 	}
    143 	-re -wrap "^${::hex}(?:\\s+<\[^>\]+>)?:\\s+Cannot access memory at address ${::hex}" {
    144 	    set value "unavailable"
    145 	}
    146     }
    147     return $value
    148 }
    149 
    150 set ptr_expected_value [read_ptr_value]
    151 if { $ptr_expected_value eq "" } {
    152     untested "could not find expected value for library_ptr"
    153     return
    154 }
    155 
    156 # Now compile a second executable.  This one doesn't load the shared
    157 # library as an actual shared library, but instead mmaps the library
    158 # into the executable.
    159 #
    160 # Load this executable within GDB and confirm that we can use the
    161 # offset we calculated previously to view the value of 'library_ptr'.
    162 set opts [list debug additional_flags=-DSHLIB_FILENAME=\"$library_filename\"]
    163 if {[prepare_for_testing "prepare second executable" $binfile \
    164 	 $srcfile3 $opts] != 0} {
    165     return
    166 }
    167 
    168 if {![runto_main]} {
    169     return
    170 }
    171 
    172 gdb_breakpoint [gdb_get_line_number "Undefined behavior here" $srcfile3]
    173 gdb_continue_to_breakpoint "run to breakpoint"
    174 
    175 set library_base_address \
    176     [get_hexadecimal_valueof "library_base_address" "unknown"]
    177 set ptr_address [format 0x%x [expr $library_base_address + $ptr_offset]]
    178 
    179 set ptr_value [read_ptr_value]
    180 gdb_assert { $ptr_value == $ptr_expected_value } \
    181     "check value of pointer variable"
    182 
    183 # Now rerun the second executable outside of GDB.  The executable should crash
    184 # and generate a corefile.
    185 set corefile [core_find $binfile]
    186 if {$corefile eq ""} {
    187     untested "could not generate core file"
    188     return
    189 }
    190 
    191 # Load a core file from the global COREFILE.  Use TESTNAME as the name
    192 # of the test.
    193 #
    194 # If LINE_RE is not the empty string then this is a regexp for a line
    195 # that we expect to see in the output when loading the core file, if
    196 # the line is not present then this test will fail.
    197 #
    198 # Any lines beginning with 'warning: ' will cause this test to fail.
    199 #
    200 # A couple of other standard lines that are produced when loading a
    201 # core file are also checked for, just to make sure the core file
    202 # loading has progressed as expected.
    203 proc load_core_file { testname { line_re "" } } {
    204     set code {}
    205 
    206     if { $line_re ne "" } {
    207 	append code {
    208 	    -re "^$line_re\r\n" {
    209 		set saw_expected_line true
    210 		exp_continue
    211 	    }
    212 	}
    213 	set saw_expected_line false
    214     } else {
    215 	set saw_expected_line true
    216     }
    217 
    218     set saw_unknown_warning false
    219     set saw_generated_by_line false
    220     set saw_prog_terminated_line false
    221 
    222     append code {
    223 	-re "^warning: \[^\r\n\]+\r\n" {
    224 	    set saw_unknown_warning true
    225 	    exp_continue
    226 	}
    227 
    228 	-re "^Core was generated by \[^\r\n\]+\r\n" {
    229 	    set saw_generated_by_line true
    230 	    exp_continue
    231 	}
    232 
    233 	-re "^Program terminated with signal SIGSEGV, Segmentation fault\\.\r\n" {
    234 	    set saw_prog_terminated_line true
    235 	    exp_continue
    236 	}
    237 
    238 	-re "^$::gdb_prompt $" {
    239 	    gdb_assert {$saw_generated_by_line \
    240 			    && $saw_prog_terminated_line \
    241 			    && $saw_expected_line \
    242 			    && !$saw_unknown_warning} \
    243 		$gdb_test_name
    244 	}
    245 
    246 	-re "^\[^\r\n\]*\r\n" {
    247 	    exp_continue
    248 	}
    249     }
    250 
    251     set res [catch { return [gdb_test_multiple "core-file $::corefile" \
    252 				 "$testname" $code] } string]
    253 
    254     if {$res == 1} {
    255 	global errorInfo errorCode
    256 	return -code error -errorinfo $errorInfo -errorcode $errorCode $string
    257     } elseif {$res == 2} {
    258 	return $string
    259     } else {
    260 	# We expect RES to be 2 (TCL_RETURN) or 1 (TCL_ERROR).  If we get
    261 	# here then somehow the 'catch' above finished without hitting
    262 	# either of those cases, which is .... weird.
    263 	perror "unexepcted return value, code = $res, value = $string"
    264 	return -1
    265     }
    266 }
    267 
    268 # And now restart GDB, load the core-file and check that the library shows as
    269 # being mapped in, and that we can still read the library_ptr value from
    270 # memory.
    271 clean_restart $binfile
    272 
    273 load_core_file "load core file"
    274 
    275 set library_base_address [get_hexadecimal_valueof "library_base_address" \
    276 			      "unknown" "get library_base_address in core-file"]
    277 set ptr_address [format 0x%x [expr $library_base_address + $ptr_offset]]
    278 
    279 set ptr_value [read_ptr_value]
    280 gdb_assert { $ptr_value == $ptr_expected_value } \
    281     "check value of pointer variable from core-file"
    282 
    283 # Now move the shared library file away and restart GDB.  This time when we
    284 # load the core-file we should see a warning that GDB has failed to map in the
    285 # library file.  An attempt to read the variable from the library file should
    286 # fail / give a warning.
    287 set library_backup_filename [standard_output_file "libfoo.so.backup"]
    288 remote_exec build "mv \"$library_filename\" \"$library_backup_filename\""
    289 
    290 clean_restart $binfile
    291 
    292 load_core_file "load corefile with library file missing" \
    293     "warning: Can't open file [string_to_regexp $library_filename] during file-backed mapping note processing"
    294 
    295 set ptr_value [read_ptr_value]
    296 gdb_assert { $ptr_value eq "unavailable" } \
    297     "check value of pointer is unavailable with library file missing"
    298 
    299 # Now symlink the .build-id/xx/xxx...xxx filename within the debug
    300 # directory to library we just moved aside.  Restart GDB and setup the
    301 # debug-file-directory before loading the core file.
    302 #
    303 # GDB should lookup the file to map via the build-id link in the
    304 # .build-id/ directory.
    305 set debugdir [standard_output_file "debugdir"]
    306 set build_id_filename \
    307     $debugdir/[build_id_debug_filename_get $library_backup_filename ""]
    308 
    309 remote_exec build "mkdir -p [file dirname $build_id_filename]"
    310 remote_exec build "ln -sf $library_backup_filename $build_id_filename"
    311 
    312 clean_restart $binfile
    313 
    314 gdb_test_no_output "set debug-file-directory $debugdir" \
    315     "set debug-file-directory"
    316 
    317 load_core_file "load corefile, lookup in debug-file-directory"
    318 
    319 set ptr_value [read_ptr_value]
    320 gdb_assert { $ptr_value == $ptr_expected_value } \
    321     "check value of pointer variable from core-file, lookup in debug-file-directory"
    322 
    323 # Build a new version of the shared library, keep the library the same size,
    324 # but change the contents so the build-id changes.  Then restart GDB and load
    325 # the core-file again.  GDB should spot that the build-id for the shared
    326 # library is not as expected, and should refuse to map in the shared library.
    327 if {[build_executable "build second version of shared library" \
    328 	 $library_filename $srcfile2 \
    329 	 { debug shlib build-id \
    330 	       additional_flags=-DPOINTER_VALUE=0x11223344 }] != 0} {
    331     return
    332 }
    333 
    334 clean_restart $binfile
    335 
    336 load_core_file "load corefile with wrong library in place" \
    337     "warning: File [string_to_regexp $library_filename] doesn't match build-id from core-file during file-backed mapping processing"
    338 
    339 set ptr_value [read_ptr_value]
    340 gdb_assert { $ptr_value eq "unavailable" } \
    341     "check value of pointer is unavailable with wrong library in place"
    342 
    343 # Setup a debuginfod server which can serve the original shared library file.
    344 # Then restart GDB and load the core-file.  GDB should download the original
    345 # shared library from debuginfod and use that to provide the file backed
    346 # mapping.
    347 if {![allow_debuginfod_tests]} {
    348     untested "skippig debuginfod parts of this test"
    349     return
    350 }
    351 
    352 set server_dir [standard_output_file "debuginfod.server"]
    353 file mkdir $server_dir
    354 file rename -force $library_backup_filename $server_dir
    355 
    356 prepare_for_debuginfod cache db
    357 
    358 set url [start_debuginfod $db $server_dir]
    359 if { $url eq "" } {
    360     unresolved "failed to start debuginfod server"
    361     return
    362 }
    363 
    364 with_debuginfod_env $cache {
    365     setenv DEBUGINFOD_URLS $url
    366 
    367     clean_restart
    368     gdb_test_no_output "set debuginfod enabled on" \
    369 	"enabled debuginfod for initial test"
    370     gdb_load $binfile
    371 
    372     load_core_file "load corefile, download library from debuginfod" \
    373 	"Downloading\[^\r\n\]* file [string_to_regexp $library_filename]\\.\\.\\."
    374 
    375     set ptr_value [read_ptr_value]
    376     gdb_assert { $ptr_value == $ptr_expected_value } \
    377 	"check value of pointer variable after downloading library file"
    378 }
    379 
    380 stop_debuginfod
    381