From patchwork Mon Jun 1 19:22:50 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guinevere Larsen X-Patchwork-Id: 136204 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 43F224BA2E30 for ; Mon, 1 Jun 2026 19:24:30 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 43F224BA2E30 Authentication-Results: sourceware.org; dkim=fail reason="signature verification failed" (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=cn+p5sdL X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id D70F04BA2E0E for ; Mon, 1 Jun 2026 19:23:46 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org D70F04BA2E0E Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org D70F04BA2E0E Authentication-Results: sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1780341827; cv=none; b=BO9GAONZhMhcV67aM06LWexkhV77FwkD7t0KkA+si7Sa7QBkc1X+mxb6flVWqFFxQ8QVw/S0jDlAjj0zjafbxKDPeG8woBTbZckwLmvKKxwPElx73zyi/9ITaGPjiHkhiAN5usM+zCv/ee85dk2GFB1UsDlygEljBoEIgFpC/M4= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1780341827; c=relaxed/simple; bh=FxHdquZFRUe3QLGOtK4eS2Rh6avv9dK0HxoTxEBPMAA=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=vv/3igEMPtSvDKSBf5Jr9xiXfYDNOxbhPpzy/YmbVeArBD59gp/6cW8Ttv0UMCDQiqgc2awtPBYDP02/HpAnBwYynNjJvuBkS7V1BEtZSGawTwq3hzUip6ZXFUmWdtkPM0eNGWr0fgKrY98Soc26fjn6NPNxcW1Q5MT2lQ8TXIw= ARC-Authentication-Results: i=1; sourceware.org; dkim=fail (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=cn+p5sdL reason="signature verification failed" DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org D70F04BA2E0E DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780341826; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=9Hm+lo+buMdskH+Vk91a5WR9UQdnaDQyygq2w1LxXog=; b=cn+p5sdL4gOimYkCc4mWlxG14oETt2xDeqilXvCmBOGAzxgVUrcJtOXi9jHWoBmPROtPcx IhQtbiW7oQ9/42OTa++vqlSSTbUEogMe2hl94umAGHyYoefMxXZlB0uXOkKUtLxpaxcqPo L8YuvRI9k8U1wG9/58R8nz6F66n/8N4= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-486-sHNo4Bl8P4aCoo2lWWQ8lg-1; Mon, 01 Jun 2026 15:23:45 -0400 X-MC-Unique: sHNo4Bl8P4aCoo2lWWQ8lg-1 X-Mimecast-MFC-AGG-ID: sHNo4Bl8P4aCoo2lWWQ8lg_1780341824 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 612D819560B8 for ; Mon, 1 Jun 2026 19:23:43 +0000 (UTC) Received: from fedora.redhat.com (unknown [10.96.134.144]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 2100B19560A6; Mon, 1 Jun 2026 19:23:41 +0000 (UTC) From: Guinevere Larsen To: gdb-patches@sourceware.org Cc: Guinevere Larsen Subject: [PATCH v7 4/5] gdb: extend the [[N]]::foo syntax for files Date: Mon, 1 Jun 2026 16:22:50 -0300 Message-ID: <20260601192251.60958-5-guinevere@redhat.com> In-Reply-To: <20260601192251.60958-1-guinevere@redhat.com> References: <20260601192251.60958-1-guinevere@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: AYX82CzX03Fiims7_xrL_tS2EKOoB9QRas8x6t2zrYw_1780341824 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-8.3 required=5.0 tests=BAYES_00, DKIM_INVALID, DKIM_SIGNED, GIT_PATCH_0, KAM_DMARC_QUARANTINE, KAM_DMARC_STATUS, RCVD_IN_DNSWL_BLOCKED, RCVD_IN_HOSTKARMA_W, RCVD_IN_MSPIKE_H5, RCVD_IN_MSPIKE_WL, RCVD_IN_SBL_CSS, SPF_HELO_PASS, SPF_NONE, TXREP shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~patchwork=sourceware.org@sourceware.org This commit implements the missing support for [[N]]::'file.c'::var syntax that was skipped on the previous commit. This is done with a new rule, 'block: block COLONCOLON FILENAME', a few changes to classify_name and adding a value to the parser_state. classify_name now restricts its search for files to ones available in the provided linker namespace (if there was one), which is found in the parser_state. To explain why the search has to be restricted, imagine the following scenario: An inferior has 2 linker namespaces. In namespace 0, there is a library with the file 'foo' loaded; in namespace 1, there is a function 'foo', which is in the stack. If the user were to type [[1]]::foo::var, it could be possible that foo is mis-classified as a filename, then when executing the rule 'block: block COLONCOLON FILENAME' we search for the file with the filename restriction and error out because there are no files named that. Therefore, the safest way to ensure we're always classifying things correctly is to use the linker namespace in the classify_name function. And the reason the linker namespace must be in the parser_state is because, while running classify_name, we don't have access to the previous states to see if there was a LINKER_NAMESPACE token, so adding it to the global state is the only way to pass it along. The new rule only exists to make sure that it is only used in the intended syntax, instead of allowing for something like function::filename::variable. This commit also adds a link to the solib_ops relevant to the expression being parsed, as a way to identify if linker namespaces are supported; this could be achieved by guarding the search functions instead, but I think that this is more reliable. The new rule uses "block" rather than a linker namespace specific token, because if we allowed that token to also reduce into block, the new rule would never be triggered as the simple reduction would be preferred. If we didn't allow the linker namespace to reduce into block, the rest of the syntaxes for [[N]]::foo would be more involved, so the option in this commit seemed like the simplest solution. The rule "block: block COLONCOLON name" also had to be updated because, if a user tried to search for a file not present in the linker namespace, GDB would miscategorize it to 'name' instead of 'filename', and if no function of the same name was found, the error would only say "No function \"foo\" (...)" which could confuse a user. The new error says "Nothing named \"foo\" (...)" instead since it is impossible for us to disambiguate between filename or function name, and both would be valid in this code path. The parser_state changes were not used for the previous commit because the lookup_symbol calls would lead to an even more intrusive change for no real gain, since the search would be rerun any way. There is an argument to be made that the results could be different if only namespace had a type and another had a variable, but a situation where that significantly changes the result is quite unlikely, I think. --- gdb/c-exp.y | 23 ++++++++++-- gdb/linespec.c | 4 +-- gdb/parse.c | 3 +- gdb/parser-defs.h | 37 ++++++++++++++++++-- gdb/rust-parse.c | 3 +- gdb/symtab.c | 37 +++++++++++++------- gdb/symtab.h | 4 ++- gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c | 2 ++ gdb/testsuite/gdb.base/dlmopen-ns-ids.exp | 21 +++++++++++ 9 files changed, 112 insertions(+), 22 deletions(-) diff --git a/gdb/c-exp.y b/gdb/c-exp.y index 8e405e4dc41..f152b1d53f0 100644 --- a/gdb/c-exp.y +++ b/gdb/c-exp.y @@ -1064,15 +1064,18 @@ block : BLOCKNAME copy_name ($1.stoken).c_str ()); $$.search_namespace = false; $$.b_val = $1.sym.symbol->value_block (); + pstate->reset_linker_namespace (); } | FILENAME { $$ = $1; + pstate->reset_linker_namespace (); } | DOUBLESQUAREOPEN INT DOUBLESQUARECLOSE { $$.search_namespace = true; $$.namespace_val = $2.val; + pstate->set_linker_namespace ($2.val); } ; @@ -1089,12 +1092,26 @@ block : block COLONCOLON name SEARCH_FUNCTION_DOMAIN, nullptr); - if (tem.symbol == nullptr) + if (tem.symbol == nullptr && !$1.search_namespace) error (_("No function \"%ps\" in specified context."), styled_string (function_name_style.style (), copy.c_str ())); + else if (tem.symbol == nullptr && $1.search_namespace) + /* COPY can be a function or a file. There is no way + to identify which the user intended, so emit a + generic warning instead. */ + error (_("Nothing named \"%s\" in specified context."), + copy.c_str ()); $$.b_val = tem.symbol->value_block (); $$.search_namespace = false; + pstate->reset_linker_namespace (); + } + | block COLONCOLON FILENAME + { + if (!$1.search_namespace) + error (_("Filename must be the first part of the expression")); + $$ = $3; + pstate->reset_linker_namespace (); } ; @@ -1131,6 +1148,7 @@ variable: block COLONCOLON name pstate->block_tracker->update (sym); pstate->push_new (sym); + pstate->reset_linker_namespace (); } ; @@ -3207,7 +3225,8 @@ classify_name (struct parser_state *par_state, const struct block *block, || is_quoted_name) { /* See if it's a file name. */ - if (auto symtab = lookup_symtab (current_program_space, copy.c_str ()); + if (auto symtab = lookup_symtab (current_program_space, copy.c_str (), + par_state->get_linker_namespace ()); symtab != nullptr) { yylval.bval.b_val diff --git a/gdb/linespec.c b/gdb/linespec.c index 5371d8426ae..38a30f04f77 100644 --- a/gdb/linespec.c +++ b/gdb/linespec.c @@ -3628,11 +3628,11 @@ collect_symtabs_from_filename (const char *file, if (pspace->executing_startup) continue; - for_each_symtab (pspace, file, collector); + for_each_symtab (pspace, file, -1, collector); } } else - for_each_symtab (search_pspace, file, collector); + for_each_symtab (search_pspace, file, -1, collector); /* It is tempting to use the unordered_dense 'extract' method here, and remove the separate vector -- but it's unclear if ordering diff --git a/gdb/parse.c b/gdb/parse.c index fd190d6e15e..2ed52d689a1 100644 --- a/gdb/parse.c +++ b/gdb/parse.c @@ -426,7 +426,8 @@ parse_exp_in_context (const char **stringptr, CORE_ADDR pc, parser_state ps (lang, get_current_arch (), expression_context_block, expression_context_pc, flags, *stringptr, - completer != nullptr, tracker); + completer != nullptr, tracker, + current_program_space->solib_ops ()); scoped_restore_current_language lang_saver (lang->la_language); diff --git a/gdb/parser-defs.h b/gdb/parser-defs.h index a424f0c7b6f..299fd2b74af 100644 --- a/gdb/parser-defs.h +++ b/gdb/parser-defs.h @@ -26,6 +26,7 @@ #include "expression.h" #include "symtab.h" #include "expop.h" +#include "solib.h" struct block; struct language_defn; @@ -147,7 +148,8 @@ struct parser_state : public expr_builder parser_flags flags, const char *input, bool completion, - innermost_block_tracker *tracker) + innermost_block_tracker *tracker, + const solib_ops *ops) : expr_builder (lang, gdbarch), expression_context_block (context_block), expression_context_pc (context_pc), @@ -157,7 +159,8 @@ struct parser_state : public expr_builder comma_terminates ((flags & PARSER_COMMA_TERMINATES) != 0), parse_completion (completion), void_context_p ((flags & PARSER_VOID_CONTEXT) != 0), - debug ((flags & PARSER_DEBUG) != 0) + debug ((flags & PARSER_DEBUG) != 0), + m_solib_ops (ops) { } @@ -267,6 +270,26 @@ struct parser_state : public expr_builder push (expr::make_operation (std::move (lhs), std::move (rhs))); } + void set_linker_namespace (LONGEST ns_id) + { + if (m_solib_ops == nullptr) + error (_("Linker namespaces require an active inferior")); + if (m_solib_ops->supports_namespaces ()) + m_linker_namespace = ns_id; + else + error (_("Linker namespaces are not supported")); + } + + void reset_linker_namespace () + { + m_linker_namespace = -1; + } + + LONGEST get_linker_namespace () const + { + return m_linker_namespace; + } + /* Function called from the various parsers' yyerror functions to throw an error. The error will include a message identifying the location of the error within the current expression. */ @@ -327,6 +350,16 @@ struct parser_state : public expr_builder /* Stack of operations. */ std::vector m_operations; + + /* If the expression is being restricted to a specific namespace, this is + where that information is stored for the block lookup. It should be + accessed through setter/getters to ensure that the linker namespace is + only set when the gdbarch supports it. */ + LONGEST m_linker_namespace = -1; + + /* Used to figure out if an inferior is capable of handling linker + namespaces at all. */ + const solib_ops *m_solib_ops; }; /* A string token, either a char-string or bit-string. Char-strings are diff --git a/gdb/rust-parse.c b/gdb/rust-parse.c index 0c4f2a501ab..728fc88842b 100644 --- a/gdb/rust-parse.c +++ b/gdb/rust-parse.c @@ -2305,7 +2305,8 @@ rust_lex_tests (void) { /* Set up dummy "parser", so that rust_type works. */ parser_state ps (language_def (language_rust), current_inferior ()->arch (), - nullptr, 0, 0, nullptr, 0, nullptr); + nullptr, 0, 0, nullptr, 0, nullptr, + current_program_space->solib_ops ()); rust_parser parser (&ps); rust_lex_test_one (&parser, "", 0); diff --git a/gdb/symtab.c b/gdb/symtab.c index 5eab8ad600a..d7317a758b1 100644 --- a/gdb/symtab.c +++ b/gdb/symtab.c @@ -630,14 +630,11 @@ compare_filenames_for_search (const char *filename, const char *search_name) && STRIP_DRIVE_SPEC (filename) == &filename[len - search_len])); } -/* Return the first symtab in PSPACE matching NAME and for which - CALLBACK returns true. - - See documentation for for_each_symtab for how exactly NAME is matched. */ +/* See symtab.h. */ static symtab * find_symtab (program_space *pspace, const char *name, - find_symtab_callback_ftype callback) + LONGEST linker_ns, find_symtab_callback_ftype callback) { gdb::unique_xmalloc_ptr real_path; @@ -649,10 +646,23 @@ find_symtab (program_space *pspace, const char *name, gdb_assert (IS_ABSOLUTE_PATH (real_path.get ())); } - for (objfile &objfile : pspace->objfiles ()) + std::vector objfiles_to_search; + if (linker_ns >= 0) + { + gdb_assert (pspace->solib_ops ()->supports_namespaces ()); + objfiles_to_search + = get_objfiles_in_linker_namespace (linker_ns, pspace); + } + else + { + for (objfile &objf : pspace->objfiles ()) + objfiles_to_search.push_back (&objf); + } + + for (objfile *objfile : objfiles_to_search) if (symtab *result - = objfile.find_symtab_matching_filename (name, real_path.get (), - callback); + = objfile->find_symtab_matching_filename (name, real_path.get (), + callback); result != nullptr) return result; @@ -663,9 +673,9 @@ find_symtab (program_space *pspace, const char *name, void for_each_symtab (program_space *pspace, const char *name, - for_each_symtab_callback_ftype callback) + LONGEST linker_ns, for_each_symtab_callback_ftype callback) { - find_symtab (pspace, name, [&] (symtab *symtab) + find_symtab (pspace, name, linker_ns, [&] (symtab *symtab) { callback (symtab); return false; @@ -675,9 +685,10 @@ for_each_symtab (program_space *pspace, const char *name, /* See symtab.h. */ symtab * -lookup_symtab (program_space *pspace, const char *name) +lookup_symtab (program_space *pspace, const char *name, LONGEST linker_ns) { - return find_symtab (pspace, name, [&] (symtab *symtab) { return true; }); + return find_symtab (pspace, name, linker_ns, + [&] (symtab *symtab) { return true; }); } @@ -6192,7 +6203,7 @@ collect_file_symbol_completion_matches (completion_tracker &tracker, /* Go through symtabs for SRCFILE and check the externs and statics for symbols which match. */ - for_each_symtab (current_program_space, srcfile, [&] (symtab *s) + for_each_symtab (current_program_space, srcfile, -1, [&] (symtab *s) { add_symtab_completions (s->compunit (), tracker, mode, lookup_name, diff --git a/gdb/symtab.h b/gdb/symtab.h index 3feac5fcf6c..f73aedd9c02 100644 --- a/gdb/symtab.h +++ b/gdb/symtab.h @@ -2074,7 +2074,8 @@ const char *multiple_symbols_select_mode (void); /* Lookup a symbol table in PSPACE by source file name. */ -extern symtab *lookup_symtab (program_space *pspace, const char *name); +extern symtab *lookup_symtab (program_space *pspace, const char *name, + LONGEST linker_ns = -1); /* An object of this type is passed as the 'is_a_field_of_this' argument to lookup_symbol and lookup_symbol_in_language. */ @@ -2806,6 +2807,7 @@ using for_each_symtab_callback_ftype = std::function; Call CALLBACK with each symtab that is found. */ void for_each_symtab (program_space *pspace, const char *name, + LONGEST linker_ns, for_each_symtab_callback_ftype callback); /* Callback type for function find_symtab. */ diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c b/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c index 02b2bbe6417..35c5124a65e 100644 --- a/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c +++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids-main.c @@ -24,6 +24,8 @@ #include #include +int global_main_file = 1312; + int main (void) { diff --git a/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp index 72b92304325..78daed4518e 100644 --- a/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp +++ b/gdb/testsuite/gdb.base/dlmopen-ns-ids.exp @@ -321,9 +321,15 @@ proc_with_prefix test_print_namespace_symbol {} { clean_restart gdb_load $::binfile + set ns0 [ns_id_for_command 0] set ns1 [ns_id_for_command 1] set ns2 [ns_id_for_command 2] + # Test printing variables before starting the inferior + gdb_test "print ${ns1}::gdb_dlmopen_glob" \ + "Linker namespaces require an active inferior" \ + "Before starting inferior" + if { ![runto_main] } { return } @@ -355,12 +361,27 @@ proc_with_prefix test_print_namespace_symbol {} { gdb_test "print ${ns2}::func_with_other_call::n" \ "No frame is currently executing in block func_with_other_call." + # Test that complex expressions work with multiple namespaces. + # At this point, the global in namespace 1 is 0, and in namespace 2 is 1 + # and the current namespace is 1. + gdb_test "print ${ns2}::gdb_dlmopen_glob + gdb_dlmopen_glob" ".* = 1" + gdb_test "print ${ns2}::'${::srcfile_lib}'::gdb_dlmopen_glob + gdb_dlmopen_glob" \ + ".* = 1" + gdb_test "print ${ns2}::'${::srcfile_lib}'::gdb_dlmopen_glob + '${::srcfile}'::global_main_file" \ + ".* = 1312" + # 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." + + # Minimal testing for finding files in namespaces. + gdb_test "print ${ns1}::'${::srcfile_lib}'::gdb_dlmopen_glob" \ + ".* = 2" + gdb_test "print ${ns0}::'${::srcfile_lib}'::gdb_dlmopen_glob" \ + "Nothing named .${::srcfile_lib}. in specified context." } test_info_shared