1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
|
# 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 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
|