aboutsummaryrefslogtreecommitdiff
path: root/gdb/solib-svr4.c
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/solib-svr4.c')
-rw-r--r--gdb/solib-svr4.c377
1 files changed, 365 insertions, 12 deletions
diff --git a/gdb/solib-svr4.c b/gdb/solib-svr4.c
index 398123f..458c4ba 100644
--- a/gdb/solib-svr4.c
+++ b/gdb/solib-svr4.c
@@ -1,6 +1,6 @@
/* Handle SVR4 shared libraries for GDB, the GNU Debugger.
- Copyright (C) 1990-2024 Free Software Foundation, Inc.
+ Copyright (C) 1990-2025 Free Software Foundation, Inc.
This file is part of GDB.
@@ -405,11 +405,68 @@ struct svr4_info
The special entry zero is reserved for a linear list to support
gdbstubs that do not support namespaces. */
std::map<CORE_ADDR, std::vector<svr4_so>> solib_lists;
+
+ /* Mapping between r_debug[_ext] addresses and a user-friendly
+ identifier for the namespace. A vector is used to make it
+ easy to assign new internal IDs to namespaces.
+
+ For gdbservers that don't support namespaces, the first (and only)
+ entry of the vector will be 0.
+
+ A note on consistency. We can't make the IDs be consistent before
+ and after the initial relocation of the inferior (when the global
+ _r_debug is relocated, as mentioned in the previous comment). It is
+ likely that this is a non-issue, since the inferior can't have called
+ dlmopen yet, but I think it is worth noting.
+
+ The only issue I am aware at this point is that, if when parsing an
+ XML file, we read an LMID that given by an XML file (and read in
+ library_list_start_library) is the identifier obtained with dlinfo
+ instead of the address of r_debug[_ext], and after attaching the
+ inferior adds another SO to that namespace, we might double-count it
+ since we won't have access to the LMID later on. However, this is
+ already a problem with the existing solib_lists code. */
+ std::vector<CORE_ADDR> namespace_id;
+
+ /* This identifies which namespaces are active. A namespace is considered
+ active when there is at least one shared object loaded into it. */
+ std::set<size_t> active_namespaces;
+
+ /* This flag indicates whether initializations related to the
+ GLIBC TLS module id tracking code have been performed. */
+ bool glibc_tls_slots_inited = false;
+
+ /* A vector of link map addresses for GLIBC TLS slots. See comment
+ for tls_maybe_fill_slot for more information. */
+ std::vector<CORE_ADDR> glibc_tls_slots;
};
/* Per-program-space data key. */
static const registry<program_space>::key<svr4_info> solib_svr4_pspace_data;
+/* Check if the lmid address is already assigned an ID in the svr4_info,
+ and if not, assign it one and add it to the list of known namespaces. */
+static void
+svr4_maybe_add_namespace (svr4_info *info, CORE_ADDR lmid)
+{
+ int i;
+ for (i = 0; i < info->namespace_id.size (); i++)
+ {
+ if (info->namespace_id[i] == lmid)
+ break;
+ }
+ if (i == info->namespace_id.size ())
+ info->namespace_id.push_back (lmid);
+
+ info->active_namespaces.insert (i);
+
+ /* Create or update the convenience variable "active_namespaces".
+ It only needs to be updated here, as this only changes when a
+ dlmopen or dlclose call happens. */
+ set_internalvar_integer (lookup_internalvar ("_active_linker_namespaces"),
+ info->active_namespaces.size ());
+}
+
/* Return whether DEBUG_BASE is the default namespace of INFO. */
static bool
@@ -586,10 +643,10 @@ read_program_header (int type, int *p_arch_size, CORE_ADDR *base_addr)
return buf;
}
+/* See solib-svr4.h. */
-/* Return program interpreter string. */
-static std::optional<gdb::byte_vector>
-find_program_interpreter (void)
+std::optional<gdb::byte_vector>
+svr4_find_program_interpreter ()
{
/* If we have a current exec_bfd, use its section table. */
if (current_program_space->exec_bfd ()
@@ -1041,14 +1098,18 @@ library_list_start_library (struct gdb_xml_parser *parser,
/* Older versions did not supply lmid. Put the element into the flat
list of the special namespace zero in that case. */
gdb_xml_value *at_lmid = xml_find_attribute (attributes, "lmid");
+ svr4_info *info = get_svr4_info (current_program_space);
if (at_lmid == nullptr)
- solist = list->cur_list;
+ {
+ solist = list->cur_list;
+ svr4_maybe_add_namespace (info, 0);
+ }
else
{
ULONGEST lmid = *(ULONGEST *) at_lmid->value.get ();
solist = &list->solib_lists[lmid];
+ svr4_maybe_add_namespace (info, lmid);
}
-
solist->emplace_back (name, std::move (li));
}
@@ -1286,6 +1347,8 @@ svr4_current_sos_direct (struct svr4_info *info)
/* Remove any old libraries. We're going to read them back in again. */
info->solib_lists.clear ();
+ info->active_namespaces.clear ();
+
/* Fall back to manual examination of the target if the packet is not
supported or gdbserver failed to find DT_DEBUG. gdb.server/solib-list.exp
tests a case where gdbserver cannot find the shared libraries list while
@@ -1333,7 +1396,10 @@ svr4_current_sos_direct (struct svr4_info *info)
ignore_first = true;
auto cleanup = make_scope_exit ([info] ()
- { info->solib_lists.clear (); });
+ {
+ info->solib_lists.clear ();
+ info->active_namespaces.clear ();
+ });
/* Collect the sos in each namespace. */
CORE_ADDR debug_base = info->debug_base;
@@ -1343,8 +1409,11 @@ svr4_current_sos_direct (struct svr4_info *info)
/* Walk the inferior's link map list, and build our so_list list. */
lm = solib_svr4_r_map (debug_base);
if (lm != 0)
- svr4_read_so_list (info, lm, 0, info->solib_lists[debug_base],
- ignore_first);
+ {
+ svr4_maybe_add_namespace (info, debug_base);
+ svr4_read_so_list (info, lm, 0, info->solib_lists[debug_base],
+ ignore_first);
+ }
}
/* On Solaris, the dynamic linker is not in the normal list of
@@ -1361,8 +1430,11 @@ svr4_current_sos_direct (struct svr4_info *info)
{
/* Add the dynamic linker's namespace unless we already did. */
if (info->solib_lists.find (debug_base) == info->solib_lists.end ())
- svr4_read_so_list (info, debug_base, 0, info->solib_lists[debug_base],
- 0);
+ {
+ svr4_maybe_add_namespace (info, debug_base);
+ svr4_read_so_list (info, debug_base, 0, info->solib_lists[debug_base],
+ 0);
+ }
}
cleanup.release ();
@@ -1516,6 +1588,198 @@ svr4_fetch_objfile_link_map (struct objfile *objfile)
return 0;
}
+/* Return true if bfd section BFD_SECT is a thread local section
+ (i.e. either named ".tdata" or ".tbss"), and false otherwise. */
+
+static bool
+is_thread_local_section (struct bfd_section *bfd_sect)
+{
+ return ((strcmp (bfd_sect->name, ".tdata") == 0
+ || strcmp (bfd_sect->name, ".tbss") == 0)
+ && bfd_sect->size != 0);
+}
+
+/* Return true if objfile OBJF contains a thread local section, and
+ false otherwise. */
+
+static bool
+has_thread_local_section (const objfile *objf)
+{
+ for (obj_section *objsec : objf->sections ())
+ if (is_thread_local_section (objsec->the_bfd_section))
+ return true;
+ return false;
+}
+
+/* Return true if solib SO contains a thread local section, and false
+ otherwise. */
+
+static bool
+has_thread_local_section (const solib &so)
+{
+ for (const target_section &p : so.sections)
+ if (is_thread_local_section (p.the_bfd_section))
+ return true;
+ return false;
+}
+
+/* For the MUSL C library, given link map address LM_ADDR, return the
+ corresponding TLS module id, or 0 if not found.
+
+ Background: Unlike the mechanism used by glibc (see below), the
+ scheme used by the MUSL C library is pretty simple. If the
+ executable contains TLS variables it gets module id 1. Otherwise,
+ the first shared object loaded which contains TLS variables is
+ assigned to module id 1. TLS-containing shared objects are then
+ assigned consecutive module ids, based on the order that they are
+ loaded. When unloaded via dlclose, module ids are reassigned as if
+ that module had never been loaded. */
+
+int
+musl_link_map_to_tls_module_id (CORE_ADDR lm_addr)
+{
+ /* When lm_addr is zero, the program is statically linked. Any TLS
+ variables will be in module id 1. */
+ if (lm_addr == 0)
+ return 1;
+
+ int mod_id = 0;
+ if (has_thread_local_section (current_program_space->symfile_object_file))
+ mod_id++;
+
+ struct svr4_info *info = get_svr4_info (current_program_space);
+
+ /* Cause svr4_current_sos() to be run if it hasn't been already. */
+ if (info->main_lm_addr == 0)
+ solib_add (NULL, 0, auto_solib_add);
+
+ /* Handle case where lm_addr corresponds to the main program.
+ Return value is either 0, when there are no TLS variables, or 1,
+ when there are. */
+ if (lm_addr == info->main_lm_addr)
+ return mod_id;
+
+ /* Iterate through the shared objects, possibly incrementing the
+ module id, and returning mod_id should a match be found. */
+ for (const solib &so : current_program_space->solibs ())
+ {
+ if (has_thread_local_section (so))
+ mod_id++;
+
+ auto *li = gdb::checked_static_cast<lm_info_svr4 *> (so.lm_info.get ());
+ if (li->lm_addr == lm_addr)
+ return mod_id;
+ }
+ return 0;
+}
+
+/* For GLIBC, given link map address LM_ADDR, return the corresponding TLS
+ module id, or 0 if not found. */
+
+int
+glibc_link_map_to_tls_module_id (CORE_ADDR lm_addr)
+{
+ /* When lm_addr is zero, the program is statically linked. Any TLS
+ variables will be in module id 1. */
+ if (lm_addr == 0)
+ return 1;
+
+ /* Look up lm_addr in the TLS slot data structure. */
+ struct svr4_info *info = get_svr4_info (current_program_space);
+ auto it = std::find (info->glibc_tls_slots.begin (),
+ info->glibc_tls_slots.end (),
+ lm_addr);
+ if (it == info->glibc_tls_slots.end ())
+ return 0;
+ else
+ return 1 + it - info->glibc_tls_slots.begin ();
+}
+
+/* Conditionally, based on whether the shared object, SO, contains TLS
+ variables, assign a link map address to a TLS module id slot. This
+ code is GLIBC-specific and may only work for specific GLIBC
+ versions. That said, it is known to work for (at least) GLIBC
+ versions 2.27 thru 2.40.
+
+ Background: In order to implement internal TLS address lookup
+ code, it is necessary to find the module id that has been
+ associated with a specific link map address. In GLIBC, the TLS
+ module id is stored in struct link_map, in the member
+ 'l_tls_modid'. While the first several members of struct link_map
+ are part of the SVR4 ABI, the offset to l_tls_modid definitely is
+ not. Therefore, since we don't know the offset to l_tls_modid, we
+ cannot simply look it up - which is a shame, because things would
+ be so much more easy and obviously accurate, if we could access
+ l_tls_modid.
+
+ GLIBC has a concept of TLS module id slots. These slots are
+ allocated consecutively as shared objects containing TLS variables
+ are loaded. When unloaded (e.g. via dlclose()), the corresponding
+ slot is marked as unused, but may be used again when later loading
+ a shared object.
+
+ The functions tls_maybe_fill_slot and tls_maybe_erase_slot are
+ associated with the observers 'solib_loaded' and 'solib_unloaded'.
+ They (attempt to) track use of TLS module id slots in the same way
+ that GLIBC does, which will hopefully provide an accurate module id
+ when asked to provide it via glibc_link_map_to_tls_module_id(),
+ above. */
+
+static void
+tls_maybe_fill_slot (solib &so)
+{
+ struct svr4_info *info = get_svr4_info (current_program_space);
+ if (!info->glibc_tls_slots_inited)
+ {
+ /* Cause svr4_current_sos() to be run if it hasn't been already. */
+ if (info->main_lm_addr == 0)
+ svr4_current_sos_direct (info);
+
+ /* Quit early when main_lm_addr is still 0. */
+ if (info->main_lm_addr == 0)
+ return;
+
+ /* Also quit early when symfile_object_file is not yet known. */
+ if (current_program_space->symfile_object_file == nullptr)
+ return;
+
+ if (has_thread_local_section (current_program_space->symfile_object_file))
+ info->glibc_tls_slots.push_back (info->main_lm_addr);
+ info->glibc_tls_slots_inited = true;
+ }
+
+ if (has_thread_local_section (so))
+ {
+ auto it = std::find (info->glibc_tls_slots.begin (),
+ info->glibc_tls_slots.end (),
+ 0);
+ auto *li = gdb::checked_static_cast<lm_info_svr4 *> (so.lm_info.get ());
+ if (it == info->glibc_tls_slots.end ())
+ info->glibc_tls_slots.push_back (li->lm_addr);
+ else
+ *it = li->lm_addr;
+ }
+}
+
+/* Remove a link map address from the TLS module slot data structure.
+ As noted above, this code is GLIBC-specific. */
+
+static void
+tls_maybe_erase_slot (program_space *pspace, const solib &so,
+ bool still_in_use, bool silent)
+{
+ if (still_in_use)
+ return;
+
+ struct svr4_info *info = get_svr4_info (pspace);
+ auto *li = gdb::checked_static_cast<lm_info_svr4 *> (so.lm_info.get ());
+ auto it = std::find (info->glibc_tls_slots.begin (),
+ info->glibc_tls_slots.end (),
+ li->lm_addr);
+ if (it != info->glibc_tls_slots.end ())
+ *it = 0;
+}
+
/* On some systems, the only way to recognize the link map entry for
the main executable file is by looking at its name. Return
non-zero iff SONAME matches one of the known main executable names. */
@@ -1778,6 +2042,10 @@ solist_update_incremental (svr4_info *info, CORE_ADDR debug_base,
return 0;
prev_lm = 0;
+
+ /* If the list is empty, we are seeing a new namespace for the
+ first time, so assign it an internal ID. */
+ svr4_maybe_add_namespace (info, debug_base);
}
else
prev_lm = solist.back ().lm_info->lm_addr;
@@ -1845,6 +2113,8 @@ disable_probes_interface (svr4_info *info)
free_probes_table (info);
info->solib_lists.clear ();
+ info->namespace_id.clear ();
+ info->active_namespaces.clear ();
}
/* Update the solib list as appropriate when using the
@@ -2307,7 +2577,7 @@ enable_break (struct svr4_info *info, int from_tty)
/* Find the program interpreter; if not found, warn the user and drop
into the old breakpoint at symbol code. */
std::optional<gdb::byte_vector> interp_name_holder
- = find_program_interpreter ();
+ = svr4_find_program_interpreter ();
if (interp_name_holder)
{
const char *interp_name = (const char *) interp_name_holder->data ();
@@ -3042,6 +3312,8 @@ svr4_solib_create_inferior_hook (int from_tty)
/* Clear the probes-based interface's state. */
free_probes_table (info);
info->solib_lists.clear ();
+ info->namespace_id.clear ();
+ info->active_namespaces.clear ();
/* Relocate the main executable if necessary. */
svr4_relocate_main_executable ();
@@ -3460,6 +3732,80 @@ svr4_find_solib_addr (solib &so)
return li->l_addr_inferior;
}
+/* See solib_ops::find_solib_ns in solist.h. */
+
+static int
+svr4_find_solib_ns (const solib &so)
+{
+ CORE_ADDR debug_base = find_debug_base_for_solib (&so);
+ svr4_info *info = get_svr4_info (current_program_space);
+ for (int i = 0; i < info->namespace_id.size (); i++)
+ {
+ if (info->namespace_id[i] == debug_base)
+ {
+ gdb_assert (info->active_namespaces.count (i) == 1);
+ return i;
+ }
+ }
+ error (_("No namespace found"));
+}
+
+/* see solib_ops::num_active_namespaces in solist.h. */
+static int
+svr4_num_active_namespaces ()
+{
+ svr4_info *info = get_svr4_info (current_program_space);
+ return info->active_namespaces.size ();
+}
+
+/* See solib_ops::get_solibs_in_ns in solist.h. */
+static std::vector<const solib *>
+svr4_get_solibs_in_ns (int nsid)
+{
+ std::vector<const solib*> ns_solibs;
+ svr4_info *info = get_svr4_info (current_program_space);
+
+ /* If the namespace ID is inactive, there will be no active
+ libraries, so we can have an early exit, as a treat. */
+ if (info->active_namespaces.count (nsid) != 1)
+ return ns_solibs;
+
+ /* Since we only have the names of solibs in a given namespace,
+ we'll need to walk through the solib list of the inferior and
+ find which solib objects correspond to which svr4_so. We create
+ an unordered map with the names and lm_info to check things
+ faster, and to be able to remove SOs from the map, to avoid
+ returning the dynamic linker multiple times. */
+ CORE_ADDR debug_base = info->namespace_id[nsid];
+ std::unordered_map<std::string, const lm_info_svr4 *> namespace_solibs;
+ for (svr4_so &so : info->solib_lists[debug_base])
+ {
+ namespace_solibs[so.name]
+ = gdb::checked_static_cast<const lm_info_svr4 *>
+ (so.lm_info.get ());
+ }
+ for (const solib &so: current_program_space->solibs ())
+ {
+ auto *lm_inferior
+ = gdb::checked_static_cast<const lm_info_svr4 *> (so.lm_info.get ());
+
+ /* This is inspired by the svr4_same, by finding the svr4_so object
+ in the map, and then double checking if the lm_info is considered
+ the same. */
+ if (namespace_solibs.count (so.so_original_name) > 0
+ && namespace_solibs[so.so_original_name]->l_addr_inferior
+ == lm_inferior->l_addr_inferior)
+ {
+ ns_solibs.push_back (&so);
+ /* Remove the SO from the map, so that we don't end up
+ printing the dynamic linker multiple times. */
+ namespace_solibs.erase (so.so_original_name);
+ }
+ }
+
+ return ns_solibs;
+}
+
const struct solib_ops svr4_so_ops =
{
svr4_relocate_section_addresses,
@@ -3475,6 +3821,9 @@ const struct solib_ops svr4_so_ops =
svr4_update_solib_event_breakpoints,
svr4_handle_solib_event,
svr4_find_solib_addr,
+ svr4_find_solib_ns,
+ svr4_num_active_namespaces,
+ svr4_get_solibs_in_ns,
};
void _initialize_svr4_solib ();
@@ -3483,4 +3832,8 @@ _initialize_svr4_solib ()
{
gdb::observers::free_objfile.attach (svr4_free_objfile_observer,
"solib-svr4");
+
+ /* Set up observers for tracking GLIBC TLS module id slots. */
+ gdb::observers::solib_loaded.attach (tls_maybe_fill_slot, "solib-svr4");
+ gdb::observers::solib_unloaded.attach (tls_maybe_erase_slot, "solib-svr4");
}