* [PATCH v8] gdb: Print linker namespace when showing a frame
@ 2026-05-07 13:48 Guinevere Larsen
2026-06-22 20:34 ` [PING][PATCH " Guinevere Larsen
0 siblings, 1 reply; 4+ messages in thread
From: Guinevere Larsen @ 2026-05-07 13:48 UTC (permalink / raw)
To: gdb-patches; +Cc: Guinevere Larsen, Eli Zaretskii
When a user is stopped in a private linker namespace, the only way for
them to realize that is using the _linker_namespace convenience
variable. While serviceable, this is a sub-optimal solution, as most
users are unaware of convenience variables.
This commit introduces a new way for users to be informed of the linker
namespace of a function, by printing it along with the function name.
This is done by using the proposed syntax for symbols and locations,
like so:
#0 [[0]]::main ()
This is done by introducing a new function on solib.h,
linker_namespace_prefix, that returns either an empty string (if only
one namespace is active, or we can't determine the PC for the frame), or
the correct identifier. This should be printed manually every time GDB
will print a frame, so that we don't confuse other systems that look at
function names.
A previous revision would break GDB's ability to strip the parameters of
C++ symbols, so this commit also adds a C++ SO to the test. However,
libstdc++ never gets unloaded, so many changes were required to make the
rest of the test still valid.
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
---
gdb/NEWS | 4 +
gdb/doc/gdb.texinfo | 9 +++
gdb/solib.c | 43 ++++++++---
gdb/solib.h | 13 ++++
gdb/stack.c | 2 +
.../gdb.base/dlmopen-ns-ids-framefilter.py | 31 ++++++++
.../gdb.base/dlmopen-ns-ids-libc++.cc | 36 +++++++++
gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c | 19 ++++-
gdb/testsuite/gdb.base/dlmopen-ns-ids.exp | 74 ++++++++++++++++---
gdb/testsuite/gdb.mi/mi-dlmopen.exp | 2 +-
10 files changed, 206 insertions(+), 27 deletions(-)
create mode 100644 gdb/testsuite/gdb.base/dlmopen-ns-ids-framefilter.py
create mode 100644 gdb/testsuite/gdb.base/dlmopen-ns-ids-libc++.cc
diff --git a/gdb/NEWS b/gdb/NEWS
index 480e1854002..941178c06a4 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -78,6 +78,10 @@
* The Windows native target now supports non-stop mode. This feature
requires Windows 10 or later.
+* When debugging an inferior with multiple linker namespaces, functions
+ will be printed like so [[N]]::foo, where N is the namespace that
+ contains foo. For example [[0]]::main.
+
* New targets
GNU/Linux/MicroBlaze (gdbserver) microblazeel-*linux*
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index ab0216ff477..98a43f3a57e 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -22431,6 +22431,15 @@ possibility that those objects may cross-talk. Each set of isolated
shared objects is said to belong to a ``namespace'', and linker related
actions such as relocations do not cross namespace boundaries.
+When multiple linker namespaces are active, @value{GDBN} will print the
+identifier of the namespace that contains the function, when a function
+is printed. It will look like this:
+
+@smallexample
+#0 [[1]]::inc (n=0) at dlmopen-ns-ids-lib.c
+#1 0x0000000000400591 in [[0]]::main () at dlmopen-ns-ids-main.c
+@end smallexample
+
@kindex info dll
@item info dll @var{regex}
This is an alias of @code{info sharedlibrary}.
diff --git a/gdb/solib.c b/gdb/solib.c
index 782b844aa2f..add842b5771 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -1796,6 +1796,36 @@ solib_linker_namespace_count (program_space *pspace)
return 0;
}
+/* See solib.h. */
+
+int
+linker_namespace_for_addr (CORE_ADDR addr, program_space *pspace)
+{
+ for (const solib &so : pspace->solibs ())
+ if (solib_contains_address_p (so, addr))
+ {
+ if (so.ops ().supports_namespaces ())
+ return so.ops ().find_solib_ns (so);
+ break;
+ }
+
+ return 0;
+}
+
+/* See solib.h. */
+
+std::string
+linker_namespace_prefix (const frame_info_ptr &frame)
+{
+ std::optional<CORE_ADDR> pc = get_frame_pc_if_available (frame);
+ program_space *pspace = get_frame_program_space (frame);
+ if (!pc.has_value () || solib_linker_namespace_count (pspace) <= 1)
+ return std::string ("");
+
+ return string_printf ("[[%d]]::", linker_namespace_for_addr (pc.value (),
+ pspace));
+}
+
/* Implementation of the linker_namespace convenience variable.
This returns the GDB internal identifier of the linker namespace,
@@ -1804,19 +1834,10 @@ solib_linker_namespace_count (program_space *pspace)
static value *
linker_namespace_make_value (gdbarch *gdbarch, internalvar *var,
- void *ignore)
+ void *ignore)
{
- int nsid = 0;
CORE_ADDR curr_pc = get_frame_pc (get_selected_frame ());
-
- for (const solib &so : current_program_space->solibs ())
- if (solib_contains_address_p (so, curr_pc))
- {
- if (so.ops ().supports_namespaces ())
- nsid = so.ops ().find_solib_ns (so);
-
- break;
- }
+ int nsid = linker_namespace_for_addr (curr_pc, current_program_space);
/* If the PC is not in an SO, or the solib_ops doesn't support
linker namespaces, the inferior is in the default namespace. */
diff --git a/gdb/solib.h b/gdb/solib.h
index 564655c4a57..39931bb1b93 100644
--- a/gdb/solib.h
+++ b/gdb/solib.h
@@ -334,6 +334,19 @@ extern const char *solib_name_from_address (struct program_space *, CORE_ADDR);
extern bool solib_contains_address_p (const solib &, CORE_ADDR);
+/* Given the address ADDR, return which linker namespace contains
+ this address in PSPACE. If ADDR is present in multiple namespaces,
+ an arbitrary one is returned. If ADDR is not present in any
+ namespace, or if the target doesn't support linker namespaces,
+ returns 0. */
+
+extern int linker_namespace_for_addr (CORE_ADDR addr, program_space *pspace);
+
+/* Calculate the linker namespace identifier, and turn it into a prefix
+ to be used to print a frame. */
+
+extern std::string linker_namespace_prefix (const frame_info_ptr &frame);
+
/* Return whether the data starting at VADDR, size SIZE, must be kept
in a core file for shared libraries loaded before "gcore" is used
to be handled correctly when the core file is loaded. This only
diff --git a/gdb/stack.c b/gdb/stack.c
index 7329430adab..ac270718a91 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -1306,6 +1306,7 @@ print_frame (struct ui_out *uiout,
annotate_frame_function_name ();
string_file stb;
+ gdb_puts (linker_namespace_prefix (frame).c_str (), &stb);
gdb_puts (funname ? funname.get () : "??", &stb);
uiout->field_stream ("func", stb, function_name_style.style ());
uiout->wrap_hint (3);
@@ -1466,6 +1467,7 @@ info_frame_command_core (const frame_info_ptr &fi, bool selected_frame_p)
fputs_styled ("<unavailable>", metadata_style.style (), gdb_stdout);
gdb_stdout->wrap_here (3);
+ gdb_puts (linker_namespace_prefix (fi).c_str ());
if (funname.get () != nullptr)
{
gdb_puts (" in ");
diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids-framefilter.py b/gdb/testsuite/gdb.base/dlmopen-ns-ids-framefilter.py
new file mode 100644
index 00000000000..11d96fcfbc4
--- /dev/null
+++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids-framefilter.py
@@ -0,0 +1,31 @@
+# Copyright (C) 2026 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 file is part of the GDB testsuite. It tests if printing the
+# linker namespace id is affected by a null frame filter
+
+import gdb
+
+
+class NullFilter:
+ def __init__(self):
+ self.name = "null filter"
+ self.priority = 100
+ self.enabled = True
+ gdb.frame_filters[self.name] = self
+
+ def filter(self, iterator):
+ return iterator
diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids-libc++.cc b/gdb/testsuite/gdb.base/dlmopen-ns-ids-libc++.cc
new file mode 100644
index 00000000000..9abf3f25096
--- /dev/null
+++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids-libc++.cc
@@ -0,0 +1,36 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2025-2026 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/>.
+
+*/
+
+int gdb_dlmopen_glob = 0;
+
+int
+cpp_inc (int n)
+{
+ int amount = gdb_dlmopen_glob;
+ return n + amount; /* bp.inc. */
+}
+
+extern "C" {
+ __attribute__((visibility ("default")))
+ int
+ inc (int n)
+ {
+ return cpp_inc (n);
+ }
+}
diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c b/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c
index 4f08a5135b2..f13eb9168de 100644
--- a/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c
+++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c
@@ -27,7 +27,7 @@
int
main (void)
{
- void *handle[4];
+ void *handle[5];
int (*fun) (int);
Lmid_t lmid;
int dl;
@@ -41,20 +41,31 @@ main (void)
handle[2] = dlmopen (LM_ID_NEWLM, DSO_NAME, RTLD_LAZY | RTLD_LOCAL);
assert (handle[2] != NULL);
+ /* We open the C++ SO on the main namespace because libstdc++ marks a
+ lot of objects as GNU_UNIQUE meaning that they effectively can't be
+ unloaded, so the final namespace would never be fully unloaded. */
+ handle[3] = dlmopen (LM_ID_BASE, CPP_DSO_NAME, RTLD_LAZY | RTLD_LOCAL);
+ assert (handle[3] != NULL);
+
for (dl = 2; dl >= 0; dl--)
{
fun = dlsym (handle[dl], "inc");
fun (dl);
}
+ /* C++ call, to make sure the linker namespace doesn't break anything
+ of the C++ handling of symbols. */
+ fun = dlsym (handle[3], "inc");
+ fun (-1);
dlclose (handle[0]); /* TAG: first dlclose */
dlclose (handle[1]); /* TAG: second dlclose */
dlclose (handle[2]); /* TAG: third dlclose */
+ dlclose (handle[3]); /* TAG: fourth dlclose */
- handle[3] = dlmopen (LM_ID_NEWLM, DSO_NAME, RTLD_LAZY | RTLD_LOCAL);
- dlinfo (handle[3], RTLD_DI_LMID, &lmid);
+ handle[4] = dlmopen (LM_ID_NEWLM, DSO_NAME, RTLD_LAZY | RTLD_LOCAL);
+ dlinfo (handle[4], RTLD_DI_LMID, &lmid);
- dlclose (handle[3]); /* TAG: fourth dlclose */
+ dlclose (handle[4]); /* TAG: final dlclose */
return 0;
}
diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
index e40bc213454..cc1696f4229 100644
--- a/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
+++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
@@ -21,20 +21,29 @@
require allow_dlmopen_tests
-standard_testfile -main.c -lib.c
+standard_testfile -main.c -lib.c -framefilter.py -libc++.cc
set srcfile_lib $srcfile2
+set srcfile_libcpp $srcfile4
set so_name dlmopen-lib.so
+set cpp_so_name dlmopen-libc++.so
set binfile_lib [standard_output_file $so_name]
+set binfile_libcpp [standard_output_file $cpp_so_name]
if { [build_executable "build shlib" $binfile_lib $srcfile_lib \
[list debug shlib]] == -1 } {
return
}
+if { [build_executable "build shlib" $binfile_libcpp $srcfile_libcpp \
+ [list debug shlib c++]] == -1 } {
+ return
+}
+
if { [build_executable "failed to build" $testfile $srcfile \
[list additional_flags=-DDSO_NAME=\"$binfile_lib\" \
- shlib_load debug]] } {
+ additional_flags=-DCPP_DSO_NAME=\"$binfile_libcpp\" \
+ shlib_load debug]] } {
return
}
@@ -107,11 +116,13 @@ proc check_no_duplicates {} {
proc get_first_so_ns {} {
set ns -1
set lib_regexp [string_to_regexp ${::binfile_lib}]
+ set libcpp_regexp [string_to_regexp ${::binfile_libcpp}]
+ set both_regexp "($lib_regexp|$libcpp_regexp)"
gdb_test_multiple "info sharedlibrary $::so_name" "get SO namespace" -lbl {
-re "\r\nFrom\\s+To\\s+\(Linker NS\\s+\)?Syms\\s+Read\\s+Shared Object Library(?=\r\n)" {
exp_continue
}
- -re "\r\n$::hex\\s+$::hex\\s+($::decimal)\\s+\[^\r\n]+${lib_regexp}(?=\r\n)" {
+ -re "\r\n$::hex\\s+$::hex\\s+($::decimal)\\s+\[^\r\n]+${both_regexp}(?=\r\n)" {
if {$ns == -1} {
set ns $expect_out(1,string)
}
@@ -155,8 +166,8 @@ proc test_info_shared {} {
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"
+ gdb_breakpoint [gdb_get_line_number "TAG: final dlclose"]
+ gdb_continue_to_breakpoint "TAG: final 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
@@ -192,6 +203,11 @@ proc_with_prefix test_conv_vars {} {
gdb_test "print \$_linker_namespace" ".* = 0" \
"Still in the default namespace"
+ # There should be no namespace ID visible, since there's
+ # only one namespace loaded.
+ gdb_test "backtrace" "\#0\\s+main .*" \
+ "No namespace ID in backtrace"
+
gdb_breakpoint "inc" allow-pending
gdb_breakpoint [gdb_get_line_number "TAG: first dlclose"]
@@ -200,14 +216,41 @@ proc_with_prefix test_conv_vars {} {
gdb_test "print \$_linker_namespace" ".* = $dl" \
"Verify we're in namespace $dl"
+
+ gdb_test "frame" "\#0\\s+\\\[\\\[$dl\\\]\\\]::inc.*" \
+ "Namespace ID in the frame"
}
# Check that we display the namespace of the selected
# frame, not the lowermost one.
- gdb_test "up" "\#1.*in main.*"
+ gdb_test "up" "\#1.*in \\\[\\\[0\\\]\\\]::main.*"
gdb_test "print \$_linker_namespace" ".* = 0" \
"print namespace of selected frame"
+ gdb_test "backtrace" [multi_line \
+ "\#0\\s+\\\[\\\[1\\\]\\\]::inc \[^\r\n\]*" \
+ "\#1\\s+$::hex in \\\[\\\[0\\\]\\\]::main \[^\r\n\]*" ] \
+ "raw backtrace"
+
+ set remote_python_file [gdb_remote_download host \
+ $::srcdir/$::subdir/$::srcfile3]
+ gdb_test_no_output "source $remote_python_file" "load frame filter"
+ gdb_test "backtrace" [multi_line \
+ "\#0\\s+\\\[\\\[1\\\]\\\]::inc \[^\r\n\]*" \
+ "\#1\\s+$::hex in \\\[\\\[0\\\]\\\]::main \[^\r\n\]*" ] \
+ "With null frame filter"
+
+ # Ensure that the code for identifying and removing parameters for
+ # C++ symbols still works with linker namespace IDs.
+ gdb_continue_to_breakpoint "inc on c++ SO"
+ gdb_test "step" "\\\[\\\[0\\\]\\\]::cpp_inc \\\(n=-1\\\).*" "step into c++ symbol"
+ gdb_test "backtrace" [multi_line \
+ "\#0\\s+\\\[\\\[0\\\]\\\]::cpp_inc \\\(n=-1\\\)\[^\r\n\]*" \
+ "\#1\\s+$::hex in \\\[\\\[0\\\]\\\]::inc \\\(n=-1\\\)\[^\r\n\]*" \
+ "\#2\\s+$::hex in \\\[\\\[0\\\]\\\]::main \[^\r\n\]*" ] \
+ "bt through c++ symbols"
+
+ # Check that everything still works as we close namespaces.
gdb_continue_to_breakpoint "first dlclose"
gdb_test "print \$_linker_namespace_count" "4" "all SOs loaded"
@@ -264,15 +307,23 @@ proc test_info_linker_namespaces {} {
# Some systems may add libc and libm to every loaded namespace,
# others may load only one or neither, because the SO doesn't
- # actually use either library. The best we can do is check if
- # we found the dynamic linker, and up to 2 more libraries.
- gdb_assert {$n_libraries <= 3} "the correct number of libraries was reported"
+ # actually use either library. Similarly, because of the c++
+ # SO, libstdc++ and libgcc_s may be included. The best we can
+ # do is check if we found the dynamic linker, the SO, and up to
+ # 4 more libraries. Ensuring that only the right one shows up is
+ # in the foreach block.
+ gdb_assert {$n_libraries <= 6} "the correct number of libraries was reported"
set binfile_lib_re [string_to_regexp $::binfile_lib]
- foreach_with_prefix ns {1 2 3} {
+ foreach_with_prefix ns {1 2 3 0} {
set found_test_so false
set n_libraries 999
+ set expected_libs 4
+ if {$ns == 0} {
+ set expected_libs 6
+ set binfile_lib_re [string_to_regexp $::binfile_libcpp]
+ }
gdb_test_multiple "info linker-namespaces $ns" "print namespace $ns" {
-re ".*($::decimal) librar(?:y|ies) loaded in linker namespace $ns:\r\n" {
@@ -290,7 +341,8 @@ proc test_info_linker_namespaces {} {
# actually use either library. The best we can do is check if
# we found the dynamic linker, the test SO, and maybe up to 2
# more libraries.
- gdb_assert {$n_libraries <= 4} "the correct number of libraries was reported"
+ gdb_assert {$n_libraries <= $expected_libs} \
+ "the correct number of libraries was reported"
gdb_assert {$found_test_so} "this testfile's SO was reported"
}
diff --git a/gdb/testsuite/gdb.mi/mi-dlmopen.exp b/gdb/testsuite/gdb.mi/mi-dlmopen.exp
index ff854ac7dd4..832c6ea6fe6 100644
--- a/gdb/testsuite/gdb.mi/mi-dlmopen.exp
+++ b/gdb/testsuite/gdb.mi/mi-dlmopen.exp
@@ -156,7 +156,7 @@ proc check_solib_unload_events {} {
-disp keep -func main -file ".*$::srcfile" -line $::bp_main
# Run past all the dlopen and dlmopen calls.
- mi_execute_to "exec-continue" "breakpoint-hit" main "" ".*" $::bp_loaded \
+ mi_execute_to "exec-continue" "breakpoint-hit" {\[\[0\]\]::main} "" ".*" $::bp_loaded \
{"" "disp=\"keep\""} "continue until all libraries are loaded"
# Check that the dynamic linker has now been loaded multiple times.
base-commit: 9e6e0c3cd9e6d5ee4953f72a805cff5f150aa2fd
--
2.54.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PING][PATCH v8] gdb: Print linker namespace when showing a frame
2026-05-07 13:48 [PATCH v8] gdb: Print linker namespace when showing a frame Guinevere Larsen
@ 2026-06-22 20:34 ` Guinevere Larsen
2026-06-23 11:57 ` Eli Zaretskii
0 siblings, 1 reply; 4+ messages in thread
From: Guinevere Larsen @ 2026-06-22 20:34 UTC (permalink / raw)
To: gdb-patches, Guinevere Larsen; +Cc: Eli Zaretskii
Ping :)
On 5/7/26 10:48 AM, Guinevere Larsen wrote:
> When a user is stopped in a private linker namespace, the only way for
> them to realize that is using the _linker_namespace convenience
> variable. While serviceable, this is a sub-optimal solution, as most
> users are unaware of convenience variables.
>
> This commit introduces a new way for users to be informed of the linker
> namespace of a function, by printing it along with the function name.
> This is done by using the proposed syntax for symbols and locations,
> like so:
>
> #0 [[0]]::main ()
>
> This is done by introducing a new function on solib.h,
> linker_namespace_prefix, that returns either an empty string (if only
> one namespace is active, or we can't determine the PC for the frame), or
> the correct identifier. This should be printed manually every time GDB
> will print a frame, so that we don't confuse other systems that look at
> function names.
>
> A previous revision would break GDB's ability to strip the parameters of
> C++ symbols, so this commit also adds a C++ SO to the test. However,
> libstdc++ never gets unloaded, so many changes were required to make the
> rest of the test still valid.
>
> Reviewed-By: Eli Zaretskii <eliz@gnu.org>
> ---
> gdb/NEWS | 4 +
> gdb/doc/gdb.texinfo | 9 +++
> gdb/solib.c | 43 ++++++++---
> gdb/solib.h | 13 ++++
> gdb/stack.c | 2 +
> .../gdb.base/dlmopen-ns-ids-framefilter.py | 31 ++++++++
> .../gdb.base/dlmopen-ns-ids-libc++.cc | 36 +++++++++
> gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c | 19 ++++-
> gdb/testsuite/gdb.base/dlmopen-ns-ids.exp | 74 ++++++++++++++++---
> gdb/testsuite/gdb.mi/mi-dlmopen.exp | 2 +-
> 10 files changed, 206 insertions(+), 27 deletions(-)
> create mode 100644 gdb/testsuite/gdb.base/dlmopen-ns-ids-framefilter.py
> create mode 100644 gdb/testsuite/gdb.base/dlmopen-ns-ids-libc++.cc
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 480e1854002..941178c06a4 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -78,6 +78,10 @@
> * The Windows native target now supports non-stop mode. This feature
> requires Windows 10 or later.
>
> +* When debugging an inferior with multiple linker namespaces, functions
> + will be printed like so [[N]]::foo, where N is the namespace that
> + contains foo. For example [[0]]::main.
> +
> * New targets
>
> GNU/Linux/MicroBlaze (gdbserver) microblazeel-*linux*
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index ab0216ff477..98a43f3a57e 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -22431,6 +22431,15 @@ possibility that those objects may cross-talk. Each set of isolated
> shared objects is said to belong to a ``namespace'', and linker related
> actions such as relocations do not cross namespace boundaries.
>
> +When multiple linker namespaces are active, @value{GDBN} will print the
> +identifier of the namespace that contains the function, when a function
> +is printed. It will look like this:
> +
> +@smallexample
> +#0 [[1]]::inc (n=0) at dlmopen-ns-ids-lib.c
> +#1 0x0000000000400591 in [[0]]::main () at dlmopen-ns-ids-main.c
> +@end smallexample
> +
> @kindex info dll
> @item info dll @var{regex}
> This is an alias of @code{info sharedlibrary}.
> diff --git a/gdb/solib.c b/gdb/solib.c
> index 782b844aa2f..add842b5771 100644
> --- a/gdb/solib.c
> +++ b/gdb/solib.c
> @@ -1796,6 +1796,36 @@ solib_linker_namespace_count (program_space *pspace)
> return 0;
> }
>
> +/* See solib.h. */
> +
> +int
> +linker_namespace_for_addr (CORE_ADDR addr, program_space *pspace)
> +{
> + for (const solib &so : pspace->solibs ())
> + if (solib_contains_address_p (so, addr))
> + {
> + if (so.ops ().supports_namespaces ())
> + return so.ops ().find_solib_ns (so);
> + break;
> + }
> +
> + return 0;
> +}
> +
> +/* See solib.h. */
> +
> +std::string
> +linker_namespace_prefix (const frame_info_ptr &frame)
> +{
> + std::optional<CORE_ADDR> pc = get_frame_pc_if_available (frame);
> + program_space *pspace = get_frame_program_space (frame);
> + if (!pc.has_value () || solib_linker_namespace_count (pspace) <= 1)
> + return std::string ("");
> +
> + return string_printf ("[[%d]]::", linker_namespace_for_addr (pc.value (),
> + pspace));
> +}
> +
> /* Implementation of the linker_namespace convenience variable.
>
> This returns the GDB internal identifier of the linker namespace,
> @@ -1804,19 +1834,10 @@ solib_linker_namespace_count (program_space *pspace)
>
> static value *
> linker_namespace_make_value (gdbarch *gdbarch, internalvar *var,
> - void *ignore)
> + void *ignore)
> {
> - int nsid = 0;
> CORE_ADDR curr_pc = get_frame_pc (get_selected_frame ());
> -
> - for (const solib &so : current_program_space->solibs ())
> - if (solib_contains_address_p (so, curr_pc))
> - {
> - if (so.ops ().supports_namespaces ())
> - nsid = so.ops ().find_solib_ns (so);
> -
> - break;
> - }
> + int nsid = linker_namespace_for_addr (curr_pc, current_program_space);
>
> /* If the PC is not in an SO, or the solib_ops doesn't support
> linker namespaces, the inferior is in the default namespace. */
> diff --git a/gdb/solib.h b/gdb/solib.h
> index 564655c4a57..39931bb1b93 100644
> --- a/gdb/solib.h
> +++ b/gdb/solib.h
> @@ -334,6 +334,19 @@ extern const char *solib_name_from_address (struct program_space *, CORE_ADDR);
>
> extern bool solib_contains_address_p (const solib &, CORE_ADDR);
>
> +/* Given the address ADDR, return which linker namespace contains
> + this address in PSPACE. If ADDR is present in multiple namespaces,
> + an arbitrary one is returned. If ADDR is not present in any
> + namespace, or if the target doesn't support linker namespaces,
> + returns 0. */
> +
> +extern int linker_namespace_for_addr (CORE_ADDR addr, program_space *pspace);
> +
> +/* Calculate the linker namespace identifier, and turn it into a prefix
> + to be used to print a frame. */
> +
> +extern std::string linker_namespace_prefix (const frame_info_ptr &frame);
> +
> /* Return whether the data starting at VADDR, size SIZE, must be kept
> in a core file for shared libraries loaded before "gcore" is used
> to be handled correctly when the core file is loaded. This only
> diff --git a/gdb/stack.c b/gdb/stack.c
> index 7329430adab..ac270718a91 100644
> --- a/gdb/stack.c
> +++ b/gdb/stack.c
> @@ -1306,6 +1306,7 @@ print_frame (struct ui_out *uiout,
> annotate_frame_function_name ();
>
> string_file stb;
> + gdb_puts (linker_namespace_prefix (frame).c_str (), &stb);
> gdb_puts (funname ? funname.get () : "??", &stb);
> uiout->field_stream ("func", stb, function_name_style.style ());
> uiout->wrap_hint (3);
> @@ -1466,6 +1467,7 @@ info_frame_command_core (const frame_info_ptr &fi, bool selected_frame_p)
> fputs_styled ("<unavailable>", metadata_style.style (), gdb_stdout);
>
> gdb_stdout->wrap_here (3);
> + gdb_puts (linker_namespace_prefix (fi).c_str ());
> if (funname.get () != nullptr)
> {
> gdb_puts (" in ");
> diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids-framefilter.py b/gdb/testsuite/gdb.base/dlmopen-ns-ids-framefilter.py
> new file mode 100644
> index 00000000000..11d96fcfbc4
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids-framefilter.py
> @@ -0,0 +1,31 @@
> +# Copyright (C) 2026 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 file is part of the GDB testsuite. It tests if printing the
> +# linker namespace id is affected by a null frame filter
> +
> +import gdb
> +
> +
> +class NullFilter:
> + def __init__(self):
> + self.name = "null filter"
> + self.priority = 100
> + self.enabled = True
> + gdb.frame_filters[self.name] = self
> +
> + def filter(self, iterator):
> + return iterator
> diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids-libc++.cc b/gdb/testsuite/gdb.base/dlmopen-ns-ids-libc++.cc
> new file mode 100644
> index 00000000000..9abf3f25096
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids-libc++.cc
> @@ -0,0 +1,36 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> + Copyright 2025-2026 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/>.
> +
> +*/
> +
> +int gdb_dlmopen_glob = 0;
> +
> +int
> +cpp_inc (int n)
> +{
> + int amount = gdb_dlmopen_glob;
> + return n + amount; /* bp.inc. */
> +}
> +
> +extern "C" {
> + __attribute__((visibility ("default")))
> + int
> + inc (int n)
> + {
> + return cpp_inc (n);
> + }
> +}
> diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c b/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c
> index 4f08a5135b2..f13eb9168de 100644
> --- a/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c
> +++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c
> @@ -27,7 +27,7 @@
> int
> main (void)
> {
> - void *handle[4];
> + void *handle[5];
> int (*fun) (int);
> Lmid_t lmid;
> int dl;
> @@ -41,20 +41,31 @@ main (void)
> handle[2] = dlmopen (LM_ID_NEWLM, DSO_NAME, RTLD_LAZY | RTLD_LOCAL);
> assert (handle[2] != NULL);
>
> + /* We open the C++ SO on the main namespace because libstdc++ marks a
> + lot of objects as GNU_UNIQUE meaning that they effectively can't be
> + unloaded, so the final namespace would never be fully unloaded. */
> + handle[3] = dlmopen (LM_ID_BASE, CPP_DSO_NAME, RTLD_LAZY | RTLD_LOCAL);
> + assert (handle[3] != NULL);
> +
> for (dl = 2; dl >= 0; dl--)
> {
> fun = dlsym (handle[dl], "inc");
> fun (dl);
> }
> + /* C++ call, to make sure the linker namespace doesn't break anything
> + of the C++ handling of symbols. */
> + fun = dlsym (handle[3], "inc");
> + fun (-1);
>
> dlclose (handle[0]); /* TAG: first dlclose */
> dlclose (handle[1]); /* TAG: second dlclose */
> dlclose (handle[2]); /* TAG: third dlclose */
> + dlclose (handle[3]); /* TAG: fourth dlclose */
>
> - handle[3] = dlmopen (LM_ID_NEWLM, DSO_NAME, RTLD_LAZY | RTLD_LOCAL);
> - dlinfo (handle[3], RTLD_DI_LMID, &lmid);
> + handle[4] = dlmopen (LM_ID_NEWLM, DSO_NAME, RTLD_LAZY | RTLD_LOCAL);
> + dlinfo (handle[4], RTLD_DI_LMID, &lmid);
>
> - dlclose (handle[3]); /* TAG: fourth dlclose */
> + dlclose (handle[4]); /* TAG: final dlclose */
>
> return 0;
> }
> diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
> index e40bc213454..cc1696f4229 100644
> --- a/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
> +++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
> @@ -21,20 +21,29 @@
>
> require allow_dlmopen_tests
>
> -standard_testfile -main.c -lib.c
> +standard_testfile -main.c -lib.c -framefilter.py -libc++.cc
>
> set srcfile_lib $srcfile2
> +set srcfile_libcpp $srcfile4
> set so_name dlmopen-lib.so
> +set cpp_so_name dlmopen-libc++.so
> set binfile_lib [standard_output_file $so_name]
> +set binfile_libcpp [standard_output_file $cpp_so_name]
>
> if { [build_executable "build shlib" $binfile_lib $srcfile_lib \
> [list debug shlib]] == -1 } {
> return
> }
>
> +if { [build_executable "build shlib" $binfile_libcpp $srcfile_libcpp \
> + [list debug shlib c++]] == -1 } {
> + return
> +}
> +
> if { [build_executable "failed to build" $testfile $srcfile \
> [list additional_flags=-DDSO_NAME=\"$binfile_lib\" \
> - shlib_load debug]] } {
> + additional_flags=-DCPP_DSO_NAME=\"$binfile_libcpp\" \
> + shlib_load debug]] } {
> return
> }
>
> @@ -107,11 +116,13 @@ proc check_no_duplicates {} {
> proc get_first_so_ns {} {
> set ns -1
> set lib_regexp [string_to_regexp ${::binfile_lib}]
> + set libcpp_regexp [string_to_regexp ${::binfile_libcpp}]
> + set both_regexp "($lib_regexp|$libcpp_regexp)"
> gdb_test_multiple "info sharedlibrary $::so_name" "get SO namespace" -lbl {
> -re "\r\nFrom\\s+To\\s+\(Linker NS\\s+\)?Syms\\s+Read\\s+Shared Object Library(?=\r\n)" {
> exp_continue
> }
> - -re "\r\n$::hex\\s+$::hex\\s+($::decimal)\\s+\[^\r\n]+${lib_regexp}(?=\r\n)" {
> + -re "\r\n$::hex\\s+$::hex\\s+($::decimal)\\s+\[^\r\n]+${both_regexp}(?=\r\n)" {
> if {$ns == -1} {
> set ns $expect_out(1,string)
> }
> @@ -155,8 +166,8 @@ proc test_info_shared {} {
> 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"
> + gdb_breakpoint [gdb_get_line_number "TAG: final dlclose"]
> + gdb_continue_to_breakpoint "TAG: final 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
> @@ -192,6 +203,11 @@ proc_with_prefix test_conv_vars {} {
> gdb_test "print \$_linker_namespace" ".* = 0" \
> "Still in the default namespace"
>
> + # There should be no namespace ID visible, since there's
> + # only one namespace loaded.
> + gdb_test "backtrace" "\#0\\s+main .*" \
> + "No namespace ID in backtrace"
> +
> gdb_breakpoint "inc" allow-pending
> gdb_breakpoint [gdb_get_line_number "TAG: first dlclose"]
>
> @@ -200,14 +216,41 @@ proc_with_prefix test_conv_vars {} {
>
> gdb_test "print \$_linker_namespace" ".* = $dl" \
> "Verify we're in namespace $dl"
> +
> + gdb_test "frame" "\#0\\s+\\\[\\\[$dl\\\]\\\]::inc.*" \
> + "Namespace ID in the frame"
> }
>
> # Check that we display the namespace of the selected
> # frame, not the lowermost one.
> - gdb_test "up" "\#1.*in main.*"
> + gdb_test "up" "\#1.*in \\\[\\\[0\\\]\\\]::main.*"
> gdb_test "print \$_linker_namespace" ".* = 0" \
> "print namespace of selected frame"
>
> + gdb_test "backtrace" [multi_line \
> + "\#0\\s+\\\[\\\[1\\\]\\\]::inc \[^\r\n\]*" \
> + "\#1\\s+$::hex in \\\[\\\[0\\\]\\\]::main \[^\r\n\]*" ] \
> + "raw backtrace"
> +
> + set remote_python_file [gdb_remote_download host \
> + $::srcdir/$::subdir/$::srcfile3]
> + gdb_test_no_output "source $remote_python_file" "load frame filter"
> + gdb_test "backtrace" [multi_line \
> + "\#0\\s+\\\[\\\[1\\\]\\\]::inc \[^\r\n\]*" \
> + "\#1\\s+$::hex in \\\[\\\[0\\\]\\\]::main \[^\r\n\]*" ] \
> + "With null frame filter"
> +
> + # Ensure that the code for identifying and removing parameters for
> + # C++ symbols still works with linker namespace IDs.
> + gdb_continue_to_breakpoint "inc on c++ SO"
> + gdb_test "step" "\\\[\\\[0\\\]\\\]::cpp_inc \\\(n=-1\\\).*" "step into c++ symbol"
> + gdb_test "backtrace" [multi_line \
> + "\#0\\s+\\\[\\\[0\\\]\\\]::cpp_inc \\\(n=-1\\\)\[^\r\n\]*" \
> + "\#1\\s+$::hex in \\\[\\\[0\\\]\\\]::inc \\\(n=-1\\\)\[^\r\n\]*" \
> + "\#2\\s+$::hex in \\\[\\\[0\\\]\\\]::main \[^\r\n\]*" ] \
> + "bt through c++ symbols"
> +
> + # Check that everything still works as we close namespaces.
> gdb_continue_to_breakpoint "first dlclose"
> gdb_test "print \$_linker_namespace_count" "4" "all SOs loaded"
>
> @@ -264,15 +307,23 @@ proc test_info_linker_namespaces {} {
>
> # Some systems may add libc and libm to every loaded namespace,
> # others may load only one or neither, because the SO doesn't
> - # actually use either library. The best we can do is check if
> - # we found the dynamic linker, and up to 2 more libraries.
> - gdb_assert {$n_libraries <= 3} "the correct number of libraries was reported"
> + # actually use either library. Similarly, because of the c++
> + # SO, libstdc++ and libgcc_s may be included. The best we can
> + # do is check if we found the dynamic linker, the SO, and up to
> + # 4 more libraries. Ensuring that only the right one shows up is
> + # in the foreach block.
> + gdb_assert {$n_libraries <= 6} "the correct number of libraries was reported"
>
> set binfile_lib_re [string_to_regexp $::binfile_lib]
>
> - foreach_with_prefix ns {1 2 3} {
> + foreach_with_prefix ns {1 2 3 0} {
> set found_test_so false
> set n_libraries 999
> + set expected_libs 4
> + if {$ns == 0} {
> + set expected_libs 6
> + set binfile_lib_re [string_to_regexp $::binfile_libcpp]
> + }
>
> gdb_test_multiple "info linker-namespaces $ns" "print namespace $ns" {
> -re ".*($::decimal) librar(?:y|ies) loaded in linker namespace $ns:\r\n" {
> @@ -290,7 +341,8 @@ proc test_info_linker_namespaces {} {
> # actually use either library. The best we can do is check if
> # we found the dynamic linker, the test SO, and maybe up to 2
> # more libraries.
> - gdb_assert {$n_libraries <= 4} "the correct number of libraries was reported"
> + gdb_assert {$n_libraries <= $expected_libs} \
> + "the correct number of libraries was reported"
> gdb_assert {$found_test_so} "this testfile's SO was reported"
> }
>
> diff --git a/gdb/testsuite/gdb.mi/mi-dlmopen.exp b/gdb/testsuite/gdb.mi/mi-dlmopen.exp
> index ff854ac7dd4..832c6ea6fe6 100644
> --- a/gdb/testsuite/gdb.mi/mi-dlmopen.exp
> +++ b/gdb/testsuite/gdb.mi/mi-dlmopen.exp
> @@ -156,7 +156,7 @@ proc check_solib_unload_events {} {
> -disp keep -func main -file ".*$::srcfile" -line $::bp_main
>
> # Run past all the dlopen and dlmopen calls.
> - mi_execute_to "exec-continue" "breakpoint-hit" main "" ".*" $::bp_loaded \
> + mi_execute_to "exec-continue" "breakpoint-hit" {\[\[0\]\]::main} "" ".*" $::bp_loaded \
> {"" "disp=\"keep\""} "continue until all libraries are loaded"
>
> # Check that the dynamic linker has now been loaded multiple times.
>
> base-commit: 9e6e0c3cd9e6d5ee4953f72a805cff5f150aa2fd
--
Cheers,
Guinevere Larsen
it/its
she/her (deprecated)
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PING][PATCH v8] gdb: Print linker namespace when showing a frame
2026-06-22 20:34 ` [PING][PATCH " Guinevere Larsen
@ 2026-06-23 11:57 ` Eli Zaretskii
2026-06-23 12:07 ` Guinevere Larsen
0 siblings, 1 reply; 4+ messages in thread
From: Eli Zaretskii @ 2026-06-23 11:57 UTC (permalink / raw)
To: Guinevere Larsen; +Cc: gdb-patches, guinevere
> Date: Mon, 22 Jun 2026 17:34:13 -0300
> Cc: Eli Zaretskii <eliz@gnu.org>
> From: Guinevere Larsen <guinevere@redhat.com>
>
> Ping :)
I already approved the documentation parts, didn't I?
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PING][PATCH v8] gdb: Print linker namespace when showing a frame
2026-06-23 11:57 ` Eli Zaretskii
@ 2026-06-23 12:07 ` Guinevere Larsen
0 siblings, 0 replies; 4+ messages in thread
From: Guinevere Larsen @ 2026-06-23 12:07 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: gdb-patches
On 6/23/26 8:57 AM, Eli Zaretskii wrote:
>> Date: Mon, 22 Jun 2026 17:34:13 -0300
>> Cc: Eli Zaretskii <eliz@gnu.org>
>> From: Guinevere Larsen <guinevere@redhat.com>
>>
>> Ping :)
> I already approved the documentation parts, didn't I?
>
I think so. That's why it is useful to only give the review tag once you
have approved the documentation parts
--
Cheers,
Guinevere Larsen
it/its
she/her (deprecated)
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-06-23 12:07 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-05-07 13:48 [PATCH v8] gdb: Print linker namespace when showing a frame Guinevere Larsen
2026-06-22 20:34 ` [PING][PATCH " Guinevere Larsen
2026-06-23 11:57 ` Eli Zaretskii
2026-06-23 12:07 ` Guinevere Larsen
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox