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
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
|
# This testcase is part of GDB, the GNU debugger.
#
# Copyright 2021-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/>.
#
#
# Test shared libraries loaded into different namespaces with dlmopen().
#
# We test that GDB shows the correct number of instances of the libraries
# the test loaded while unloading them one-by-one.
require allow_dlmopen_tests
# Don't use 'dlmopen.c' as the source file name, glibc also has a file
# with that name. Within our tests, we set the source directory search
# path order to:
#
# (1) the test source directory,
# (2) the compilation directory, and then
# (3) the current working directory.
#
# Because (1) is first when we try to place a breakpoint on
# 'dlmopen.c', if the test source file has that name, then GDB will
# find both the test source file, and the source file from glibc.
#
# We could work around this by making (2) first in the source
# directory list, but that only works when the glibc source is
# installed. If it isn't then GDB will try the compilation directory,
# fail to find the source, then try the test source directory, get a
# hit, and so still confuse the two files.
#
# You might think the problem can be solved by specifying the absolute
# path to the source file. This doesn't work because the glibc file
# has its filename recorded as just "dlmopen.c", as such GDB has to
# figure out an absolute path to the file (if possible). The absolute
# path is figured out based on where GDB can find a matching file in
# the source directory list, and because of the confusion above, GDB
# will usually think the test 'dlmopen.c' and the glibc 'dlmopen.c'
# are actually the same file.
#
# The conclusion is that it is just easier to rename the test source
# file to avoid conflicts with glibc.
standard_testfile -main.c -lib.c -lib-dep.c
set basename_lib dlmopen-lib
set srcfile_lib $srcfile2
set binfile_lib1 [standard_output_file $basename_lib.1.so]
set binfile_lib2 [standard_output_file $basename_lib.2.so]
set srcfile_lib_dep $srcfile3
set binfile_lib_dep [standard_output_file $basename_lib-dep.so]
if { [build_executable "build shlib dep" $binfile_lib_dep $srcfile_lib_dep \
{debug shlib}] == -1 } {
return
}
if { [build_executable "build shlib" $binfile_lib1 $srcfile_lib \
[list debug shlib_load shlib libs=$binfile_lib_dep]] == -1 } {
return
}
if { [build_executable "build shlib" $binfile_lib2 $srcfile_lib \
[list debug shlib_load shlib libs=$binfile_lib_dep]] == -1 } {
return
}
if { [build_executable "failed to build" $testfile $srcfile \
[list additional_flags=-DDSO1_NAME=\"$binfile_lib1\" \
additional_flags=-DDSO2_NAME=\"$binfile_lib2\" \
shlib_load debug]] } {
return
}
# Some locations needed by the tests.
set bp_inc [gdb_get_line_number "bp.inc" $srcfile_lib]
set bp_main [gdb_get_line_number "bp.main" $srcfile]
# Figure out the file name for the dynamic linker.
set dyln_name [section_get $binfile .interp]
if { $dyln_name eq "" } {
unsupported "couldn't find dynamic linker name"
return
}
# Return true if FILENAME is the dynamic linker. Otherwise return false.
proc is_dyln { filename } {
return [expr {$filename eq $::dyln_name}]
}
# Check that 'info shared' show NUM occurrences of DSO.
proc check_dso_count { dso num } {
global gdb_prompt hex
set count 0
gdb_test_multiple "info shared" "info shared" {
-re "$hex $hex \(\[\[$::decimal\]\]\\s+\)\?Yes \[^\r\n\]*$dso\r\n" {
# use longer form so debug remote does not interfere
set count [expr $count + 1]
exp_continue
}
-re "$gdb_prompt " {
verbose -log "library: $dso, expected: $num, found: $count"
gdb_assert {$count == $num} "$gdb_test_name"
}
}
}
# The DSO part of the test. We run it once per DSO call.
proc test_dlmopen_one { ndso1 ndso2 exp_glob } {
global srcfile_lib srcfile_lib basename_lib bp_inc
# Try to reach the breakpoint in the dynamically loaded library.
gdb_continue_to_breakpoint "cont to bp.inc" \
".*$srcfile_lib:$bp_inc\r\n.*"
# We opened all DSOs initially and close them one by one.
with_test_prefix "dso 1" { check_dso_count $basename_lib.1.so $ndso1 }
with_test_prefix "dso 2" { check_dso_count $basename_lib.2.so $ndso2 }
# This might help debugging.
gdb_test "info breakpoints" ".*"
gdb_test "print \$pc" ".*"
# We expect different instances of GDB_DLMOPEN_GLOB per DSO.
gdb_test "print amount" "= $exp_glob"
gdb_test "print gdb_dlmopen_glob" "= $exp_glob"
# Modify that DSO's instance, which should leave the others intact.
gdb_test "print &gdb_dlmopen_glob" "= .*"
gdb_test "print gdb_dlmopen_glob = -1" "= -1"
}
# The actual test. We run it twice.
proc test_dlmopen {} {
global srcfile basename_lib bp_main
# Note that when loading dlmopen-lib.1.so and dlmopen-lib.2.so into
# the same namespace, dlmopen-lib-dep.so is loaded only once, so in
# this case, the changes to gdb_dlmopen_glob inside test_dlmopen_one
# will actually be visible.
#
# Hence, we supply the expected value of this variable as argument to
# test_dlmopen_one.
with_test_prefix "dlmopen 1" { test_dlmopen_one 3 1 1 }
with_test_prefix "dlmopen 2" { test_dlmopen_one 2 1 1 }
with_test_prefix "dlmopen 3" { test_dlmopen_one 1 1 1 }
with_test_prefix "dlmopen 4" { test_dlmopen_one 0 1 -1 }
with_test_prefix "main" {
# Try to reach the breakpoint in the dynamically loaded library.
gdb_continue_to_breakpoint "cont to bp.main" \
".*$srcfile:$bp_main\r\n.*"
# The library should not be listed.
with_test_prefix "dso 1" { check_dso_count $basename_lib.1.so 0 }
with_test_prefix "dso 2" { check_dso_count $basename_lib.2.so 0 }
}
}
# Setup for calling 'test_dlmopen', this is the version of the test
# that doesn't use 'attach'.
proc test_dlmopen_no_attach {} {
clean_restart $::binfile
if { ![runto_main] } {
return
}
# Remove the pause. We only need it for the attach test.
gdb_test "print wait_for_gdb = 0" "\\\$1 = 0"
# Break in the to-be-loaded library and at the end of main.
delete_breakpoints
gdb_breakpoint $::srcfile_lib:$::bp_inc allow-pending
gdb_breakpoint $::srcfile:$::bp_main
test_dlmopen
}
# Setup for calling 'test_dlmopen', this is the version of the test
# that does use 'attach'.
proc test_dlmopen_with_attach {} {
if { ![can_spawn_for_attach] } {
unsupported "cannot run attach tests"
return
}
clean_restart $::binfile
# Start the test program.
set test_spawn_id [spawn_wait_for_attach $::binfile]
set testpid [spawn_id_get_pid $test_spawn_id]
# Attach.
if { ![gdb_attach $testpid] } {
return
}
with_test_prefix "attach" {
# Remove the pause. We no longer need it.
gdb_test "print wait_for_gdb = 0" "\\\$1 = 0"
# Set the same breakpoints again. This time, however, we do not allow the
# breakpoint to be pending since the library has already been loaded.
gdb_breakpoint $::srcfile_lib:$::bp_inc
gdb_breakpoint $::srcfile:$::bp_main
test_dlmopen
}
}
# Run 'info sharedlibrary' and count the number of mappings that look
# like they might be the dynamic linker. This will only work for
# Linux right now.
proc get_dyld_info {} {
if { ![istarget *-linux*] } {
return [list 0 ""]
}
set dyld_count 0
set dyld_start_addr ""
gdb_test_multiple "info sharedlibrary" "" {
-re "From\\s+To\\s+\(NS\\s+\)?Syms\\s+Read\\s+Shared Object Library\r\n" {
exp_continue
}
-re "^($::hex)\\s+$::hex\\s+\(\#$::decimal\\s+\)?\[^/\]+(/\[^\r\n\]+)\r\n" {
set addr $expect_out(1,string)
set lib $expect_out(3,string)
if { [is_dyln $lib] } {
# This looks like it might be the dynamic linker.
incr dyld_count
if { $dyld_start_addr eq "" } {
set dyld_start_addr $addr
} elseif { $dyld_start_addr ne $addr } {
set dyld_start_addr "MULTIPLE"
}
}
exp_continue
}
-re "\\(\\*\\): Shared library is missing debugging information\\.\r\n" {
exp_continue
}
-re "^$::gdb_prompt $" {
}
}
if { $dyld_start_addr eq "MULTIPLE" } {
set dyld_start_addr ""
}
return [list $dyld_count $dyld_start_addr]
}
# The inferior for this test causes the dynamic linker to be appear
# multiple times in the inferior's shared library list, but (at least
# with glibc), the dynamic linker is really only mapped in once. That
# is, each of the dynamic linker instances that appear in the 'info
# sharedlibrary' output, will have the same address range.
#
# This test creates a user breakpoint in the dynamic linker, and then
# runs over the dlcose calls, which unmap all but one of the dynamic
# linker instances.
#
# The expectation is that the user breakpoint in the dynamic linker
# should still be active. Older versions of GDB had a bug where the
# breakpoint would become pending.
proc_with_prefix test_solib_unmap_events { } {
# This test relies on finding the dynamic linker library, and is
# currently written assuming Linux.
if { ![istarget *-linux*] } {
unsupport "cannot find dynamic linker library on this target"
return
}
clean_restart $::binfile
if { ![runto_main] } {
return
}
# Check that before any of our dlopen/dlmopen calls, we can find a
# single copy of the dynamic linker in the shared library list.
set dyld_info [get_dyld_info]
set dyld_count [lindex $dyld_info 0]
if { $dyld_count != 1 } {
unsupported "initial dyld state appears strange"
return
}
# Continue the inferior until all solib are loaded.
set alarm_lineno [gdb_get_line_number "alarm" $::srcfile]
gdb_breakpoint ${::srcfile}:${alarm_lineno}
gdb_continue_to_breakpoint "all solib are now loaded"
# Check that we have multiple copies of dynamic linker loaded, and
# that the dynamic linker is only loaded at a single address.
set dyld_info [get_dyld_info]
set dyld_count [lindex $dyld_info 0]
set dyld_start_addr [lindex $dyld_info 1]
# If we didn't find a suitable dynamic linker address, or we
# didn't find multiple copies of the dynamic linker, then
# something has gone wrong with the test setup.
if { $dyld_count < 2 } {
unsupported "multiple copies of the dynamic linker not found"
return
}
if { $dyld_start_addr eq "" } {
unsupported "unable to find suitable dynamic linker start address"
return
}
# Check the address we found is (likely) writable.
gdb_test_multiple "x/1i $dyld_start_addr" "check b/p address" {
-re -wrap "Cannot access memory at address \[^\r\n\]+" {
unsupported "dynamic linker address is not accessible"
return
}
-re -wrap "" {
}
}
# Create a breakpoint within the dynamic linker. It is pretty unlikely
# that this breakpoint will ever be hit, but just in case it is, make it
# conditional, with a condition that will never be true. All we really
# care about for this test is whether the breakpoint will be made
# pending or not (it should not).
gdb_test "break *$dyld_start_addr if (0)" \
"Breakpoint $::decimal at $::hex\[^\r\n\]+" \
"create breakpoint within dynamic linker"
set bpnum [get_integer_valueof "\$bpnum" INVALID "get bpnum"]
# Now continue until the 'bp.main' location, this will unload some
# copies, but not all copies, of the dynamic linker.
gdb_test "print wait_for_gdb = 0" " = 0"
set bp_main [gdb_get_line_number "bp.main" $::srcfile]
gdb_breakpoint $::srcfile:$bp_main
gdb_continue_to_breakpoint "stop at bp.main"
# At one point, GDB would incorrectly mark the breakpoints in the
# dynamic linker as pending when some instances of the library were
# unloaded, despite there really only being one copy of the dynamic
# linker actually loaded into the inferior's address space.
gdb_test_multiple "info breakpoints $bpnum" "check b/p status" {
-re -wrap "$bpnum\\s+breakpoint\\s+keep\\s+y\\s+<PENDING>\\s+\\*$::hex\\s*\r\n\\s+stop only if \\(0\\)" {
fail $gdb_test_name
}
-re -wrap "$bpnum\\s+breakpoint\\s+keep\\s+y\\s+$::hex\\s*\[^\r\n\]+\r\n\\s+stop only if \\(0\\)" {
pass $gdb_test_name
}
}
# With all the dlclose calls now complete, we should be back to a
# single copy of the dynamic linker.
set dyld_info [get_dyld_info]
set dyld_count [lindex $dyld_info 0]
gdb_assert { $dyld_count == 1 } \
"one dynamic linker found after dlclose calls"
}
# Check that we can 'next' over the dlclose calls without GDB giving any
# warnings or errors.
proc_with_prefix test_next_over_dlclose {} {
clean_restart $::binfile
if { ![runto_main] } {
return
}
set dlclose_lineno [gdb_get_line_number "dlclose" $::srcfile]
gdb_breakpoint $::srcfile:$dlclose_lineno
gdb_breakpoint $::srcfile:$::bp_main
# Remove the pause. We no longer need it.
gdb_test "print wait_for_gdb = 0" "\\\$1 = 0"
set loc_re [multi_line \
"\[^\r\n\]+/[string_to_regexp $::srcfile]:$dlclose_lineno" \
"$dlclose_lineno\\s+dlclose \[^\r\n\]+"]
# This loop mirrors the loop in dlmopen.c where the inferior performs
# four calls to dlclose. Here we continue to the dlclose, and then use
# 'next' to step over the call. As part of the 'next' GDB will insert
# breakpoints to catch longjmps into the multiple copies of libc which
# have been loaded. Each dlclose call will cause a copy of libc to be
# unloaded, which should disable the longjmp breakpoint that GDB
# previously inserted.
#
# At one point a bug in GDB meant that we failed to correctly disable
# the longjmp breakpoints and GDB would try to remove the breakpoint
# from a library after it had been unloaded, which would trigger a
# warning.
for { set i 0 } { $i < 4 } { incr i } {
gdb_continue_to_breakpoint "continue to dlclose $i" $loc_re
gdb_test "next" "^$::decimal\\s+for \\(dl = 0;\[^\r\n\]+\\)" \
"next over dlclose $i"
}
# Just to confirm that we are where we think we are, continue to the
# final 'return' line in main. If this isn't hit then we likely went
# wrong earlier.
gdb_continue_to_breakpoint "continue to final return" \
[multi_line \
"\[^\r\n\]+/[string_to_regexp $::srcfile]:$::bp_main" \
"$::bp_main\\s+return 0;\[^\r\n\]+"]
}
# Run the actual tests.
test_dlmopen_no_attach
test_dlmopen_with_attach
test_solib_unmap_events
test_next_over_dlclose
|