[v7,3/5] gdb: Make the parser recognize the [[N]] syntax for variables

Message ID 20260601192251.60958-4-guinevere@redhat.com
State New
Headers
Series Introduce syntax for linker-namespace specific symbols |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gdb_check--master-arm fail Test failed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 success Test passed

Commit Message

Guinevere Larsen June 1, 2026, 7:22 p.m. UTC
  This commit adds a couple new rules to the YACC parser, that will allow
GDB to recognize expressions like [[N]]::var.

This new syntax reuses the existing rules for searching for "block"s,
because it allows, with minimal changes to the .y file as a whole, while
also allowing [[N]]::function::symbol work. Notably, [[N]]::'file.c'::var
does not work yet, as this code change is also involved and would pollute
the current commit, so will be done in a separate one in the same series.

To restrict the search to a desired namespace, a new function is
introduced,  lookup_symbol_in_linker_namespace.  This function will only
perform the symbol lookup in the objfiles that belong to the requested
namespace.

This commit also changes the error message when "print var" if "var" is
only present in namespaces other than the current one. Before this commit,
lookup_minimal_symbol would find them as "external" symbols with
incomplete information - outputting "<symbol> has incomplete
information, cast it to its declared type." - which could be confusing for
the users. This commit changes the parser rule "variable: name_not_typename"
so that, if multiple namespaces are active, we'll look for minimal symbols
only in the current namespace.

Known limitations:
* When using [[N]]::foo::var, if that specific foo version is not on the
  stack, the error will be unable to include the linker namespace
  requested.  So if a user sees "foo" in the stack this could be
  confusing, but it should be mitigated when a future patch adds the
  namespace ID to the backtrace where applicable.
* Completion is not supported.  That's because the function used to
  complete symbols expects only text in the form "file.c:symbol", so
  can't handle the double colon.  This isn't unique to linker
  namespaces, though, since func::var will also not complete, it is just
  a quirk of how :: completion work at this point.
* The expression that parses [[N]] only takes an integer.  That's
  because there aren't many expressions that would make sense in there,
  and the work of accepting an arbitrary expression felt too much for
  little gain.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
---
 gdb/NEWS                                     |   5 +
 gdb/c-exp.y                                  |  72 ++++++++---
 gdb/doc/gdb.texinfo                          |   6 +
 gdb/minsyms.c                                | 122 ++++++++++++-------
 gdb/minsyms.h                                |   8 ++
 gdb/parser-defs.h                            |  13 ++
 gdb/symtab.c                                 |  40 ++++++
 gdb/symtab.h                                 |   8 ++
 gdb/testsuite/gdb.base/dlmopen-ns-ids-lib.c  |  14 +++
 gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c |   3 +
 gdb/testsuite/gdb.base/dlmopen-ns-ids.exp    |  53 ++++++++
 11 files changed, 280 insertions(+), 64 deletions(-)
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index 7c8cf9af4c2..256af323844 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -421,6 +421,11 @@  info threads [-gid] [-stopped] [-running] [ID]...
   These new flags can be useful to get a reduced list when there is a
   large number of threads.
 
+print
+  The print command now accepts the following syntax to print values
+  from a specific linker namespace: `[[N]]::foo'.  N is the namespace
+  identifier as understood by GDB.
+
 * GDB-internal Thread Local Storage (TLS) support
 
   ** Linux targets for the x86_64, aarch64, ppc64, s390x, and riscv
diff --git a/gdb/c-exp.y b/gdb/c-exp.y
index 2829d8bccba..8e405e4dc41 100644
--- a/gdb/c-exp.y
+++ b/gdb/c-exp.y
@@ -175,7 +175,7 @@  using namespace expr;
     struct ttype tsym;
     struct symtoken ssym;
     int voidval;
-    const struct block *bval;
+    struct bval_type bval;
     enum exp_opcode opcode;
 
     struct stoken_vector svec;
@@ -263,6 +263,7 @@  static void c_print_token (FILE *file, int type, YYSTYPE value);
 %token TYPEOF
 %token DECLTYPE
 %token TYPEID
+%token DOUBLESQUAREOPEN DOUBLESQUARECLOSE
 
 /* Special type cases, put in to allow the parser to distinguish different
    legal basetypes.  */
@@ -1058,31 +1059,43 @@  exp     :       FALSEKEYWORD
 
 block	:	BLOCKNAME
 			{
-			  if ($1.sym.symbol)
-			    $$ = $1.sym.symbol->value_block ();
-			  else
+			  if ($1.sym.symbol == nullptr)
 			    error (_("No file or function \"%s\"."),
 				   copy_name ($1.stoken).c_str ());
+			  $$.search_namespace = false;
+			  $$.b_val = $1.sym.symbol->value_block ();
 			}
 	|	FILENAME
 			{
 			  $$ = $1;
 			}
+	|	DOUBLESQUAREOPEN INT DOUBLESQUARECLOSE
+			{
+			  $$.search_namespace = true;
+			  $$.namespace_val = $2.val;
+			}
 	;
 
 block	:	block COLONCOLON name
 			{
 			  std::string copy = copy_name ($3);
-			  struct symbol *tem
-			    = lookup_symbol (copy.c_str (), $1,
-					     SEARCH_FUNCTION_DOMAIN,
-					     nullptr).symbol;
+			  struct block_symbol tem;
 
-			  if (tem == nullptr)
+			  if ($1.search_namespace)
+			    tem = lookup_symbol_in_linker_namespace
+				(copy.c_str (), $1.namespace_val, SEARCH_FUNCTION_DOMAIN);
+			  else
+			    tem = lookup_symbol (copy.c_str (), $1.b_val,
+						 SEARCH_FUNCTION_DOMAIN,
+						 nullptr);
+
+			  if (tem.symbol == nullptr)
 			    error (_("No function \"%ps\" in specified context."),
 				   styled_string (function_name_style.style (),
 						  copy.c_str ()));
-			  $$ = tem->value_block (); }
+			  $$.b_val = tem.symbol->value_block ();
+			  $$.search_namespace = false;
+			}
 	;
 
 variable:	name_not_typename ENTRY
@@ -1101,9 +1114,15 @@  variable:	name_not_typename ENTRY
 variable:	block COLONCOLON name
 			{
 			  std::string copy = copy_name ($3);
-			  struct block_symbol sym
-			    = lookup_symbol (copy.c_str (), $1,
-					     SEARCH_VFT, NULL);
+			  struct block_symbol sym;
+
+			  if ($1.search_namespace)
+			    sym = lookup_symbol_in_linker_namespace
+				    (copy.c_str (), $1.namespace_val,
+				     SEARCH_VFT);
+			  else
+			    sym = lookup_symbol (copy.c_str (), $1.b_val,
+						 SEARCH_VFT, NULL);
 
 			  if (sym.symbol == 0)
 			    error (_("No symbol \"%s\" in specified context."),
@@ -1199,8 +1218,17 @@  variable:	name_not_typename
 			    {
 			      std::string arg = copy_name ($1.stoken);
 
-			      bound_minimal_symbol msymbol
-				= lookup_minimal_symbol (current_program_space, arg.c_str ());
+			      int active_linker_nss
+				= solib_linker_namespace_count
+				  (current_program_space);
+			      bound_minimal_symbol msymbol;
+
+			      if (active_linker_nss > 1)
+				msymbol = lookup_minimal_symbol_in_linker_namespace
+				  (current_program_space, arg.c_str ());
+			      else
+				msymbol = lookup_minimal_symbol
+				  (current_program_space, arg.c_str ());
 			      if (msymbol.minsym == NULL)
 				{
 				  if (!current_program_space->has_full_symbols ()
@@ -1208,6 +1236,9 @@  variable:	name_not_typename
 				    error (_("No symbol table is loaded.  Use the \"%ps\" command."),
 					   styled_string (command_style.style (),
 							  "file"));
+				  else if (active_linker_nss > 1)
+				    error (_("No symbol \"%s\" in the current linker namespace."),
+					   arg.c_str ());
 				  else
 				    error (_("No symbol \"%s\" in current context."),
 					   arg.c_str ());
@@ -2545,7 +2576,9 @@  static const struct c_token tokentab2[] =
     {"!=", NOTEQUAL, OP_NULL, 0},
     {"<=", LEQ, OP_NULL, 0},
     {">=", GEQ, OP_NULL, 0},
-    {".*", DOT_STAR, OP_NULL, FLAG_CXX}
+    {".*", DOT_STAR, OP_NULL, FLAG_CXX},
+    {"[[", DOUBLESQUAREOPEN, OP_NULL, 0},
+    {"]]", DOUBLESQUARECLOSE, OP_NULL, 0},
   };
 
 /* Identifier-like tokens.  Only type-specifiers than can appear in
@@ -3177,8 +3210,9 @@  classify_name (struct parser_state *par_state, const struct block *block,
 	  if (auto symtab = lookup_symtab (current_program_space, copy.c_str ());
 	      symtab != nullptr)
 	    {
-	      yylval.bval
+	      yylval.bval.b_val
 		= symtab->compunit ()->blockvector ()->static_block ();
+	      yylval.bval.search_namespace = false;
 
 	      return FILENAME;
 	    }
@@ -3428,7 +3462,7 @@  yylex (void)
   name_obstack.clear ();
   checkpoint = 0;
   if (current.token == FILENAME)
-    search_block = current.value.bval;
+    search_block = current.value.bval.b_val;
   else if (current.token == COLONCOLON)
     search_block = NULL;
   else
@@ -3612,7 +3646,7 @@  c_print_token (FILE *file, int type, YYSTYPE value)
       break;
 
     case FILENAME:
-      parser_fprintf (file, "bval<%s>", host_address_to_string (value.bval));
+      parser_fprintf (file, "bval<%s>", host_address_to_string (value.bval.b_val));
       break;
     }
 }
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index a698b2b8451..ce67eb28a26 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -10775,6 +10775,12 @@  you can choose a different format by specifying @samp{/@var{f}}, where
 @var{f} is a letter specifying the format; see @ref{Output Formats,,Output
 Formats}.
 
+Additionally, if the inferior supports linker namespaces, @value{GDBN}
+has implemented the following syntax to allow users to resolve symbols
+to a specific namespace: @samp{[[@var{n}]]::@var{symbol}}.  Here @var{n}
+is @value{GDBN}'s namespace identifier, which may be different from the
+inferior's identifier.
+
 @anchor{print options}
 The @code{print} command supports a number of options that allow
 overriding relevant global print settings as set by @code{set print}
diff --git a/gdb/minsyms.c b/gdb/minsyms.c
index b3538a9284a..5de058fa1ef 100644
--- a/gdb/minsyms.c
+++ b/gdb/minsyms.c
@@ -324,7 +324,10 @@  lookup_minimal_symbol_demangled (const lookup_name_info &lookup_name,
     }
 }
 
-/* Look through all the current minimal symbol tables and find the
+/* Main implementation for the lookup_minimal_symbol function.
+   Receives a list of objfiles in which to look for the symbol.
+
+   Look through all the current minimal symbol tables and find the
    first minimal symbol that matches NAME.  If OBJF is non-NULL, limit
    the search to that objfile.  If SFILE is non-NULL, the only file-scope
    symbols considered will be from that source file (global symbols are
@@ -344,9 +347,9 @@  lookup_minimal_symbol_demangled (const lookup_name_info &lookup_name,
    Obviously, there must be distinct mangled names for each of these,
    but the demangled names are all the same: S::S or S::~S.  */
 
-bound_minimal_symbol
-lookup_minimal_symbol (program_space *pspace, const char *name, objfile *objf,
-		       const char *sfile)
+static bound_minimal_symbol
+lookup_minimal_symbol_in_objfiles (std::vector<objfile *> objfile_list,
+				   const char *name, const char *sfile)
 {
   found_minimal_symbols found;
 
@@ -362,53 +365,48 @@  lookup_minimal_symbol (program_space *pspace, const char *name, objfile *objf,
 
   lookup_name_info lookup_name (name, symbol_name_match_type::FULL);
 
-  for (objfile &objfile : pspace->objfiles ())
+  for (objfile *objfile : objfile_list)
     {
       if (found.external_symbol.minsym != NULL)
 	break;
 
-      if (objf == NULL || objf == &objfile
-	  || objf == objfile.separate_debug_objfile_backlink)
+      symbol_lookup_debug_printf ("lookup_minimal_symbol (%s, %s, %s)",
+				  name, sfile != NULL ? sfile : "NULL",
+				  objfile_debug_name (objfile));
+
+      /* Do two passes: the first over the ordinary hash table,
+	 and the second over the demangled hash table.  */
+      lookup_minimal_symbol_mangled (name, sfile, objfile,
+				     objfile->per_bfd->msymbol_hash,
+				     mangled_hash, mangled_cmp, found);
+
+      /* If not found, try the demangled hash table.  */
+      if (found.external_symbol.minsym == NULL)
 	{
-	  symbol_lookup_debug_printf ("lookup_minimal_symbol (%s, %s, %s, %s)",
-				      host_address_to_string (pspace),
-				      name, sfile != NULL ? sfile : "NULL",
-				      objfile_debug_name (&objfile));
-
-	  /* Do two passes: the first over the ordinary hash table,
-	     and the second over the demangled hash table.  */
-	  lookup_minimal_symbol_mangled (name, sfile, &objfile,
-					 objfile.per_bfd->msymbol_hash,
-					 mangled_hash, mangled_cmp, found);
-
-	  /* If not found, try the demangled hash table.  */
-	  if (found.external_symbol.minsym == NULL)
+	  /* Once for each language in the demangled hash names
+	     table (usually just zero or one languages).  */
+	  for (unsigned iter = 0; iter < nr_languages; ++iter)
 	    {
-	      /* Once for each language in the demangled hash names
-		 table (usually just zero or one languages).  */
-	      for (unsigned iter = 0; iter < nr_languages; ++iter)
-		{
-		  if (!objfile.per_bfd->demangled_hash_languages.test (iter))
-		    continue;
-		  enum language lang = (enum language) iter;
-
-		  unsigned int hash
-		    = (lookup_name.search_name_hash (lang)
-		       % MINIMAL_SYMBOL_HASH_SIZE);
-
-		  symbol_name_matcher_ftype *match
-		    = language_def (lang)->get_symbol_name_matcher
-							(lookup_name);
-		  struct minimal_symbol **msymbol_demangled_hash
-		    = objfile.per_bfd->msymbol_demangled_hash;
-
-		  lookup_minimal_symbol_demangled (lookup_name, sfile, &objfile,
-						   msymbol_demangled_hash,
-						   hash, match, found);
-
-		  if (found.external_symbol.minsym != NULL)
-		    break;
-		}
+	      if (!objfile->per_bfd->demangled_hash_languages.test (iter))
+		continue;
+	      enum language lang = (enum language) iter;
+
+	      unsigned int hash
+		= (lookup_name.search_name_hash (lang)
+		   % MINIMAL_SYMBOL_HASH_SIZE);
+
+	      symbol_name_matcher_ftype *match
+		= language_def (lang)->get_symbol_name_matcher
+						    (lookup_name);
+	      struct minimal_symbol **msymbol_demangled_hash
+		= objfile->per_bfd->msymbol_demangled_hash;
+
+	      lookup_minimal_symbol_demangled (lookup_name, sfile, objfile,
+					       msymbol_demangled_hash,
+					       hash, match, found);
+
+	      if (found.external_symbol.minsym != NULL)
+		break;
 	    }
 	}
     }
@@ -461,6 +459,40 @@  lookup_minimal_symbol (program_space *pspace, const char *name, objfile *objf,
   return {};
 }
 
+/* See minsyms.h.  */
+
+bound_minimal_symbol
+lookup_minimal_symbol (program_space *pspace, const char *name, objfile *objf,
+		       const char *sfile)
+{
+  std::vector<objfile *> search_objfiles;
+  if (objf != nullptr)
+    {
+      search_objfiles.push_back (objf);
+      if (objf->separate_debug_objfile_backlink != nullptr)
+	search_objfiles.push_back (objf->separate_debug_objfile_backlink);
+    }
+  else
+    {
+      for (objfile &objfile : pspace->objfiles ())
+	search_objfiles.push_back (&objfile);
+    }
+
+  return lookup_minimal_symbol_in_objfiles (search_objfiles, name, sfile);
+}
+
+/* See minsyms.h.  */
+
+bound_minimal_symbol
+lookup_minimal_symbol_in_linker_namespace (program_space *pspace,
+					   const char *name)
+{
+  return lookup_minimal_symbol_in_objfiles
+      (get_objfiles_in_linker_namespace (get_current_linker_namespace (),
+					 pspace),
+       name, nullptr);
+}
+
 /* See gdbsupport/symbol.h.  */
 
 int
diff --git a/gdb/minsyms.h b/gdb/minsyms.h
index 03d05b6e328..b8cd639a4d4 100644
--- a/gdb/minsyms.h
+++ b/gdb/minsyms.h
@@ -191,6 +191,14 @@  bound_minimal_symbol lookup_minimal_symbol (program_space *pspace,
 					    objfile *obj = nullptr,
 					    const char *sfile = nullptr);
 
+/* Same as above, but only look for a minimal symbol in the objfiles that
+   belong to the current linker namespace.  This function is only meant to
+   be called when attempting to resolve a symbol from a user command, and
+   there are multiple linker namespaces available to resolve it in.  */
+
+bound_minimal_symbol lookup_minimal_symbol_in_linker_namespace
+  (program_space *pspace, const char *name);
+
 /* Look through all the minimal symbol tables in PSPACE and find the
    first minimal symbol that matches NAME and has text type.  If OBJF
    is non-NULL, limit the search to that objfile.  Returns a bound
diff --git a/gdb/parser-defs.h b/gdb/parser-defs.h
index cfa9d9c111d..a424f0c7b6f 100644
--- a/gdb/parser-defs.h
+++ b/gdb/parser-defs.h
@@ -362,6 +362,19 @@  struct ttype
     struct type *type;
   };
 
+/* A struct to hold all the necessary information when finding a block,
+   or finding symbols inside a block.  */
+struct bval_type
+  {
+    /* Whether we are explicitly searching a block in a namespace.  */
+    bool search_namespace;
+    /* This is where the result of the search will be stored.  */
+    const struct block *b_val;
+    /* If this block, or a parent one, were searched for in a specific
+       namespace, this is where the namespace is stored.  */
+    LONGEST namespace_val;
+  };
+
 struct symtoken
   {
     struct stoken stoken;
diff --git a/gdb/symtab.c b/gdb/symtab.c
index 95287ab42f7..5eab8ad600a 100644
--- a/gdb/symtab.c
+++ b/gdb/symtab.c
@@ -2588,6 +2588,46 @@  lookup_static_symbol (const char *name, const domain_search_flags domain)
 
 /* See symtab.h.  */
 
+struct block_symbol
+lookup_symbol_in_linker_namespace (const char *name, LONGEST nsid,
+				   const domain_search_flags domain)
+{
+  if (!current_program_space->solib_ops ()->supports_namespaces ())
+    error (_("Linker namespaces are not supported by the inferior."));
+
+  std::vector<objfile *> objfiles_in_namespace
+    = get_objfiles_in_linker_namespace (nsid, current_program_space);
+
+  if (objfiles_in_namespace.size () == 0)
+    error (_("Namespace [[%s]] is inactive"), plongest (nsid));
+
+  symbol_lookup_debug_printf ("lookup_symbol_in_linker_namespace (%s, %s, %s)",
+			      plongest (nsid), name, domain_name (domain).c_str ());
+  demangle_result_storage storage;
+  const char *demangled_name
+    = demangle_for_lookup (name, current_language->la_language, storage);
+
+  /* We look for both global and static symbols in here, the order
+     is arbitrary.  */
+  for (objfile *objf : objfiles_in_namespace)
+    {
+      struct block_symbol bsym
+	= lookup_global_symbol_from_objfile (objf, GLOBAL_BLOCK,
+					     demangled_name, domain);
+      if (bsym.symbol != nullptr)
+	return bsym;
+
+      bsym = lookup_global_symbol_from_objfile (objf, STATIC_BLOCK,
+						demangled_name, domain);
+      if (bsym.symbol != nullptr)
+	return bsym;
+    }
+
+  return {};
+}
+
+/* See symtab.h.  */
+
 struct block_symbol
 lookup_global_symbol (const char *name,
 		      const struct block *block,
diff --git a/gdb/symtab.h b/gdb/symtab.h
index 0a7ad3668b4..3feac5fcf6c 100644
--- a/gdb/symtab.h
+++ b/gdb/symtab.h
@@ -2735,6 +2735,14 @@  extern struct block_symbol
 				     const char *name,
 				     const domain_search_flags domain);
 
+/* Lookup symbol NAME from DOMAIN in the linker namespace NSID.
+   This generates a list of all objfiles in NSID, then searches
+   those objfiles for the given symbol.  Searches for both global or
+   static symbols.  */
+extern struct block_symbol
+  lookup_symbol_in_linker_namespace (const char *name, LONGEST nsid,
+				     const domain_search_flags domain);
+
 extern unsigned int symtab_create_debug;
 
 /* Print a "symtab-create" debug statement.  */
diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids-lib.c b/gdb/testsuite/gdb.base/dlmopen-ns-ids-lib.c
index 99846fe0f96..ae74cab61bf 100644
--- a/gdb/testsuite/gdb.base/dlmopen-ns-ids-lib.c
+++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids-lib.c
@@ -26,3 +26,17 @@  inc (int n)
   int amount = gdb_dlmopen_glob;
   return n + amount;  /* bp.inc.  */
 }
+
+static int
+change_global (int n)
+{
+  gdb_dlmopen_glob += n;
+  return n;
+}
+
+__attribute__((visibility ("default")))
+int
+func_with_other_call (int n)
+{
+  return change_global (n + 1);
+}
diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c b/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c
index 4f08a5135b2..02b2bbe6417 100644
--- a/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c
+++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c
@@ -47,6 +47,9 @@  main (void)
       fun (dl);
     }
 
+  fun = dlsym (handle[0], "func_with_other_call");
+  fun (0);
+
   dlclose (handle[0]); /* TAG: first dlclose */
   dlclose (handle[1]); /* TAG: second dlclose */
   dlclose (handle[2]); /* TAG: third dlclose */
diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
index e40bc213454..72b92304325 100644
--- a/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
+++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp
@@ -123,6 +123,10 @@  proc get_first_so_ns {} {
     return $ns
 }
 
+proc ns_id_for_command {ns} {
+    return "\[\[$ns\]\]"
+}
+
 # Run the tests relating to the command "info sharedlibrary", to
 # verify that the namespace ID is consistent.
 proc test_info_shared {} {
@@ -311,6 +315,55 @@  proc test_info_linker_namespaces {} {
 		    ".*" ] "print namespaces with no argument"
 }
 
+# Test that we can use the [[N]]::symbol syntax, and its variations
+# like [[N]]::func::symbol.
+proc_with_prefix test_print_namespace_symbol {} {
+    clean_restart
+    gdb_load $::binfile
+
+    set ns1 [ns_id_for_command 1]
+    set ns2 [ns_id_for_command 2]
+
+    if { ![runto_main] } {
+	return
+    }
+
+    gdb_breakpoint [gdb_get_line_number "TAG: first dlclose"]
+    gdb_breakpoint "change_global" allow-pending
+
+    # Test printing variables for non-existent namespace.
+    gdb_test "print ${ns1}::gdb_dlmopen_glob" "Namespace ..1.. is inactive" \
+	"Before loading namespaces"
+
+    gdb_continue_to_breakpoint "change_global"
+
+    # Test finding location of variables.
+    gdb_test "print ${ns1}::gdb_dlmopen_glob" ".* = 0" "before changing"
+    gdb_test_no_output "set ${ns1}::gdb_dlmopen_glob = 1"
+    gdb_test "print ${ns1}::gdb_dlmopen_glob" ".* = 1" "after changing"
+    gdb_test "print ${ns2}::gdb_dlmopen_glob" ".* = 0" "other namespace"
+
+    gdb_test "print gdb_dlmopen_glob" ".* = 1" "changed the right variable"
+
+    # Test finding functions in specific namespaces.
+    gdb_test "print ${ns1}::inc (2)" ".* = 3"
+    gdb_test "print ${ns2}::inc (2)" ".* = 2"
+
+    # Test finding a specific block in the backtrace.
+    gdb_test "print n" ".* = 1"
+    gdb_test "print ${ns1}::func_with_other_call::n" ".* = 0"
+    gdb_test "print ${ns2}::func_with_other_call::n" \
+	"No frame is currently executing in block func_with_other_call."
+
+    # Leave to default namespace.
+    gdb_continue_to_breakpoint "TAG: first dlclose"
+    # This global doesn't exist in the default namespace.  Rather than
+    # returning a random one, we just say we didn't find one.
+    gdb_test "print gdb_dlmopen_glob" \
+	"No symbol .gdb_dlmopen_glob. in the current linker namespace."
+}
+
 test_info_shared
 test_conv_vars
 test_info_linker_namespaces
+test_print_namespace_symbol