Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH v7] gdb: Print linker namespace when showing a frame
@ 2026-01-13 17:43 Guinevere Larsen
  2026-01-13 20:08 ` Eli Zaretskii
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Guinevere Larsen @ 2026-01-13 17:43 UTC (permalink / raw)
  To: gdb-patches; +Cc: Guinevere Larsen

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 method to general_symbol_info, which
uses part of the functionality behind the _linker_namespace variable
, namely, find the linker namespace that contains the given address on
the given program space.  The reason print_name was not changed to do
this is that the old method returns a "const char *", but the new one
needs to return a string, and the refactor was very complicated, so the
new method is introduced.

The namespace ID is only printed if multiple namespaces are active,
otherwise no change in behavior is expected. This commit also updates
the test gdb.base/dlmopen-ns-ids.exp to test this functionality.
---
 gdb/NEWS                                      |  4 +++
 gdb/doc/gdb.texinfo                           |  9 ++++++
 gdb/solib.c                                   | 29 ++++++++++-------
 gdb/solib.h                                   |  8 +++++
 gdb/stack.c                                   |  9 ++++--
 gdb/symtab.c                                  | 15 +++++++++
 gdb/symtab.h                                  | 13 ++++++--
 .../gdb.base/dlmopen-ns-ids-framefilter.py    | 31 +++++++++++++++++++
 gdb/testsuite/gdb.base/dlmopen-ns-ids.exp     | 25 +++++++++++++--
 gdb/testsuite/gdb.mi/mi-dlmopen.exp           |  2 +-
 10 files changed, 125 insertions(+), 20 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/dlmopen-ns-ids-framefilter.py

diff --git a/gdb/NEWS b/gdb/NEWS
index cd0303fa199..80dfc25f824 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -54,6 +54,10 @@
   on the index.  Any existing indexes should be regenerated.
 * Support for Floating Point Mode Register (FPMR) in AArch64.
 
+* 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 7059f73935c..d18710ca273 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -22406,6 +22406,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 492ff5580c9..a3e40940ddd 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -1825,6 +1825,22 @@ 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;
+}
+
 /* Implementation of the linker_namespace convenience variable.
 
    This returns the GDB internal identifier of the linker namespace,
@@ -1833,19 +1849,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 4709669c54e..48cc709f2de 100644
--- a/gdb/solib.h
+++ b/gdb/solib.h
@@ -335,6 +335,14 @@ 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);
+
 /* 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 9d8e9da6aa8..bdfd41b7827 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -1276,7 +1276,9 @@ find_frame_funname (const frame_info_ptr &frame, enum language *funlang,
   func = get_frame_function (frame);
   if (func)
     {
-      const char *print_name = func->print_name ();
+      std::string print_name
+	= func->print_name_with_namespace (get_frame_pc_if_available (frame),
+					   get_frame_program_space (frame));
 
       *funlang = func->language ();
       if (funcp)
@@ -1288,13 +1290,13 @@ find_frame_funname (const frame_info_ptr &frame, enum language *funlang,
 	     stored in the symbol table, but we stored a version
 	     with DMGL_PARAMS turned on, and here we don't want to
 	     display parameters.  So remove the parameters.  */
-	  funname = cp_remove_params (print_name);
+	  funname = cp_remove_params (print_name.c_str ());
 	}
 
       /* If we didn't hit the C++ case above, set *funname
 	 here.  */
       if (funname == NULL)
-	funname.reset (xstrdup (print_name));
+	funname.reset (xstrdup (print_name.c_str ()));
     }
   else
     {
@@ -1362,6 +1364,7 @@ print_frame (struct ui_out *uiout,
     annotate_frame_function_name ();
 
     string_file stb;
+
     gdb_puts (funname ? funname.get () : "??", &stb);
     uiout->field_stream ("func", stb, function_name_style.style ());
     uiout->wrap_hint (3);
diff --git a/gdb/symtab.c b/gdb/symtab.c
index 63505a94d41..bf4c3c7183f 100644
--- a/gdb/symtab.c
+++ b/gdb/symtab.c
@@ -756,6 +756,21 @@ gdb_mangle_name (struct type *type, int method_id, int signature_id)
 
 /* See symtab.h.  */
 
+std::string
+general_symbol_info::print_name_with_namespace (std::optional <CORE_ADDR> pc,
+						program_space *pspace)
+{
+  std::string name = print_name ();
+  if (pc.has_value ()
+      && solib_linker_namespace_count (pspace) > 1)
+    return string_printf
+      ("[[%d]]::%s", linker_namespace_for_addr (*pc, pspace),
+       name.c_str ());
+  return name;
+}
+
+/* See symtab.h.  */
+
 void
 general_symbol_info::set_demangled_name (const char *name,
 					 struct obstack *obstack)
diff --git a/gdb/symtab.h b/gdb/symtab.h
index b771851f03a..4b0e21c4cee 100644
--- a/gdb/symtab.h
+++ b/gdb/symtab.h
@@ -448,9 +448,10 @@ struct general_symbol_info
   /* Short version as to when to use which name accessor:
      Use natural_name () to refer to the name of the symbol in the original
      source code.  Use linkage_name () if you want to know what the linker
-     thinks the symbol's name is.  Use print_name () for output.  Use
-     demangled_name () if you specifically need to know whether natural_name ()
-     and linkage_name () are different.  */
+     thinks the symbol's name is.  Use print_name_with_namespace () for most
+     output.  Use print_name () if you have a reason to always omit the
+     namespace identifier.  Use demangled_name () if you specifically need
+     to know whether natural_name () and linkage_name () are different.  */
 
   const char *linkage_name () const
   { return m_name; }
@@ -470,6 +471,12 @@ struct general_symbol_info
   const char *print_name () const
   { return demangle ? natural_name () : linkage_name (); }
 
+  /* Similar to print_name, but may prefix the name of the symbol with the
+     linker namespace identifier, if the target supports it and the inferior
+     has more than one identifier loaded.  */
+  std::string print_name_with_namespace
+    (std::optional <CORE_ADDR> pc, program_space *pspace);
+
   /* Return the demangled name for a symbol based on the language for
      that symbol.  If no demangled name exists, return NULL.  */
   const char *demangled_name () const;
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.exp b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
index e40bc213454..e9832664566 100644
--- a/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
+++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
@@ -21,7 +21,7 @@
 
 require allow_dlmopen_tests
 
-standard_testfile -main.c -lib.c
+standard_testfile -main.c -lib.c -framefilter.py
 
 set srcfile_lib $srcfile2
 set so_name dlmopen-lib.so
@@ -192,6 +192,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 +205,30 @@ 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"
+
     gdb_continue_to_breakpoint "first dlclose"
     gdb_test "print \$_linker_namespace_count" "4" "all SOs loaded"
 
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: 6b8fb74a9403092c4e4813c728e20ed10a84676f
-- 
2.52.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-05-06 20:08 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-01-13 17:43 [PATCH v7] gdb: Print linker namespace when showing a frame Guinevere Larsen
2026-01-13 20:08 ` Eli Zaretskii
2026-03-05 12:18 ` [PING][PATCH " Guinevere Larsen
2026-04-20 16:28 ` [PATCH " Andrew Burgess
2026-05-06 20: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