GDB has had basic support for linkage namespaces for some time already, but only in the sense of managing multiple copies of the same shared object being loaded, and a very fragile way to find the correct copy of a symbol (see PR shlibs/32054). This commit is the first step in improving the user experience around multiple namespace support. It introduces a user-friendly identifier for namespaces, in the format #<number>, that will keep consistent between dlmopen and dlclose calls. The plan is for this identifier to be usable in expressions like `print #1::var` to find a specific instance of a symbol, and so the identifier must not be a valid C++ or Ada namespace identifier, otherwise disambiguation becomes a problem. Support for those expressions has not been implemented yet, it is only mentioned to explain why the identifier looks like this.
While doing some more development on the next patch for this series, it occurred to me that we also identify frames with #N, so this would be overlap of the meaning of #. This will eventually be somewhat confusing as we could possibly have the following:
(gdb) bt
#0 #1::roll () at random.c:6
#1 0x00000000004011b8 in #1::hit (chance=0, acc_boost=0,
evasion_boost=0) at random.c:9
#2 0x000000000040126c in #0::main () at random.c:20
My question is: is this confusing enough that we should figure out a different syntax? and if so, what other invalid character should I use for a namespace?
Also I have an unrelated note about my patch inlined.
<snipping for convenience>The namespace identifiers are stored via a vector inside svr4_info object. The vector stores the address of the r_debug objects used by glibc to identify each namespace, and the user-friendly ID is the index of the r_debug in the vector. This commit also introduces a set storing the indices of active namespaces. The glibc I used to develop this patch (glibc 2.40 on Fedora 41) doesn't allow an SO to be loaded into a deactivated namespace, and requesting a new namespace when a namespace was previously closed will reuse that namespace. Because of how this is implemented, this patch lets GDB easily track the exact namespace IDs that the inferior will see. Finally, two new solib_ops function pointers were added, find_solib_ns and num_active_namespaces, to allow code outside of solib-svr4 to find and use the namespace identifiers and the number of namespaces, respectively. As a sanity check, the command `info sharedlibrary` has been changed to display the namespace identifier when the inferior has more than one active namespace. With this final change, a couple of tests had to be tweaked to handle the possible new column, and a new test has been created to make sure that the column appears and disappears as needed, and that GDB can track the value of the LMID for namespaces. ---
diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
new file mode 100644
index 00000000000..890ee091be9
--- /dev/null
+++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
@@ -0,0 +1,108 @@
+# This testcase is part of GDB, the GNU debugger.
+#
+# Copyright 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 several things related to handling linker namespaces:
+# * That the user-facing namespace ID is consistent;
+
+require allow_dlmopen_tests
+
+standard_testfile -main.c -lib.c
+
+set srcfile_lib $srcfile2
+set binfile_lib [standard_output_file dlmopen-lib.so]
+
+if { [build_executable "build shlib" $binfile_lib $srcfile_lib \
+ [list debug shlib]] == -1 } {
+ return
+}
+
+if { [build_executable "failed to build" $testfile $srcfile \
+ [list additional_flags=-DDSO_NAME=\"$binfile_lib\" \
+ shlib_load debug]] } {
+ return
+}
+
+# Run the command "info sharedlibrary" and get the first namespace
+# for the so
+proc get_first_so_ns {} {
+ set ns -1
+ gdb_test_multiple "info sharedlibrary" "get SO namespace" -lbl {
+ -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]+$::binfile_lib.*" {
+ set ns $expect_out(1,string)
+ }
+ -re "^$::gdb_prompt $" {
+ }
+ -re "^\[^\r\n\]+\r\n" {
+ exp_continue
+ }
+ }
+ return $ns
+}
+
+# Run the tests relating to the command "info sharedlibrary", to
+# verify that the namespace ID is consistent.
+proc test_info_shared {} {
+ clean_restart $::binfile
+
+ if { ![runto_main] } {
+ return
+ }
+
+ # First test that we don't print a namespace column at the start.
+ gdb_test "info sharedlibrary" \
+ "From\\s+To\\s+Syms\\s+Read\\s+Shared Object Library.*" \
+ "before loading anything"
+
+ gdb_test_no_output "set wait_for_gdb = 0"
This is leftover from an earlier version where I thought about using attach, but decided against it since dlmopen already tests it.
I've removed this from my local branch
-- Cheers, Guinevere Larsen She/Her/Hers
+
+ gdb_breakpoint [gdb_get_line_number "TAG: first dlclose"]
+ gdb_continue_to_breakpoint "TAG: first dlclose"
+
+ # Next, test that we *do* print a namespace column after loading SOs.
+ gdb_test "info sharedlibrary" \
+ "From\\s+To\\s+NS\\s+Syms\\s+Read\\s+Shared Object Library.*" \
+ "after loading everything"
+
+ gdb_assert {[get_first_so_ns] == 1} "before closing any library"
+
+ gdb_test "next" ".*second dlclose.*" "close first library"
+ gdb_assert {[get_first_so_ns] == 2} "after closing one library"
+
+ gdb_test "next" ".*third dlclose.*" "close second library"
+ gdb_assert {[get_first_so_ns] == 3} "before closing two libraries"
+
+ gdb_breakpoint [gdb_get_line_number "TAG: fourth dlclose"]
+ gdb_continue_to_breakpoint "TAG: fourth dlclose"
+ # As of writing this test, glibc's LMID is just an index on an array of
+ # namespaces. After closing a namespace, requesting a new one will
+ # return the index of the lowest-closed namespace, so this will likely
+ # be namespace 1, and because of glibc's reuse of the r_debug object,
+ # GDB should be able to assign the same number.
+ gdb_assert {[get_first_so_ns] == [get_integer_valueof "lmid" "-1"]} \
+ "reopen a namespace"
+
+ gdb_test "next" ".*return 0.*" "final namespace inactive"
+ gdb_test "info sharedlibrary" \
+ "From\\s+To\\s+Syms\\s+Read\\s+Shared Object Library.*" \
+ "after unloading everything"
+}
+
+test_info_shared
diff --git a/gdb/testsuite/gdb.base/dlmopen.exp b/gdb/testsuite/gdb.base/dlmopen.exp
index a8e3b08c016..6f8a3bc9de4 100644
--- a/gdb/testsuite/gdb.base/dlmopen.exp
+++ b/gdb/testsuite/gdb.base/dlmopen.exp
@@ -106,7 +106,7 @@ proc check_dso_count { dso num } {
set count 0
gdb_test_multiple "info shared" "info shared" {
- -re "$hex $hex Yes \[^\r\n\]*$dso\r\n" {
+ -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
@@ -233,12 +233,12 @@ proc get_dyld_info {} {
set dyld_count 0
set dyld_start_addr ""
gdb_test_multiple "info sharedlibrary" "" {
- -re "From\\s+To\\s+Syms\\s+Read\\s+Shared Object Library\r\n" {
+ -re "From\\s+To\\s+\(NS\\s+\)?Syms\\s+Read\\s+Shared Object Library\r\n" {
exp_continue
}
- -re "^($::hex)\\s+$::hex\\s+\[^/\]+(/\[^\r\n\]+)\r\n" {
+ -re "^($::hex)\\s+$::hex\\s+\(\#$::decimal\\s+\)?\[^/\]+(/\[^\r\n\]+)\r\n" {
set addr $expect_out(1,string)
- set lib $expect_out(2,string)
+ set lib $expect_out(3,string)
if { [is_dyln $lib] } {
# This looks like it might be the dynamic linker.
diff --git a/gdb/testsuite/gdb.mi/mi-dlmopen.exp b/gdb/testsuite/gdb.mi/mi-dlmopen.exp
index a5743f83bb8..c0208ebcc51 100644
--- a/gdb/testsuite/gdb.mi/mi-dlmopen.exp
+++ b/gdb/testsuite/gdb.mi/mi-dlmopen.exp
@@ -81,12 +81,12 @@ proc get_dyld_info {} {
set dyld_count 0
set dyld_start_addr ""
gdb_test_multiple "info sharedlibrary" "" {
- -re "~\"From\\s+To\\s+Syms\\s+Read\\s+Shared Object Library\\\\n\"\r\n" {
+ -re "~\"From\\s+To(\\s+NS)?\\s+Syms\\s+Read\\s+Shared Object Library\\\\n\"\r\n" {
exp_continue
}
- -re "^~\"($::hex)\\s+$::hex\\s+\[^/\]+(/\[^\r\n\]+)\\\\n\"\r\n" {
+ -re "^~\"($::hex)\\s+${::hex}(\\s+$::decimal)?\\s+\[^/\]+(/\[^\r\n\]+)\\\\n\"\r\n" {
set addr $expect_out(1,string)
- set lib $expect_out(2,string)
+ set lib $expect_out(3,string)
if { [is_dyln $lib] } {
# This looks like it might be the dynamic linker.
base-commit: 82455bdab80f9a4d4f94a6e8590a1bdc58c4010e