Implement internal TLS address lookup for select Linux targets

This commit adds non-architecture-specific support for internal TLS
address lookup for targets which register their support with the new
file svr4-tls-tdep.c.  By "internal", I mean support which does not
rely on libthread_db.  Knowledge of how to traverse TLS data
structures is contained in this commit along with the next commit
containing architecture specific knowledge regarding TLS offsets,
registers, and such.

The new function 'svr4_tls_get_thread_local_address' is a gdbarch method.
It should be passed as an argument to
set_gdbarch_get_thread_local_address in architecture specific
<arch>-linux-tdep.c files which wish to offer internal TLS support.

The architecture specific tdep files need to define a get_tls_dtv_addr
method - as the name suggests, it needs to return the address of the
DTV (dynamic thread vector) via architecture specific means.  This
usually entails fetching the thread pointer via a register or registers
assigned to this purpose, and then using that value to locate the
address of the DTV from within the TCB (thread control block).

Additionally, some architectures also need to provide a DTP offset,
which is used by the MUSL C library to adjust the value obtained
from a DTV entry to that of the start of the TLS block for a particular
thread.  This is provided, when necessary, by a get_tls_dtp_offset
method.

Both methods, get_tls_dtv_addr and get_tls_dtp_offset, are registered
with data structures maintained by linux-tdep.c via the new function
svr4_tls_register_tls_methods().  Thus, for example, on RISC-V,
riscv_linux_init_abi() will make the following two calls, the first
for registering the internal get_thread_local_address gdbarch method
and the second for registering riscv-specific methods for obtaining
the DTV address and DTP offset:

  set_gdbarch_get_thread_local_address (gdbarch,
					svr4_tls_get_thread_local_address);
  svr4_tls_register_tls_methods (info, gdbarch, riscv_linux_get_tls_dtv_addr,
				 riscv_linux_get_tls_dtp_offset);

Internal TLS support is provided for two C libraries, GLIBC, and MUSL.
Details for accessing the various TLS data structures differ between
these libraries.  As a consequence, linux-tdep.h defines a new enum,
svr4_tls_libc, with values svr4_tls_libc_unknown, svr4_tls_libc_musl,
and svr4_tls_libc_glibc.  A new static function libc_tls_sniffer uses
heuristics to (try to) decide whether a program was linked against
GLIBC or MUSL.  Working out what the heuristics should be, especially
for statically linked binaries, turned out to be harder than I thought
it would be.

A new maintenance setting, force-internal-tls-address-lookup, has been
added, which, when set to 'on', will (as the name suggests) force the
internal TLS lookup mechanisms to be used.  Otherwise, if thread_db
support is available (via the thread stratum), that will be preferred
since it should be more accurate.  I expect that this setting will be
mostly used by test cases in the GDB test suite.  The new test cases
that are part of this series all use it, with iterations using both
'on' and 'off' for all of the tests therein.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=24548
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31563
Tested-By: Luis Machado <luis.machado@arm.com>
Approved-By: Luis Machado <luis.machado@arm.com>
This commit is contained in:
Kevin Buettner
2025-04-23 21:39:28 -07:00
parent 3b2dcfb789
commit 85e1d8f93d
4 changed files with 319 additions and 0 deletions

View File

@@ -898,6 +898,7 @@ ALL_TARGET_OBS = \
sparc-ravenscar-thread.o \
sparc-sol2-tdep.o \
sparc-tdep.o \
svr4-tls-tdep.o \
symfile-mem.o \
tic6x-linux-tdep.o \
tic6x-tdep.o \
@@ -1522,6 +1523,7 @@ HFILES_NO_SRCDIR = \
stabsread.h \
stack.h \
stap-probe.h \
svr4-tls-tdep.h \
symfile.h \
symtab.h \
target.h \
@@ -1885,6 +1887,7 @@ ALLDEPFILES = \
sparc64-obsd-tdep.c \
sparc64-sol2-tdep.c \
sparc64-tdep.c \
svr4-tls-tdep.c \
tilegx-linux-nat.c \
tilegx-linux-tdep.c \
tilegx-tdep.c \

View File

@@ -3137,6 +3137,7 @@ VM_DONTDUMP flag (\"dd\" in /proc/PID/smaps) when generating the corefile. For\
more information about this file, refer to the manpage of proc(5) and core(5)."),
NULL, show_dump_excluded_mappings,
&setlist, &showlist);
}
/* Fetch (and possibly build) an appropriate `link_map_offsets' for

256
gdb/svr4-tls-tdep.c Normal file
View File

@@ -0,0 +1,256 @@
/* Target-dependent code for GNU/Linux, architecture independent.
Copyright (C) 2009-2024 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 "svr4-tls-tdep.h"
#include "solib-svr4.h"
#include "inferior.h"
#include "objfiles.h"
#include "cli/cli-cmds.h"
#include <optional>
struct svr4_tls_gdbarch_data
{
/* Method for looking up TLS DTV. */
get_tls_dtv_addr_ftype *get_tls_dtv_addr = nullptr;
/* Method for looking up the TLS DTP offset. */
get_tls_dtp_offset_ftype *get_tls_dtp_offset = nullptr;
/* Cached libc value for TLS lookup purposes. */
enum svr4_tls_libc libc = svr4_tls_libc_unknown;
};
static const registry<gdbarch>::key<svr4_tls_gdbarch_data>
svr4_tls_gdbarch_data_handle;
static struct svr4_tls_gdbarch_data *
get_svr4_tls_gdbarch_data (struct gdbarch *gdbarch)
{
struct svr4_tls_gdbarch_data *result = svr4_tls_gdbarch_data_handle.get (gdbarch);
if (result == nullptr)
result = svr4_tls_gdbarch_data_handle.emplace (gdbarch);
return result;
}
/* When true, force internal TLS address lookup instead of lookup via
the thread stratum. */
static bool force_internal_tls_address_lookup = false;
/* For TLS lookup purposes, use heuristics to decide whether program
was linked against MUSL or GLIBC. */
static enum svr4_tls_libc
libc_tls_sniffer (struct gdbarch *gdbarch)
{
/* Check for cached libc value. */
svr4_tls_gdbarch_data *gdbarch_data = get_svr4_tls_gdbarch_data (gdbarch);
if (gdbarch_data->libc != svr4_tls_libc_unknown)
return gdbarch_data->libc;
svr4_tls_libc libc = svr4_tls_libc_unknown;
/* Fetch the program interpreter. */
std::optional<gdb::byte_vector> interp_name_holder
= svr4_find_program_interpreter ();
if (interp_name_holder)
{
/* A dynamically linked program linked against MUSL will have a
"ld-musl-" in its interpreter name. (Two examples of MUSL
interpreter names are "/lib/ld-musl-x86_64.so.1" and
"lib/ld-musl-aarch64.so.1".) If it's not found, assume GLIBC. */
const char *interp_name = (const char *) interp_name_holder->data ();
if (strstr (interp_name, "/ld-musl-") != nullptr)
libc = svr4_tls_libc_musl;
else
libc = svr4_tls_libc_glibc;
gdbarch_data->libc = libc;
return libc;
}
/* If there is no interpreter name, it's statically linked. For
programs with TLS data, a program statically linked against MUSL
will have the symbols 'main_tls' and 'builtin_tls'. If both of
these are present, assume that it was statically linked against
MUSL, otherwise assume GLIBC. */
if (lookup_minimal_symbol (current_program_space, "main_tls").minsym
!= nullptr
&& lookup_minimal_symbol (current_program_space, "builtin_tls").minsym
!= nullptr)
libc = svr4_tls_libc_musl;
else
libc = svr4_tls_libc_glibc;
gdbarch_data->libc = libc;
return libc;
}
/* Implement gdbarch method, get_thread_local_address, for architectures
which provide a method for determining the DTV and possibly the DTP
offset. */
CORE_ADDR
svr4_tls_get_thread_local_address (struct gdbarch *gdbarch, ptid_t ptid,
CORE_ADDR lm_addr, CORE_ADDR offset)
{
svr4_tls_gdbarch_data *gdbarch_data = get_svr4_tls_gdbarch_data (gdbarch);
/* Use the target's get_thread_local_address method when:
- No method has been provided for finding the TLS DTV.
or
- The thread stratum has been pushed (at some point) onto the
target stack, except when 'force_internal_tls_address_lookup'
has been set.
The idea here is to prefer use of of the target's thread_stratum
method since it should be more accurate. */
if (gdbarch_data->get_tls_dtv_addr == nullptr
|| (find_target_at (thread_stratum) != nullptr
&& !force_internal_tls_address_lookup))
{
struct target_ops *target = current_inferior ()->top_target ();
return target->get_thread_local_address (ptid, lm_addr, offset);
}
else
{
/* Details, found below, regarding TLS layout is for the GNU C
library (glibc) and the MUSL C library (musl), circa 2024.
While some of this layout is defined by the TLS ABI, some of
it, such as how/where to find the DTV pointer in the TCB, is
not. A good source of ABI info for some architectures can be
found in "ELF Handling For Thread-Local Storage" by Ulrich
Drepper. That document is worth consulting even for
architectures not described there, since the general approach
and terminology is used regardless.
Some architectures, such as aarch64, are not described in
that document, so some details had to ferreted out using the
glibc source code. Likewise, the MUSL source code was
consulted for details which differ from GLIBC. */
enum svr4_tls_libc libc = libc_tls_sniffer (gdbarch);
int mod_id;
if (libc == svr4_tls_libc_glibc)
mod_id = glibc_link_map_to_tls_module_id (lm_addr);
else /* Assume MUSL. */
mod_id = musl_link_map_to_tls_module_id (lm_addr);
if (mod_id == 0)
throw_error (TLS_GENERIC_ERROR, _("Unable to determine TLS module id"));
/* Use the architecture specific DTV fetcher to obtain the DTV. */
CORE_ADDR dtv_addr = gdbarch_data->get_tls_dtv_addr (gdbarch, ptid, libc);
/* In GLIBC, The DTV (dynamic thread vector) is an array of
structs consisting of two fields, the first of which is a
pointer to the TLS block of interest. (The second field is a
pointer that assists with memory management, but that's not
of interest here.) Also, the 0th entry is the generation
number, but although it's a single scalar, the 0th entry is
padded to be the same size as all the rest. Thus each
element of the DTV array is two pointers in size.
In MUSL, the DTV is simply an array of pointers. The 0th
entry is still the generation number, but contains no padding
aside from that which is needed to make it pointer sized. */
int m; /* Multiplier, for size of DTV entry. */
switch (libc)
{
case svr4_tls_libc_glibc:
m = 2;
break;
default:
m = 1;
break;
}
/* Obtain TLS block address. Module ids start at 1, so there's
no need to adjust it to skip over the 0th entry of the DTV,
which is the generation number. */
CORE_ADDR dtv_elem_addr
= dtv_addr + mod_id * m * (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT);
gdb::byte_vector buf (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT);
if (target_read_memory (dtv_elem_addr, buf.data (), buf.size ()) != 0)
throw_error (TLS_GENERIC_ERROR, _("Unable to fetch TLS block address"));
const struct builtin_type *builtin = builtin_type (gdbarch);
CORE_ADDR tls_block_addr = gdbarch_pointer_to_address
(gdbarch, builtin->builtin_data_ptr,
buf.data ());
/* When the TLS block addr is 0 or -1, this usually indicates that
the TLS storage hasn't been allocated yet. (In GLIBC, some
architectures use 0 while others use -1.) */
if (tls_block_addr == 0 || tls_block_addr == (CORE_ADDR) -1)
throw_error (TLS_NOT_ALLOCATED_YET_ERROR, _("TLS not allocated yet"));
/* MUSL (and perhaps other C libraries, though not GLIBC) have
TLS implementations for some architectures which, for some
reason, have DTV entries which must be negatively offset by
DTP_OFFSET in order to obtain the TLS block address.
DTP_OFFSET is a constant in the MUSL sources - these offsets,
when they're non-zero, seem to be either 0x800 or 0x8000,
and are present for riscv[32/64], powerpc[32/64], m68k, and
mips.
Use the architecture specific get_tls_dtp_offset method, if
present, to obtain this offset. */
ULONGEST dtp_offset
= gdbarch_data->get_tls_dtp_offset == nullptr
? 0
: gdbarch_data->get_tls_dtp_offset (gdbarch, ptid, libc);
return tls_block_addr - dtp_offset + offset;
}
}
/* See svr4-tls-tdep.h. */
void
svr4_tls_register_tls_methods (struct gdbarch_info info, struct gdbarch *gdbarch,
get_tls_dtv_addr_ftype *get_tls_dtv_addr,
get_tls_dtp_offset_ftype *get_tls_dtp_offset)
{
gdb_assert (get_tls_dtv_addr != nullptr);
svr4_tls_gdbarch_data *gdbarch_data = get_svr4_tls_gdbarch_data (gdbarch);
gdbarch_data->get_tls_dtv_addr = get_tls_dtv_addr;
gdbarch_data->get_tls_dtp_offset = get_tls_dtp_offset;
}
void _initialize_svr4_tls_tdep ();
void
_initialize_svr4_tls_tdep ()
{
add_setshow_boolean_cmd ("force-internal-tls-address-lookup", class_obscure,
&force_internal_tls_address_lookup, _("\
Set to force internal TLS address lookup."), _("\
Show whether GDB is forced to use internal TLS address lookup."), _("\
When resolving addresses for TLS (Thread Local Storage) variables,\n\
GDB will attempt to use facilities provided by the thread library (i.e.\n\
libthread_db). If those facilities aren't available, GDB will fall\n\
back to using some internal (to GDB), but possibly less accurate\n\
mechanisms to resolve the addresses for TLS variables. When this flag\n\
is set, GDB will force use of the fall-back TLS resolution mechanisms.\n\
This flag is used by some GDB tests to ensure that the internal fallback\n\
code is exercised and working as expected. The default is to not force\n\
the internal fall-back mechanisms to be used."),
NULL, NULL,
&maintenance_set_cmdlist,
&maintenance_show_cmdlist);
}

59
gdb/svr4-tls-tdep.h Normal file
View File

@@ -0,0 +1,59 @@
/* Target-dependent code for GNU/Linux, architecture independent.
Copyright (C) 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/>. */
#ifndef GDB_SVR4_TLS_TDEP_H
#define GDB_SVR4_TLS_TDEP_H
/* C library variants for TLS lookup. */
enum svr4_tls_libc
{
svr4_tls_libc_unknown,
svr4_tls_libc_musl,
svr4_tls_libc_glibc
};
/* Function type for "get_tls_dtv_addr" method. */
typedef CORE_ADDR (get_tls_dtv_addr_ftype) (struct gdbarch *gdbarch,
ptid_t ptid,
enum svr4_tls_libc libc);
/* Function type for "get_tls_dtp_offset" method. */
typedef CORE_ADDR (get_tls_dtp_offset_ftype) (struct gdbarch *gdbarch,
ptid_t ptid,
enum svr4_tls_libc libc);
/* Register architecture specific methods for fetching the TLS DTV
and TLS DTP, used by linux_get_thread_local_address. */
extern void svr4_tls_register_tls_methods
(struct gdbarch_info info, struct gdbarch *gdbarch,
get_tls_dtv_addr_ftype *get_tls_dtv_addr,
get_tls_dtp_offset_ftype *get_tls_dtp_offset = nullptr);
/* Used as a gdbarch method for get_thread_local_address when the tdep
file also defines a suitable method for obtaining the TLS DTV.
See linux_init_abi(), above. */
CORE_ADDR
svr4_tls_get_thread_local_address (struct gdbarch *gdbarch, ptid_t ptid,
CORE_ADDR lm_addr, CORE_ADDR offset);
#endif /* GDB_SVR4_TLS_TDEP_H */