[PATCH 3/3] GDB: Introduce "info namespaces" command

Guinevere Larsen guinevere@redhat.com
Mon Apr 7 14:54:43 GMT 2025


Continuing to improve GDB's ability to debug linker namespaces, this
commit adds the command "info namespaces". The command is similar to
"info sharedlibrary" but focused on improved readability when the
inferior has multiple linker namespaces active. This command can
be used in 2 different ways, with or without an argument.

When called without argument, the command will print the number of
namespaces, and for each active namespace, it's identifier, how many
libraries are loaded in it, and all the libraries (in a similar table to
what "info sharedlibrary" shows). As an example, this is what GDB's
output could look like:

  (gdb) info namespaces
  There are 2 namespaces loaded
  There are 3 libraries loaded in namespace [[0]]
  Displaying libraries for namespace [[0]]:
  From                To                  Syms Read   Shared Object Library
  0x00007ffff7fc6000  0x00007ffff7fff000  Yes         /lib64/ld-linux-x86-64.so.2
  0x00007ffff7ebc000  0x00007ffff7fa2000  Yes (*)     /lib64/libm.so.6
  0x00007ffff7cc9000  0x00007ffff7ebc000  Yes (*)     /lib64/libc.so.6
  (*): Shared library is missing debugging information.

  There are 4 libraries loaded in namespace [[1]]
  Displaying libraries for namespace [[1]]:
  From                To                  Syms Read   Shared Object Library
  0x00007ffff7fc6000  0x00007ffff7fff000  Yes         /lib64/ld-linux-x86-64.so.2
  0x00007ffff7fb9000  0x00007ffff7fbe000  Yes         gdb.base/dlmopen-ns-ids/dlmopen-lib.so
  0x00007ffff7bc4000  0x00007ffff7caa000  Yes (*)     /lib64/libm.so.6
  0x00007ffff79d1000  0x00007ffff7bc4000  Yes (*)     /lib64/libc.so.6
  (*): Shared library is missing debugging information.

When called with an argument, the argument must be a namespace
identifier (either with or without the square brackets decorators). In
this situation, the command will truncate the output to only show the
relevant information for the requested namespace. For example:

  (gdb) info namespaces 0
  There are 3 libraries loaded in namespace [[0]]
  Displaying libraries for namespace [[0]]:
  From                To                  Syms Read   Shared Object Library
  0x00007ffff7fc6000  0x00007ffff7fff000  Yes         /lib64/ld-linux-x86-64.so.2
  0x00007ffff7ebc000  0x00007ffff7fa2000  Yes (*)     /lib64/libm.so.6
  0x00007ffff7cc9000  0x00007ffff7ebc000  Yes (*)     /lib64/libc.so.6
  (*): Shared library is missing debugging information.

The test gdb.base/dlmopen-ns-id.exp has been extended to test this new
command.
---
 gdb/NEWS                                  |  5 ++
 gdb/doc/gdb.texinfo                       | 15 ++++
 gdb/solib-svr4.c                          | 49 ++++++++++++
 gdb/solib.c                               | 91 ++++++++++++++++++++++
 gdb/solist.h                              |  4 +
 gdb/testsuite/gdb.base/dlmopen-ns-ids.exp | 93 +++++++++++++++++++++++
 6 files changed, 257 insertions(+)

diff --git a/gdb/NEWS b/gdb/NEWS
index 4bf380cbadd..758c7e1e543 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -59,6 +59,11 @@ show riscv numeric-register-names
   (e.g 'x1') or their abi names (e.g. 'ra').
   Defaults to 'off', matching the old behaviour (abi names).
 
+info namespaces
+info namespaces [[N]]
+  Print information about the given namespace (identified as N), or about
+  all the namespaces if no argument is given.
+
 * Changed commands
 
 info sharedlibrary
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index f0ebf4b7dcf..aaa1431fff5 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -22223,6 +22223,21 @@ command for this.  This command exists for historical reasons.  It is
 less useful than setting a catchpoint, because it does not allow for
 conditions or commands as a catchpoint does.
 
+@table @code
+@item info namespaces
+@item info namespaces @code{[[N]]}
+
+With no argument, print the number of linker namespaces which are
+currently active - that is, namespaces that have libraries loaded
+on them.  Then, it prints the number of libraries loaded into each
+namespace, and all the libraries loaded into them, in the same way
+as @code{info sharedlibrary}.
+
+If an argument is provided, only prints the library count and table
+for the provided namespace.  The surrounding square brackets are
+optional.
+@end table
+
 @table @code
 @item set stop-on-solib-events
 @kindex set stop-on-solib-events
diff --git a/gdb/solib-svr4.c b/gdb/solib-svr4.c
index 60b61e7e07d..76823afde26 100644
--- a/gdb/solib-svr4.c
+++ b/gdb/solib-svr4.c
@@ -3558,6 +3558,54 @@ svr4_num_active_namespaces ()
   return info->active_namespaces.size ();
 }
 
+/* See solib_ops::get_solibs_in_ns in solist.h.  */
+static std::vector<const solib *>
+svr4_get_solibs_in_ns (int nsid)
+{
+  std::vector<const solib*> ns_solibs;
+  svr4_info *info = get_svr4_info (current_program_space);
+
+  /* If the namespace ID is inactive, there will be no active
+     libraries, so we can have an early exit, as a treat.  */
+  if (info->active_namespaces.count (nsid) != 1)
+    return ns_solibs;
+
+  /* Since we only have the names of solibs in a given namespace,
+     we'll need to walk through the solib list of the inferior and
+     find which solib objects correspond to which svr4_so.  We create
+     an unordered map with the names and lm_info to check things
+     faster, and to be able to remove SOs from the map, to avoid
+     returning the dynamic linker multiple times.  */
+  CORE_ADDR debug_base = info->namespace_id[nsid];
+  std::unordered_map<std::string, const lm_info_svr4 *> namespace_solibs;
+  for (svr4_so &so : info->solib_lists[debug_base])
+    {
+      namespace_solibs[so.name]
+	= gdb::checked_static_cast<const lm_info_svr4 *>
+	    (so.lm_info.get ());
+    }
+  for (const solib &so: current_program_space->solibs ())
+    {
+      auto *lm_inferior
+	= gdb::checked_static_cast<const lm_info_svr4 *> (so.lm_info.get ());
+
+      /* This is inspired by the svr4_same, by finding the svr4_so object
+	 in the map, and then double checking if the lm_info is considered
+	 the same.  */
+      if (namespace_solibs.count (so.so_original_name) > 0
+	  && namespace_solibs[so.so_original_name]->l_addr_inferior
+	      == lm_inferior->l_addr_inferior)
+	{
+	  ns_solibs.push_back (&so);
+	  /* Remove the SO from the map, so that we don't end up
+	     printing the dynamic linker multiple times.  */
+	  namespace_solibs.erase (so.so_original_name);
+	}
+    }
+
+  return ns_solibs;
+}
+
 const struct solib_ops svr4_so_ops =
 {
   svr4_relocate_section_addresses,
@@ -3575,6 +3623,7 @@ const struct solib_ops svr4_so_ops =
   svr4_find_solib_addr,
   svr4_find_solib_ns,
   svr4_num_active_namespaces,
+  svr4_get_solibs_in_ns,
 };
 
 void _initialize_svr4_solib ();
diff --git a/gdb/solib.c b/gdb/solib.c
index c232d1338b2..d946d3795ac 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -1149,6 +1149,94 @@ info_sharedlibrary_command (const char *pattern, int from_tty)
     }
 }
 
+/* Implement the "info namespaces" command.  If the current
+   gdbarch's solib_ops object does not support multiple namespaces,
+   this command would just look like "info sharedlibrary", so point
+   the user to that command instead.
+   If solib_ops does support multiple namespaces, this command
+   will group the libraries by linker namespace, or only print the
+   libraries in the supplied namespace.  */
+static void
+info_linker_namespace_command (const char *pattern, int from_tty)
+{
+  const solib_ops *ops = gdbarch_so_ops (current_inferior ()->arch ());
+  /* This command only really makes sense for inferiors that support
+     linker namespaces, so we can leave early.  */
+  if (ops->num_active_namespaces == nullptr)
+    error (_("Current inferior does not support linker namespaces." \
+	     "Use \"info sharedlibrary\" instead"));
+
+  struct ui_out *uiout = current_uiout;
+  std::vector<std::pair<int, std::vector<const solib *>>> all_solibs_to_print;
+
+  if (pattern != nullptr)
+    while (*pattern == ' ')
+      pattern++;
+
+  if (pattern == nullptr || pattern[0] == '\0')
+    {
+      uiout->message (_ ("There are %d namespaces loaded\n"),
+		      ops->num_active_namespaces ());
+
+      int printed = 0;
+      for (int i = 0; printed < ops->num_active_namespaces (); i++)
+	{
+	  std::vector<const solib *> solibs_to_print
+	    = ops->get_solibs_in_ns (i);
+	  if (solibs_to_print.size () > 0)
+	    {
+	      all_solibs_to_print.push_back (std::make_pair
+					      (i, solibs_to_print));
+	      printed++;
+	    }
+	}
+    }
+  else
+    {
+      int ns;
+      /* Check if the pattern includes the optional [[ and ]] decorators.
+	 To match multiple occurrences, '+' needs to be escaped, and every
+	 escape sequence must be doubled to survive the compiler pass.  */
+      re_comp ("^\\[\\[[0-9]\\+\\]\\]$");
+      if (re_exec (pattern))
+	ns = strtol (pattern+2, nullptr, 10);
+      else
+	{
+	  char * end = nullptr;
+	  ns = strtol (pattern, &end, 10);
+	  if (end[0] != '\0')
+	    error (_ ("Invalid namespace identifier: %s"), pattern);
+	}
+
+      all_solibs_to_print.push_back
+	(std::make_pair (ns, ops->get_solibs_in_ns (ns)));
+    }
+
+  bool ns_separator = false;
+
+  for (auto &solibs_pair : all_solibs_to_print)
+    {
+      if (ns_separator)
+	uiout->message ("\n\n");
+      else
+	ns_separator = true;
+      int ns = solibs_pair.first;
+      std::vector<const solib *> solibs_to_print = solibs_pair.second;
+      if (solibs_to_print.size () == 0)
+	{
+	  uiout->message (_("Namespace [[%d]] is not active.\n"), ns);
+	  /* If we got here, a specific namespace was requested, so there
+	     will only be one vector.  We can leave early.  */
+	  break;
+	}
+      uiout->message (_ ("There are %ld libraries loaded in namespace [[%d]]\n"),
+			solibs_to_print.size (), ns);
+      uiout->message (_ ("Displaying libraries for namespace [[%d]]:\n"), ns);
+
+      print_solib_list_table (solibs_to_print, false);
+    }
+}
+
 /* See solib.h.  */
 
 bool
@@ -1790,6 +1878,9 @@ _initialize_solib ()
   add_com ("nosharedlibrary", class_files, no_shared_libraries_command,
 	   _ ("Unload all shared object library symbols."));
 
+  add_info ("namespaces", info_linker_namespace_command,
+      _ ("Get information about linker namespaces in the inferior."));
+
   add_setshow_boolean_cmd ("auto-solib-add", class_support, &auto_solib_add,
 			   _ ("\
 Set autoloading of shared library symbols."),
diff --git a/gdb/solist.h b/gdb/solist.h
index 03d2392b19d..5f029054399 100644
--- a/gdb/solist.h
+++ b/gdb/solist.h
@@ -194,6 +194,10 @@ struct solib_ops
 
   /* Returns the number of active namespaces in the inferior.  */
   int (*num_active_namespaces) ();
+
+  /* Returns all solibs for a given namespace.  If the namespace is not
+     active, returns an empty vector.  */
+  std::vector<const solib *> (*get_solibs_in_ns) (int ns);
 };
 
 /* A unique pointer to a so_list.  */
diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
index 51d461d05a3..095433896ac 100644
--- a/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
+++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
@@ -157,5 +157,98 @@ proc_with_prefix test_conv_vars {} {
     gdb_continue_to_end "" continue 1
 }
 
+# Run several tests relating to the command "info namespaces".
+proc test_info_namespaces {} {
+    clean_restart $::binfile
+
+    if { ![runto_main] } {
+	return
+    }
+
+    with_test_prefix "info namespaces" {
+	gdb_breakpoint [gdb_get_line_number "TAG: first dlclose"]
+	gdb_continue_to_breakpoint "TAG: first dlclose"
+    }
+
+    # First, test printing a single namespace, and ensure all of
+    # them are correct, using both syntaxes.
+    set found_libm false
+    set found_libc false
+    set found_all_libs false
+    gdb_test_multiple "info namespaces \[\[0\]\]" "print namespace 0" -lbl {
+	-re "^\r\nThere are ($::decimal) libraries loaded in namespace \\\[\\\[0\\\]\\\]" {
+	    set libs $expect_out(1,string)
+	    set found_all_libs [expr $libs == 3]
+	    exp_continue
+	}
+	-re "^\r\n\[^\r\n\]+libm\.so\[^\r\n\]*(?=\r\n)" {
+	    set found_libm true
+	    exp_continue
+	}
+	-re "^\r\n\[^\r\n\]+libc\.so\[^\r\n\]*(?=\r\n)" {
+	    set found_libc true
+	    exp_continue
+	}
+	-re "^\r\n$::gdb_prompt $" {
+	    gdb_assert $found_libm "libm was reported"
+	    gdb_assert $found_libc "libc was reported"
+	    gdb_assert $found_all_libs "the correct number of libraries was reported"
+	}
+	-re "(^\r\n)?\[^\r\n\]+(?=\r\n)" {
+	    exp_continue
+	}
+    }
+    foreach_with_prefix ns {1 2 3} {
+	set found_libm false
+	set found_libc false
+	set found_test_so false
+	set found_all_libs false
+	gdb_test_multiple "info namespaces $ns" "print namespace $ns" -lbl {
+	    -re "^\r\nThere are ($::decimal) libraries loaded in namespace \\\[\\\[$ns\\\]\\\]" {
+		set libs $expect_out(1,string)
+		set found_all_libs [expr $libs == 4]
+		exp_continue
+	    }
+	    -re "^\r\n\[^\r\n\]+libm\.so\[^\r\n\]*(?=\r\n)" {
+		set found_libm true
+		exp_continue
+	    }
+	    -re "^\r\n\[^\r\n\]+libc\.so\[^\r\n\]*(?=\r\n)" {
+		set found_libc true
+		exp_continue
+	    }
+	    -re "^\r\n\[^\r\n\]+${::binfile_lib}\[^\r\n\]*(?=\r\n)" {
+		set found_test_so true
+		exp_continue
+	    }
+	    -re "^\r\n$::gdb_prompt $" {
+		gdb_assert $found_libm "libm was reported"
+		gdb_assert $found_libc "libc was reported"
+		gdb_assert $found_test_so "this testfle's SO was reported"
+		gdb_assert $found_all_libs "the correct number of libraries was reported"
+	    }
+	    -re "(^\r\n)?\[^\r\n\]+(?=\r\n)" {
+		exp_continue
+	    }
+	}
+    }
+
+    # These patterns are simpler, and purposefully glob multiple lines.
+    # The point is to ensure that we find and display all the namespaces,
+    # without worrying about the libraries printed, since that was tested
+    # above.
+    gdb_test "info namespaces" \
+	[multi_line "There are 4 namespaces loaded" \
+		    "There are 3 libraries loaded in namespace ..0.." \
+		    ".*" \
+		    "There are 4 libraries loaded in namespace ..1.." \
+		    ".*" \
+		    "There are 4 libraries loaded in namespace ..2.." \
+		    ".*" \
+		    "There are 4 libraries loaded in namespace ..3.." \
+		    ".*" ] "print namespaces with no argument"
+}
+
 test_info_shared
 test_conv_vars
+test_info_namespaces
-- 
2.49.0



More information about the Gdb-patches mailing list