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