From patchwork Thu Nov 28 03:54:00 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Aaron Merey X-Patchwork-Id: 102001 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 3AABA3858C39 for ; Thu, 28 Nov 2024 03:56:56 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 3AABA3858C39 Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=duX/tXmv 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 4989E3858C42 for ; Thu, 28 Nov 2024 03:54:15 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 4989E3858C42 Authentication-Results: sourceware.org; dmarc=pass (p=none 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 4989E3858C42 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1732766055; cv=none; b=MQDXA4o71MPElFweQ9FIvRrVevyK4+MCD27I/tJGg38rufnAdLlot4bmRD24Cjok8OLaoVF4rK10VmtASW1k3PW9F+04S6q0y/gIoNtoWoa08oDmwYHwszDZxu8vt7jb3E/AZoLaiqyF+Ve4HVXQ8V0lI9OnhAsoRp6wLhVGMM4= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1732766055; c=relaxed/simple; bh=uazg8pxMFAL+oEc6ASvvzOs0jEI3b+U6JfYOr5MI2CM=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=P61me0qRD3cgtfLICQm/W0QNhoqjiVmSGxEU+l0SNNg24fHY2r/6rke+MnTMH3QQ8i+B45HO6BiOIoCMwXnOwxMGsp5sdiEaJmZ1BVkFKxhBHLDFf9Lqfd5opE76+QXerJDQCjl/SO6xk0isEW/nlX7EoGjQcH4WXzS+9QdmEKQ= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 4989E3858C42 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1732766055; 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=vkIcnRMRwHKzzYSEW2Ei3cSCj+mAi2J1ikqjKUXEGjY=; b=duX/tXmvhz6lFcFztzLr1QHig/6toe58VVF2ZJwUMWO2kwvttK1pEXaTy0lvBIwoJRQp6u wVU0HJQkznfmb7jwvwkq9haFcumyuuTaBfWPMSkbFX96lNBrfOTzxK/JOtLjDqjKnLgOAa SDp6VzwCgLT0UL+I2fBzf+zUXsZTtjA= Received: from mx-prod-mc-04.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-628-rHVliy6eM4Kr56dIFB2SFw-1; Wed, 27 Nov 2024 22:54:13 -0500 X-MC-Unique: rHVliy6eM4Kr56dIFB2SFw-1 X-Mimecast-MFC-AGG-ID: rHVliy6eM4Kr56dIFB2SFw 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-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 92A1619560B0 for ; Thu, 28 Nov 2024 03:54:12 +0000 (UTC) Received: from fedora.redhat.com (unknown [10.22.64.69]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id BEC5F195E480; Thu, 28 Nov 2024 03:54:11 +0000 (UTC) From: Aaron Merey To: gdb-patches@sourceware.org Cc: aburgess@redhat.com, Aaron Merey Subject: [PATCH 1/3 v4] gdb/progspace: Add reverse safe iterator Date: Wed, 27 Nov 2024 22:54:00 -0500 Message-ID: <20241128035402.438052-2-amerey@redhat.com> In-Reply-To: <20241128035402.438052-1-amerey@redhat.com> References: <20241128035402.438052-1-amerey@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: BxdzRQQ-r8cEuZG0sODsUCuq39Z0lP1w5kZYXDw-lsc_1732766052 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-11.6 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.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 patch changes progspace objfile_list insertion so that separate debug objfiles are placed into the list after the parent objfile, instead of before. Additionally qf_require_partial_symbols now returns a safe_range. These changes are intended to prepare gdb for on-demand debuginfo downloading and the downloading of .gdb_index sections. With on-demand downloading enabled, gdb might need to delete a .gdb_index quick_symbol_functions from a parent objfile while looping the objfile's list of quick_symbol_functions because the separate debug objfile has just been downloaded. The use of a safe_range prevents this removal from causing iterator invalidation. gdb might also download a debuginfo file during symtab expansion. In this case an objfile will be added to the current progspace's objfiles_list during iteration over the list (for example, in iterate_over_symtabs). We want these loops to also iterate over newly downloaded objfiles. So objfiles need to be inserted into objfiles_list after their parent since it is during the search of the parent objfile for some symbol or filename that the separate debug objfile might be downloaded. To facilitate the safe deletion of objfiles, this patch also adds basic_safe_reverse_range and basic_safe_reverse_iterator. This allows objfiles to be removed from the objfiles_list in a loop without iterator invalidation. If a forward safe iterator were to be used, the deletion of an objfile could invalidate the safe iterator's reference to the next objfile in the objfiles_list. This can happen when the deletion of an objfile causes the deletion of a separate debug objfile that happens to the be next element in the objfiles_list. The standard reverse iterator is not suitable for safe objfile deletion. In order to safely delete the first objfile in the objfiles_list, the standard reverse iterator's underlying begin iterator would have to be decremented, resulting in undefined behavior. A small change was also made to a testcase in py-objfile.exp to account for the new placement of separate debug objfiles in objfiles_list. --- v4: rebased onto gdb's current master branch v3: https://sourceware.org/pipermail/gdb-patches/2024-September/211524.html gdb/jit.c | 7 +- gdb/objfiles.c | 16 +++- gdb/objfiles.h | 17 +++- gdb/progspace.c | 19 +++- gdb/progspace.h | 30 ++++-- gdb/symfile-debug.c | 34 +++---- gdb/testsuite/gdb.python/py-objfile.exp | 2 +- gdbsupport/safe-iterator.h | 118 ++++++++++++++++++++++++ 8 files changed, 201 insertions(+), 42 deletions(-) diff --git a/gdb/jit.c b/gdb/jit.c index 77d41bf86ba..24507cd6876 100644 --- a/gdb/jit.c +++ b/gdb/jit.c @@ -1244,11 +1244,10 @@ jit_breakpoint_re_set (void) static void jit_inferior_exit_hook (struct inferior *inf) { - for (objfile *objf : current_program_space->objfiles_safe ()) + current_program_space->remove_objfiles_if ([&] (const objfile *objf) { - if (objf->jited_data != nullptr && objf->jited_data->addr != 0) - objf->unlink (); - } + return (objf->jited_data != nullptr) && (objf->jited_data->addr != 0); + }); } void diff --git a/gdb/objfiles.c b/gdb/objfiles.c index 207a0cb22eb..fa5d43ad70b 100644 --- a/gdb/objfiles.c +++ b/gdb/objfiles.c @@ -411,6 +411,14 @@ objfile::unlink () this->pspace ()->remove_objfile (this); } +/* See objfiles.h. */ + +qf_safe_range +objfile::qf_safe () +{ + return qf_safe_range (qf_range (qf.begin (), qf.end ())); +} + /* Free all separate debug objfile of OBJFILE, but don't free OBJFILE itself. */ @@ -723,14 +731,12 @@ have_full_symbols (program_space *pspace) void objfile_purge_solibs (program_space *pspace) { - for (objfile *objf : pspace->objfiles_safe ()) + pspace->remove_objfiles_if ([&] (const objfile *objf) { /* We assume that the solib package has been purged already, or will be soon. */ - - if (!(objf->flags & OBJF_USERLOADED) && (objf->flags & OBJF_SHARED)) - objf->unlink (); - } + return !(objf->flags & OBJF_USERLOADED) && (objf->flags & OBJF_SHARED); + }); } /* See objfiles.h. */ diff --git a/gdb/objfiles.h b/gdb/objfiles.h index 2121fd31792..29f88266e39 100644 --- a/gdb/objfiles.h +++ b/gdb/objfiles.h @@ -365,6 +365,12 @@ class separate_debug_iterator typedef iterator_range separate_debug_range; +/* See objfile::qf_safe. */ + +using qf_list = std::forward_list; +using qf_range = iterator_range; +using qf_safe_range = basic_safe_range; + /* Sections in an objfile. The section offsets are stored in the OBJFILE. */ @@ -770,8 +776,15 @@ struct objfile : intrusive_list_node const struct sym_fns *sf = nullptr; /* The "quick" (aka partial) symbol functions for this symbol - reader. */ - std::forward_list qf; + reader. Many quick_symbol_functions methods may result + in the deletion of a quick_symbol_functions from this + qf_list. It is recommended that qf_safe be used to iterate + over the qf_list. */ + qf_list qf; + + /* Returns an iterable object that allows for safe deletion during + iteration. See gdbsupport/safe-iterator.h. */ + qf_safe_range qf_safe (); /* Per objfile data-pointers required by other GDB modules. */ diff --git a/gdb/progspace.c b/gdb/progspace.c index 0734b44414a..8ba5f4d8fcc 100644 --- a/gdb/progspace.c +++ b/gdb/progspace.c @@ -148,14 +148,14 @@ program_space::free_all_objfiles () void program_space::add_objfile (std::unique_ptr &&objfile, - struct objfile *before) + struct objfile *after) { - if (before == nullptr) + if (after == nullptr) m_objfiles_list.push_back (std::move (objfile)); else { - gdb_assert (before->is_linked ()); - m_objfiles_list.insert (m_objfiles_list.iterator_to (*before), + gdb_assert (after->is_linked ()); + m_objfiles_list.insert (++m_objfiles_list.iterator_to (*after), std::move (objfile)); } } @@ -180,6 +180,17 @@ program_space::remove_objfile (struct objfile *objfile) /* See progspace.h. */ +void +program_space::remove_objfiles_if + (gdb::function_view predicate) +{ + for (objfile *objf : objfiles_safe ()) + if (predicate (objf)) + remove_objfile (objf); +} + +/* See progspace.h. */ + struct objfile * program_space::objfile_for_address (CORE_ADDR address) { diff --git a/gdb/progspace.h b/gdb/progspace.h index d426dfaabf9..fe844e3976c 100644 --- a/gdb/progspace.h +++ b/gdb/progspace.h @@ -196,26 +196,33 @@ struct program_space return objfiles_range (objfiles_iterator (m_objfiles_list.begin ())); } - using objfiles_safe_range = basic_safe_range; + using objfiles_safe_reverse_range + = basic_safe_reverse_range; /* An iterable object that can be used to iterate over all objfiles. The basic use is in a foreach, like: for (objfile *objf : pspace->objfiles_safe ()) { ... } - This variant uses a basic_safe_iterator so that objfiles can be - deleted during iteration. */ - objfiles_safe_range objfiles_safe () + This variant uses a basic_safe_reverse_iterator so that objfiles + can be deleted during iteration. + + The use of a reverse iterator helps ensure that separate debug + objfiles are deleted before their parent objfile. This prevents + iterator invalidation due to the deletion of a parent objfile. */ + + objfiles_safe_reverse_range objfiles_safe () { - return objfiles_safe_range - (objfiles_range (objfiles_iterator (m_objfiles_list.begin ()))); + return objfiles_safe_reverse_range + (objfiles_range (objfiles_iterator (m_objfiles_list.begin ()), + objfiles_iterator (m_objfiles_list.end ()))); } - /* Add OBJFILE to the list of objfiles, putting it just before - BEFORE. If BEFORE is nullptr, it will go at the end of the + /* Add OBJFILE to the list of objfiles, putting it just after + AFTER. If AFTER is nullptr, it will go at the end of the list. */ void add_objfile (std::unique_ptr &&objfile, - struct objfile *before); + struct objfile *after); /* Remove OBJFILE from the list of objfiles. */ void remove_objfile (struct objfile *objfile); @@ -227,6 +234,11 @@ struct program_space /* Free all the objfiles associated with this program space. */ void free_all_objfiles (); + /* Remove all objfiles associated with this program space for which + PREDICATE evaluates to true. */ + void remove_objfiles_if + (gdb::function_view predicate); + /* Return the objfile containing ADDRESS, or nullptr if the address is outside all objfiles in this progspace. */ struct objfile *objfile_for_address (CORE_ADDR address); diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c index b21a5a433f3..60ed1d5cd8c 100644 --- a/gdb/symfile-debug.c +++ b/gdb/symfile-debug.c @@ -84,7 +84,7 @@ objfile::has_partial_symbols () them, then that is an indication that they are in fact available. Without this function the symbols may have been already read in but they also may not be present in this objfile. */ - for (const auto &iter : qf) + for (const auto &iter : qf_safe ()) { retval = iter->has_symbols (this); if (retval) @@ -107,7 +107,7 @@ objfile::has_unexpanded_symtabs () objfile_debug_name (this)); bool result = false; - for (const auto &iter : qf) + for (const auto &iter : qf_safe ()) { if (iter->has_unexpanded_symtabs (this)) { @@ -132,7 +132,7 @@ objfile::find_last_source_symtab () gdb_printf (gdb_stdlog, "qf->find_last_source_symtab (%s)\n", objfile_debug_name (this)); - for (const auto &iter : qf) + for (const auto &iter : qf_safe ()) { retval = iter->find_last_source_symtab (this); if (retval != nullptr) @@ -156,7 +156,7 @@ objfile::forget_cached_source_info () for (compunit_symtab *cu : compunits ()) cu->forget_cached_source_info (); - for (const auto &iter : qf) + for (const auto &iter : qf_safe ()) iter->forget_cached_source_info (this); } @@ -203,7 +203,7 @@ objfile::map_symtabs_matching_filename return result; }; - for (const auto &iter : qf) + for (const auto &iter : qf_safe ()) { if (!iter->expand_symtabs_matching (this, match_one_filename, @@ -266,7 +266,7 @@ objfile::lookup_symbol (block_enum kind, const lookup_name_info &name, return true; }; - for (const auto &iter : qf) + for (const auto &iter : qf_safe ()) { if (!iter->expand_symtabs_matching (this, nullptr, @@ -296,7 +296,7 @@ objfile::print_stats (bool print_bcache) gdb_printf (gdb_stdlog, "qf->print_stats (%s, %d)\n", objfile_debug_name (this), print_bcache); - for (const auto &iter : qf) + for (const auto &iter : qf_safe ()) iter->print_stats (this, print_bcache); } @@ -307,7 +307,7 @@ objfile::dump () gdb_printf (gdb_stdlog, "qf->dump (%s)\n", objfile_debug_name (this)); - for (const auto &iter : qf) + for (const auto &iter : qf_safe ()) iter->dump (this); } @@ -322,7 +322,7 @@ objfile::expand_symtabs_for_function (const char *func_name) lookup_name_info base_lookup (func_name, symbol_name_match_type::FULL); lookup_name_info lookup_name = base_lookup.make_ignore_params (); - for (const auto &iter : qf) + for (const auto &iter : qf_safe ()) iter->expand_symtabs_matching (this, nullptr, &lookup_name, @@ -340,7 +340,7 @@ objfile::expand_all_symtabs () gdb_printf (gdb_stdlog, "qf->expand_all_symtabs (%s)\n", objfile_debug_name (this)); - for (const auto &iter : qf) + for (const auto &iter : qf_safe ()) iter->expand_all_symtabs (this); } @@ -358,7 +358,7 @@ objfile::expand_symtabs_with_fullname (const char *fullname) return filename_cmp (basenames ? basename : fullname, filename) == 0; }; - for (const auto &iter : qf) + for (const auto &iter : qf_safe ()) iter->expand_symtabs_matching (this, file_matcher, nullptr, @@ -391,7 +391,7 @@ objfile::expand_symtabs_matching host_address_to_string (&expansion_notify), domain_name (domain).c_str ()); - for (const auto &iter : qf) + for (const auto &iter : qf_safe ()) if (!iter->expand_symtabs_matching (this, file_matcher, lookup_name, symbol_matcher, expansion_notify, search_flags, domain, @@ -417,7 +417,7 @@ objfile::find_pc_sect_compunit_symtab (bound_minimal_symbol msymbol, host_address_to_string (section), warn_if_readin); - for (const auto &iter : qf) + for (const auto &iter : qf_safe ()) { retval = iter->find_pc_sect_compunit_symtab (this, msymbol, pc, section, warn_if_readin); @@ -445,7 +445,7 @@ objfile::map_symbol_filenames (gdb::function_view fun, objfile_debug_name (this), need_fullname); - for (const auto &iter : qf) + for (const auto &iter : qf_safe ()) iter->map_symbol_filenames (this, fun, need_fullname); } @@ -457,7 +457,7 @@ objfile::compute_main_name () "qf->compute_main_name (%s)\n", objfile_debug_name (this)); - for (const auto &iter : qf) + for (const auto &iter : qf_safe ()) iter->compute_main_name (this); } @@ -471,7 +471,7 @@ objfile::find_compunit_symtab_by_address (CORE_ADDR address) hex_string (address)); struct compunit_symtab *result = NULL; - for (const auto &iter : qf) + for (const auto &iter : qf_safe ()) { result = iter->find_compunit_symtab_by_address (this, address); if (result != nullptr) @@ -496,7 +496,7 @@ objfile::lookup_global_symbol_language (const char *name, enum language result = language_unknown; *symbol_found_p = false; - for (const auto &iter : qf) + for (const auto &iter : qf_safe ()) { result = iter->lookup_global_symbol_language (this, name, domain, symbol_found_p); diff --git a/gdb/testsuite/gdb.python/py-objfile.exp b/gdb/testsuite/gdb.python/py-objfile.exp index 2f5b7752b43..8460c300de0 100644 --- a/gdb/testsuite/gdb.python/py-objfile.exp +++ b/gdb/testsuite/gdb.python/py-objfile.exp @@ -135,7 +135,7 @@ gdb_test "p main" "= {} $hex
" \ gdb_py_test_silent_cmd "python objfile.add_separate_debug_file(\"${binfile}\")" \ "Add separate debug file file" 1 -gdb_py_test_silent_cmd "python sep_objfile = gdb.objfiles()\[0\]" \ +gdb_py_test_silent_cmd "python sep_objfile = gdb.objfiles()\[1\]" \ "Get separate debug info objfile" 1 gdb_test "python print (sep_objfile.owner.filename)" "${testfile}2" \ diff --git a/gdbsupport/safe-iterator.h b/gdbsupport/safe-iterator.h index f69f3896758..26e51e9eb68 100644 --- a/gdbsupport/safe-iterator.h +++ b/gdbsupport/safe-iterator.h @@ -136,4 +136,122 @@ class basic_safe_range Range m_range; }; +/* A reverse basic_safe_iterator. See basic_safe_iterator for intended use. */ + +template +class basic_safe_reverse_iterator +{ +public: + typedef basic_safe_reverse_iterator self_type; + typedef typename Iterator::value_type value_type; + typedef typename Iterator::reference reference; + typedef typename Iterator::pointer pointer; + typedef typename Iterator::iterator_category iterator_category; + typedef typename Iterator::difference_type difference_type; + + /* Construct the iterator using ARG, and construct the end iterator + using ARG2. ARG and ARG2 should be forward iterators, typically + from begin and end methods, respectively. + + For example if ARG1 is created with container.begin and ARG2 is + is created with container.end, then the basic_safe_reverse_iterator + will traverse from the last element in the container to the first + element in the container. */ + template + explicit basic_safe_reverse_iterator (Arg &&arg, Arg &&arg2) + : m_begin (std::forward (arg)), + m_end (std::forward (arg2)), + m_it (m_begin), + m_next (m_end) + { + /* M_IT and M_NEXT are initialized as one-past-end. Set M_IT to point + to the last element and set M_NEXT to point to the second last element, + if such elements exist. */ + if (m_begin != m_end) + { + /* Set M_IT to the last element in the list. */ + for (auto it = std::next (m_begin); it != m_end; ++it) + ++m_it; + + /* Set M_NEXT to the element before M_IT, if one exists. */ + if (m_it != m_begin) + { + m_next = m_it; + --m_next; + } + } + } + + typename std::invoke_result::type + operator* () const + { return *m_it; } + + self_type &operator++ () + { + m_it = m_next; + + if (m_it != m_end) + { + /* Use M_BEGIN only if we sure that it is valid. */ + if (m_it == m_begin) + m_next = m_end; + else + --m_next; + } + + return *this; + } + + bool operator== (const self_type &other) const + { return m_it == other.m_it; } + + bool operator!= (const self_type &other) const + { return m_it != other.m_it; } + +private: + /* The first element. */ + Iterator m_begin {}; + + /* A one-past-end iterator. */ + Iterator m_end {}; + + /* The current element. */ + Iterator m_it {}; + + /* The next element. Always one element ahead of M_IT. */ + Iterator m_next {}; +}; + +/* A range adapter that wraps a forward range, and then returns + safe reverse iterators wrapping the original range's iterators. */ + +template +class basic_safe_reverse_range +{ +public: + + typedef basic_safe_reverse_iterator iterator; + + /* RANGE must be a forward range. basic_safe_reverse_iterators + will be used to traverse the forward range from the last element + to the first. */ + explicit basic_safe_reverse_range (Range range) + : m_range (range) + { + } + + iterator begin () + { + return iterator (m_range.begin (), m_range.end ()); + } + + iterator end () + { + return iterator (m_range.end (), m_range.end ()); + } + +private: + + Range m_range; +}; #endif /* COMMON_SAFE_ITERATOR_H */ From patchwork Thu Nov 28 03:54:01 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Aaron Merey X-Patchwork-Id: 102000 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 6CF823858C51 for ; Thu, 28 Nov 2024 03:56:49 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 6CF823858C51 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=LfASl0v7 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 B015B3858CDB for ; Thu, 28 Nov 2024 03:54:17 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org B015B3858CDB Authentication-Results: sourceware.org; dmarc=pass (p=none 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 B015B3858CDB Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1732766057; cv=none; b=NnFuZrZufdEn6u+km30ugFWvIl1BOhp+R2CI5X8yJgbCKxp50ug+JS5cSxBEs+OtJZ0BqvwYoGfhncw+lTDJuaLdOoQhqLRGYJNmtV1OKBbdJGCu4/KewPEOSVQsMjLNkAMC0QoS/12yIglQwCA6HPLuvlh2rUzCnW/2FNWNDFM= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1732766057; c=relaxed/simple; bh=nv3VhS9QavzSzxPNZpu9btU+rLktoX5ij9nbsfoY7ho=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=Xfo3pe9IPjo4fnO3C4kiWOevzAg9wNUcuUPnhDtgWmE8iCAsDjVHo5YIAXEM+7SwU9UPnHJQLM9OiW8Oz9VY/CXt3Ik2I56OvTqioKg4Bs5Iy+SDGzr7xmSMyGU6QSYiOoYdGemhZwCTSF8Gzcg4Zu8uj/kpHYDLxgb+E2HsiLw= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org B015B3858CDB DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1732766057; 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=2Kpj21UKuCy8I37aKnK0RwTp4u67jyGQThNyUt2YFco=; b=LfASl0v7aZy4DrcwoLee+EkwLcadz+YU+MuqgNu5UPwqCYwtwh4FIdV3CMbOYs3aMEFP6h NWQKoFT4Ah1fnYvUeIoLKP+CYWn9m01LHOJcyjsqfbgguerYu9bGaKlyjBHVKaqQX2jAXl pynEjqc+2cZXJz/CyQ8QbgDt9ufLdJ4= Received: from mx-prod-mc-05.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-372-KEmliZ5TMYqGjQ-tbZHt-A-1; Wed, 27 Nov 2024 22:54:15 -0500 X-MC-Unique: KEmliZ5TMYqGjQ-tbZHt-A-1 X-Mimecast-MFC-AGG-ID: KEmliZ5TMYqGjQ-tbZHt-A 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-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 983B2195608B for ; Thu, 28 Nov 2024 03:54:14 +0000 (UTC) Received: from fedora.redhat.com (unknown [10.22.64.69]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id A89A9195E480; Thu, 28 Nov 2024 03:54:13 +0000 (UTC) From: Aaron Merey To: gdb-patches@sourceware.org Cc: aburgess@redhat.com, Aaron Merey Subject: [PATCH 2/3 v8] gdb/debuginfod: Support on-demand debuginfo downloading Date: Wed, 27 Nov 2024 22:54:01 -0500 Message-ID: <20241128035402.438052-3-amerey@redhat.com> In-Reply-To: <20241128035402.438052-1-amerey@redhat.com> References: <20241128035402.438052-1-amerey@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: yG7PVLe9QeSrv1-HXO6HdH9Z2tB7m4Gw_R6pYO3ATCo_1732766054 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-11.1 required=5.0 tests=BAYES_00, DKIM_INVALID, DKIM_SIGNED, GIT_PATCH_0, KAM_DMARC_NONE, KAM_DMARC_STATUS, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.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 At the beginning of a session, gdb may attempt to download debuginfo for all shared libraries associated with the process or core file being debugged. This can be a waste of time and storage space when much of the debuginfo ends up not being used during the session. To reduce the gdb's startup latency and to download only the debuginfo that is really needed, this patch adds on-demand downloading of debuginfo. 'set debuginfo enabled on' now causes gdb to attempt to download a .gdb_index for each shared library instead of its full debuginfo. Each corresponding separate debuginfo will be deferred until gdb needs to expand symtabs associated with the debuginfo's index. Because these indices are significantly smaller than their corresponding debuginfo, this generally reduces the total amount of data gdb downloads. Reductions of 80%-95% have been observed when debugging large GUI programs. (gdb) set debuginfod enabled on (gdb) start Downloading section .gdb_index for /lib64/libcurl.so.4 [...] 1826 client->server_mhandle = curl_multi_init (); (gdb) step Downloading separate debug info for /lib64/libcurl.so.4 Downloading separate debug info for [libcurl dwz] Downloading source file /usr/src/debug/curl-7.85.0-6.fc37.x86_64/build-full/lib/../../lib/multi.c curl_multi_init () at ../../lib/multi.c:457 457 { (gdb) Some of the key functions in this patch include dwarf2_has_separate_index which downloads the separate .gdb_index. If successful, a shared library objfile owns the index until its separate debug objfile is downloaded or found to be unavailable. read_full_dwarf_from_debuginfod downloads the full debuginfo and initializes the separate debug objfile. It is called by functions such as dw2_expand_symtabs_matching_symbol and dwarf2_gdb_index::expand_all_symtabs when symtab expansion is required. --- v8: Rebased onto gdb's current master branch v7: https://sourceware.org/pipermail/gdb-patches/2024-September/211525.html gdb/dwarf2/frame.c | 13 ++ gdb/dwarf2/frame.h | 4 + gdb/dwarf2/index-cache.c | 22 ++- gdb/dwarf2/index-cache.h | 11 ++ gdb/dwarf2/public.h | 5 + gdb/dwarf2/read-gdb-index.c | 68 ++++++--- gdb/dwarf2/read.c | 143 ++++++++++++++++++- gdb/dwarf2/read.h | 5 + gdb/dwarf2/section.c | 6 +- gdb/elfread.c | 3 +- gdb/frame.c | 27 +++- gdb/objfile-flags.h | 4 + gdb/objfiles.h | 19 +++ gdb/quick-symbol.h | 4 + gdb/symfile-debug.c | 11 ++ gdb/symfile.c | 12 +- gdb/symtab.c | 18 ++- gdb/testsuite/gdb.debuginfod/libsection1.c | 43 ++++++ gdb/testsuite/gdb.debuginfod/libsection2.c | 37 +++++ gdb/testsuite/gdb.debuginfod/section.c | 29 ++++ gdb/testsuite/gdb.debuginfod/section.exp | 157 +++++++++++++++++++++ gdb/testsuite/lib/debuginfod-support.exp | 25 +++- gdb/testsuite/lib/gdb.exp | 8 +- 23 files changed, 640 insertions(+), 34 deletions(-) create mode 100644 gdb/testsuite/gdb.debuginfod/libsection1.c create mode 100644 gdb/testsuite/gdb.debuginfod/libsection2.c create mode 100644 gdb/testsuite/gdb.debuginfod/section.c create mode 100644 gdb/testsuite/gdb.debuginfod/section.exp diff --git a/gdb/dwarf2/frame.c b/gdb/dwarf2/frame.c index 6d8a4fb5a9e..657a519e811 100644 --- a/gdb/dwarf2/frame.c +++ b/gdb/dwarf2/frame.c @@ -1619,6 +1619,19 @@ set_comp_unit (struct objfile *objfile, struct comp_unit *unit) return dwarf2_frame_bfd_data.set (abfd, unit); } +/* See frame.h. */ + +void +dwarf2_clear_frame_data (struct objfile *objfile) +{ + bfd *abfd = objfile->obfd.get (); + + if (gdb_bfd_requires_relocations (abfd)) + dwarf2_frame_objfile_data.clear (objfile); + else + dwarf2_frame_bfd_data.clear (abfd); +} + /* Find the FDE for *PC. Return a pointer to the FDE, and store the initial location associated with it into *PC. */ diff --git a/gdb/dwarf2/frame.h b/gdb/dwarf2/frame.h index 2167310fbdf..423a9355b8a 100644 --- a/gdb/dwarf2/frame.h +++ b/gdb/dwarf2/frame.h @@ -237,6 +237,10 @@ void dwarf2_append_unwinders (struct gdbarch *gdbarch); extern const struct frame_base * dwarf2_frame_base_sniffer (const frame_info_ptr &this_frame); +/* Delete OBJFILEs comp_unit. */ + +extern void dwarf2_clear_frame_data (struct objfile * objfile); + /* Compute the DWARF CFA for a frame. */ CORE_ADDR dwarf2_frame_cfa (const frame_info_ptr &this_frame); diff --git a/gdb/dwarf2/index-cache.c b/gdb/dwarf2/index-cache.c index a04d5d662d8..3d52693a38d 100644 --- a/gdb/dwarf2/index-cache.c +++ b/gdb/dwarf2/index-cache.c @@ -217,14 +217,22 @@ index_cache::lookup_gdb_index (const bfd_build_id *build_id, /* Compute where we would expect a gdb index file for this build id to be. */ std::string filename = make_index_filename (build_id, INDEX4_SUFFIX); + return lookup_gdb_index (filename.c_str (), resource); +} + +/* See index-cache.h. */ + +gdb::array_view +index_cache::lookup_gdb_index (const char *filename, + std::unique_ptr *resource) +{ try { - index_cache_debug ("trying to read %s", - filename.c_str ()); + index_cache_debug ("trying to read %s", filename); /* Try to map that file. */ index_cache_resource_mmap *mmap_resource - = new index_cache_resource_mmap (filename.c_str ()); + = new index_cache_resource_mmap (filename); /* Yay, it worked! Hand the resource to the caller. */ resource->reset (mmap_resource); @@ -236,7 +244,7 @@ index_cache::lookup_gdb_index (const bfd_build_id *build_id, catch (const gdb_exception_error &except) { index_cache_debug ("couldn't read %s: %s", - filename.c_str (), except.what ()); + filename, except.what ()); } return {}; @@ -253,6 +261,12 @@ index_cache::lookup_gdb_index (const bfd_build_id *build_id, return {}; } +gdb::array_view +index_cache::lookup_gdb_index_debuginfod + (const char *index_path, std::unique_ptr *resource) +{ + return {}; +} #endif /* See dwarf-index-cache.h. */ diff --git a/gdb/dwarf2/index-cache.h b/gdb/dwarf2/index-cache.h index 08a2d56493c..0c3f574e1b9 100644 --- a/gdb/dwarf2/index-cache.h +++ b/gdb/dwarf2/index-cache.h @@ -91,6 +91,17 @@ class index_cache lookup_gdb_index (const bfd_build_id *build_id, std::unique_ptr *resource); + /* Look for an index file located at INDEX_PATH. If found, return the + contents as an array_view and store the underlying resources (allocated + memory, mapped file, etc) in RESOURCE. The returned array_view is valid + as long as RESOURCE is not destroyed. + + If no matching index file is found, return an empty array view. This + function does not exit early if the index cache has not been enabled. */ + gdb::array_view + lookup_gdb_index (const char *filename, + std::unique_ptr *resource); + /* Return the number of cache hits. */ unsigned int n_hits () const { return m_n_hits; } diff --git a/gdb/dwarf2/public.h b/gdb/dwarf2/public.h index bc419ff208f..6523854386c 100644 --- a/gdb/dwarf2/public.h +++ b/gdb/dwarf2/public.h @@ -44,4 +44,9 @@ extern bool dwarf2_initialize_objfile extern void dwarf2_build_frame_info (struct objfile *); +/* Query debuginfod for the .gdb_index associated with OBJFILE. + Used to defer separate debuginfo downloading until necessary. */ + +extern bool dwarf2_has_separate_index (struct objfile *); + #endif /* DWARF2_PUBLIC_H */ diff --git a/gdb/dwarf2/read-gdb-index.c b/gdb/dwarf2/read-gdb-index.c index c0a33a0c969..1a1e597a9b8 100644 --- a/gdb/dwarf2/read-gdb-index.c +++ b/gdb/dwarf2/read-gdb-index.c @@ -151,6 +151,7 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions gdb.dwarf2/gdb-index.exp testcase. */ void dump (struct objfile *objfile) override; + /* Expand symtabs matching a given symbol or file. */ bool expand_symtabs_matching (struct objfile *objfile, gdb::function_view file_matcher, @@ -161,8 +162,34 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions domain_search_flags domain, gdb::function_view lang_matcher) override; + + /* Calls dwarf2_base_index_functions::expand_all_symtabs and downloads + debuginfo if necessary. */ + void expand_all_symtabs (struct objfile *objfile) override; + + /* Calls dwarf2_base_index_functions::find_last_source_symtab and downloads + debuginfo if necessary. */ + struct symtab *find_last_source_symtab (struct objfile *objfile) override; }; +void +dwarf2_gdb_index::expand_all_symtabs (struct objfile *objfile) +{ + if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0) + read_full_dwarf_from_debuginfod (objfile); + + dwarf2_base_index_functions::expand_all_symtabs (objfile); +} + +struct symtab * +dwarf2_gdb_index::find_last_source_symtab (struct objfile *objfile) +{ + if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0) + read_full_dwarf_from_debuginfod (objfile); + + return dwarf2_base_index_functions::find_last_source_symtab (objfile); +} + /* This dumps minimal information about the index. It is called via "mt print objfiles". One use is to verify .gdb_index has been loaded by the @@ -496,6 +523,11 @@ create_cus_from_gdb_index (dwarf2_per_bfd *per_bfd, gdb_assert (per_bfd->all_units.empty ()); per_bfd->all_units.reserve ((cu_list_elements + dwz_elements) / 2); + /* An index might be read before the debug_info section is available. + Create a placeholder section. */ + if (per_bfd->infos.empty ()) + per_bfd->infos.resize (1); + create_cus_from_gdb_index_list (per_bfd, cu_list, cu_list_elements, &per_bfd->infos[0], 0); @@ -662,28 +694,32 @@ dwarf2_read_gdb_index /* If there is a .dwz file, read it so we can get its CU list as well. */ - dwz = dwarf2_get_dwz_file (per_bfd); - if (dwz != NULL) + if (get_gdb_index_contents_dwz != nullptr) { mapped_gdb_index dwz_map; const gdb_byte *dwz_types_ignore; offset_type dwz_types_elements_ignore; + dwz = dwarf2_get_dwz_file (per_bfd); - gdb::array_view dwz_index_content - = get_gdb_index_contents_dwz (objfile, dwz); - - if (dwz_index_content.empty ()) - return 0; - - if (!read_gdb_index_from_buffer (bfd_get_filename (dwz->dwz_bfd.get ()), - 1, dwz_index_content, &dwz_map, - &dwz_list, &dwz_list_elements, - &dwz_types_ignore, - &dwz_types_elements_ignore)) + if (dwz != nullptr) { - warning (_("could not read '.gdb_index' section from %s; skipping"), - bfd_get_filename (dwz->dwz_bfd.get ())); - return 0; + gdb::array_view dwz_index_content + = get_gdb_index_contents_dwz (objfile, dwz); + + if (dwz_index_content.empty ()) + return 0; + + if (!read_gdb_index_from_buffer (bfd_get_filename + (dwz->dwz_bfd.get ()), + 1, dwz_index_content, &dwz_map, + &dwz_list, &dwz_list_elements, + &dwz_types_ignore, + &dwz_types_elements_ignore)) + { + warning (_("could not read '.gdb_index' section from %s; skipping"), + bfd_get_filename (dwz->dwz_bfd.get ())); + return 0; + } } } diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c index 5a284be1f90..d11123f34fe 100644 --- a/gdb/dwarf2/read.c +++ b/gdb/dwarf2/read.c @@ -33,6 +33,7 @@ #include "dwarf2/attribute.h" #include "dwarf2/comp-unit-head.h" #include "dwarf2/cu.h" +#include "dwarf2/frame.h" #include "dwarf2/index-cache.h" #include "dwarf2/index-common.h" #include "dwarf2/leb.h" @@ -97,6 +98,8 @@ #include "dwarf2/error.h" #include #include "gdbsupport/unordered_set.h" +#include "inferior.h" +#include "debuginfod-support.h" /* When == 1, print basic high level tracing messages. When > 1, be more verbose. @@ -2383,6 +2386,16 @@ dw2_expand_symtabs_matching_symbol || (symbol_matcher != NULL && !symbol_matcher (qualified))) continue; + /* There is a match but this objfile's debuginfo has not been + acquired yet. Download it then return early to expand CUs + from the debuginfo instead. */ + if (per_objfile != nullptr + && (per_objfile->objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0) + { + read_full_dwarf_from_debuginfod (per_objfile->objfile); + return false; + } + matches.push_back (bounds.first->idx); } } @@ -3037,6 +3050,15 @@ dwarf2_base_index_functions::find_pc_sect_compunit_symtab if (data == nullptr) return nullptr; + if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0) + { + /* PC matches a symbol in the index but full debuginfo hasn't + been acquired yet. Download it and return early in order to + expand symtabs in the debuginfo. */ + read_full_dwarf_from_debuginfod (objfile); + return nullptr; + } + if (warn_if_readin && per_objfile->symtab_set_p (data)) warning (_("(Internal error: pc %s in read in CU, but not in symtab.)"), paddress (objfile->arch (), pc)); @@ -3138,6 +3160,9 @@ dwarf2_base_index_functions::has_symbols (struct objfile *objfile) bool dwarf2_base_index_functions::has_unexpanded_symtabs (struct objfile *objfile) { + if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0) + return true; + dwarf2_per_objfile *per_objfile = get_dwarf2_per_objfile (objfile); for (const auto &per_cu : per_objfile->per_bfd->all_units) @@ -3207,6 +3232,31 @@ get_gdb_index_contents_from_cache_dwz (objfile *obj, dwz_file *dwz) return global_index_cache.lookup_gdb_index (build_id, &dwz->index_cache_res); } +/* Query debuginfod for the .gdb_index matching OBJFILE's build-id. Return the + contents if successful. */ + +static gdb::array_view +get_gdb_index_contents_from_debuginfod (objfile *objfile, + dwarf2_per_bfd *per_bfd) +{ + const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ()); + if (build_id == nullptr) + return {}; + + gdb::unique_xmalloc_ptr index_path; + scoped_fd fd = debuginfod_section_query (build_id->data, build_id->size, + bfd_get_filename + (objfile->obfd.get ()), + ".gdb_index", + &index_path); + if (fd.get () < 0) + return {}; + + return global_index_cache.lookup_gdb_index + (index_path.get (), &per_bfd->index_cache_res); +} + + static void start_debug_info_reader (dwarf2_per_objfile *); /* See dwarf2/public.h. */ @@ -3216,11 +3266,13 @@ dwarf2_initialize_objfile (struct objfile *objfile, const struct dwarf2_debug_sections *names, bool can_copy) { - if (!dwarf2_has_info (objfile, names, can_copy)) + if (!dwarf2_has_info (objfile, names, can_copy) + && (objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0) return false; dwarf2_per_objfile *per_objfile = get_dwarf2_per_objfile (objfile); dwarf2_per_bfd *per_bfd = per_objfile->per_bfd; + bool separate_index = false; dwarf_read_debug_printf ("called"); @@ -3256,6 +3308,15 @@ dwarf2_initialize_objfile (struct objfile *objfile, dwarf_read_debug_printf ("found gdb index from cache"); global_index_cache.hit (); } + /* Try to read just a separately downloaded gdb index. */ + else if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0 + && dwarf2_read_gdb_index (per_objfile, + get_gdb_index_contents_from_debuginfod, + nullptr)) + { + dwarf_read_debug_printf ("found .gdb_index from debuginfod"); + separate_index = true; + } else { global_index_cache.miss (); @@ -3267,11 +3328,91 @@ dwarf2_initialize_objfile (struct objfile *objfile, if (dwarf_synchronous) per_bfd->index_table->wait_completely (); objfile->qf.push_front (per_bfd->index_table->make_quick_functions ()); + + if (separate_index) + objfile->qf.begin ()->get ()->from_separate_index = true; } return true; } +/* See read.h. */ + +void +read_full_dwarf_from_debuginfod (struct objfile *objfile) +{ + gdb_assert ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0); + + SCOPE_EXIT { objfile->remove_deferred_status (); }; + + const struct bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ()); + if (build_id == nullptr) + return; + + const char *filename = bfd_get_filename (objfile->obfd.get ()); + gdb::unique_xmalloc_ptr symfile_path; + scoped_fd fd; + + fd = debuginfod_debuginfo_query (build_id->data, build_id->size, + filename, &symfile_path); + if (fd.get () < 0) + return; + + /* Separate debuginfo successfully retrieved from server. */ + gdb_bfd_ref_ptr debug_bfd = symfile_bfd_open (symfile_path.get ()); + if (debug_bfd == nullptr + || !build_id_verify (debug_bfd.get (), build_id->size, build_id->data)) + { + warning (_("File \"%s\" from debuginfod cannot be opened as bfd"), + filename); + return; + } + + /* Clear frame data so it can be recalculated using DWARF. */ + dwarf2_clear_frame_data (objfile); + + /* This may trigger a dwz download. */ + symbol_file_add_separate (debug_bfd, symfile_path.get (), + current_inferior ()->symfile_flags, objfile); +} + +/* See public.h. */ + +bool +dwarf2_has_separate_index (struct objfile *objfile) +{ + if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0) + return true; + if ((objfile->flags & OBJF_MAINLINE) != 0) + return false; + if (!IS_DIR_SEPARATOR (*objfile_filename (objfile))) + return false; + + const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ()); + + if (build_id == nullptr) + return false; + + gdb::unique_xmalloc_ptr index_path; + scoped_fd fd = debuginfod_section_query (build_id->data, + build_id->size, + bfd_get_filename + (objfile->obfd.get ()), + ".gdb_index", + &index_path); + + if (fd.get () < 0) + return false; + + /* We found a separate .gdb_index file so a separate debuginfo file should + exist, but we don't want to download it until necessary. Associate the + index with this objfile and defer the debuginfo download until symtabs + referenced by the index need to be expanded. */ + objfile->flags |= OBJF_DOWNLOAD_DEFERRED; + dwarf2_initialize_objfile (objfile); + return true; +} + /* Find the base address of the compilation unit for range lists and diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h index 1cc30eb285a..337a5e9ce80 100644 --- a/gdb/dwarf2/read.h +++ b/gdb/dwarf2/read.h @@ -982,4 +982,9 @@ extern void create_all_units (dwarf2_per_objfile *per_objfile); extern htab_up create_quick_file_names_table (unsigned int nr_initial_entries); +/* If OBJFILE contains information from a separately downloaded .gdb_index, + attempt to download the full debuginfo. */ + +extern void read_full_dwarf_from_debuginfod (struct objfile *); + #endif /* DWARF2READ_H */ diff --git a/gdb/dwarf2/section.c b/gdb/dwarf2/section.c index e0b63fd0e08..e5bcd6eb724 100644 --- a/gdb/dwarf2/section.c +++ b/gdb/dwarf2/section.c @@ -54,7 +54,11 @@ dwarf2_section_info::get_bfd_owner () const section = get_containing_section (); gdb_assert (!section->is_virtual); } - gdb_assert (section->s.section != nullptr); + + /* This error may occur when attempting to expand symtabs for an objfile + with OBJF_DOWNLOAD_DEFERRED set. */ + if (section->s.section == nullptr) + error (_("Can't find owner of DWARF section")); return section->s.section->owner; } diff --git a/gdb/elfread.c b/gdb/elfread.c index e81233c6be8..0e1655194eb 100644 --- a/gdb/elfread.c +++ b/gdb/elfread.c @@ -1216,7 +1216,8 @@ elf_symfile_read_dwarf2 (struct objfile *objfile, && objfile->separate_debug_objfile_backlink == NULL) { if (objfile->find_and_add_separate_symbol_file (symfile_flags)) - gdb_assert (objfile->separate_debug_objfile != nullptr); + gdb_assert (objfile->separate_debug_objfile != nullptr + || (objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0); else has_dwarf2 = false; } diff --git a/gdb/frame.c b/gdb/frame.c index a6900b28072..54c632079bd 100644 --- a/gdb/frame.c +++ b/gdb/frame.c @@ -1778,14 +1778,10 @@ restore_selected_frame (frame_id frame_id, int frame_level) selected_frame = nullptr; } -/* Lookup the frame_info object for the selected frame FRAME_ID / - FRAME_LEVEL and cache the result. - - If FRAME_LEVEL > 0 and the originally selected frame isn't found, - warn and select the innermost (current) frame. */ +/* See lookup_selected_frame. */ static void -lookup_selected_frame (struct frame_id a_frame_id, int frame_level) +lookup_selected_frame_1 (struct frame_id &a_frame_id, int frame_level) { frame_info_ptr frame = NULL; int count; @@ -1857,6 +1853,25 @@ lookup_selected_frame (struct frame_id a_frame_id, int frame_level) } } +/* Lookup the frame_info object for the selected frame FRAME_ID / + FRAME_LEVEL and cache the result. + + If FRAME_LEVEL > 0 and the originally selected frame isn't found, + warn and select the innermost (current) frame. */ + +static void +lookup_selected_frame (struct frame_id a_frame_id, int frame_level) +{ + lookup_selected_frame_1 (selected_frame_id, selected_frame_level); + + /* It is possible for lookup_selected_frame_1 to cause a new objfile + to be loaded. However some objfile observers may choose to clear + selected_frame when an objfile is loaded. Work around this by + calling lookup_selected_frame_1 again if the first call failed. */ + if (selected_frame == nullptr) + lookup_selected_frame_1 (selected_frame_id, selected_frame_level); +} + bool has_stack_frames () { diff --git a/gdb/objfile-flags.h b/gdb/objfile-flags.h index 43d888e2a5a..08e4a75a9f2 100644 --- a/gdb/objfile-flags.h +++ b/gdb/objfile-flags.h @@ -56,6 +56,10 @@ enum objfile_flag : unsigned /* User requested that we do not read this objfile's symbolic information. */ OBJF_READNEVER = 1 << 6, + + /* A separate .gdb_index has been downloaded for this objfile. + Debuginfo for this objfile can be downloaded when required. */ + OBJF_DOWNLOAD_DEFERRED = 1 << 7, }; DEF_ENUM_FLAGS_TYPE (enum objfile_flag, objfile_flags); diff --git a/gdb/objfiles.h b/gdb/objfiles.h index 29f88266e39..42841b494f5 100644 --- a/gdb/objfiles.h +++ b/gdb/objfiles.h @@ -629,6 +629,25 @@ struct objfile : intrusive_list_node domain_search_flags domain, bool *symbol_found_p); + /* Used to clear OBJF_DOWNLOAD_DEFERRED status when the debug objfile has + either been acquired or could not be found. */ + void remove_deferred_status () + { + flags &= ~OBJF_DOWNLOAD_DEFERRED; + + /* Remove quick_symbol_functions derived from a separately downloaded + index. If available the separate debug objfile's index will be used + instead, since that objfile actually contains the symbols and CUs + referenced in the index. + + No more than one element of qf should have from_separate_index set + to true. */ + qf.remove_if ([&] (const quick_symbol_functions_up &qf_up) + { + return qf_up->from_separate_index; + }); + } + /* Return the relocation offset applied to SECTION. */ CORE_ADDR section_offset (bfd_section *section) const { diff --git a/gdb/quick-symbol.h b/gdb/quick-symbol.h index 0d76e1860ed..b557181eda3 100644 --- a/gdb/quick-symbol.h +++ b/gdb/quick-symbol.h @@ -202,6 +202,10 @@ struct quick_symbol_functions virtual void compute_main_name (struct objfile *objfile) { } + + /* True if this quick_symbol_functions is derived from a separately + downloaded index. */ + bool from_separate_index = false; }; typedef std::unique_ptr quick_symbol_functions_up; diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c index 60ed1d5cd8c..cc7ec6cf01a 100644 --- a/gdb/symfile-debug.c +++ b/gdb/symfile-debug.c @@ -36,6 +36,7 @@ #include "cli/cli-style.h" #include "build-id.h" #include "debuginfod-support.h" +#include "dwarf2/public.h" /* We need to save a pointer to the real symbol functions. Plus, the debug versions are malloc'd because we have to NULL out the @@ -603,6 +604,16 @@ objfile::find_and_add_separate_symbol_file (symfile_add_flags symfile_flags) = simple_find_and_open_separate_symbol_file (this, find_separate_debug_file_by_debuglink, &warnings); + /* Attempt to download only an index from the separate debug info. + As with debuginfod_find_and_open_separate_symbol_file, only attempt + this once. */ + if (debug_bfd == nullptr && attempt == 0 + && dwarf2_has_separate_index (this)) + { + has_dwarf2 = true; + break; + } + /* Only try debuginfod on the first attempt. Sure, we could imagine an extension that somehow adds the required debug info to the debuginfod server but, at least for now, we don't support this diff --git a/gdb/symfile.c b/gdb/symfile.c index 4bc0c4916bd..25ef1e30052 100644 --- a/gdb/symfile.c +++ b/gdb/symfile.c @@ -979,6 +979,10 @@ syms_from_objfile (struct objfile *objfile, static void finish_new_objfile (struct objfile *objfile, symfile_add_flags add_flags) { + struct objfile *parent = objfile->separate_debug_objfile_backlink; + bool was_deferred + = (parent != nullptr) && ((parent->flags & OBJF_DOWNLOAD_DEFERRED) != 0); + /* If this is the main symbol file we have to clean up all users of the old main symbol file. Otherwise it is sufficient to fixup all the breakpoints that may have been redefined by this symbol file. */ @@ -989,7 +993,7 @@ finish_new_objfile (struct objfile *objfile, symfile_add_flags add_flags) clear_symtab_users (add_flags); } - else if ((add_flags & SYMFILE_DEFER_BP_RESET) == 0) + else if ((add_flags & SYMFILE_DEFER_BP_RESET) == 0 && !was_deferred) { breakpoint_re_set (); } @@ -1112,6 +1116,12 @@ symbol_file_add_with_addrs (const gdb_bfd_ref_ptr &abfd, const char *name, if (objfile->sf != nullptr) finish_new_objfile (objfile, add_flags); + /* Remove deferred status now in case any observers trigger symtab + expansion. Otherwise gdb might try to read parent for psymbols + when it should read the separate debug objfile instead. */ + if (parent != nullptr && ((parent->flags & OBJF_DOWNLOAD_DEFERRED) != 0)) + parent->remove_deferred_status (); + gdb::observers::new_objfile.notify (objfile); return objfile; diff --git a/gdb/symtab.c b/gdb/symtab.c index edc19ff5c15..f477a51edae 100644 --- a/gdb/symtab.c +++ b/gdb/symtab.c @@ -2993,14 +2993,30 @@ find_pc_sect_compunit_symtab (CORE_ADDR pc, struct obj_section *section) if (best_cust != NULL) return best_cust; + int warn_if_readin = 1; + /* Not found in symtabs, search the "quick" symtabs (e.g. psymtabs). */ for (objfile *objf : current_program_space->objfiles ()) { + bool was_deferred = (objf->flags & OBJF_DOWNLOAD_DEFERRED) != 0; + struct compunit_symtab *result - = objf->find_pc_sect_compunit_symtab (msymbol, pc, section, 1); + = objf->find_pc_sect_compunit_symtab (msymbol, pc, section, + warn_if_readin); + if (result != NULL) return result; + + /* If OBJF's separate debug info was just acquired, disable + warn_if_readin for the next iteration of this loop. This prevents + a spurious warning in case an observer already triggered expansion + of the separate debug objfile's symtabs. */ + if (was_deferred && objf->separate_debug_objfile != nullptr + && (objf->flags & OBJF_DOWNLOAD_DEFERRED) == 0) + warn_if_readin = 0; + else + warn_if_readin = 1; } return NULL; diff --git a/gdb/testsuite/gdb.debuginfod/libsection1.c b/gdb/testsuite/gdb.debuginfod/libsection1.c new file mode 100644 index 00000000000..e4f05db0a79 --- /dev/null +++ b/gdb/testsuite/gdb.debuginfod/libsection1.c @@ -0,0 +1,43 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2023-2024 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 . */ + +#include +#include +#include + +extern void libsection2_test (); +extern void *libsection2_thread_test (void *); + +static volatile int flag = 0; + +void +libsection1_test () +{ + pthread_t thr; + + printf ("In libsection1\n"); + libsection2_test (); + + pthread_create (&thr, NULL, libsection2_thread_test, (void *) &flag); + + /* Give the new thread a chance to actually enter libsection2_thread_test. */ + while (!flag) + ; + + printf ("Cancelling thread\n"); + pthread_cancel (thr); +} diff --git a/gdb/testsuite/gdb.debuginfod/libsection2.c b/gdb/testsuite/gdb.debuginfod/libsection2.c new file mode 100644 index 00000000000..3c82808e5fe --- /dev/null +++ b/gdb/testsuite/gdb.debuginfod/libsection2.c @@ -0,0 +1,37 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2023-2024 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 . */ + +#include +#include + +void +libsection2_test () +{ + printf ("In libsection2\n"); +} + +void * +libsection2_thread_test (void *arg) +{ + int *flag = (int *) arg; + printf ("In thread test\n"); + + while (1) + *flag = 1; + + return NULL; +} diff --git a/gdb/testsuite/gdb.debuginfod/section.c b/gdb/testsuite/gdb.debuginfod/section.c new file mode 100644 index 00000000000..fa9ac536a6e --- /dev/null +++ b/gdb/testsuite/gdb.debuginfod/section.c @@ -0,0 +1,29 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2023-2024 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 . */ + +#include + +extern void libsection1_test (); + +int +main () +{ + libsection1_test (); + printf ("in section exec\n"); + + return 0; +} diff --git a/gdb/testsuite/gdb.debuginfod/section.exp b/gdb/testsuite/gdb.debuginfod/section.exp new file mode 100644 index 00000000000..381791bca50 --- /dev/null +++ b/gdb/testsuite/gdb.debuginfod/section.exp @@ -0,0 +1,157 @@ +# Copyright 2023-2024 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 . + +# Test debuginfod functionality + +standard_testfile + +load_lib debuginfod-support.exp + +clean_restart +require allow_debuginfod_tests +require allow_debuginfod_section_downloads + +# BINFILE calls a function from LIB_SL1. +set libfile1 "libsection1" +set libsrc1 $srcdir/$subdir/$libfile1.c +set lib_sl1 [standard_output_file $libfile1.sl] + +# LIB_SL1 calls functions from LIB_SL2. +set libfile2 "libsection2" +set libsrc2 $srcdir/$subdir/$libfile2.c +set lib_sl2 [standard_output_file $libfile2.sl] + +# Build LIB_SL2, LIB_SL1 and BINFILE. +if { [build_executable "build $libfile2" $lib_sl2 $libsrc2 \ + {debug build-id shlib}] != 0 } { + return -1 +} + +if { [build_executable "build $libfile1" $lib_sl1 $libsrc1 \ + [list debug build-id shlib_pthreads shlib=$lib_sl2]] != 0 } { + return -1 +} + +if { [build_executable "build executable" $binfile $srcfile \ + [list debug build-id shlib=$lib_sl1 shlib=$lib_sl2]] != 0 } { + return -1 +} + +# Make sure libsection1 and libsection2 contain .gdb_index. +if { [ensure_gdb_index $lib_sl1 "" "libsection1"] != 1 } { + untested "failed to add .gdb_index to $libfile1" + return -1 +} + +if { [ensure_gdb_index $lib_sl2 "" "libsection2"] != 1 } { + untested "failed to add .gdb_index to $libfile2" + return -1 +} + +# Strip solib debuginfo into separate files. +if { [gdb_gnu_strip_debug $lib_sl1 ""] != 0} { + fail "strip $lib_sl1 debuginfo" + return -1 +} + +if { [gdb_gnu_strip_debug $lib_sl2 ""] != 0} { + fail "strip $lib_sl2 debuginfo" + return -1 +} + +# Move debuginfo files into directory that debuginfod will serve from. +set debugdir [standard_output_file "debug"] +set debuginfo_sl1 [standard_output_file $libfile1.sl.debug] +set debuginfo_sl2 [standard_output_file $libfile2.sl.debug] + +file mkdir $debugdir +file rename -force $debuginfo_sl1 $debugdir +file rename -force $debuginfo_sl2 $debugdir + +# Restart GDB and clear the debuginfod client cache. Then load BINFILE into +# GDB and start running it. Match output with pattern RES and use TESTNAME +# as the test name. +proc_with_prefix clean_restart_with_prompt { binfile testname } { + global cache + + # Delete client cache so debuginfo downloads again. + file delete -force $cache + clean_restart + + gdb_test_no_output "set debuginfod enabled on" \ + "clean_restart enable $testname" + gdb_load $binfile + + runto_main +} + +# Tests with no debuginfod server running. +proc_with_prefix no_url { } { + global binfile libfile1 libfile2 + + gdb_load $binfile + if {![runto_main]} { + return + } + + # Check that no section is downloaded and no debuginfo is found. + gdb_test "info sharedlibrary" ".*Yes \\(\\*\\).*$libfile1.*" \ + "found no url lib1" + gdb_test "info sharedlibrary" ".*Yes \\(\\*\\).*$libfile2.*" \ + "found no url lib2" +} + +# Tests with a debuginfod server running. +proc_with_prefix local_url { } { + global binfile + global libsrc1 lib_sl1 libfile1 + global libsrc2 lib_sl2 libfile2 + global debugdir db + + set url [start_debuginfod $db $debugdir] + if { $url == "" } { + unresolved "failed to start debuginfod server" + return + } + + # Point GDB to the server. + setenv DEBUGINFOD_URLS $url + + clean_restart_with_prompt $binfile "index" + + # Download debuginfo when stepping into a function. + set res ".*separate debug info for $lib_sl1.*\"In ${libfile1}\\\\n\".*" + gdb_test "step" $res "step" + + clean_restart_with_prompt $binfile "break" + + # Download debuginfo when setting a breakpoint. + set res ".*separate debug info for $lib_sl2.*" + gdb_test "br libsection2_test" $res "break set" + + # Hit the breakpoint. + set res ".*Breakpoint 2, libsection2_test.*\"In ${libfile2}\\\\n\".*" + gdb_test "c" $res "break continue" +} + +# Create CACHE and DB directories ready for debuginfod to use. +prepare_for_debuginfod cache db + +with_debuginfod_env $cache { + no_url + local_url +} + +stop_debuginfod diff --git a/gdb/testsuite/lib/debuginfod-support.exp b/gdb/testsuite/lib/debuginfod-support.exp index 0096448567e..4d8bdcdc7cb 100644 --- a/gdb/testsuite/lib/debuginfod-support.exp +++ b/gdb/testsuite/lib/debuginfod-support.exp @@ -113,6 +113,8 @@ proc with_debuginfod_env { cache body } { proc start_debuginfod { db debugdir } { global debuginfod_spawn_id spawn_id + set logfile [standard_output_file "server_log"] + # Find an unused port. set port 7999 set found false @@ -127,7 +129,8 @@ proc start_debuginfod { db debugdir } { set old_spawn_id $spawn_id } - spawn debuginfod -vvvv -d $db -p $port -F $debugdir + spawn sh -c "debuginfod -vvvv -d $db -p $port -F $debugdir 2>&1 \ + | tee $logfile" set debuginfod_spawn_id $spawn_id if { [info exists old_spawn_id] } { @@ -194,3 +197,23 @@ proc stop_debuginfod { } { unset debuginfod_spawn_id } } + +# Return true if gdb is configured to download ELF/DWARF sections from +# debuginfod servers. Otherwise return false. +proc allow_debuginfod_section_downloads { } { + set cmd "maint set debuginfod download-sections on" + set msg "enable section downloads" + + gdb_test_multiple $cmd $msg { + -re -wrap ".*not compiled into GDB.*" { + return false + } + -re -wrap "^" { + return true + } + -re -wrap "" { + fail "$gdb_test_name (unexpected output)" + return false + } + } +} diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp index 7ee2043f0f8..86b6db3d72d 100644 --- a/gdb/testsuite/lib/gdb.exp +++ b/gdb/testsuite/lib/gdb.exp @@ -10123,10 +10123,14 @@ proc get_index_type { objfile { testname "" } } { # STYLE controls which style of index to add, if needed. The empty # string (the default) means .gdb_index; "-dwarf-5" means .debug_names. -proc ensure_gdb_index { binfile {style ""} } { +proc ensure_gdb_index { binfile {style ""} {testname_prefix ""} } { set testfile [file tail $binfile] - set test "check if index present" + + if { $testname_prefix != "" } { + set test "$testname_prefix $test" + } + set index_type [get_index_type $testfile $test] if { $index_type eq "gdb" || $index_type eq "dwarf5" } { From patchwork Thu Nov 28 03:54:02 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Aaron Merey X-Patchwork-Id: 102002 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 8D8623858C60 for ; Thu, 28 Nov 2024 03:57:50 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 8D8623858C60 Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=XzVwDYQj 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 366933858CDA for ; Thu, 28 Nov 2024 03:54:19 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 366933858CDA Authentication-Results: sourceware.org; dmarc=pass (p=none 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 366933858CDA Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1732766059; cv=none; b=mV/UDmEUfhxwXKuLDpieKjH8IfYOd7rVFhED385RTKH1NS1rnjMO+lZb991rrAROWUa47Q3y1ed6QtqiAPRW2jHk/AV3BQBxAYPuM+bILcaU4FdQB6lnxcD1Skw+5n9R1DFkHxBzOQEsJRg4i2NBOL6mPu8l7SwSOxPwcMNGjXA= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1732766059; c=relaxed/simple; bh=v93Iu4IkommrZHKXcrQ1mewGc+B3nAwAkULu3/37bHQ=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=K5lJQC86U7U6f3Mr3zxxJcgTRWXY47WT87K+3qELElaz3iPHa8hjn0b+ViLdwBeOzde99twkhqnwjZ+mx/l8Sl/jx927dySRioig0fPVRn5NttjXL7CrIwVJ1S+G2cKB8EWjLqozhvtjm864BRgofrpF2yaj7iwn9VUBe8heiKY= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 366933858CDA DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1732766058; 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=w6e66nfu0UME2k1ouw1oAEVlzM73HjEKENC1ALnqOEo=; b=XzVwDYQjVZEsy7NlbhOnFe/jOUtwwEbOABKw1EIFYkFyY3fKUQwiYGZQwgUYtGYMILlx1o tyVp4thsyLAKDpg/yY0BVtj60Uc2BPY0NvUzO3ISTvfX3W1Ud1HW7zUn2cef3rrhx04LK4 h2t3I6XmkUgQi9Lb0IGlg18ZlqGfgZk= Received: from mx-prod-mc-05.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-620-A2qheQTmOfKLXRw8tJLRSA-1; Wed, 27 Nov 2024 22:54:17 -0500 X-MC-Unique: A2qheQTmOfKLXRw8tJLRSA-1 X-Mimecast-MFC-AGG-ID: A2qheQTmOfKLXRw8tJLRSA 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-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 60FAA19560AB for ; Thu, 28 Nov 2024 03:54:16 +0000 (UTC) Received: from fedora.redhat.com (unknown [10.22.64.69]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 912A1195E483; Thu, 28 Nov 2024 03:54:15 +0000 (UTC) From: Aaron Merey To: gdb-patches@sourceware.org Cc: aburgess@redhat.com, Aaron Merey Subject: [PATCH 3/3 v7] gdb/debuginfod: Add .debug_line downloading Date: Wed, 27 Nov 2024 22:54:02 -0500 Message-ID: <20241128035402.438052-4-amerey@redhat.com> In-Reply-To: <20241128035402.438052-1-amerey@redhat.com> References: <20241128035402.438052-1-amerey@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: Yyj_i8XEtjn86-DnmGY8DyeF8o87yOqE0EdZ1nFxtXk_1732766056 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-11.6 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.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 ELF/DWARF section downloading allows gdb to download .gdb_index files in order to defer full debuginfo downloads. However .gdb_index does not contain any information regarding source filenames. When a gdb command includes a filename argument (ex. 'break main.c:50'), this results in the mass downloading of all deferred debuginfo so that gdb can search the debuginfo for matching source filenames. This can result in unnecessary downloads. To improve this, have gdb instead download each debuginfo's .debug_line (and .debug_line_str if using DWARF5) when executing these commands. Download full debuginfo only when its .debug_line contains a matching filename. Since the combined size of .debug_line and .debug_line_str is only about 1% the size of the corresponding debuginfo, significant time can be saved by checking these sections before choosing to download an entire debuginfo. This patch also redirects stdout and stderr of the debuginfod server used by testsuite/gdb.debuginfod tests to a server_log standard output file. While adding tests for this patch I ran into an issue where the test server would block when logging to stderr, presumably because the stderr buffer filled up and wasn't being read from. Redirecting the log to a file fixes this and also makes the server log more accessible when debugging test failures. --- v7: Rebased onto gdb's current master branch v6: https://sourceware.org/pipermail/gdb-patches/2024-September/211526.html gdb/cli-out.c | 11 +- gdb/completer.c | 18 +- gdb/dwarf2/line-header.c | 161 +++++++++++------ gdb/dwarf2/line-header.h | 10 ++ gdb/dwarf2/read-gdb-index.c | 65 ++++++- gdb/dwarf2/read.c | 209 +++++++++++++++++++++++ gdb/dwarf2/read.h | 37 ++++ gdb/mi/mi-out.c | 9 +- gdb/testsuite/gdb.debuginfod/section.exp | 28 +++ gdb/ui-out.c | 3 + gdb/ui-out.h | 20 +++ 11 files changed, 517 insertions(+), 54 deletions(-) diff --git a/gdb/cli-out.c b/gdb/cli-out.c index d8a542d1b9b..ec671c15300 100644 --- a/gdb/cli-out.c +++ b/gdb/cli-out.c @@ -307,16 +307,23 @@ cli_ui_out::do_progress_notify (const std::string &msg, if (info.state == progress_update::START) { + std::string prefix; + if (cur_prefix_state == prefix_state_t::NEWLINE_NEEDED) + { + prefix = "\n"; + cur_prefix_state = prefix_state_t::NEWLINE_PRINTED; + } + if (stream->isatty () && current_ui->input_interactive_p () && chars_per_line >= MIN_CHARS_PER_LINE) { - gdb_printf (stream, "%s\n", msg.c_str ()); + gdb_printf (stream, "%s\n", (prefix + msg).c_str ()); info.state = progress_update::BAR; } else { - gdb_printf (stream, "%s...\n", msg.c_str ()); + gdb_printf (stream, "%s...\n", (prefix + msg).c_str ()); info.state = progress_update::WORKING; } } diff --git a/gdb/completer.c b/gdb/completer.c index 4735aa5ed10..5d3837cbc0b 100644 --- a/gdb/completer.c +++ b/gdb/completer.c @@ -1753,6 +1753,10 @@ complete_line_internal_1 (completion_tracker &tracker, { /* We've recognized a full command. */ + /* Disable pagination since responding to the pagination prompt + overwrites rl_line_buffer. */ + scoped_restore pag_restore = make_scoped_restore (&pagination_enabled, false); + if (p == tmp_command + point) { /* There is no non-whitespace in the line beyond the @@ -1852,7 +1856,8 @@ complete_line_internal_1 (completion_tracker &tracker, } /* Wrapper around complete_line_internal_1 to handle - MAX_COMPLETIONS_REACHED_ERROR. */ + MAX_COMPLETIONS_REACHED_ERROR and possible progress update + interactions. */ static void complete_line_internal (completion_tracker &tracker, @@ -1860,6 +1865,11 @@ complete_line_internal (completion_tracker &tracker, const char *line_buffer, int point, complete_line_internal_reason reason) { + scoped_restore restore_prefix_state + = make_scoped_restore + (&cur_prefix_state, + ui_out::progress_update::prefix_state::NEWLINE_NEEDED); + try { complete_line_internal_1 (tracker, text, line_buffer, point, reason); @@ -1869,6 +1879,12 @@ complete_line_internal (completion_tracker &tracker, if (except.error != MAX_COMPLETIONS_REACHED_ERROR) throw; } + + /* If progress update messages printed, then the text being completed + needs to be printed again. */ + if (cur_prefix_state + == ui_out::progress_update::prefix_state::NEWLINE_PRINTED) + rl_forced_update_display (); } /* See completer.h. */ diff --git a/gdb/dwarf2/line-header.c b/gdb/dwarf2/line-header.c index eddb2ef7ae8..1d24386c0eb 100644 --- a/gdb/dwarf2/line-header.c +++ b/gdb/dwarf2/line-header.c @@ -101,12 +101,15 @@ read_checked_initial_length_and_offset (bfd *abfd, const gdb_byte *buf, { LONGEST length = read_initial_length (abfd, buf, bytes_read); - gdb_assert (cu_header->initial_length_size == 4 - || cu_header->initial_length_size == 8 - || cu_header->initial_length_size == 12); + if (cu_header != nullptr) + { + gdb_assert (cu_header->initial_length_size == 4 + || cu_header->initial_length_size == 8 + || cu_header->initial_length_size == 12); - if (cu_header->initial_length_size != *bytes_read) - complaint (_("intermixed 32-bit and 64-bit DWARF sections")); + if (cu_header->initial_length_size != *bytes_read) + complaint (_("intermixed 32-bit and 64-bit DWARF sections")); + } *offset_size = (*bytes_read == 4) ? 4 : 8; return length; @@ -115,21 +118,27 @@ read_checked_initial_length_and_offset (bfd *abfd, const gdb_byte *buf, /* Read directory or file name entry format, starting with byte of format count entries, ULEB128 pairs of entry formats, ULEB128 of entries count and the entries themselves in the described entry - format. */ + format. + + .debug_line and .debug_line_str sections are stored in LINE_BUFP + and LINE_STR_DATA respectively. */ static void -read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd, - const gdb_byte **bufp, struct line_header *lh, - unsigned int offset_size, - void (*callback) (struct line_header *lh, - const char *name, - dir_index d_index, - unsigned int mod_time, - unsigned int length)) +read_formatted_entries + (bfd *abfd, const gdb_byte **line_bufp, + const gdb::array_view line_str_data, + struct line_header *lh, + unsigned int offset_size, + void (*callback) (struct line_header *lh, + const char *name, + dir_index d_index, + unsigned int mod_time, + unsigned int length)) { gdb_byte format_count, formati; ULONGEST data_count, datai; - const gdb_byte *buf = *bufp; + const gdb_byte *buf = *line_bufp; + const gdb_byte *str_buf = line_str_data.data (); const gdb_byte *format_header_data; unsigned int bytes_read; @@ -156,7 +165,7 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd, ULONGEST content_type = read_unsigned_leb128 (abfd, format, &bytes_read); format += bytes_read; - ULONGEST form = read_unsigned_leb128 (abfd, format, &bytes_read); + ULONGEST form = read_unsigned_leb128 (abfd, format, &bytes_read); format += bytes_read; std::optional string; @@ -171,12 +180,24 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd, case DW_FORM_line_strp: { - const char *str - = per_objfile->read_line_string (buf, offset_size); + if (line_str_data.empty ()) + warning (_("Dwarf Error: DW_FORM_line_strp used without " \ + "required section")); + + if (line_str_data.size () <= offset_size) + warning (_("Dwarf Error: DW_FORM_line_strp pointing outside " \ + "of section .debug_line_str")); + + ULONGEST str_offset = read_offset (abfd, buf, offset_size); + const char *str; + if (str_buf[str_offset] == '\0') + str = nullptr; + else + str = (const char *) (str_buf + str_offset); string.emplace (str); buf += offset_size; + break; } - break; case DW_FORM_data1: uint.emplace (read_1_byte (abfd, buf)); @@ -247,28 +268,30 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd, callback (lh, fe.name, fe.d_index, fe.mod_time, fe.length); } - *bufp = buf; + *line_bufp = buf; } /* See line-header.h. */ line_header_up -dwarf_decode_line_header (sect_offset sect_off, bool is_dwz, - dwarf2_per_objfile *per_objfile, - struct dwarf2_section_info *section, - const struct comp_unit_head *cu_header, - const char *comp_dir) +dwarf_decode_line_header (bfd *abfd, + gdb::array_view line_data, + gdb::array_view line_str_data, + const gdb_byte **debug_line_ptr, + bool is_dwz, + const struct comp_unit_head *cu_header, + const char *comp_dir) { - const gdb_byte *line_ptr; + const gdb_byte *line_ptr, *buf; unsigned int bytes_read, offset_size; int i; const char *cur_dir, *cur_file; - bfd *abfd = section->get_bfd_owner (); + buf = *debug_line_ptr; /* Make sure that at least there's room for the total_length field. That could be 12 bytes long, but we're just going to fudge that. */ - if (to_underlying (sect_off) + 4 >= section->size) + if (buf + 4 >= line_data.data () + line_data.size ()) { dwarf2_statement_list_fits_in_line_number_section_complaint (); return 0; @@ -276,24 +299,26 @@ dwarf_decode_line_header (sect_offset sect_off, bool is_dwz, line_header_up lh (new line_header (comp_dir)); - lh->sect_off = sect_off; + lh->sect_off = (sect_offset) (buf - line_data.data ()); lh->offset_in_dwz = is_dwz; - line_ptr = section->buffer + to_underlying (sect_off); + line_ptr = buf; /* Read in the header. */ LONGEST unit_length - = read_checked_initial_length_and_offset (abfd, line_ptr, cu_header, + = read_checked_initial_length_and_offset (abfd, buf, cu_header, &bytes_read, &offset_size); - line_ptr += bytes_read; - const gdb_byte *start_here = line_ptr; + line_ptr += bytes_read; - if (line_ptr + unit_length > (section->buffer + section->size)) + if (line_ptr + unit_length > buf + line_data.size ()) { dwarf2_statement_list_fits_in_line_number_section_complaint (); return 0; } + + const gdb_byte *start_here = line_ptr; + lh->statement_program_end = start_here + unit_length; lh->version = read_2_bytes (abfd, line_ptr); line_ptr += 2; @@ -302,7 +327,7 @@ dwarf_decode_line_header (sect_offset sect_off, bool is_dwz, /* This is a version we don't understand. The format could have changed in ways we don't handle properly so just punt. */ complaint (_("unsupported version in .debug_line section")); - return NULL; + return nullptr; } if (lh->version >= 5) { @@ -319,13 +344,14 @@ dwarf_decode_line_header (sect_offset sect_off, bool is_dwz, complaint (_("unsupported segment selector size %u " "in .debug_line section"), segment_selector_size); - return NULL; + return nullptr; } } LONGEST header_length = read_offset (abfd, line_ptr, offset_size); line_ptr += offset_size; lh->statement_program_start = line_ptr + header_length; + lh->minimum_instruction_length = read_1_byte (abfd, line_ptr); line_ptr += 1; @@ -346,12 +372,16 @@ dwarf_decode_line_header (sect_offset sect_off, bool is_dwz, lh->default_is_stmt = read_1_byte (abfd, line_ptr); line_ptr += 1; + lh->line_base = read_1_signed_byte (abfd, line_ptr); line_ptr += 1; + lh->line_range = read_1_byte (abfd, line_ptr); line_ptr += 1; + lh->opcode_base = read_1_byte (abfd, line_ptr); line_ptr += 1; + lh->standard_opcode_lengths.reset (new unsigned char[lh->opcode_base]); lh->standard_opcode_lengths[0] = 1; /* This should never be used anyway. */ @@ -364,21 +394,23 @@ dwarf_decode_line_header (sect_offset sect_off, bool is_dwz, if (lh->version >= 5) { /* Read directory table. */ - read_formatted_entries (per_objfile, abfd, &line_ptr, lh.get (), - offset_size, - [] (struct line_header *header, const char *name, - dir_index d_index, unsigned int mod_time, - unsigned int length) + read_formatted_entries + (abfd, &line_ptr, line_str_data, + lh.get (), offset_size, + [] (struct line_header *header, const char *name, + dir_index d_index, unsigned int mod_time, + unsigned int length) { header->add_include_dir (name); }); /* Read file name table. */ - read_formatted_entries (per_objfile, abfd, &line_ptr, lh.get (), - offset_size, - [] (struct line_header *header, const char *name, - dir_index d_index, unsigned int mod_time, - unsigned int length) + read_formatted_entries + (abfd, &line_ptr, line_str_data, + lh.get (), offset_size, + [] (struct line_header *header, const char *name, + dir_index d_index, unsigned int mod_time, + unsigned int length) { header->add_file_name (name, d_index, mod_time, length); }); @@ -386,7 +418,7 @@ dwarf_decode_line_header (sect_offset sect_off, bool is_dwz, else { /* Read directory table. */ - while ((cur_dir = read_direct_string (abfd, line_ptr, &bytes_read)) != NULL) + while ((cur_dir = read_direct_string (abfd, line_ptr, &bytes_read)) != nullptr) { line_ptr += bytes_read; lh->add_include_dir (cur_dir); @@ -394,7 +426,7 @@ dwarf_decode_line_header (sect_offset sect_off, bool is_dwz, line_ptr += bytes_read; /* Read file name table. */ - while ((cur_file = read_direct_string (abfd, line_ptr, &bytes_read)) != NULL) + while ((cur_file = read_direct_string (abfd, line_ptr, &bytes_read)) != nullptr) { unsigned int mod_time, length; dir_index d_index; @@ -412,9 +444,40 @@ dwarf_decode_line_header (sect_offset sect_off, bool is_dwz, line_ptr += bytes_read; } - if (line_ptr > (section->buffer + section->size)) + if (line_ptr > (buf + line_data.size ())) complaint (_("line number info header doesn't " "fit in `.debug_line' section")); + *debug_line_ptr += unit_length + offset_size; return lh; } + +line_header_up +dwarf_decode_line_header (sect_offset sect_off, bool is_dwz, + dwarf2_per_objfile *per_objfile, + struct dwarf2_section_info *section, + const struct comp_unit_head *cu_header, + const char *comp_dir) +{ + struct objfile *objfile = per_objfile->objfile; + struct dwarf2_per_bfd *per_bfd = per_objfile->per_bfd; + + /* Read .debug_line. */ + dwarf2_section_info *line_sec = &per_bfd->line; + bfd_size_type line_size = line_sec->get_size (objfile); + + gdb::array_view line (line_sec->buffer, line_size); + + /* Read .debug_line_str. */ + dwarf2_section_info *line_str_sec = &per_bfd->line_str; + bfd_size_type line_str_size = line_str_sec->get_size (objfile); + + gdb::array_view line_str (line_str_sec->buffer, + line_str_size); + + const gdb_byte *line_ptr = line.data () + to_underlying (sect_off); + + return dwarf_decode_line_header + (per_bfd->obfd, line, line_str, &line_ptr, + is_dwz, cu_header, comp_dir); +} diff --git a/gdb/dwarf2/line-header.h b/gdb/dwarf2/line-header.h index 7da59725c98..a6288fb6773 100644 --- a/gdb/dwarf2/line-header.h +++ b/gdb/dwarf2/line-header.h @@ -221,4 +221,14 @@ extern line_header_up dwarf_decode_line_header struct dwarf2_section_info *section, const struct comp_unit_head *cu_header, const char *comp_dir); +/* Like above but the .debug_line and .debug_line_str are stored in + LINE_DATA and LINE_STR_DATA. *DEBUG_LINE_PTR should point to a + statement program header within LINE_DATA. */ + +extern line_header_up dwarf_decode_line_header + (bfd *abfd, gdb::array_view line_data, + gdb::array_view line_str_data, + const gdb_byte **debug_line_ptr, bool is_dwz, + const comp_unit_head *cu_header, const char *comp_dir); + #endif /* DWARF2_LINE_HEADER_H */ diff --git a/gdb/dwarf2/read-gdb-index.c b/gdb/dwarf2/read-gdb-index.c index 1a1e597a9b8..4f28230bbf0 100644 --- a/gdb/dwarf2/read-gdb-index.c +++ b/gdb/dwarf2/read-gdb-index.c @@ -143,6 +143,9 @@ struct mapped_gdb_index final : public mapped_index_base } }; +struct mapped_debug_line; +typedef std::unique_ptr mapped_debug_line_up; + struct dwarf2_gdb_index : public dwarf2_base_index_functions { /* This dumps minimal information about the index. @@ -163,6 +166,14 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions gdb::function_view lang_matcher) override; + /* If OBJFILE's debuginfo download has been deferred, use a mapped_debug_line + to generate filenames. + + Otherwise call dwarf2_base_index_functions::map_symbol_filenames. */ + void map_symbol_filenames (struct objfile *objfile, + gdb::function_view fun, + bool need_fullname) override; + /* Calls dwarf2_base_index_functions::expand_all_symtabs and downloads debuginfo if necessary. */ void expand_all_symtabs (struct objfile *objfile) override; @@ -170,6 +181,15 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions /* Calls dwarf2_base_index_functions::find_last_source_symtab and downloads debuginfo if necessary. */ struct symtab *find_last_source_symtab (struct objfile *objfile) override; + + /* Filename information related to this .gdb_index. */ + mapped_debug_line_up mdl; + + /* Return true if any of the filenames in this .gdb_index's .debug_line + mapping match FILE_MATCHER. Initializes the mapping if necessary. */ + bool filename_in_debug_line + (objfile *objfile, + gdb::function_view file_matcher); }; void @@ -190,6 +210,23 @@ dwarf2_gdb_index::find_last_source_symtab (struct objfile *objfile) return dwarf2_base_index_functions::find_last_source_symtab (objfile); } +void +dwarf2_gdb_index::map_symbol_filenames + (struct objfile *objfile, + gdb::function_view fun, + bool need_fullname) +{ + if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0) + { + if (mdl == nullptr) + mdl.reset (new mapped_debug_line (objfile)); + mdl->map_filenames (fun, need_fullname); + } + else + dwarf2_base_index_functions::map_symbol_filenames (objfile, fun, + need_fullname); +} + /* This dumps minimal information about the index. It is called via "mt print objfiles". One use is to verify .gdb_index has been loaded by the @@ -305,6 +342,17 @@ dw2_expand_marked_cus return true; } +bool +dwarf2_gdb_index::filename_in_debug_line + (objfile *objfile, + gdb::function_view file_matcher) +{ + if (mdl == nullptr) + mdl.reset (new mapped_debug_line (objfile)); + + return mdl->contains_matching_filename (file_matcher); +} + bool dwarf2_gdb_index::expand_symtabs_matching (struct objfile *objfile, @@ -318,7 +366,22 @@ dwarf2_gdb_index::expand_symtabs_matching { dwarf2_per_objfile *per_objfile = get_dwarf2_per_objfile (objfile); - dw_expand_symtabs_matching_file_matcher (per_objfile, file_matcher); + if (file_matcher != nullptr) + { + if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0) + dw_expand_symtabs_matching_file_matcher (per_objfile, file_matcher); + else if (filename_in_debug_line (objfile, file_matcher)) + { + /* If OBJFILE's debuginfo hasn't been downloaded and there is a file + name to be matched, download the .debug_line (and possibly + .debug_line_str) separately. Search the separate .debug_line for + a match. If there is a match, then download the full debuginfo. + Return early to move on to expanding symtabs in the debuginfo + objfile. */ + read_full_dwarf_from_debuginfod (objfile); + return true; + } + } /* This invariant is documented in quick-functions.h. */ gdb_assert (lookup_name != nullptr || symbol_matcher == nullptr); diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c index d11123f34fe..8742c6ffd86 100644 --- a/gdb/dwarf2/read.c +++ b/gdb/dwarf2/read.c @@ -100,6 +100,8 @@ #include "gdbsupport/unordered_set.h" #include "inferior.h" #include "debuginfod-support.h" +#include "exceptions.h" +#include "gdbsupport/scoped_mmap.h" /* When == 1, print basic high level tracing messages. When > 1, be more verbose. @@ -1966,6 +1968,213 @@ dw2_get_file_names (dwarf2_per_cu_data *this_cu, return this_cu->file_names; } +#if !HAVE_SYS_MMAN_H + +bool +mapped_debug_line::contains_matching_filename + (gdb::function_view file_matcher) +{ + return false; +} + +gdb::array_view +mapped_debug_line::read_debug_line_separate + (const char *filename, std::unique_ptr *resource) +{ + return {}; +} + +bool +mapped_debug_line::read_debug_line_from_debuginfod (objfile *objfile) +{ + return false; +} + +void +mapped_debug_line::map_filenames + (gdb::function_view fun, + bool need_fullname) +{ + return; +} + +#else /* !HAVE_SYS_MMAN_H */ + +struct line_resource_mmap final : public index_cache_resource +{ + /* Try to mmap FILENAME. Throw an exception on failure, including if the + file doesn't exist. */ + line_resource_mmap (const char *filename) + : mapping (mmap_file (filename)) + {} + + scoped_mmap mapping; +}; + +/* See read.h. */ + +bool +mapped_debug_line::contains_matching_filename + (gdb::function_view file_matcher) +{ + for (line_header_up &lh : line_headers) + for (file_entry &fe : lh->file_names ()) + { + const char *filename = fe.name; + + if (file_matcher (fe.name, false)) + return true; + + bool basename_match = file_matcher (lbasename (fe.name), true); + + if (!basenames_may_differ && !basename_match) + continue; + + /* DW_AT_comp_dir is not explicitly mentioned in the .debug_line + until DWARF5. Since we don't have access to the CU at this + point we just check for a partial match on the filename. + If there is a match, the full debuginfo will be downloaded + ane the match will be re-evalute with DW_AT_comp_dir. */ + if (lh->version < 5 && fe.d_index == 0) + return basename_match; + + const char *dirname = fe.include_dir (&*lh); + std::string fullname; + + if (dirname == nullptr || IS_ABSOLUTE_PATH (filename)) + fullname = filename; + else + fullname = std::string (dirname) + SLASH_STRING + filename; + + gdb::unique_xmalloc_ptr rewritten + = rewrite_source_path (fullname.c_str ()); + if (rewritten != nullptr) + fullname = rewritten.release (); + + if (file_matcher (fullname.c_str (), false)) + return true; + } + + return false; +} + +/* See read.h. */ + +void +mapped_debug_line::map_filenames + (gdb::function_view fun, + bool need_fullname) +{ + for (line_header_up &lh : line_headers) + for (file_entry &fe : lh->file_names ()) + { + const char *filename = fe.name; + + if (!need_fullname) + { + fun (filename, nullptr); + continue; + } + + const char *dirname = fe.include_dir (&*lh); + std::string fullname; + + if (dirname == nullptr || IS_ABSOLUTE_PATH (filename)) + fullname = filename; + else + fullname = std::string (dirname) + SLASH_STRING + filename; + + gdb::unique_xmalloc_ptr rewritten + = rewrite_source_path (fullname.c_str ()); + if (rewritten != nullptr) + fullname = rewritten.release (); + + fun (filename, fullname.c_str ()); + } +} + +/* See read.h. */ + +gdb::array_view +mapped_debug_line::read_debug_line_separate + (const char *filename, std::unique_ptr *resource) +{ + if (filename == nullptr) + return {}; + + try + { + line_resource_mmap *mmap_resource + = new line_resource_mmap (filename); + + resource->reset (mmap_resource); + + return gdb::array_view + ((const gdb_byte *) mmap_resource->mapping.get (), + mmap_resource->mapping.size ()); + } + catch (const gdb_exception &except) + { + exception_print (gdb_stderr, except); + } + + return {}; +} + +/* See read.h. */ + +bool +mapped_debug_line::read_debug_line_from_debuginfod (objfile *objfile) +{ + const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ()); + if (build_id == nullptr) + return false; + + gdb::unique_xmalloc_ptr line_path; + scoped_fd line_fd = debuginfod_section_query (build_id->data, + build_id->size, + bfd_get_filename + (objfile->obfd.get ()), + ".debug_line", + &line_path); + + if (line_fd.get () < 0) + return false; + + gdb::unique_xmalloc_ptr line_str_path; + scoped_fd line_str_fd = debuginfod_section_query (build_id->data, + build_id->size, + bfd_get_filename + (objfile->obfd.get ()), + ".debug_line_str", + &line_str_path); + + line_data = read_debug_line_separate (line_path.get (), &line_resource); + line_str_data = read_debug_line_separate (line_str_path.get (), + &line_str_resource); + + const gdb_byte *line_ptr = line_data.data (); + + while (line_ptr < line_data.data () + line_data.size ()) + { + line_header_up lh + = dwarf_decode_line_header (objfile->obfd.get (), + line_data, line_str_data, + &line_ptr, false, + nullptr, nullptr); + line_headers.emplace_back (std::move (lh)); + } + + return true; +} +#endif /* !HAVE_SYS_MMAN_H */ + +mapped_debug_line::mapped_debug_line (objfile *objfile) +{ + if (!read_debug_line_from_debuginfod (objfile)) + line_headers.clear (); +} + /* A helper for the "quick" functions which computes and caches the real path for a given file name from the line table. */ diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h index 337a5e9ce80..26776ba37d3 100644 --- a/gdb/dwarf2/read.h +++ b/gdb/dwarf2/read.h @@ -31,6 +31,7 @@ #include "gdbsupport/gdb_obstack.h" #include "gdbsupport/function-view.h" #include "gdbsupport/packed.h" +#include "dwarf2/line-header.h" /* Hold 'maintenance (set|show) dwarf' commands. */ extern struct cmd_list_element *set_dwarf_cmdlist; @@ -987,4 +988,40 @@ extern htab_up create_quick_file_names_table (unsigned int nr_initial_entries); extern void read_full_dwarf_from_debuginfod (struct objfile *); +struct mapped_debug_line +{ + mapped_debug_line (objfile *objfile); + + /* Return true if any of the mapped .debug_line's filenames match + FILE_MATCHER. */ + + bool contains_matching_filename + (gdb::function_view file_matcher); + + /* Call FUN with each filename in this mapped .debug_line. Include + each file's fullname if NEED_FULLNAME is true. */ + + void map_filenames (gdb::function_view fun, + bool need_fullname); + +private: + std::vector line_headers; + + gdb::array_view line_data; + gdb::array_view line_str_data; + + std::unique_ptr line_resource; + std::unique_ptr line_str_resource; + + /* Download the .debug_line and .debug_line_str associated with OBJFILE + and populate line_headers. */ + + bool read_debug_line_from_debuginfod (objfile *objfile); + + /* Initialize line_data and line_str_data with the .debug_line and + .debug_line_str downloaded read_debug_line_from_debuginfod. */ + + gdb::array_view read_debug_line_separate + (const char *filename, std::unique_ptr *resource); +}; #endif /* DWARF2READ_H */ diff --git a/gdb/mi/mi-out.c b/gdb/mi/mi-out.c index 9ad26e76a30..97e2cce58c9 100644 --- a/gdb/mi/mi-out.c +++ b/gdb/mi/mi-out.c @@ -278,7 +278,14 @@ mi_ui_out::do_progress_notify (const std::string &msg, const char *unit, if (info.state == progress_update::START) { - gdb_printf ("%s...\n", msg.c_str ()); + std::string prefix; + if (cur_prefix_state == prefix_state_t::NEWLINE_NEEDED) + { + prefix = "\n"; + cur_prefix_state = prefix_state_t::NEWLINE_PRINTED; + } + + gdb_printf ("%s...\n", (prefix + msg).c_str ()); info.state = progress_update::WORKING; } } diff --git a/gdb/testsuite/gdb.debuginfod/section.exp b/gdb/testsuite/gdb.debuginfod/section.exp index 381791bca50..8e482364434 100644 --- a/gdb/testsuite/gdb.debuginfod/section.exp +++ b/gdb/testsuite/gdb.debuginfod/section.exp @@ -18,6 +18,7 @@ standard_testfile load_lib debuginfod-support.exp +load_lib completion-support.exp clean_restart require allow_debuginfod_tests @@ -144,6 +145,33 @@ proc_with_prefix local_url { } { # Hit the breakpoint. set res ".*Breakpoint 2, libsection2_test.*\"In ${libfile2}\\\\n\".*" gdb_test "c" $res "break continue" + + clean_restart_with_prompt $binfile "line 1" + + # List source file using .debug_line download. + set res ".*\.debug_line.*$lib_sl1.*21.*extern void libsection2_test.*" + gdb_test "list $libsrc1:21" $res "line 1 list" + + clean_restart_with_prompt $binfile "line 2" + + # Set breakpoint using .debug_line download. + set res ".*section \.debug_line for $lib_sl1.*Breakpoint 2 at.*$libsrc1.*" + gdb_test "br $libsrc1:41" $res "line 2 br" + + # Continue to breakpoint. + set res "Breakpoint 2, libsection1_test.*\"Cancelling thread\\\\n\".*" + gdb_test "c" $res "line 2 continue" + + # Check that download progress message is correctly formatted + # when printing threads. + set res ".*separate debug info for $lib_sl2\.\.\.\r\n.* 2 Thread.*" + gdb_test "info thr" $res "line thread" + + clean_restart_with_prompt $binfile "autocomplete" + + # Download debuginfo during autocompletion. + test_gdb_complete_tab_unique "br lib" \ + ".*separate debug info for $lib_sl1.*" "" } # Create CACHE and DB directories ready for debuginfod to use. diff --git a/gdb/ui-out.c b/gdb/ui-out.c index 41ce6efd14f..47c987f88a9 100644 --- a/gdb/ui-out.c +++ b/gdb/ui-out.c @@ -31,6 +31,9 @@ #include #include +/* Current state of newline prefixing for progress update messages. */ +prefix_state_t cur_prefix_state = prefix_state_t::NEWLINE_OFF; + namespace { /* A header of a ui_out_table. */ diff --git a/gdb/ui-out.h b/gdb/ui-out.h index f9d96dea875..f9fe1ed6eae 100644 --- a/gdb/ui-out.h +++ b/gdb/ui-out.h @@ -300,6 +300,21 @@ class ui_out BAR }; + /* Used to communicate the status of a newline prefix for the next progress + update message. */ + enum prefix_state + { + /* Do not modify the next progress update message. */ + NEWLINE_OFF, + + /* The next progress update message should include a newline prefix. */ + NEWLINE_NEEDED, + + /* A newline prefix was included in a debuginfod progress update + message. */ + NEWLINE_PRINTED + }; + /* SHOULD_PRINT indicates whether something should be printed for a tty. */ progress_update () { @@ -398,6 +413,11 @@ class ui_out ui_out_level *current_level () const; }; +typedef ui_out::progress_update::prefix_state prefix_state_t; + +/* Current state of the newline prefix. */ +extern prefix_state_t cur_prefix_state; + /* Start a new tuple or list on construction, and end it on destruction. Normally this is used via the typedefs ui_out_emit_tuple and ui_out_emit_list. */