|  | /* DIE indexing | 
|  |  | 
|  | Copyright (C) 2022-2025 Free Software Foundation, Inc. | 
|  |  | 
|  | This file is part of GDB. | 
|  |  | 
|  | This program is free software; you can redistribute it and/or modify | 
|  | it under the terms of the GNU General Public License as published by | 
|  | the Free Software Foundation; either version 3 of the License, or | 
|  | (at your option) any later version. | 
|  |  | 
|  | This program is distributed in the hope that it will be useful, | 
|  | but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | GNU General Public License for more details. | 
|  |  | 
|  | You should have received a copy of the GNU General Public License | 
|  | along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ | 
|  |  | 
|  | #include "dwarf2/cooked-index.h" | 
|  | #include "dwarf2/read.h" | 
|  | #include "dwarf2/stringify.h" | 
|  | #include "event-top.h" | 
|  | #include "maint.h" | 
|  | #include "observable.h" | 
|  | #include "run-on-main-thread.h" | 
|  | #include "gdbsupport/task-group.h" | 
|  | #include "cli/cli-cmds.h" | 
|  |  | 
|  | /* We don't want gdb to exit while it is in the process of writing to | 
|  | the index cache.  So, all live cooked index vectors are stored | 
|  | here, and then these are all waited for before exit proceeds.  */ | 
|  | static gdb::unordered_set<cooked_index *> active_vectors; | 
|  |  | 
|  | /* Return true if LANG requires canonicalization.  This is used | 
|  | primarily to work around an issue computing the name of "main". | 
|  | This function must be kept in sync with | 
|  | cooked_index_shard::finalize.  */ | 
|  |  | 
|  | static bool | 
|  | language_requires_canonicalization (enum language lang) | 
|  | { | 
|  | return (lang == language_ada | 
|  | || lang == language_c | 
|  | || lang == language_cplus); | 
|  | } | 
|  |  | 
|  | cooked_index::cooked_index (cooked_index_worker_up &&worker) | 
|  | : m_state (std::move (worker)) | 
|  | { | 
|  | /* ACTIVE_VECTORS is not locked, and this assert ensures that this | 
|  | will be caught if ever moved to the background.  */ | 
|  | gdb_assert (is_main_thread ()); | 
|  | active_vectors.insert (this); | 
|  | } | 
|  |  | 
|  | void | 
|  | cooked_index::start_reading () | 
|  | { | 
|  | m_state->start (); | 
|  | } | 
|  |  | 
|  | void | 
|  | cooked_index::wait (cooked_state desired_state, bool allow_quit) | 
|  | { | 
|  | gdb_assert (desired_state != cooked_state::INITIAL); | 
|  |  | 
|  | /* If the state object has been deleted, then that means waiting is | 
|  | completely done.  */ | 
|  | if (m_state == nullptr) | 
|  | return; | 
|  |  | 
|  | if (m_state->wait (desired_state, allow_quit)) | 
|  | { | 
|  | /* Only the main thread can modify this.  */ | 
|  | gdb_assert (is_main_thread ()); | 
|  | m_state.reset (nullptr); | 
|  | } | 
|  | } | 
|  |  | 
|  | void | 
|  | cooked_index::set_contents () | 
|  | { | 
|  | gdb_assert (m_shards.empty ()); | 
|  | m_shards = m_state->release_shards (); | 
|  |  | 
|  | m_state->set (cooked_state::MAIN_AVAILABLE); | 
|  |  | 
|  | /* This is run after finalization is done -- but not before.  If | 
|  | this task were submitted earlier, it would have to wait for | 
|  | finalization.  However, that would take a slot in the global | 
|  | thread pool, and if enough such tasks were submitted at once, it | 
|  | would cause a livelock.  */ | 
|  | gdb::task_group finalizers ([this] () | 
|  | { | 
|  | m_state->set (cooked_state::FINALIZED); | 
|  | m_state->write_to_cache (index_for_writing ()); | 
|  | m_state->set (cooked_state::CACHE_DONE); | 
|  | }); | 
|  |  | 
|  | for (auto &shard : m_shards) | 
|  | { | 
|  | auto this_shard = shard.get (); | 
|  | const parent_map_map *parent_maps = m_state->get_parent_map_map (); | 
|  | finalizers.add_task ([=] () | 
|  | { | 
|  | scoped_time_it time_it ("DWARF finalize worker", | 
|  | m_state->m_per_command_time); | 
|  | this_shard->finalize (parent_maps); | 
|  | }); | 
|  | } | 
|  |  | 
|  | finalizers.start (); | 
|  | } | 
|  |  | 
|  | cooked_index::~cooked_index () | 
|  | { | 
|  | /* Wait for index-creation to be done, though this one must also | 
|  | waited for by the per-BFD object to ensure the required data | 
|  | remains live.  */ | 
|  | wait (cooked_state::CACHE_DONE); | 
|  |  | 
|  | /* Remove our entry from the global list.  See the assert in the | 
|  | constructor to understand this.  */ | 
|  | gdb_assert (is_main_thread ()); | 
|  | active_vectors.erase (this); | 
|  | } | 
|  |  | 
|  | /* See cooked-index.h.  */ | 
|  |  | 
|  | dwarf2_per_cu * | 
|  | cooked_index::lookup (unrelocated_addr addr) | 
|  | { | 
|  | /* Ensure that the address maps are ready.  */ | 
|  | wait (cooked_state::MAIN_AVAILABLE, true); | 
|  | for (const auto &shard : m_shards) | 
|  | { | 
|  | dwarf2_per_cu *result = shard->lookup (addr); | 
|  | if (result != nullptr) | 
|  | return result; | 
|  | } | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | /* See cooked-index.h.  */ | 
|  |  | 
|  | std::vector<const addrmap *> | 
|  | cooked_index::get_addrmaps () | 
|  | { | 
|  | /* Ensure that the address maps are ready.  */ | 
|  | wait (cooked_state::MAIN_AVAILABLE, true); | 
|  | std::vector<const addrmap *> result; | 
|  | for (const auto &shard : m_shards) | 
|  | result.push_back (shard->m_addrmap); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /* See cooked-index.h.  */ | 
|  |  | 
|  | cooked_index::range | 
|  | cooked_index::find (const std::string &name, bool completing) | 
|  | { | 
|  | wait (cooked_state::FINALIZED, true); | 
|  | std::vector<cooked_index_shard::range> result_range; | 
|  | result_range.reserve (m_shards.size ()); | 
|  | for (auto &shard : m_shards) | 
|  | result_range.push_back (shard->find (name, completing)); | 
|  | return range (std::move (result_range)); | 
|  | } | 
|  |  | 
|  | /* See cooked-index.h.  */ | 
|  |  | 
|  | const char * | 
|  | cooked_index::get_main_name (struct obstack *obstack, enum language *lang) | 
|  | const | 
|  | { | 
|  | const cooked_index_entry *entry = get_main (); | 
|  | if (entry == nullptr) | 
|  | return nullptr; | 
|  |  | 
|  | *lang = entry->lang; | 
|  | return entry->full_name (obstack, FOR_MAIN); | 
|  | } | 
|  |  | 
|  | /* See cooked_index.h.  */ | 
|  |  | 
|  | const cooked_index_entry * | 
|  | cooked_index::get_main () const | 
|  | { | 
|  | const cooked_index_entry *best_entry = nullptr; | 
|  | for (const auto &shard : m_shards) | 
|  | { | 
|  | const cooked_index_entry *entry = shard->get_main (); | 
|  | /* Choose the first "main" we see.  We only do this for names | 
|  | not requiring canonicalization.  At this point in the process | 
|  | names might not have been canonicalized.  However, currently, | 
|  | languages that require this step also do not use | 
|  | DW_AT_main_subprogram.  An assert is appropriate here because | 
|  | this filtering is done in get_main.  */ | 
|  | if (entry != nullptr) | 
|  | { | 
|  | if ((entry->flags & IS_MAIN) != 0) | 
|  | { | 
|  | if (!language_requires_canonicalization (entry->lang)) | 
|  | { | 
|  | /* There won't be one better than this.  */ | 
|  | return entry; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | /* This is one that is named "main".  Here we don't care | 
|  | if the language requires canonicalization, due to how | 
|  | the entry is detected.  Entries like this have worse | 
|  | priority than IS_MAIN entries.  */ | 
|  | if (best_entry == nullptr) | 
|  | best_entry = entry; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return best_entry; | 
|  | } | 
|  |  | 
|  | quick_symbol_functions_up | 
|  | cooked_index::make_quick_functions () const | 
|  | { | 
|  | return quick_symbol_functions_up (new cooked_index_functions); | 
|  | } | 
|  |  | 
|  | /* See cooked-index.h.  */ | 
|  |  | 
|  | void | 
|  | cooked_index::dump (gdbarch *arch) | 
|  | { | 
|  | auto_obstack temp_storage; | 
|  |  | 
|  | gdb_printf ("  entries:\n"); | 
|  | gdb_printf ("\n"); | 
|  |  | 
|  | size_t i = 0; | 
|  | for (const cooked_index_entry *entry : this->all_entries ()) | 
|  | { | 
|  | QUIT; | 
|  |  | 
|  | gdb_printf ("    [%zu] ((cooked_index_entry *) %p)\n", i++, entry); | 
|  | gdb_printf ("    name:       %s\n", entry->name); | 
|  | gdb_printf ("    canonical:  %s\n", entry->canonical); | 
|  | gdb_printf ("    qualified:  %s\n", | 
|  | entry->full_name (&temp_storage, 0, "::")); | 
|  | gdb_printf ("    DWARF tag:  %s\n", dwarf_tag_name (entry->tag)); | 
|  | gdb_printf ("    flags:      %s\n", to_string (entry->flags).c_str ()); | 
|  | gdb_printf ("    DIE offset: %s\n", sect_offset_str (entry->die_offset)); | 
|  |  | 
|  | if ((entry->flags & IS_PARENT_DEFERRED) != 0) | 
|  | gdb_printf ("    parent:     deferred (%" PRIx64 ")\n", | 
|  | entry->get_deferred_parent ()); | 
|  | else if (entry->get_parent () != nullptr) | 
|  | gdb_printf ("    parent:     ((cooked_index_entry *) %p) [%s]\n", | 
|  | entry->get_parent (), entry->get_parent ()->name); | 
|  | else | 
|  | gdb_printf ("    parent:     ((cooked_index_entry *) 0)\n"); | 
|  |  | 
|  | gdb_printf ("\n"); | 
|  | } | 
|  |  | 
|  | const cooked_index_entry *main_entry = this->get_main (); | 
|  | if (main_entry != nullptr) | 
|  | gdb_printf ("  main: ((cooked_index_entry *) %p) [%s]\n", main_entry, | 
|  | main_entry->name); | 
|  | else | 
|  | gdb_printf ("  main: ((cooked_index_entry *) 0)\n"); | 
|  |  | 
|  | gdb_printf ("\n"); | 
|  | gdb_printf ("  address maps:\n"); | 
|  | gdb_printf ("\n"); | 
|  |  | 
|  | std::vector<const addrmap *> addrmaps = this->get_addrmaps (); | 
|  | for (i = 0; i < addrmaps.size (); ++i) | 
|  | { | 
|  | const addrmap *addrmap = addrmaps[i]; | 
|  |  | 
|  | gdb_printf ("    [%zu] ((addrmap *) %p)\n", i, addrmap); | 
|  | gdb_printf ("\n"); | 
|  |  | 
|  | if (addrmap == nullptr) | 
|  | continue; | 
|  |  | 
|  | addrmap->foreach ([arch] (CORE_ADDR start_addr, const void *obj) | 
|  | { | 
|  | QUIT; | 
|  |  | 
|  | const char *start_addr_str = paddress (arch, start_addr); | 
|  |  | 
|  | if (obj != nullptr) | 
|  | { | 
|  | const dwarf2_per_cu *per_cu | 
|  | = static_cast<const dwarf2_per_cu *> (obj); | 
|  | gdb_printf ("      [%s] ((dwarf2_per_cu *) %p)\n", | 
|  | start_addr_str, per_cu); | 
|  | } | 
|  | else | 
|  | gdb_printf ("      [%s] ((dwarf2_per_cu *) 0)\n", start_addr_str); | 
|  |  | 
|  | return 0; | 
|  | }); | 
|  |  | 
|  | gdb_printf ("\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Wait for all the index cache entries to be written before gdb | 
|  | exits.  */ | 
|  | static void | 
|  | wait_for_index_cache (int) | 
|  | { | 
|  | gdb_assert (is_main_thread ()); | 
|  | for (cooked_index *item : active_vectors) | 
|  | item->wait_completely (); | 
|  | } | 
|  |  | 
|  | /* A maint command to wait for the cache.  */ | 
|  |  | 
|  | static void | 
|  | maintenance_wait_for_index_cache (const char *args, int from_tty) | 
|  | { | 
|  | wait_for_index_cache (0); | 
|  | } | 
|  |  | 
|  | void _initialize_cooked_index (); | 
|  | void | 
|  | _initialize_cooked_index () | 
|  | { | 
|  | add_cmd ("wait-for-index-cache", class_maintenance, | 
|  | maintenance_wait_for_index_cache, _("\ | 
|  | Wait until all pending writes to the index cache have completed.\n\ | 
|  | Usage: maintenance wait-for-index-cache"), | 
|  | &maintenancelist); | 
|  |  | 
|  | gdb::observers::gdb_exiting.attach (wait_for_index_cache, "cooked-index"); | 
|  | } |