aboutsummaryrefslogtreecommitdiff
path: root/gdb
diff options
context:
space:
mode:
Diffstat (limited to 'gdb')
-rw-r--r--gdb/Makefile.in4
-rw-r--r--gdb/NEWS20
-rw-r--r--gdb/aarch64-linux-tdep.c56
-rw-r--r--gdb/amd64-linux-tdep.c38
-rw-r--r--gdb/configure.tgt11
-rw-r--r--gdb/doc/gdb.texinfo50
-rw-r--r--gdb/doc/python.texi10
-rw-r--r--gdb/dwarf2/attribute.c35
-rw-r--r--gdb/dwarf2/attribute.h18
-rw-r--r--gdb/dwarf2/read.c232
-rw-r--r--gdb/eval.c5
-rw-r--r--gdb/findvar.c3
-rw-r--r--gdb/linux-tdep.c1
-rw-r--r--gdb/minsyms.c3
-rw-r--r--gdb/ppc-linux-tdep.c63
-rw-r--r--gdb/remote.c12
-rw-r--r--gdb/riscv-linux-tdep.c79
-rw-r--r--gdb/s390-linux-tdep.c44
-rw-r--r--gdb/solib-svr4.c212
-rw-r--r--gdb/solib-svr4.h12
-rw-r--r--gdb/svr4-tls-tdep.c256
-rw-r--r--gdb/svr4-tls-tdep.h59
-rwxr-xr-xgdb/syscalls/riscv-canonicalize-syscall-gen.py136
-rw-r--r--gdb/target.c16
-rw-r--r--gdb/target.h8
-rw-r--r--gdb/testsuite/gdb.base/break1.c8
-rw-r--r--gdb/testsuite/gdb.base/tls-common.exp.tcl50
-rw-r--r--gdb/testsuite/gdb.base/tls-dlobj-lib.c87
-rw-r--r--gdb/testsuite/gdb.base/tls-dlobj.c311
-rw-r--r--gdb/testsuite/gdb.base/tls-dlobj.exp378
-rw-r--r--gdb/testsuite/gdb.base/tls-multiobj.c89
-rw-r--r--gdb/testsuite/gdb.base/tls-multiobj.exp230
-rw-r--r--gdb/testsuite/gdb.base/tls-multiobj1.c26
-rw-r--r--gdb/testsuite/gdb.base/tls-multiobj2.c26
-rw-r--r--gdb/testsuite/gdb.base/tls-multiobj3.c26
-rw-r--r--gdb/testsuite/gdb.base/tls-nothreads.c57
-rw-r--r--gdb/testsuite/gdb.base/tls-nothreads.exp248
-rw-r--r--gdb/testsuite/gdb.python/gdb_leak_detector.py1
-rw-r--r--gdb/testsuite/gdb.python/py-inferior-leak.py1
-rw-r--r--gdb/testsuite/gdb.server/no-thread-db.exp4
-rw-r--r--gdb/testsuite/gdb.threads/tls.exp2
-rw-r--r--gdb/unittests/remote-arg-selftests.c166
42 files changed, 2891 insertions, 202 deletions
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 68b5e56..9f21695 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -479,6 +479,7 @@ SELFTESTS_SRCS = \
unittests/ptid-selftests.c \
unittests/main-thread-selftests.c \
unittests/mkdir-recursive-selftests.c \
+ unittests/remote-arg-selftests.c \
unittests/rsp-low-selftests.c \
unittests/scoped_fd-selftests.c \
unittests/scoped_ignore_signal-selftests.c \
@@ -897,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 \
@@ -1521,6 +1523,7 @@ HFILES_NO_SRCDIR = \
stabsread.h \
stack.h \
stap-probe.h \
+ svr4-tls-tdep.h \
symfile.h \
symtab.h \
target.h \
@@ -1884,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 \
diff --git a/gdb/NEWS b/gdb/NEWS
index 164e872..14d3063 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -74,6 +74,26 @@ info sharedlibrary
command are now for the full memory range allocated to the shared
library.
+* GDB-internal Thread Local Storage (TLS) support
+
+ ** Linux targets for the x86_64, aarch64, ppc64, s390x, and riscv
+ architectures now have GDB-internal support for TLS address
+ lookup in addition to that traditionally provided by the
+ libthread_db library. This internal support works for programs
+ linked against either the GLIBC or MUSL C libraries. For
+ programs linked against MUSL, this new internal support provides
+ new debug functionality, allowing access to TLS variables, due to
+ the fact that MUSL does not implement the libthread_db library.
+ Internal TLS support is also useful in cross-debugging
+ situations, debugging statically linked binaries, and debugging
+ programs linked against GLIBC 2.33 and earlier, but which are not
+ linked against libpthread.
+
+ ** The command 'maint set force-internal-tls-address-lookup on' may
+ be used to force the internal TLS lookup mechanisms to be used.
+ Otherwise, TLS lookup via libthread_db will still be preferred,
+ when available.
+
* Python API
** New class gdb.Color for dealing with colors.
diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index 9cee355..b6cdcf0 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -24,6 +24,7 @@
#include "gdbarch.h"
#include "glibc-tdep.h"
#include "linux-tdep.h"
+#include "svr4-tls-tdep.h"
#include "aarch64-tdep.h"
#include "aarch64-linux-tdep.h"
#include "osabi.h"
@@ -35,6 +36,7 @@
#include "target/target.h"
#include "expop.h"
#include "auxv.h"
+#include "inferior.h"
#include "regcache.h"
#include "regset.h"
@@ -2701,6 +2703,57 @@ aarch64_use_target_description_from_corefile_notes (gdbarch *gdbarch,
return true;
}
+/* Fetch and return the TLS DTV (dynamic thread vector) address for PTID.
+ Throw a suitable TLS error if something goes wrong. */
+
+static CORE_ADDR
+aarch64_linux_get_tls_dtv_addr (struct gdbarch *gdbarch, ptid_t ptid,
+ svr4_tls_libc libc)
+{
+ /* On aarch64, the thread pointer is found in the TPIDR register.
+ Note that this is the first register in the TLS feature - see
+ features/aarch64-tls.c - and it will always be present. */
+ regcache *regcache
+ = get_thread_arch_regcache (current_inferior (), ptid, gdbarch);
+ aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (gdbarch);
+ target_fetch_registers (regcache, tdep->tls_regnum_base);
+ ULONGEST thr_ptr;
+ if (regcache->cooked_read (tdep->tls_regnum_base, &thr_ptr) != REG_VALID)
+ throw_error (TLS_GENERIC_ERROR, _("Unable to fetch thread pointer"));
+
+ CORE_ADDR dtv_ptr_addr;
+ switch (libc)
+ {
+ case svr4_tls_libc_musl:
+ /* MUSL: The DTV pointer is found at the very end of the pthread
+ struct which is located *before* the thread pointer. I.e.
+ the thread pointer will be just beyond the end of the struct,
+ so the address of the DTV pointer is found one pointer-size
+ before the thread pointer. */
+ dtv_ptr_addr = thr_ptr - (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT);
+ break;
+ case svr4_tls_libc_glibc:
+ /* GLIBC: The thread pointer (tpidr) points at the TCB (thread control
+ block). On aarch64, this struct (tcbhead_t) is defined to
+ contain two pointers. The first is a pointer to the DTV and
+ the second is a pointer to private data. So the DTV pointer
+ address is the same as the thread pointer. */
+ dtv_ptr_addr = thr_ptr;
+ break;
+ default:
+ throw_error (TLS_GENERIC_ERROR, _("Unknown aarch64 C library"));
+ break;
+ }
+ gdb::byte_vector buf (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT);
+ if (target_read_memory (dtv_ptr_addr, buf.data (), buf.size ()) != 0)
+ throw_error (TLS_GENERIC_ERROR, _("Unable to fetch DTV address"));
+
+ const struct builtin_type *builtin = builtin_type (gdbarch);
+ CORE_ADDR dtv_addr = gdbarch_pointer_to_address
+ (gdbarch, builtin->builtin_data_ptr, buf.data ());
+ return dtv_addr;
+}
+
static void
aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
{
@@ -2722,6 +2775,9 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
/* Enable TLS support. */
set_gdbarch_fetch_tls_load_module_address (gdbarch,
svr4_fetch_objfile_link_map);
+ set_gdbarch_get_thread_local_address (gdbarch,
+ svr4_tls_get_thread_local_address);
+ svr4_tls_register_tls_methods (info, gdbarch, aarch64_linux_get_tls_dtv_addr);
/* Shared library handling. */
set_gdbarch_skip_trampoline_code (gdbarch, find_solib_trampoline_target);
diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c
index 494e744..e5a2ab9 100644
--- a/gdb/amd64-linux-tdep.c
+++ b/gdb/amd64-linux-tdep.c
@@ -33,7 +33,9 @@
#include "amd64-linux-tdep.h"
#include "i386-linux-tdep.h"
#include "linux-tdep.h"
+#include "svr4-tls-tdep.h"
#include "gdbsupport/x86-xstate.h"
+#include "inferior.h"
#include "amd64-tdep.h"
#include "solib-svr4.h"
@@ -1832,6 +1834,39 @@ amd64_linux_remove_non_address_bits_watchpoint (gdbarch *gdbarch,
return (addr & amd64_linux_lam_untag_mask ());
}
+/* Fetch and return the TLS DTV (dynamic thread vector) address for PTID.
+ Throw a suitable TLS error if something goes wrong. */
+
+static CORE_ADDR
+amd64_linux_get_tls_dtv_addr (struct gdbarch *gdbarch, ptid_t ptid,
+ enum svr4_tls_libc libc)
+{
+ /* On x86-64, the thread pointer is found in the fsbase register. */
+ regcache *regcache
+ = get_thread_arch_regcache (current_inferior (), ptid, gdbarch);
+ target_fetch_registers (regcache, AMD64_FSBASE_REGNUM);
+ ULONGEST fsbase;
+ if (regcache->cooked_read (AMD64_FSBASE_REGNUM, &fsbase) != REG_VALID)
+ throw_error (TLS_GENERIC_ERROR, _("Unable to fetch thread pointer"));
+
+ /* The thread pointer (fsbase) points at the TCB (thread control
+ block). The first two members of this struct are both pointers,
+ where the first will be a pointer to the TCB (i.e. it points at
+ itself) and the second will be a pointer to the DTV (dynamic
+ thread vector). There are many other fields too, but the one
+ we care about here is the DTV pointer. Compute the address
+ of the DTV pointer, fetch it, and convert it to an address. */
+ CORE_ADDR dtv_ptr_addr = fsbase + gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT;
+ gdb::byte_vector buf (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT);
+ if (target_read_memory (dtv_ptr_addr, buf.data (), buf.size ()) != 0)
+ throw_error (TLS_GENERIC_ERROR, _("Unable to fetch DTV address"));
+
+ const struct builtin_type *builtin = builtin_type (gdbarch);
+ CORE_ADDR dtv_addr = gdbarch_pointer_to_address
+ (gdbarch, builtin->builtin_data_ptr, buf.data ());
+ return dtv_addr;
+}
+
static void
amd64_linux_init_abi_common(struct gdbarch_info info, struct gdbarch *gdbarch,
int num_disp_step_buffers)
@@ -1862,6 +1897,9 @@ amd64_linux_init_abi_common(struct gdbarch_info info, struct gdbarch *gdbarch,
/* Enable TLS support. */
set_gdbarch_fetch_tls_load_module_address (gdbarch,
svr4_fetch_objfile_link_map);
+ set_gdbarch_get_thread_local_address (gdbarch,
+ svr4_tls_get_thread_local_address);
+ svr4_tls_register_tls_methods (info, gdbarch, amd64_linux_get_tls_dtv_addr);
/* GNU/Linux uses SVR4-style shared libraries. */
set_gdbarch_skip_trampoline_code (gdbarch, find_solib_trampoline_target);
diff --git a/gdb/configure.tgt b/gdb/configure.tgt
index 44da225..72cdcee 100644
--- a/gdb/configure.tgt
+++ b/gdb/configure.tgt
@@ -150,7 +150,7 @@ aarch64*-*-linux*)
arch/aarch64-scalable-linux.o \
arch/arm.o arch/arm-linux.o arch/arm-get-next-pcs.o \
arm-tdep.o arm-linux-tdep.o \
- glibc-tdep.o linux-tdep.o solib-svr4.o \
+ glibc-tdep.o linux-tdep.o solib-svr4.o svr4-tls-tdep.o \
symfile-mem.o linux-record.o"
;;
@@ -503,7 +503,7 @@ powerpc-*-aix* | rs6000-*-* | powerpc64-*-aix*)
powerpc*-*-linux*)
# Target: PowerPC running Linux
gdb_target_obs="rs6000-tdep.o ppc-linux-tdep.o ppc-sysv-tdep.o \
- ppc64-tdep.o solib-svr4.o \
+ ppc64-tdep.o solib-svr4.o svr4-tls-tdep.o \
glibc-tdep.o symfile-mem.o linux-tdep.o \
ravenscar-thread.o ppc-ravenscar-thread.o \
linux-record.o \
@@ -524,7 +524,8 @@ powerpc*-*-*)
s390*-*-linux*)
# Target: S390 running Linux
gdb_target_obs="s390-linux-tdep.o s390-tdep.o solib-svr4.o \
- linux-tdep.o linux-record.o symfile-mem.o"
+ linux-tdep.o linux-record.o symfile-mem.o \
+ svr4-tls-tdep.o"
;;
riscv*-*-freebsd*)
@@ -536,7 +537,7 @@ riscv*-*-linux*)
# Target: Linux/RISC-V
gdb_target_obs="riscv-linux-tdep.o riscv-canonicalize-syscall-gen.o \
glibc-tdep.o linux-tdep.o solib-svr4.o symfile-mem.o \
- linux-record.o"
+ linux-record.o svr4-tls-tdep.o"
;;
riscv*-*-*)
@@ -706,7 +707,7 @@ x86_64-*-elf*)
x86_64-*-linux*)
# Target: GNU/Linux x86-64
gdb_target_obs="amd64-linux-tdep.o ${i386_tobjs} \
- i386-linux-tdep.o glibc-tdep.o \
+ i386-linux-tdep.o glibc-tdep.o svr4-tls-tdep.o \
solib-svr4.o symfile-mem.o linux-tdep.o linux-record.o \
arch/i386-linux-tdesc.o arch/amd64-linux-tdesc.o \
arch/x86-linux-tdesc-features.o"
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 48b193b..362e6e7 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -4090,6 +4090,56 @@ When @samp{on} @value{GDBN} will print additional messages when
threads are created and deleted.
@end table
+@cindex thread local storage
+@cindex @acronym{TLS}
+For some debugging targets, @value{GDBN} has support for accessing
+variables that reside in Thread Local Storage (@acronym{TLS}).
+@acronym{TLS} variables are similar to global variables, except that
+each thread has its own copy of the variable. While often used in
+multi-threaded programs, @acronym{TLS} variables can also be used in
+programs without threads. The C library variable @var{errno} is,
+perhaps, the most prominent example of a @acronym{TLS} variable that
+is frequently used in non-threaded programs. For targets where
+@value{GDBN} does not have good @acronym{TLS} support, printing or
+changing the value of @var{errno} might not be directly possible.
+
+@sc{gnu}/Linux and FreeBSD targets have support for accessing
+@acronym{TLS} variables. On @sc{gnu}/Linux, the helper library,
+@code{libthread_db}, is used to help resolve the addresses of
+@acronym{TLS} variables. Some FreeBSD and some @sc{gnu}/Linux targets
+also have @value{GDBN}-internal @acronym{TLS} resolution code.
+@sc{gnu}/Linux targets will attempt to use the @acronym{TLS} address
+lookup functionality provided by @code{libthread_db}, but will fall
+back to using its internal @acronym{TLS} support when
+@code{libthread_db} is not available. This can happen in
+cross-debugging scenarios or when debugging programs that are linked
+in such a way that @code{libthread_db} support is unavailable -- this
+includes statically linked programs, linking against @acronym{GLIBC}
+versions earlier than 2.34, but not with @code{libpthread}, and use of
+other (non-@acronym{GLIBC}) C libraries.
+
+@table @code
+@kindex maint set force-internal-tls-address-lookup
+@kindex maint show force-internal-tls-address-lookup
+@cindex internal @acronym{TLS} address lookup
+@item maint set force-internal-tls-address-lookup
+@itemx maint show force-internal-tls-address-lookup
+Turns on or off forced use of @value{GDBN}-internal @acronym{TLS}
+address lookup code. Use @code{on} to enable and @code{off} to
+disable. The default for this setting is @code{off}.
+
+When disabled, @value{GDBN} will attempt to use a helper
+@code{libthread_db} library if possible, but will fall back to use of
+its own internal @acronym{TLS} address lookup mechanisms if necessary.
+
+When enabled, @value{GDBN} will only use @value{GDBN}'s internal
+@acronym{TLS} address lookup mechanisms, if they exist.
+
+This command is only available for @sc{gnu}/Linux targets. Its
+primary use is for testing -- certain tests in the @value{GDBN} test
+suite use this command to force use of internal TLS address lookup.
+@end table
+
@node Forks
@section Debugging Forks
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index cba3a0d..afbd62f 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -257,7 +257,7 @@ Python code must not override these, or even change the options using
signals, @value{GDBN} will most likely stop working correctly. Note
that it is unfortunately common for GUI toolkits to install a
@code{SIGCHLD} handler. When creating a new Python thread, you can
-use @code{gdb.block_signals} or @code{gdb.Thread} to handle this
+use @code{gdb.blocked_signals} or @code{gdb.Thread} to handle this
correctly; see @ref{Threading in GDB}.
@item
@@ -654,22 +654,22 @@ threads, you must be careful to only call @value{GDBN}-specific
functions in the @value{GDBN} thread. @value{GDBN} provides some
functions to help with this.
-@defun gdb.block_signals ()
+@defun gdb.blocked_signals ()
As mentioned earlier (@pxref{Basic Python}), certain signals must be
-delivered to the @value{GDBN} main thread. The @code{block_signals}
+delivered to the @value{GDBN} main thread. The @code{blocked_signals}
function returns a context manager that will block these signals on
entry. This can be used when starting a new thread to ensure that the
signals are blocked there, like:
@smallexample
-with gdb.block_signals():
+with gdb.blocked_signals():
start_new_thread()
@end smallexample
@end defun
@deftp {class} gdb.Thread
This is a subclass of Python's @code{threading.Thread} class. It
-overrides the @code{start} method to call @code{block_signals}, making
+overrides the @code{start} method to call @code{blocked_signals}, making
this an easy-to-use drop-in replacement for creating threads that will
work well in @value{GDBN}.
@end deftp
diff --git a/gdb/dwarf2/attribute.c b/gdb/dwarf2/attribute.c
index 2d14ebd..8ff0353 100644
--- a/gdb/dwarf2/attribute.c
+++ b/gdb/dwarf2/attribute.c
@@ -186,6 +186,36 @@ attribute::unsigned_constant () const
/* See attribute.h. */
+std::optional<LONGEST>
+attribute::signed_constant () const
+{
+ if (form_is_strictly_signed ())
+ return u.snd;
+
+ switch (form)
+ {
+ case DW_FORM_data8:
+ case DW_FORM_udata:
+ /* Not sure if DW_FORM_udata should be handled or not. Anyway
+ for DW_FORM_data8, there's no need to sign-extend. */
+ return u.snd;
+
+ case DW_FORM_data1:
+ return sign_extend (u.unsnd, 8);
+ case DW_FORM_data2:
+ return sign_extend (u.unsnd, 16);
+ case DW_FORM_data4:
+ return sign_extend (u.unsnd, 32);
+ }
+
+ /* For DW_FORM_data16 see attribute::form_is_constant. */
+ complaint (_("Attribute value is not a constant (%s)"),
+ dwarf_form_name (form));
+ return {};
+}
+
+/* See attribute.h. */
+
bool
attribute::form_is_unsigned () const
{
@@ -296,5 +326,8 @@ attribute::as_boolean () const
return true;
else if (form == DW_FORM_flag)
return u.unsnd != 0;
- return constant_value (0) != 0;
+ /* Using signed_constant here will work even for the weird case
+ where a negative value is provided. Probably doesn't matter but
+ also seems harmless. */
+ return signed_constant ().value_or (0) != 0;
}
diff --git a/gdb/dwarf2/attribute.h b/gdb/dwarf2/attribute.h
index ec4f3d8..31ff018 100644
--- a/gdb/dwarf2/attribute.h
+++ b/gdb/dwarf2/attribute.h
@@ -114,6 +114,15 @@ struct attribute
returned. */
std::optional<ULONGEST> unsigned_constant () const;
+ /* Return a signed constant value. This only handles constant forms
+ (i.e., form_is_constant -- and not the extended list of
+ "unsigned" forms) and assumes a signed value is desired. This
+ function will sign-extend DW_FORM_data* values.
+
+ If non-constant form is used, then complaint is issued and an
+ empty value is returned. */
+ std::optional<LONGEST> signed_constant () const;
+
/* Return non-zero if ATTR's value falls in the 'constant' class, or
zero otherwise. When this function returns true, you can apply
the constant_value method to it.
@@ -166,6 +175,15 @@ struct attribute
false. */
bool form_is_strictly_signed () const;
+ /* Check if the attribute's form is an unsigned constant form. This
+ only returns true for forms that are strictly unsigned -- that
+ is, for a context-dependent form like DW_FORM_data1, this returns
+ false. */
+ bool form_is_strictly_unsigned () const
+ {
+ return form == DW_FORM_udata;
+ }
+
/* Check if the attribute's form is a form that requires
"reprocessing". */
bool form_requires_reprocessing () const;
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 7b019f9..2523ca8 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -622,15 +622,21 @@ struct variant_field
/* A variant can contain other variant parts. */
std::vector<variant_part_builder> variant_parts;
- /* If we see a DW_TAG_variant, then this will be set if this is the
- default branch. */
- bool default_branch = false;
/* If we see a DW_AT_discr_value, then this will be the discriminant
- value. */
- ULONGEST discriminant_value = 0;
+ value. Just the attribute is stored here, because we have to
+ defer deciding whether the value is signed or unsigned until the
+ end. */
+ const attribute *discriminant_attr = nullptr;
/* If we see a DW_AT_discr_list, then this is a pointer to the list
data. */
struct dwarf_block *discr_list_data = nullptr;
+
+ /* If both DW_AT_discr_value and DW_AT_discr_list are absent, then
+ this is the default branch. */
+ bool is_default () const
+ {
+ return discriminant_attr == nullptr && discr_list_data == nullptr;
+ }
};
/* This represents a DW_TAG_variant_part. */
@@ -9923,7 +9929,7 @@ handle_member_location (struct die_info *die, struct dwarf2_cu *cu,
so if we see it, we can assume that a constant form is really
a constant and not a section offset. */
if (attr->form_is_constant ())
- *offset = attr->constant_value (0);
+ *offset = attr->unsigned_constant ().value_or (0);
else if (attr->form_is_section_offset ())
dwarf2_complex_location_expr_complaint ();
else if (attr->form_is_block ()
@@ -9941,7 +9947,7 @@ handle_member_location (struct die_info *die, struct dwarf2_cu *cu,
attr = dwarf2_attr (die, DW_AT_data_bit_offset, cu);
if (attr != nullptr)
{
- *offset = attr->constant_value (0);
+ *offset = attr->unsigned_constant ().value_or (0);
return 1;
}
}
@@ -9963,7 +9969,7 @@ handle_member_location (struct die_info *die, struct dwarf2_cu *cu,
{
if (attr->form_is_constant ())
{
- LONGEST offset = attr->constant_value (0);
+ LONGEST offset = attr->unsigned_constant ().value_or (0);
/* Work around this GCC 11 bug, where it would erroneously use -1
data member locations, instead of 0:
@@ -10012,7 +10018,7 @@ handle_member_location (struct die_info *die, struct dwarf2_cu *cu,
{
attr = dwarf2_attr (die, DW_AT_data_bit_offset, cu);
if (attr != nullptr)
- field->set_loc_bitpos (attr->constant_value (0));
+ field->set_loc_bitpos (attr->unsigned_constant ().value_or (0));
}
}
@@ -10081,7 +10087,7 @@ dwarf2_add_field (struct field_info *fip, struct die_info *die,
/* Get bit size of field (zero if none). */
attr = dwarf2_attr (die, DW_AT_bit_size, cu);
if (attr != nullptr)
- fp->set_bitsize (attr->constant_value (0));
+ fp->set_bitsize (attr->unsigned_constant ().value_or (0));
else
fp->set_bitsize (0);
@@ -10090,6 +10096,7 @@ dwarf2_add_field (struct field_info *fip, struct die_info *die,
attr = dwarf2_attr (die, DW_AT_bit_offset, cu);
if (attr != nullptr && attr->form_is_constant ())
{
+ ULONGEST bit_offset = attr->unsigned_constant ().value_or (0);
if (gdbarch_byte_order (gdbarch) == BFD_ENDIAN_BIG)
{
/* For big endian bits, the DW_AT_bit_offset gives the
@@ -10097,7 +10104,7 @@ dwarf2_add_field (struct field_info *fip, struct die_info *die,
anonymous object to the MSB of the field. We don't
have to do anything special since we don't need to
know the size of the anonymous object. */
- fp->set_loc_bitpos (fp->loc_bitpos () + attr->constant_value (0));
+ fp->set_loc_bitpos (fp->loc_bitpos () + bit_offset);
}
else
{
@@ -10108,7 +10115,6 @@ dwarf2_add_field (struct field_info *fip, struct die_info *die,
the field itself. The result is the bit offset of
the LSB of the field. */
int anonymous_size;
- int bit_offset = attr->constant_value (0);
attr = dwarf2_attr (die, DW_AT_byte_size, cu);
if (attr != nullptr && attr->form_is_constant ())
@@ -10116,7 +10122,7 @@ dwarf2_add_field (struct field_info *fip, struct die_info *die,
/* The size of the anonymous object containing
the bit field is explicit, so use the
indicated size (in bytes). */
- anonymous_size = attr->constant_value (0);
+ anonymous_size = attr->unsigned_constant ().value_or (0);
}
else
{
@@ -10269,13 +10275,19 @@ convert_variant_range (struct obstack *obstack, const variant_field &variant,
{
std::vector<discriminant_range> ranges;
- if (variant.default_branch)
+ if (variant.is_default ())
return {};
if (variant.discr_list_data == nullptr)
{
- discriminant_range r
- = {variant.discriminant_value, variant.discriminant_value};
+ ULONGEST value;
+
+ if (is_unsigned)
+ value = variant.discriminant_attr->unsigned_constant ().value_or (0);
+ else
+ value = variant.discriminant_attr->signed_constant ().value_or (0);
+
+ discriminant_range r = { value, value };
ranges.push_back (r);
}
else
@@ -11089,7 +11101,7 @@ read_structure_type (struct die_info *die, struct dwarf2_cu *cu)
if (attr != nullptr)
{
if (attr->form_is_constant ())
- type->set_length (attr->constant_value (0));
+ type->set_length (attr->unsigned_constant ().value_or (0));
else
{
struct dynamic_prop prop;
@@ -11234,12 +11246,14 @@ handle_variant (struct die_info *die, struct type *type,
{
discr = dwarf2_attr (die, DW_AT_discr_list, cu);
if (discr == nullptr || discr->as_block ()->size == 0)
- variant.default_branch = true;
+ {
+ /* Nothing to do here -- default branch. */
+ }
else
variant.discr_list_data = discr->as_block ();
}
else
- variant.discriminant_value = discr->constant_value (0);
+ variant.discriminant_attr = discr;
for (die_info *variant_child : die->children ())
handle_struct_member_die (variant_child, type, fi, template_args, cu);
@@ -11565,25 +11579,30 @@ die_byte_order (die_info *die, dwarf2_cu *cu, enum bfd_endian *byte_order)
/* Assuming DIE is an enumeration type, and TYPE is its associated
type, update TYPE using some information only available in DIE's
- children. In particular, the fields are computed. */
+ children. In particular, the fields are computed. If IS_UNSIGNED
+ is set, the enumeration type's sign is already known (a true value
+ means unsigned), and so examining the constants to determine the
+ sign isn't needed; when this is unset, the enumerator constants are
+ read as signed values. */
static void
update_enumeration_type_from_children (struct die_info *die,
struct type *type,
- struct dwarf2_cu *cu)
+ struct dwarf2_cu *cu,
+ std::optional<bool> is_unsigned)
{
- int unsigned_enum = 1;
- int flag_enum = 1;
+ /* This is used to check whether the enum is signed or unsigned; for
+ simplicity, it is always correct regardless of whether
+ IS_UNSIGNED is set. */
+ bool unsigned_enum = is_unsigned.value_or (true);
+ bool flag_enum = true;
- auto_obstack obstack;
std::vector<struct field> fields;
for (die_info *child_die : die->children ())
{
struct attribute *attr;
LONGEST value;
- const gdb_byte *bytes;
- struct dwarf2_locexpr_baton *baton;
const char *name;
if (child_die->tag != DW_TAG_enumerator)
@@ -11597,19 +11616,26 @@ update_enumeration_type_from_children (struct die_info *die,
if (name == NULL)
name = "<anonymous enumerator>";
- dwarf2_const_value_attr (attr, type, name, &obstack, cu,
- &value, &bytes, &baton);
- if (value < 0)
- {
- unsigned_enum = 0;
- flag_enum = 0;
- }
+ /* Can't check UNSIGNED_ENUM here because that is
+ optimistic. */
+ if (is_unsigned.has_value () && *is_unsigned)
+ value = attr->unsigned_constant ().value_or (0);
else
{
- if (count_one_bits_ll (value) >= 2)
- flag_enum = 0;
+ /* Read as signed, either because we don't know the sign or
+ because we know it is definitely signed. */
+ value = attr->signed_constant ().value_or (0);
+
+ if (value < 0)
+ {
+ unsigned_enum = false;
+ flag_enum = false;
+ }
}
+ if (flag_enum && count_one_bits_ll (value) >= 2)
+ flag_enum = false;
+
struct field &field = fields.emplace_back ();
field.set_name (dwarf2_physname (name, child_die, cu));
field.set_loc_enumval (value);
@@ -11618,13 +11644,10 @@ update_enumeration_type_from_children (struct die_info *die,
if (!fields.empty ())
type->copy_fields (fields);
else
- flag_enum = 0;
+ flag_enum = false;
- if (unsigned_enum)
- type->set_is_unsigned (true);
-
- if (flag_enum)
- type->set_is_flag_enum (true);
+ type->set_is_unsigned (unsigned_enum);
+ type->set_is_flag_enum (flag_enum);
}
/* Given a DW_AT_enumeration_type die, set its type. We do not
@@ -11668,7 +11691,7 @@ read_enumeration_type (struct die_info *die, struct dwarf2_cu *cu)
attr = dwarf2_attr (die, DW_AT_byte_size, cu);
if (attr != nullptr)
- type->set_length (attr->constant_value (0));
+ type->set_length (attr->unsigned_constant ().value_or (0));
else
type->set_length (0);
@@ -11682,6 +11705,11 @@ read_enumeration_type (struct die_info *die, struct dwarf2_cu *cu)
if (die_is_declaration (die, cu))
type->set_is_stub (true);
+ /* If the underlying type is known, and is unsigned, then we'll
+ assume the enumerator constants are unsigned. Otherwise we have
+ to assume they are signed. */
+ std::optional<bool> is_unsigned;
+
/* If this type has an underlying type that is not a stub, then we
may use its attributes. We always use the "unsigned" attribute
in this situation, because ordinarily we guess whether the type
@@ -11694,7 +11722,8 @@ read_enumeration_type (struct die_info *die, struct dwarf2_cu *cu)
struct type *underlying_type = type->target_type ();
underlying_type = check_typedef (underlying_type);
- type->set_is_unsigned (underlying_type->is_unsigned ());
+ is_unsigned = underlying_type->is_unsigned ();
+ type->set_is_unsigned (*is_unsigned);
if (type->length () == 0)
type->set_length (underlying_type->length ());
@@ -11714,7 +11743,7 @@ read_enumeration_type (struct die_info *die, struct dwarf2_cu *cu)
Note that, as usual, this must come after set_die_type to avoid
infinite recursion when trying to compute the names of the
enumerators. */
- update_enumeration_type_from_children (die, type, cu);
+ update_enumeration_type_from_children (die, type, cu, is_unsigned);
return type;
}
@@ -12064,7 +12093,7 @@ read_array_type (struct die_info *die, struct dwarf2_cu *cu)
attr = dwarf2_attr (die, DW_AT_bit_stride, cu);
if (attr != NULL)
- bit_stride = attr->constant_value (0);
+ bit_stride = attr->unsigned_constant ().value_or (0);
/* Irix 6.2 native cc creates array types without children for
arrays with unspecified length. */
@@ -12288,7 +12317,7 @@ mark_common_block_symbol_computed (struct symbol *sym,
if (member_loc->form_is_constant ())
{
- offset = member_loc->constant_value (0);
+ offset = member_loc->unsigned_constant ().value_or (0);
baton->size += 1 /* DW_OP_addr */ + cu->header.addr_size;
}
else
@@ -12603,7 +12632,8 @@ read_tag_pointer_type (struct die_info *die, struct dwarf2_cu *cu)
attr_byte_size = dwarf2_attr (die, DW_AT_byte_size, cu);
if (attr_byte_size)
- byte_size = attr_byte_size->constant_value (cu_header->addr_size);
+ byte_size = (attr_byte_size->unsigned_constant ()
+ .value_or (cu_header->addr_size));
else
byte_size = cu_header->addr_size;
@@ -12715,7 +12745,8 @@ read_tag_reference_type (struct die_info *die, struct dwarf2_cu *cu,
type = lookup_reference_type (target_type, refcode);
attr = dwarf2_attr (die, DW_AT_byte_size, cu);
if (attr != nullptr)
- type->set_length (attr->constant_value (cu_header->addr_size));
+ type->set_length (attr->unsigned_constant ()
+ .value_or (cu_header->addr_size));
else
type->set_length (cu_header->addr_size);
@@ -12879,9 +12910,7 @@ read_tag_string_type (struct die_info *die, struct dwarf2_cu *cu)
len = dwarf2_attr (die, DW_AT_byte_size, cu);
if (len != nullptr && len->form_is_constant ())
{
- /* Pass 0 as the default as we know this attribute is constant
- and the default value will not be returned. */
- LONGEST sz = len->constant_value (0);
+ LONGEST sz = len->unsigned_constant ().value_or (0);
prop_type = objfile_int_type (objfile, sz, true);
}
else
@@ -12900,15 +12929,14 @@ read_tag_string_type (struct die_info *die, struct dwarf2_cu *cu)
else if (attr != nullptr)
{
/* This DW_AT_string_length just contains the length with no
- indirection. There's no need to create a dynamic property in this
- case. Pass 0 for the default value as we know it will not be
- returned in this case. */
- length = attr->constant_value (0);
+ indirection. There's no need to create a dynamic property in
+ this case. */
+ length = attr->unsigned_constant ().value_or (0);
}
else if ((attr = dwarf2_attr (die, DW_AT_byte_size, cu)) != nullptr)
{
/* We don't currently support non-constant byte sizes for strings. */
- length = attr->constant_value (1);
+ length = attr->unsigned_constant ().value_or (1);
}
else
{
@@ -13217,10 +13245,10 @@ get_mpz (struct dwarf2_cu *cu, gdb_mpz *value, struct attribute *attr)
? BFD_ENDIAN_BIG : BFD_ENDIAN_LITTLE,
true);
}
- else if (attr->form_is_unsigned ())
+ else if (attr->form_is_strictly_unsigned ())
*value = gdb_mpz (attr->as_unsigned ());
else
- *value = gdb_mpz (attr->constant_value (1));
+ *value = gdb_mpz (attr->signed_constant ().value_or (1));
}
/* Assuming DIE is a rational DW_TAG_constant, read the DIE's
@@ -13399,14 +13427,14 @@ finish_fixed_point_type (struct type *type, const char *suffix,
}
else if (attr->name == DW_AT_binary_scale)
{
- LONGEST scale_exp = attr->constant_value (0);
+ LONGEST scale_exp = attr->signed_constant ().value_or (0);
gdb_mpz &num_or_denom = scale_exp > 0 ? scale_num : scale_denom;
num_or_denom <<= std::abs (scale_exp);
}
else if (attr->name == DW_AT_decimal_scale)
{
- LONGEST scale_exp = attr->constant_value (0);
+ LONGEST scale_exp = attr->signed_constant ().value_or (0);
gdb_mpz &num_or_denom = scale_exp > 0 ? scale_num : scale_denom;
num_or_denom = gdb_mpz::pow (10, std::abs (scale_exp));
@@ -13618,7 +13646,7 @@ read_base_type (struct die_info *die, struct dwarf2_cu *cu)
}
attr = dwarf2_attr (die, DW_AT_byte_size, cu);
if (attr != nullptr)
- bits = attr->constant_value (0) * TARGET_CHAR_BIT;
+ bits = attr->unsigned_constant ().value_or (0) * TARGET_CHAR_BIT;
name = dwarf2_full_name (nullptr, die, cu);
if (!name)
complaint (_("DW_AT_name missing from DW_TAG_base_type"));
@@ -13769,22 +13797,28 @@ read_base_type (struct die_info *die, struct dwarf2_cu *cu)
attr = dwarf2_attr (die, DW_AT_bit_size, cu);
if (attr != nullptr && attr->form_is_constant ())
{
- unsigned real_bit_size = attr->constant_value (0);
+ unsigned real_bit_size = attr->unsigned_constant ().value_or (0);
if (real_bit_size >= 0 && real_bit_size <= 8 * type->length ())
{
attr = dwarf2_attr (die, DW_AT_data_bit_offset, cu);
/* Only use the attributes if they make sense together. */
- if (attr == nullptr
- || (attr->form_is_constant ()
- && attr->constant_value (0) >= 0
- && (attr->constant_value (0) + real_bit_size
- <= 8 * type->length ())))
+ std::optional<ULONGEST> bit_offset;
+ if (attr == nullptr)
+ bit_offset = 0;
+ else if (attr->form_is_constant ())
+ {
+ bit_offset = attr->unsigned_constant ();
+ if (bit_offset.has_value ()
+ && *bit_offset + real_bit_size > 8 * type->length ())
+ bit_offset.reset ();
+ }
+ if (bit_offset.has_value ())
{
TYPE_MAIN_TYPE (type)->type_specific.int_stuff.bit_size
= real_bit_size;
if (attr != nullptr)
TYPE_MAIN_TYPE (type)->type_specific.int_stuff.bit_offset
- = attr->constant_value (0);
+ = *bit_offset;
}
}
}
@@ -14097,8 +14131,13 @@ read_subrange_type (struct die_info *die, struct dwarf2_cu *cu)
LONGEST bias = 0;
struct attribute *bias_attr = dwarf2_attr (die, DW_AT_GNU_bias, cu);
- if (bias_attr != nullptr && bias_attr->form_is_constant ())
- bias = bias_attr->constant_value (0);
+ if (bias_attr != nullptr)
+ {
+ if (base_type->is_unsigned ())
+ bias = (LONGEST) bias_attr->unsigned_constant ().value_or (0);
+ else
+ bias = bias_attr->signed_constant ().value_or (0);
+ }
/* Normally, the DWARF producers are expected to use a signed
constant form (Eg. DW_FORM_sdata) to express negative bounds.
@@ -14185,7 +14224,7 @@ read_subrange_type (struct die_info *die, struct dwarf2_cu *cu)
attr = dwarf2_attr (die, DW_AT_byte_size, cu);
if (attr != nullptr)
- range_type->set_length (attr->constant_value (0));
+ range_type->set_length (attr->unsigned_constant ().value_or (0));
maybe_set_alignment (cu, die, range_type);
@@ -17172,13 +17211,10 @@ new_symbol (struct die_info *die, struct type *type, struct dwarf2_cu *cu,
list was that this is unspecified. We choose to always zero-extend
because that is the interpretation long in use by GCC. */
-static gdb_byte *
-dwarf2_const_value_data (const struct attribute *attr, struct obstack *obstack,
- struct dwarf2_cu *cu, LONGEST *value, int bits)
+static void
+dwarf2_const_value_data (const struct attribute *attr, LONGEST *value,
+ int bits)
{
- struct objfile *objfile = cu->per_objfile->objfile;
- enum bfd_endian byte_order = bfd_big_endian (objfile->obfd.get ()) ?
- BFD_ENDIAN_BIG : BFD_ENDIAN_LITTLE;
LONGEST l = attr->constant_value (0);
if (bits < sizeof (*value) * 8)
@@ -17186,16 +17222,8 @@ dwarf2_const_value_data (const struct attribute *attr, struct obstack *obstack,
l &= ((LONGEST) 1 << bits) - 1;
*value = l;
}
- else if (bits == sizeof (*value) * 8)
- *value = l;
else
- {
- gdb_byte *bytes = (gdb_byte *) obstack_alloc (obstack, bits / 8);
- store_unsigned_integer (bytes, bits / 8, byte_order, l);
- return bytes;
- }
-
- return NULL;
+ *value = l;
}
/* Read a constant value from an attribute. Either set *VALUE, or if
@@ -17281,16 +17309,16 @@ dwarf2_const_value_attr (const struct attribute *attr, struct type *type,
converted to host endianness, so we just need to sign- or
zero-extend it as appropriate. */
case DW_FORM_data1:
- *bytes = dwarf2_const_value_data (attr, obstack, cu, value, 8);
+ dwarf2_const_value_data (attr, value, 8);
break;
case DW_FORM_data2:
- *bytes = dwarf2_const_value_data (attr, obstack, cu, value, 16);
+ dwarf2_const_value_data (attr, value, 16);
break;
case DW_FORM_data4:
- *bytes = dwarf2_const_value_data (attr, obstack, cu, value, 32);
+ dwarf2_const_value_data (attr, value, 32);
break;
case DW_FORM_data8:
- *bytes = dwarf2_const_value_data (attr, obstack, cu, value, 64);
+ dwarf2_const_value_data (attr, value, 64);
break;
case DW_FORM_sdata:
@@ -18495,31 +18523,23 @@ dwarf2_fetch_constant_bytes (sect_offset sect_off,
zero-extend it as appropriate. */
case DW_FORM_data1:
type = die_type (die, cu);
- result = dwarf2_const_value_data (attr, obstack, cu, &value, 8);
- if (result == NULL)
- result = write_constant_as_bytes (obstack, byte_order,
- type, value, len);
+ dwarf2_const_value_data (attr, &value, 8);
+ result = write_constant_as_bytes (obstack, byte_order, type, value, len);
break;
case DW_FORM_data2:
type = die_type (die, cu);
- result = dwarf2_const_value_data (attr, obstack, cu, &value, 16);
- if (result == NULL)
- result = write_constant_as_bytes (obstack, byte_order,
- type, value, len);
+ dwarf2_const_value_data (attr, &value, 16);
+ result = write_constant_as_bytes (obstack, byte_order, type, value, len);
break;
case DW_FORM_data4:
type = die_type (die, cu);
- result = dwarf2_const_value_data (attr, obstack, cu, &value, 32);
- if (result == NULL)
- result = write_constant_as_bytes (obstack, byte_order,
- type, value, len);
+ dwarf2_const_value_data (attr, &value, 32);
+ result = write_constant_as_bytes (obstack, byte_order, type, value, len);
break;
case DW_FORM_data8:
type = die_type (die, cu);
- result = dwarf2_const_value_data (attr, obstack, cu, &value, 64);
- if (result == NULL)
- result = write_constant_as_bytes (obstack, byte_order,
- type, value, len);
+ dwarf2_const_value_data (attr, &value, 64);
+ result = write_constant_as_bytes (obstack, byte_order, type, value, len);
break;
case DW_FORM_sdata:
diff --git a/gdb/eval.c b/gdb/eval.c
index 95d2532..19770ad 100644
--- a/gdb/eval.c
+++ b/gdb/eval.c
@@ -994,9 +994,10 @@ add_struct_fields (struct type *type, completion_list &output,
output.emplace_back (concat (prefix, type->field (i).name (),
nullptr));
}
- else if (type->field (i).type ()->code () == TYPE_CODE_UNION)
+ else if (type->field (i).type ()->code () == TYPE_CODE_UNION
+ || type->field (i).type ()->code () == TYPE_CODE_STRUCT)
{
- /* Recurse into anonymous unions. */
+ /* Recurse into anonymous unions and structures. */
add_struct_fields (type->field (i).type (),
output, fieldname, namelen, prefix);
}
diff --git a/gdb/findvar.c b/gdb/findvar.c
index 2938931..9da5c48 100644
--- a/gdb/findvar.c
+++ b/gdb/findvar.c
@@ -485,7 +485,8 @@ language_defn::read_var_value (struct symbol *var,
/* Determine address of TLS variable. */
if (obj_section
&& (obj_section->the_bfd_section->flags & SEC_THREAD_LOCAL) != 0)
- addr = target_translate_tls_address (obj_section->objfile, addr);
+ addr = target_translate_tls_address (obj_section->objfile, addr,
+ var->print_name ());
}
break;
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 141c119..bbffb3d 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -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
diff --git a/gdb/minsyms.c b/gdb/minsyms.c
index 649a9f1..9ac3145 100644
--- a/gdb/minsyms.c
+++ b/gdb/minsyms.c
@@ -1688,7 +1688,8 @@ find_minsym_type_and_address (minimal_symbol *msymbol,
{
/* Skip translation if caller does not need the address. */
if (address_p != NULL)
- *address_p = target_translate_tls_address (objfile, addr);
+ *address_p = target_translate_tls_address
+ (objfile, addr, bound_msym.minsym->print_name ());
return builtin_type (objfile)->nodebug_tls_symbol;
}
diff --git a/gdb/ppc-linux-tdep.c b/gdb/ppc-linux-tdep.c
index 3050a9e..441f317 100644
--- a/gdb/ppc-linux-tdep.c
+++ b/gdb/ppc-linux-tdep.c
@@ -49,6 +49,7 @@
#include "arch-utils.h"
#include "xml-syscall.h"
#include "linux-tdep.h"
+#include "svr4-tls-tdep.h"
#include "linux-record.h"
#include "record-full.h"
#include "infrun.h"
@@ -2071,6 +2072,63 @@ ppc64_linux_gcc_target_options (struct gdbarch *gdbarch)
return "";
}
+/* Fetch and return the TLS DTV (dynamic thread vector) address for PTID.
+ Throw a suitable TLS error if something goes wrong. */
+
+static CORE_ADDR
+ppc64_linux_get_tls_dtv_addr (struct gdbarch *gdbarch, ptid_t ptid,
+ enum svr4_tls_libc libc)
+{
+ /* On ppc64, the thread pointer is found in r13. Fetch this
+ register. */
+ regcache *regcache
+ = get_thread_arch_regcache (current_inferior (), ptid, gdbarch);
+ int thread_pointer_regnum = PPC_R0_REGNUM + 13;
+ target_fetch_registers (regcache, thread_pointer_regnum);
+ ULONGEST thr_ptr;
+ if (regcache->cooked_read (thread_pointer_regnum, &thr_ptr) != REG_VALID)
+ throw_error (TLS_GENERIC_ERROR, _("Unable to fetch thread pointer"));
+
+ /* The thread pointer (r13) is an address that is 0x7000 ahead of
+ the *end* of the TCB (thread control block). The field
+ holding the DTV address is at the very end of the TCB.
+ Therefore, the DTV pointer address can be found by
+ subtracting (0x7000+8) from the thread pointer. Compute the
+ address of the DTV pointer, fetch it, and convert it to an
+ address. */
+ CORE_ADDR dtv_ptr_addr = thr_ptr - 0x7000 - 8;
+ gdb::byte_vector buf (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT);
+ if (target_read_memory (dtv_ptr_addr, buf.data (), buf.size ()) != 0)
+ throw_error (TLS_GENERIC_ERROR, _("Unable to fetch DTV address"));
+
+ const struct builtin_type *builtin = builtin_type (gdbarch);
+ CORE_ADDR dtv_addr = gdbarch_pointer_to_address
+ (gdbarch, builtin->builtin_data_ptr, buf.data ());
+ return dtv_addr;
+}
+
+/* For internal TLS lookup, return the DTP offset, which is the offset
+ to subtract from a DTV entry, in order to obtain the address of the
+ TLS block. */
+
+static ULONGEST
+ppc_linux_get_tls_dtp_offset (struct gdbarch *gdbarch, ptid_t ptid,
+ svr4_tls_libc libc)
+{
+ if (libc == svr4_tls_libc_musl)
+ {
+ /* This value is DTP_OFFSET, which represents the value to
+ subtract from the DTV entry. For PPC, it can be found in
+ MUSL's arch/powerpc64/pthread_arch.h and
+ arch/powerpc32/pthread_arch.h. (Both values are the same.)
+ It represents the value to subtract from the DTV entry, once
+ it has been fetched from the DTV array. */
+ return 0x8000;
+ }
+ else
+ return 0;
+}
+
static displaced_step_prepare_status
ppc_linux_displaced_step_prepare (gdbarch *arch, thread_info *thread,
CORE_ADDR &displaced_pc)
@@ -2284,6 +2342,11 @@ ppc_linux_init_abi (struct gdbarch_info info,
set_gdbarch_gnu_triplet_regexp (gdbarch, ppc64_gnu_triplet_regexp);
/* Set GCC target options. */
set_gdbarch_gcc_target_options (gdbarch, ppc64_linux_gcc_target_options);
+ /* Internal thread local address support. */
+ set_gdbarch_get_thread_local_address (gdbarch,
+ svr4_tls_get_thread_local_address);
+ svr4_tls_register_tls_methods (info, gdbarch, ppc64_linux_get_tls_dtv_addr,
+ ppc_linux_get_tls_dtp_offset);
}
set_gdbarch_core_read_description (gdbarch, ppc_linux_core_read_description);
diff --git a/gdb/remote.c b/gdb/remote.c
index 75cc21c..73dc426 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -80,6 +80,7 @@
#include "async-event.h"
#include "gdbsupport/selftest.h"
#include "cli/cli-style.h"
+#include "gdbsupport/remote-args.h"
/* The remote target. */
@@ -10835,16 +10836,15 @@ remote_target::extended_remote_run (const std::string &args)
if (!args.empty ())
{
- int i;
+ std::vector<std::string> split_args = gdb::remote_args::split (args);
- gdb_argv argv (args.c_str ());
- for (i = 0; argv[i] != NULL; i++)
+ for (const auto &a : split_args)
{
- if (strlen (argv[i]) * 2 + 1 + len >= get_remote_packet_size ())
+ if (a.size () * 2 + 1 + len >= get_remote_packet_size ())
error (_("Argument list too long for run packet"));
rs->buf[len++] = ';';
- len += 2 * bin2hex ((gdb_byte *) argv[i], rs->buf.data () + len,
- strlen (argv[i]));
+ len += 2 * bin2hex ((gdb_byte *) a.c_str (), rs->buf.data () + len,
+ a.size ());
}
}
diff --git a/gdb/riscv-linux-tdep.c b/gdb/riscv-linux-tdep.c
index f21039a..e1ea615 100644
--- a/gdb/riscv-linux-tdep.c
+++ b/gdb/riscv-linux-tdep.c
@@ -20,6 +20,7 @@
#include "osabi.h"
#include "glibc-tdep.h"
#include "linux-tdep.h"
+#include "svr4-tls-tdep.h"
#include "solib-svr4.h"
#include "regset.h"
#include "tramp-frame.h"
@@ -28,6 +29,7 @@
#include "record-full.h"
#include "linux-record.h"
#include "riscv-linux-tdep.h"
+#include "inferior.h"
extern unsigned int record_debug;
@@ -426,6 +428,79 @@ riscv64_linux_record_tdep_init (struct gdbarch *gdbarch,
riscv_linux_record_tdep.arg6 = RISCV_A5_REGNUM;
}
+/* Fetch and return the TLS DTV (dynamic thread vector) address for PTID.
+ Throw a suitable TLS error if something goes wrong. */
+
+static CORE_ADDR
+riscv_linux_get_tls_dtv_addr (struct gdbarch *gdbarch, ptid_t ptid,
+ svr4_tls_libc libc)
+{
+ /* On RISC-V, the thread pointer is found in TP. */
+ regcache *regcache
+ = get_thread_arch_regcache (current_inferior (), ptid, gdbarch);
+ int thread_pointer_regnum = RISCV_TP_REGNUM;
+ target_fetch_registers (regcache, thread_pointer_regnum);
+ ULONGEST thr_ptr;
+ if (regcache->cooked_read (thread_pointer_regnum, &thr_ptr) != REG_VALID)
+ throw_error (TLS_GENERIC_ERROR, _("Unable to fetch thread pointer"));
+
+ CORE_ADDR dtv_ptr_addr;
+ switch (libc)
+ {
+ case svr4_tls_libc_musl:
+ /* MUSL: The DTV pointer is found at the very end of the pthread
+ struct which is located *before* the thread pointer. I.e.
+ the thread pointer will be just beyond the end of the struct,
+ so the address of the DTV pointer is found one pointer-size
+ before the thread pointer. */
+ dtv_ptr_addr
+ = thr_ptr - (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT);
+ break;
+ case svr4_tls_libc_glibc:
+ /* GLIBC: The thread pointer (TP) points just beyond the end of
+ the TCB (thread control block). On RISC-V, this struct
+ (tcbhead_t) is defined to contain two pointers. The first is
+ a pointer to the DTV and the second is a pointer to private
+ data. So the DTV pointer address is 16 bytes (i.e. the size of
+ two pointers) before thread pointer. */
+
+ dtv_ptr_addr
+ = thr_ptr - 2 * (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT);
+ break;
+ default:
+ throw_error (TLS_GENERIC_ERROR, _("Unknown RISC-V C library"));
+ break;
+ }
+
+ gdb::byte_vector buf (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT);
+ if (target_read_memory (dtv_ptr_addr, buf.data (), buf.size ()) != 0)
+ throw_error (TLS_GENERIC_ERROR, _("Unable to fetch DTV address"));
+
+ const struct builtin_type *builtin = builtin_type (gdbarch);
+ CORE_ADDR dtv_addr = gdbarch_pointer_to_address
+ (gdbarch, builtin->builtin_data_ptr, buf.data ());
+ return dtv_addr;
+}
+
+/* For internal TLS lookup, return the DTP offset, which is the offset
+ to subtract from a DTV entry, in order to obtain the address of the
+ TLS block. */
+
+static ULONGEST
+riscv_linux_get_tls_dtp_offset (struct gdbarch *gdbarch, ptid_t ptid,
+ svr4_tls_libc libc)
+{
+ if (libc == svr4_tls_libc_musl)
+ {
+ /* This value is DTP_OFFSET in MUSL's arch/riscv64/pthread_arch.h.
+ It represents the value to subtract from the DTV entry, once
+ it has been loaded. */
+ return 0x800;
+ }
+ else
+ return 0;
+}
+
/* Initialize RISC-V Linux ABI info. */
static void
@@ -451,6 +526,10 @@ riscv_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
/* Enable TLS support. */
set_gdbarch_fetch_tls_load_module_address (gdbarch,
svr4_fetch_objfile_link_map);
+ 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);
set_gdbarch_iterate_over_regset_sections
(gdbarch, riscv_linux_iterate_over_regset_sections);
diff --git a/gdb/s390-linux-tdep.c b/gdb/s390-linux-tdep.c
index 04c523b..bd1f42c 100644
--- a/gdb/s390-linux-tdep.c
+++ b/gdb/s390-linux-tdep.c
@@ -29,6 +29,7 @@
#include "gdbcore.h"
#include "linux-record.h"
#include "linux-tdep.h"
+#include "svr4-tls-tdep.h"
#include "objfiles.h"
#include "osabi.h"
#include "regcache.h"
@@ -40,6 +41,7 @@
#include "target.h"
#include "trad-frame.h"
#include "xml-syscall.h"
+#include "inferior.h"
#include "features/s390-linux32v1.c"
#include "features/s390-linux32v2.c"
@@ -1124,6 +1126,45 @@ s390_init_linux_record_tdep (struct linux_record_tdep *record_tdep,
record_tdep->ioctl_FIOQSIZE = 0x545e;
}
+/* Fetch and return the TLS DTV (dynamic thread vector) address for PTID.
+ Throw a suitable TLS error if something goes wrong. */
+
+static CORE_ADDR
+s390_linux_get_tls_dtv_addr (struct gdbarch *gdbarch, ptid_t ptid,
+ enum svr4_tls_libc libc)
+{
+ /* On S390, the thread pointer is found in two registers A0 and A1
+ (or, using gdb naming, acr0 and acr1) A0 contains the top 32
+ bits of the address and A1 contains the bottom 32 bits. */
+ regcache *regcache
+ = get_thread_arch_regcache (current_inferior (), ptid, gdbarch);
+ target_fetch_registers (regcache, S390_A0_REGNUM);
+ target_fetch_registers (regcache, S390_A1_REGNUM);
+ ULONGEST thr_ptr_lo, thr_ptr_hi, thr_ptr;
+ if (regcache->cooked_read (S390_A0_REGNUM, &thr_ptr_hi) != REG_VALID
+ || regcache->cooked_read (S390_A1_REGNUM, &thr_ptr_lo) != REG_VALID)
+ throw_error (TLS_GENERIC_ERROR, _("Unable to fetch thread pointer"));
+ thr_ptr = (thr_ptr_hi << 32) + thr_ptr_lo;
+
+ /* The thread pointer points at the TCB (thread control block). The
+ first two members of this struct are both pointers, where the
+ first will be a pointer to the TCB (i.e. it points at itself)
+ and the second will be a pointer to the DTV (dynamic thread
+ vector). There are many other fields too, but the one we care
+ about here is the DTV pointer. Compute the address of the DTV
+ pointer, fetch it, and convert it to an address. */
+ CORE_ADDR dtv_ptr_addr
+ = thr_ptr + gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT;
+ gdb::byte_vector buf (gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT);
+ if (target_read_memory (dtv_ptr_addr, buf.data (), buf.size ()) != 0)
+ throw_error (TLS_GENERIC_ERROR, _("Unable to fetch DTV address"));
+
+ const struct builtin_type *builtin = builtin_type (gdbarch);
+ CORE_ADDR dtv_addr = gdbarch_pointer_to_address
+ (gdbarch, builtin->builtin_data_ptr, buf.data ());
+ return dtv_addr;
+}
+
/* Initialize OSABI common for GNU/Linux on 31- and 64-bit systems. */
static void
@@ -1152,6 +1193,9 @@ s390_linux_init_abi_any (struct gdbarch_info info, struct gdbarch *gdbarch)
/* Enable TLS support. */
set_gdbarch_fetch_tls_load_module_address (gdbarch,
svr4_fetch_objfile_link_map);
+ set_gdbarch_get_thread_local_address (gdbarch,
+ svr4_tls_get_thread_local_address);
+ svr4_tls_register_tls_methods (info, gdbarch, s390_linux_get_tls_dtv_addr);
/* Support reverse debugging. */
set_gdbarch_process_record_signal (gdbarch, s390_linux_record_signal);
diff --git a/gdb/solib-svr4.c b/gdb/solib-svr4.c
index 2a2745d..458c4ba 100644
--- a/gdb/solib-svr4.c
+++ b/gdb/solib-svr4.c
@@ -431,6 +431,14 @@ struct svr4_info
/* 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. */
@@ -635,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 ()
@@ -1580,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. */
@@ -2377,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 ();
@@ -3632,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");
}
diff --git a/gdb/solib-svr4.h b/gdb/solib-svr4.h
index c08bacf..e59c8e4 100644
--- a/gdb/solib-svr4.h
+++ b/gdb/solib-svr4.h
@@ -112,4 +112,16 @@ extern struct link_map_offsets *svr4_lp64_fetch_link_map_offsets (void);
SVR4 run time loader. */
int svr4_in_dynsym_resolve_code (CORE_ADDR pc);
+/* For the MUSL C library, given link map address LM_ADDR, return the
+ corresponding TLS module id, or 0 if not found. */
+int musl_link_map_to_tls_module_id (CORE_ADDR lm_addr);
+
+/* 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);
+
+/* Return program interpreter string. */
+
+std::optional<gdb::byte_vector> svr4_find_program_interpreter ();
+
#endif /* GDB_SOLIB_SVR4_H */
diff --git a/gdb/svr4-tls-tdep.c b/gdb/svr4-tls-tdep.c
new file mode 100644
index 0000000..56e1470
--- /dev/null
+++ b/gdb/svr4-tls-tdep.c
@@ -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);
+}
diff --git a/gdb/svr4-tls-tdep.h b/gdb/svr4-tls-tdep.h
new file mode 100644
index 0000000..73efc02
--- /dev/null
+++ b/gdb/svr4-tls-tdep.h
@@ -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 */
diff --git a/gdb/syscalls/riscv-canonicalize-syscall-gen.py b/gdb/syscalls/riscv-canonicalize-syscall-gen.py
index 30e52b7..40039bb 100755
--- a/gdb/syscalls/riscv-canonicalize-syscall-gen.py
+++ b/gdb/syscalls/riscv-canonicalize-syscall-gen.py
@@ -82,51 +82,59 @@ tail = """\
class Generator:
- def _get_gdb_syscalls(self, gdb_syscalls_path: _Path) -> list[str]:
- gdb_syscalls: list[str] = []
- with open(gdb_syscalls_path, "r", encoding="UTF-8") as file:
- lines = file.readlines()
- for line in lines:
- match = re.search(r"\s*(?P<name>gdb_sys_[^S]+)\S*=", line)
- if match:
- gdb_syscalls.append(match.group("name").strip())
- return gdb_syscalls
-
- def _get_canon_syscalls_lines(self, syscalls_path: _Path, gdb_syscalls: list[str]) -> list[str]:
- canon_syscalls: dict[int, str] = {}
- with open(syscalls_path, "r", encoding="UTF-8") as file:
- lines = file.readlines()
- for line in lines:
- match = re.match(r"#define\s+__NR_(?P<name>[^\s]+)\s+(?P<number>\d+)", line)
- if match:
- syscall_name = match.group("name")
- syscall_num = int(match.group("number"))
- gdb_syscall_name = f"gdb_sys_{syscall_name}"
- if gdb_syscall_name in gdb_syscalls:
- value = f" case {syscall_num}: return {gdb_syscall_name};\n"
- canon_syscalls[syscall_num] = value
- # this is a place for corner cases
- elif syscall_name == "mmap":
- gdb_old_syscall_name = "gdb_old_mmap"
- value = f" case {syscall_num}: return {gdb_old_syscall_name};\n"
- canon_syscalls[syscall_num] = value
- else:
- value = f" /* case {syscall_num}: return {gdb_syscall_name}; */\n"
- canon_syscalls[syscall_num] = value
- return [canon_syscalls[syscall_num] for syscall_num in sorted(canon_syscalls)]
-
- def generate(self, syscalls_path: _Path) -> None:
- repo_path = _Path(__file__).parent.parent.parent
- gdb_syscalls_path = repo_path / "gdb" / "linux-record.h"
- canon_syscalls_path = repo_path / "gdb" / "riscv-canonicalize-syscall-gen.c"
-
- gdb_syscalls = self._get_gdb_syscalls(gdb_syscalls_path)
- canon_syscalls_lines = self._get_canon_syscalls_lines(syscalls_path, gdb_syscalls)
-
- with open(canon_syscalls_path, "w", encoding="UTF-8") as file:
- file.writelines(head)
- file.writelines(canon_syscalls_lines)
- file.writelines(tail)
+ def _get_gdb_syscalls(self, gdb_syscalls_path: _Path) -> list[str]:
+ gdb_syscalls: list[str] = []
+ with open(gdb_syscalls_path, "r", encoding="UTF-8") as file:
+ lines = file.readlines()
+ for line in lines:
+ match = re.search(r"\s*(?P<name>gdb_sys_[^S]+)\S*=", line)
+ if match:
+ gdb_syscalls.append(match.group("name").strip())
+ return gdb_syscalls
+
+ def _get_canon_syscalls_lines(
+ self, syscalls_path: _Path, gdb_syscalls: list[str]
+ ) -> list[str]:
+ canon_syscalls: dict[int, str] = {}
+ with open(syscalls_path, "r", encoding="UTF-8") as file:
+ lines = file.readlines()
+ for line in lines:
+ match = re.match(
+ r"#define\s+__NR_(?P<name>[^\s]+)\s+(?P<number>\d+)", line
+ )
+ if match:
+ syscall_name = match.group("name")
+ syscall_num = int(match.group("number"))
+ gdb_syscall_name = f"gdb_sys_{syscall_name}"
+ if gdb_syscall_name in gdb_syscalls:
+ value = f" case {syscall_num}: return {gdb_syscall_name};\n"
+ canon_syscalls[syscall_num] = value
+ # this is a place for corner cases
+ elif syscall_name == "mmap":
+ gdb_old_syscall_name = "gdb_sys_old_mmap"
+ value = (
+ f" case {syscall_num}: return {gdb_old_syscall_name};\n"
+ )
+ canon_syscalls[syscall_num] = value
+ else:
+ value = f" /* case {syscall_num}: return {gdb_syscall_name}; */\n"
+ canon_syscalls[syscall_num] = value
+ return [canon_syscalls[syscall_num] for syscall_num in sorted(canon_syscalls)]
+
+ def generate(self, syscalls_path: _Path) -> None:
+ repo_path = _Path(__file__).parent.parent.parent
+ gdb_syscalls_path = repo_path / "gdb" / "linux-record.h"
+ canon_syscalls_path = repo_path / "gdb" / "riscv-canonicalize-syscall-gen.c"
+
+ gdb_syscalls = self._get_gdb_syscalls(gdb_syscalls_path)
+ canon_syscalls_lines = self._get_canon_syscalls_lines(
+ syscalls_path, gdb_syscalls
+ )
+
+ with open(canon_syscalls_path, "w", encoding="UTF-8") as file:
+ file.writelines(head)
+ file.writelines(canon_syscalls_lines)
+ file.writelines(tail)
help_message = """\
@@ -136,28 +144,28 @@ from path to riscv linux syscalls.
def setup_parser() -> argparse.ArgumentParser:
- parser = argparse.ArgumentParser(description=help_message)
- parser.add_argument(
- "-i",
- "--input",
- type=_Path,
- required=True,
- help="path to riscv linux syscalls (glibc/sysdeps/unix/sysv/linux/riscv/rv64/arch-syscall.h)",
- )
- return parser
+ parser = argparse.ArgumentParser(description=help_message)
+ parser.add_argument(
+ "-i",
+ "--input",
+ type=_Path,
+ required=True,
+ help="path to riscv linux syscalls (glibc/sysdeps/unix/sysv/linux/riscv/rv64/arch-syscall.h)",
+ )
+ return parser
def main(argv: list[str]) -> int:
- try:
- parser = setup_parser()
- args = parser.parse_args(argv)
- generator = Generator()
- generator.generate(args.input)
- return 0
- except RuntimeError as e:
- print(str(e))
- return -1
+ try:
+ parser = setup_parser()
+ args = parser.parse_args(argv)
+ generator = Generator()
+ generator.generate(args.input)
+ return 0
+ except RuntimeError as e:
+ print(str(e))
+ return -1
if __name__ == "__main__":
- sys.exit(main(sys.argv[1:]))
+ sys.exit(main(sys.argv[1:]))
diff --git a/gdb/target.c b/gdb/target.c
index 4a1964e..522bed8 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -1250,11 +1250,21 @@ generic_tls_error (void)
_("Cannot find thread-local variables on this target"));
}
-/* Using the objfile specified in OBJFILE, find the address for the
- current thread's thread-local storage with offset OFFSET. */
+/* See target.h. */
+
CORE_ADDR
-target_translate_tls_address (struct objfile *objfile, CORE_ADDR offset)
+target_translate_tls_address (struct objfile *objfile, CORE_ADDR offset,
+ const char *name)
{
+ if (!target_has_registers ())
+ {
+ if (name == nullptr)
+ error (_("Cannot translate TLS address without registers"));
+ else
+ error (_("Cannot find address of TLS symbol `%s' without registers"),
+ name);
+ }
+
volatile CORE_ADDR addr = 0;
struct target_ops *target = current_inferior ()->top_target ();
gdbarch *gdbarch = current_inferior ()->arch ();
diff --git a/gdb/target.h b/gdb/target.h
index 004494d..2d3bac7 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -2472,8 +2472,14 @@ extern void target_pre_inferior ();
extern void target_preopen (int);
+/* Using the objfile specified in OBJFILE, find the address for the
+ current thread's thread-local storage with offset OFFSET. If it's
+ provided, NAME might be used to indicate the relevant variable
+ in an error message. */
+
extern CORE_ADDR target_translate_tls_address (struct objfile *objfile,
- CORE_ADDR offset);
+ CORE_ADDR offset,
+ const char *name = nullptr);
/* Return the "section" containing the specified address. */
const struct target_section *target_section_by_addr (struct target_ops *target,
diff --git a/gdb/testsuite/gdb.base/break1.c b/gdb/testsuite/gdb.base/break1.c
index 110341c..26c4663 100644
--- a/gdb/testsuite/gdb.base/break1.c
+++ b/gdb/testsuite/gdb.base/break1.c
@@ -23,7 +23,13 @@ struct some_struct
{
int a_field;
int b_field;
- union { int z_field; };
+ union
+ {
+ struct
+ {
+ int z_field;
+ };
+ };
};
struct some_struct values[50];
diff --git a/gdb/testsuite/gdb.base/tls-common.exp.tcl b/gdb/testsuite/gdb.base/tls-common.exp.tcl
new file mode 100644
index 0000000..7aa7f46
--- /dev/null
+++ b/gdb/testsuite/gdb.base/tls-common.exp.tcl
@@ -0,0 +1,50 @@
+# Copyright 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.
+
+# Require statement, variables and procs used by tls-nothreads.exp,
+# tls-multiobj.exp, and tls-dlobj.exp.
+
+# The tests listed above are known to work for the targets listed on
+# the 'require' line, below.
+#
+# At the moment, only the Linux target is listed, but, ideally, these
+# tests should be run on other targets too. E.g, testing on FreeBSD
+# shows many failures which should be addressed in some fashion before
+# enabling it for that target.
+
+require {is_any_target "*-*-linux*"}
+
+# These are the targets which have support for internal TLS lookup:
+
+set internal_tls_linux_targets {"x86_64-*-linux*" "aarch64-*-linux*"
+ "riscv*-*-linux*" "powerpc64*-*-linux*"
+ "s390x*-*-linux*"}
+
+# The "maint set force-internal-tls-address-lookup" command is only
+# available for certain Linux architectures. Don't attempt to force
+# use of internal TLS support for architectures which don't support
+# it.
+
+if [is_any_target {*}$internal_tls_linux_targets] {
+ set internal_tls_iters { false true }
+} else {
+ set internal_tls_iters { false }
+}
+
+# Set up a kfail with message KFAIL_MSG when KFAIL_COND holds, then
+# issue gdb_test with command CMD and regular expression RE.
+
+proc gdb_test_with_kfail {cmd re kfail_cond kfail_msg} {
+ if [uplevel 1 [list expr $kfail_cond]] {
+ setup_kfail $kfail_msg *-*-*
+ }
+ gdb_test $cmd $re
+}
diff --git a/gdb/testsuite/gdb.base/tls-dlobj-lib.c b/gdb/testsuite/gdb.base/tls-dlobj-lib.c
new file mode 100644
index 0000000..c69bab7
--- /dev/null
+++ b/gdb/testsuite/gdb.base/tls-dlobj-lib.c
@@ -0,0 +1,87 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 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 <http://www.gnu.org/licenses/>. */
+
+/* This program needs to be compiled with preprocessor symbol set to
+ a small integer, e.g. "gcc -DN=1 ..." With N defined, the CONCAT2
+ and CONCAT3 macros will construct suitable names for the global
+ variables and functions. */
+
+#define CONCAT2(a,b) CONCAT2_(a,b)
+#define CONCAT2_(a,b) a ## b
+
+#define CONCAT3(a,b,c) CONCAT3_(a,b,c)
+#define CONCAT3_(a,b,c) a ## b ## c
+
+/* For N=1, this ends up being...
+ __thread int tls_lib1_tbss_1;
+ __thread int tls_lib1_tbss_2;
+ __thread int tls_lib1_tdata_1 = 196;
+ __thread int tls_lib1_tdata_2 = 197; */
+
+__thread int CONCAT3(tls_lib, N, _tbss_1);
+__thread int CONCAT3(tls_lib, N, _tbss_2);
+__thread int CONCAT3(tls_lib, N, _tdata_1) = CONCAT2(N, 96);
+__thread int CONCAT3(tls_lib, N, _tdata_2) = CONCAT2(N, 97);
+
+/* Substituting for N, define function:
+
+ int get_tls_libN_var (int which) . */
+
+int
+CONCAT3(get_tls_lib, N, _var) (int which)
+{
+ switch (which)
+ {
+ case 0:
+ return -1;
+ case 1:
+ return CONCAT3(tls_lib, N, _tbss_1);
+ case 2:
+ return CONCAT3(tls_lib, N, _tbss_2);
+ case 3:
+ return CONCAT3(tls_lib, N, _tdata_1);
+ case 4:
+ return CONCAT3(tls_lib, N, _tdata_2);
+ }
+ return -1;
+}
+
+/* Substituting for N, define function:
+
+ void set_tls_libN_var (int which, int val) . */
+
+void
+CONCAT3(set_tls_lib, N, _var) (int which, int val)
+{
+ switch (which)
+ {
+ case 0:
+ break;
+ case 1:
+ CONCAT3(tls_lib, N, _tbss_1) = val;
+ break;
+ case 2:
+ CONCAT3(tls_lib, N, _tbss_2) = val;
+ break;
+ case 3:
+ CONCAT3(tls_lib, N, _tdata_1) = val;
+ break;
+ case 4:
+ CONCAT3(tls_lib, N, _tdata_2) = val;
+ break;
+ }
+}
diff --git a/gdb/testsuite/gdb.base/tls-dlobj.c b/gdb/testsuite/gdb.base/tls-dlobj.c
new file mode 100644
index 0000000..322bdda
--- /dev/null
+++ b/gdb/testsuite/gdb.base/tls-dlobj.c
@@ -0,0 +1,311 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 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 <http://www.gnu.org/licenses/>. */
+
+#include <dlfcn.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+typedef void (*setter_ftype) (int which, int val);
+
+__thread int tls_main_tbss_1;
+__thread int tls_main_tbss_2;
+__thread int tls_main_tdata_1 = 96;
+__thread int tls_main_tdata_2 = 97;
+
+extern void set_tls_lib10_var (int which, int val);
+extern void set_tls_lib11_var (int which, int val);
+
+volatile int data;
+
+static void
+set_tls_main_var (int which, int val)
+{
+ switch (which)
+ {
+ case 1:
+ tls_main_tbss_1 = val;
+ break;
+ case 2:
+ tls_main_tbss_2 = val;
+ break;
+ case 3:
+ tls_main_tdata_1 = val;
+ break;
+ case 4:
+ tls_main_tdata_2 = val;
+ break;
+ }
+}
+
+void
+use_it (int a)
+{
+ data = a;
+}
+
+static void *
+load_dso (char *dso_name, int n, setter_ftype *setterp)
+{
+ char buf[80];
+ void *sym;
+ void *handle = dlopen (dso_name, RTLD_NOW | RTLD_GLOBAL);
+ if (handle == NULL)
+ {
+ fprintf (stderr, "dlopen of DSO '%s' failed: %s\n", dso_name, dlerror ());
+ exit (1);
+ }
+ sprintf (buf, "set_tls_lib%d_var", n);
+ sym = dlsym (handle, buf);
+ assert (sym != NULL);
+ *setterp = sym;
+
+ /* Some libc implementations (for some architectures) refuse to
+ initialize TLS data structures (specifically, the DTV) without
+ first calling dlsym on one of the TLS symbols. */
+ sprintf (buf, "tls_lib%d_tdata_1", n);
+ assert (dlsym (handle, buf) != NULL);
+
+ return handle;
+}
+
+int
+main (int argc, char **argv)
+{
+ int i, status;
+ setter_ftype s0, s1, s2, s3, s4, s10, s11;
+ void *h1 = load_dso (OBJ1, 1, &s1);
+ void *h2 = load_dso (OBJ2, 2, &s2);
+ void *h3 = load_dso (OBJ3, 3, &s3);
+ void *h4 = load_dso (OBJ4, 4, &s4);
+ s0 = set_tls_main_var;
+ s10 = set_tls_lib10_var;
+ s11 = set_tls_lib11_var;
+
+ use_it (0); /* main-breakpoint-1 */
+
+ /* Set TLS variables in main program and all libraries. */
+ for (i = 1; i <= 4; i++)
+ s0 (i, 10 + i);
+ for (i = 1; i <= 4; i++)
+ s1 (i, 110 + i);
+ for (i = 1; i <= 4; i++)
+ s2 (i, 210 + i);
+ for (i = 1; i <= 4; i++)
+ s3 (i, 310 + i);
+ for (i = 1; i <= 4; i++)
+ s4 (i, 410 + i);
+ for (i = 1; i <= 4; i++)
+ s10 (i, 1010 + i);
+ for (i = 1; i <= 4; i++)
+ s11 (i, 1110 + i);
+
+ use_it (0); /* main-breakpoint-2 */
+
+ /* Unload lib2 and lib3. */
+ status = dlclose (h2);
+ assert (status == 0);
+ status = dlclose (h3);
+ assert (status == 0);
+
+ /* Set TLS variables in main program and in libraries which are still
+ loaded. */
+ for (i = 1; i <= 4; i++)
+ s0 (i, 20 + i);
+ for (i = 1; i <= 4; i++)
+ s1 (i, 120 + i);
+ for (i = 1; i <= 4; i++)
+ s4 (i, 420 + i);
+ for (i = 1; i <= 4; i++)
+ s10 (i, 1020 + i);
+ for (i = 1; i <= 4; i++)
+ s11 (i, 1120 + i);
+
+ use_it (0); /* main-breakpoint-3 */
+
+ /* Load lib3. */
+ h3 = load_dso (OBJ3, 3, &s3);
+
+ /* Set TLS vars again; currently, only lib2 is not loaded. */
+ for (i = 1; i <= 4; i++)
+ s0 (i, 30 + i);
+ for (i = 1; i <= 4; i++)
+ s1 (i, 130 + i);
+ for (i = 1; i <= 4; i++)
+ s3 (i, 330 + i);
+ for (i = 1; i <= 4; i++)
+ s4 (i, 430 + i);
+ for (i = 1; i <= 4; i++)
+ s10 (i, 1030 + i);
+ for (i = 1; i <= 4; i++)
+ s11 (i, 1130 + i);
+
+ use_it (0); /* main-breakpoint-4 */
+
+ /* Unload lib1 and lib4; load lib2. */
+ status = dlclose (h1);
+ assert (status == 0);
+ status = dlclose (h4);
+ assert (status == 0);
+ h2 = load_dso (OBJ2, 2, &s2);
+
+ /* Set TLS vars; currently, lib2 and lib3 are loaded,
+ lib1 and lib4 are not. */
+ for (i = 1; i <= 4; i++)
+ s0 (i, 40 + i);
+ for (i = 1; i <= 4; i++)
+ s2 (i, 240 + i);
+ for (i = 1; i <= 4; i++)
+ s3 (i, 340 + i);
+ for (i = 1; i <= 4; i++)
+ s10 (i, 1040 + i);
+ for (i = 1; i <= 4; i++)
+ s11 (i, 1140 + i);
+
+ use_it (0); /* main-breakpoint-5 */
+
+ /* Load lib4 and lib1. Unload lib2. */
+ h4 = load_dso (OBJ4, 4, &s4);
+ h1 = load_dso (OBJ1, 1, &s1);
+ status = dlclose (h2);
+ assert (status == 0);
+
+ /* Set TLS vars; currently, lib1, lib3, and lib4 are loaded;
+ lib2 is not loaded. */
+ for (i = 1; i <= 4; i++)
+ s0 (i, 50 + i);
+ for (i = 1; i <= 4; i++)
+ s1 (i, 150 + i);
+ for (i = 1; i <= 4; i++)
+ s3 (i, 350 + i);
+ for (i = 1; i <= 4; i++)
+ s4 (i, 450 + i);
+ for (i = 1; i <= 4; i++)
+ s10 (i, 1050 + i);
+ for (i = 1; i <= 4; i++)
+ s11 (i, 1150 + i);
+
+ use_it (0); /* main-breakpoint-6 */
+
+ /* Load lib2, unload lib1, lib3, and lib4; then load lib3 again. */
+ h2 = load_dso (OBJ2, 2, &s2);
+ status = dlclose (h1);
+ assert (status == 0);
+ status = dlclose (h3);
+ assert (status == 0);
+ status = dlclose (h4);
+ assert (status == 0);
+ h3 = load_dso (OBJ3, 3, &s3);
+
+ /* Set TLS vars; currently, lib2 and lib3 are loaded;
+ lib1 and lib4 are not loaded. */
+ for (i = 1; i <= 4; i++)
+ s0 (i, 60 + i);
+ for (i = 1; i <= 4; i++)
+ s2 (i, 260 + i);
+ for (i = 1; i <= 4; i++)
+ s3 (i, 360 + i);
+ for (i = 1; i <= 4; i++)
+ s10 (i, 1060 + i);
+ for (i = 1; i <= 4; i++)
+ s11 (i, 1160 + i);
+
+ use_it (0); /* main-breakpoint-7 */
+
+ /* Unload lib3 and lib2, then (re)load lib4, lib3, lib2, and lib1,
+ in that order. */
+ status = dlclose (h3);
+ assert (status == 0);
+ status = dlclose (h2);
+ assert (status == 0);
+ h4 = load_dso (OBJ4, 4, &s4);
+ h3 = load_dso (OBJ3, 3, &s3);
+ h2 = load_dso (OBJ2, 2, &s2);
+ h1 = load_dso (OBJ1, 1, &s1);
+
+ /* Set TLS vars; currently, lib1, lib2, lib3, and lib4 are all
+ loaded. */
+ for (i = 1; i <= 4; i++)
+ s0 (i, 70 + i);
+ for (i = 1; i <= 4; i++)
+ s1 (i, 170 + i);
+ for (i = 1; i <= 4; i++)
+ s2 (i, 270 + i);
+ for (i = 1; i <= 4; i++)
+ s3 (i, 370 + i);
+ for (i = 1; i <= 4; i++)
+ s4 (i, 470 + i);
+ for (i = 1; i <= 4; i++)
+ s10 (i, 1070 + i);
+ for (i = 1; i <= 4; i++)
+ s11 (i, 1170 + i);
+
+ use_it (0); /* main-breakpoint-8 */
+
+ /* Unload lib3, lib1, and lib4. */
+ status = dlclose (h3);
+ assert (status == 0);
+ status = dlclose (h1);
+ assert (status == 0);
+ status = dlclose (h4);
+ assert (status == 0);
+
+ /* Set TLS vars; currently, lib2 is loaded; lib1, lib3, and lib4 are
+ not. */
+ for (i = 1; i <= 4; i++)
+ s0 (i, 80 + i);
+ for (i = 1; i <= 4; i++)
+ s2 (i, 280 + i);
+ for (i = 1; i <= 4; i++)
+ s10 (i, 1080 + i);
+ for (i = 1; i <= 4; i++)
+ s11 (i, 1180 + i);
+
+ use_it (0); /* main-breakpoint-9 */
+
+ /* Load lib3, unload lib2, load lib4. */
+ h3 = load_dso (OBJ3, 3, &s3);
+ status = dlclose (h2);
+ assert (status == 0);
+ h4 = load_dso (OBJ4, 4, &s4);
+
+ /* Set TLS vars; currently, lib3 and lib4 are loaded; lib1 and lib2
+ are not. */
+ for (i = 1; i <= 4; i++)
+ s0 (i, 90 + i);
+ for (i = 1; i <= 4; i++)
+ s3 (i, 390 + i);
+ for (i = 1; i <= 4; i++)
+ s4 (i, 490 + i);
+ for (i = 1; i <= 4; i++)
+ s10 (i, 1090 + i);
+ for (i = 1; i <= 4; i++)
+ s11 (i, 1190 + i);
+
+ use_it (0); /* main-breakpoint-10 */
+
+ /* Attempt to keep variables in the main program from being optimized
+ away. */
+ use_it (tls_main_tbss_1);
+ use_it (tls_main_tbss_2);
+ use_it (tls_main_tdata_1);
+ use_it (tls_main_tdata_2);
+
+ use_it (100); /* main-breakpoint-last */
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.base/tls-dlobj.exp b/gdb/testsuite/gdb.base/tls-dlobj.exp
new file mode 100644
index 0000000..02f2ff8
--- /dev/null
+++ b/gdb/testsuite/gdb.base/tls-dlobj.exp
@@ -0,0 +1,378 @@
+# Copyright 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.
+
+# Test that the GDB-internal TLS link map to module id mapping code
+# works correctly when debugging a program which is linked against
+# shared objects and which also loads and unloads other shared objects
+# in different orders. For targets which have GDB-internal TLS
+# support, it'll check both GDB-internal TLS support as well as that
+# provided by a helper library such as libthread_db.
+
+source $srcdir/$subdir/tls-common.exp.tcl
+
+require allow_shlib_tests
+
+standard_testfile
+
+set libsrc "${srcdir}/${subdir}/${testfile}-lib.c"
+
+# These will be dlopen'd:
+set lib1obj [standard_output_file "${testfile}1-lib.so"]
+set lib2obj [standard_output_file "${testfile}2-lib.so"]
+set lib3obj [standard_output_file "${testfile}3-lib.so"]
+set lib4obj [standard_output_file "${testfile}4-lib.so"]
+
+# These will be dynamically linked with the main program:
+set lib10obj [standard_output_file "${testfile}10-lib.so"]
+set lib11obj [standard_output_file "${testfile}11-lib.so"]
+
+# Due to problems with some versions of glibc, we expect some tests to
+# fail due to TLS storage not being allocated/initialized. Test
+# command CMD using regular expression RE, and use XFAIL instead of
+# FAIL when the relevant RE is matched and COND is true when evaluated
+# in the upper level.
+
+proc gdb_test_with_xfail { cmd re cond} {
+ gdb_test_multiple $cmd $cmd {
+ -re -wrap $re {
+ pass $gdb_test_name
+ }
+ -re -wrap "The inferior has not yet allocated storage for thread-local variables.*" {
+ if [ uplevel 1 [list expr $cond]] {
+ xfail $gdb_test_name
+ } else {
+ fail $gdb_test_name
+ }
+ }
+ }
+}
+
+proc do_tests {force_internal_tls} {
+ clean_restart $::binfile
+ if ![runto_main] {
+ return
+ }
+
+ if $force_internal_tls {
+ gdb_test_no_output "maint set force-internal-tls-address-lookup on"
+ }
+
+ gdb_breakpoint [gdb_get_line_number "main-breakpoint-1"]
+ gdb_continue_to_breakpoint "main-breakpoint-1"
+
+ with_test_prefix "before assignments" {
+ gdb_test "print tls_main_tbss_1" ".* = 0"
+ gdb_test "print tls_main_tbss_2" ".* = 0"
+ gdb_test "print tls_main_tdata_1" ".* = 96"
+ gdb_test "print tls_main_tdata_2" ".* = 97"
+
+ # For these tests, where we're attempting to access TLS vars
+ # in a dlopen'd library, but before assignment to any of the
+ # vars, so it could happen that storage hasn't been allocated
+ # yet. But it might also work. (When testing against MUSL,
+ # things just work; GLIBC ends to produce the TLS error.) So
+ # accept either the right answer or a TLS error message.
+
+ set tlserr "The inferior has not yet allocated storage for thread-local variables.*"
+ foreach n {1 2 3 4} {
+ gdb_test "print tls_lib${n}_tbss_1" \
+ "0|${tlserr}"
+ gdb_test "print tls_lib${n}_tbss_2" \
+ "0|${tlserr}"
+ gdb_test "print tls_lib${n}_tdata_1" \
+ "96|${tlserr}"
+ gdb_test "print tls_lib${n}_tdata_2" \
+ "97|${tlserr}"
+ }
+ foreach n {10 11} {
+ gdb_test "print tls_lib${n}_tbss_1" ".* = 0"
+ gdb_test "print tls_lib${n}_tbss_2" ".* = 0"
+ gdb_test "print tls_lib${n}_tdata_1" ".* = ${n}96"
+ gdb_test "print tls_lib${n}_tdata_2" ".* = ${n}97"
+ }
+ }
+
+ gdb_breakpoint [gdb_get_line_number "main-breakpoint-2"]
+ gdb_continue_to_breakpoint "main-breakpoint-2"
+
+ with_test_prefix "at main-breakpoint-2" {
+ gdb_test "print tls_main_tbss_1" ".* = 11"
+ gdb_test "print tls_main_tbss_2" ".* = 12"
+ gdb_test "print tls_main_tdata_1" ".* = 13"
+ gdb_test "print tls_main_tdata_2" ".* = 14"
+
+ foreach n {1 2 3 4 10 11} {
+ gdb_test "print tls_lib${n}_tbss_1" ".* = ${n}11"
+ gdb_test "print tls_lib${n}_tbss_2" ".* = ${n}12"
+ gdb_test "print tls_lib${n}_tdata_1" ".* = ${n}13"
+ gdb_test "print tls_lib${n}_tdata_2" ".* = ${n}14"
+ }
+ }
+
+ gdb_breakpoint [gdb_get_line_number "main-breakpoint-3"]
+ gdb_continue_to_breakpoint "main-breakpoint-3"
+
+ # At this point lib2 and lib3 have been unloaded. Also, TLS vars
+ # in remaining libraries have been changed.
+
+ with_test_prefix "at main-breakpoint-3" {
+ gdb_test "print tls_main_tbss_1" ".* = 21"
+ gdb_test "print tls_main_tbss_2" ".* = 22"
+ gdb_test "print tls_main_tdata_1" ".* = 23"
+ gdb_test "print tls_main_tdata_2" ".* = 24"
+
+ foreach n {1 4 10 11} {
+ gdb_test "print tls_lib${n}_tbss_1" ".* = ${n}21"
+ gdb_test "print tls_lib${n}_tbss_2" ".* = ${n}22"
+ gdb_test "print tls_lib${n}_tdata_1" ".* = ${n}23"
+ gdb_test "print tls_lib${n}_tdata_2" ".* = ${n}24"
+ }
+ }
+
+ gdb_breakpoint [gdb_get_line_number "main-breakpoint-4"]
+ gdb_continue_to_breakpoint "main-breakpoint-4"
+
+ # lib3 has been loaded again; lib2 is the only one not loaded.
+
+ with_test_prefix "at main-breakpoint-4" {
+ gdb_test "print tls_main_tbss_1" ".* = 31"
+ gdb_test "print tls_main_tbss_2" ".* = 32"
+ gdb_test "print tls_main_tdata_1" ".* = 33"
+ gdb_test "print tls_main_tdata_2" ".* = 34"
+
+ set cond { $n == 3 }
+ foreach n {1 3 4 10 11} {
+ gdb_test_with_xfail "print tls_lib${n}_tbss_1" ".* = ${n}31" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tbss_2" ".* = ${n}32" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tdata_1" ".* = ${n}33" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tdata_2" ".* = ${n}34" $cond
+ }
+ }
+
+ gdb_breakpoint [gdb_get_line_number "main-breakpoint-5"]
+ gdb_continue_to_breakpoint "main-breakpoint-5"
+
+ # lib2 and lib3 are loaded; lib1 and lib4 are not.
+
+ with_test_prefix "at main-breakpoint-5" {
+ gdb_test "print tls_main_tbss_1" ".* = 41"
+ gdb_test "print tls_main_tbss_2" ".* = 42"
+ gdb_test "print tls_main_tdata_1" ".* = 43"
+ gdb_test "print tls_main_tdata_2" ".* = 44"
+
+ set cond { $n == 2 || $n == 3 }
+ foreach n {2 3 10 11} {
+ gdb_test_with_xfail "print tls_lib${n}_tbss_1" ".* = ${n}41" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tbss_2" ".* = ${n}42" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tdata_1" ".* = ${n}43" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tdata_2" ".* = ${n}44" $cond
+ }
+ }
+
+ gdb_breakpoint [gdb_get_line_number "main-breakpoint-6"]
+ gdb_continue_to_breakpoint "main-breakpoint-6"
+
+ # lib1, lib3 and lib4 are loaded; lib2 is not loaded.
+
+ with_test_prefix "at main-breakpoint-6" {
+ gdb_test "print tls_main_tbss_1" ".* = 51"
+ gdb_test "print tls_main_tbss_2" ".* = 52"
+ gdb_test "print tls_main_tdata_1" ".* = 53"
+ gdb_test "print tls_main_tdata_2" ".* = 54"
+
+ set cond { $n == 1 || $n == 3 || $n == 4}
+ foreach n {1 3 4 10 11} {
+ gdb_test_with_xfail "print tls_lib${n}_tbss_1" ".* = ${n}51" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tbss_2" ".* = ${n}52" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tdata_1" ".* = ${n}53" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tdata_2" ".* = ${n}54" $cond
+ }
+ }
+
+ gdb_breakpoint [gdb_get_line_number "main-breakpoint-7"]
+ gdb_continue_to_breakpoint "main-breakpoint-7"
+
+ # lib2 and lib3 are loaded; lib1 and lib4 are not.
+
+ with_test_prefix "at main-breakpoint-7" {
+ gdb_test "print tls_main_tbss_1" ".* = 61"
+ gdb_test "print tls_main_tbss_2" ".* = 62"
+ gdb_test "print tls_main_tdata_1" ".* = 63"
+ gdb_test "print tls_main_tdata_2" ".* = 64"
+
+ set cond { $n == 2 || $n == 3 }
+ foreach n {2 3 10 11} {
+ gdb_test_with_xfail "print tls_lib${n}_tbss_1" ".* = ${n}61" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tbss_2" ".* = ${n}62" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tdata_1" ".* = ${n}63" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tdata_2" ".* = ${n}64" $cond
+ }
+ }
+
+ gdb_breakpoint [gdb_get_line_number "main-breakpoint-8"]
+ gdb_continue_to_breakpoint "main-breakpoint-8"
+
+ # lib1, lib2, lib3, and lib4 are all loaded.
+
+ with_test_prefix "at main-breakpoint-8" {
+ gdb_test "print tls_main_tbss_1" ".* = 71"
+ gdb_test "print tls_main_tbss_2" ".* = 72"
+ gdb_test "print tls_main_tdata_1" ".* = 73"
+ gdb_test "print tls_main_tdata_2" ".* = 74"
+
+ foreach n {1 2 3 4 10 11} {
+ gdb_test "print tls_lib${n}_tbss_1" ".* = ${n}71"
+ gdb_test "print tls_lib${n}_tbss_2" ".* = ${n}72"
+ gdb_test "print tls_lib${n}_tdata_1" ".* = ${n}73"
+ gdb_test "print tls_lib${n}_tdata_2" ".* = ${n}74"
+ }
+ }
+
+ gdb_breakpoint [gdb_get_line_number "main-breakpoint-9"]
+ gdb_continue_to_breakpoint "main-breakpoint-9"
+
+ # lib2 is loaded; lib1, lib3, and lib4 are not.
+
+ with_test_prefix "at main-breakpoint-9" {
+ gdb_test "print tls_main_tbss_1" ".* = 81"
+ gdb_test "print tls_main_tbss_2" ".* = 82"
+ gdb_test "print tls_main_tdata_1" ".* = 83"
+ gdb_test "print tls_main_tdata_2" ".* = 84"
+
+ foreach n {2 10 11} {
+ gdb_test "print tls_lib${n}_tbss_1" ".* = ${n}81"
+ gdb_test "print tls_lib${n}_tbss_2" ".* = ${n}82"
+ gdb_test "print tls_lib${n}_tdata_1" ".* = ${n}83"
+ gdb_test "print tls_lib${n}_tdata_2" ".* = ${n}84"
+ }
+ }
+
+ gdb_breakpoint [gdb_get_line_number "main-breakpoint-10"]
+ gdb_continue_to_breakpoint "main-breakpoint-10"
+
+ # lib3 and lib4 are loaded; lib1 and lib2 are not.
+
+ with_test_prefix "at main-breakpoint-10" {
+ gdb_test "print tls_main_tbss_1" ".* = 91"
+ gdb_test "print tls_main_tbss_2" ".* = 92"
+ gdb_test "print tls_main_tdata_1" ".* = 93"
+ gdb_test "print tls_main_tdata_2" ".* = 94"
+
+ set cond { $n == 3 || $n == 4 }
+ foreach n {3 4 10 11} {
+ gdb_test_with_xfail "print tls_lib${n}_tbss_1" ".* = ${n}91" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tbss_2" ".* = ${n}92" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tdata_1" ".* = ${n}93" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tdata_2" ".* = ${n}94" $cond
+ }
+ }
+
+ # gdb_interact
+
+ set corefile ${::binfile}.core
+ set core_supported 0
+ if { ![is_remote host] } {
+ set core_supported [gdb_gcore_cmd $corefile "save corefile"]
+ }
+
+ # Finish test early if no core file was made.
+ if !$core_supported {
+ return
+ }
+
+ clean_restart $::binfile
+
+ set core_loaded [gdb_core_cmd $corefile "load corefile"]
+ if { $core_loaded == -1 } {
+ return
+ }
+
+ with_test_prefix "core file" {
+ if $force_internal_tls {
+ gdb_test_no_output "maint set force-internal-tls-address-lookup on"
+ }
+
+ gdb_test "print tls_main_tbss_1" ".* = 91"
+ gdb_test "print tls_main_tbss_2" ".* = 92"
+ gdb_test "print tls_main_tdata_1" ".* = 93"
+ gdb_test "print tls_main_tdata_2" ".* = 94"
+
+ set cond { $n == 3 || $n == 4 }
+ foreach n {3 4 10 11} {
+ gdb_test_with_xfail "print tls_lib${n}_tbss_1" ".* = ${n}91" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tbss_2" ".* = ${n}92" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tdata_1" ".* = ${n}93" $cond
+ gdb_test_with_xfail "print tls_lib${n}_tdata_2" ".* = ${n}94" $cond
+ }
+ }
+}
+
+# Build shared objects for dlopen:
+if { [gdb_compile_shlib $libsrc $lib1obj [list debug additional_flags=-DN=1]] != "" } {
+ untested "failed to compile shared object"
+ return -1
+}
+if { [gdb_compile_shlib $libsrc $lib2obj [list debug additional_flags=-DN=2]] != "" } {
+ untested "failed to compile shared object"
+ return -1
+}
+if { [gdb_compile_shlib $libsrc $lib3obj [list debug additional_flags=-DN=3]] != "" } {
+ untested "failed to compile shared object"
+ return -1
+}
+if { [gdb_compile_shlib $libsrc $lib4obj [list debug additional_flags=-DN=4]] != "" } {
+ untested "failed to compile shared object"
+ return -1
+}
+
+# Build shared objects to link against main program:
+if { [gdb_compile_shlib $libsrc $lib10obj [list debug additional_flags=-DN=10]] != "" } {
+ untested "failed to compile shared object"
+ return -1
+}
+if { [gdb_compile_shlib $libsrc $lib11obj [list debug additional_flags=-DN=11]] != "" } {
+ untested "failed to compile shared object"
+ return -1
+}
+
+# Use gdb_compile_pthreads to build and link the main program for
+# testing. It's also possible to run the tests using plain old
+# gdb_compile, but this adds complexity with setting up additional
+# KFAILs. (When run using GLIBC versions earlier than 2.34, a program
+# that's not dynamically linked against libpthread will lack a working
+# libthread_db, and, therefore, won't be able to access thread local
+# storage without GDB-internal TLS support. Additional complications
+# arise from when testing on x86_64 with -m32, which tends to work
+# okay on GLIBC 2.34 and newer, but not older versions. It gets messy
+# to properly sort out all of these cases.)
+#
+# This test was originally written to do it both ways, i.e. with both
+# both gdb_compile and gdb_compile_pthreads, but the point of this
+# test is to check that the link map address to TLS module id mapping
+# code works correctly in programs which use lots of dlopen and
+# dlclose calls in various orders - and that can be done using just
+# gdb_compile_pthreads.
+
+if { [gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable \
+ [list debug shlib_load \
+ shlib=${lib10obj} \
+ shlib=${lib11obj} \
+ additional_flags=-DOBJ1=\"${lib1obj}\" \
+ additional_flags=-DOBJ2=\"${lib2obj}\" \
+ additional_flags=-DOBJ3=\"${lib3obj}\" \
+ additional_flags=-DOBJ4=\"${lib4obj}\" \
+ ]] != "" } {
+ untested "failed to compile"
+} else {
+ foreach_with_prefix force_internal_tls $internal_tls_iters {
+ do_tests $force_internal_tls
+ }
+}
diff --git a/gdb/testsuite/gdb.base/tls-multiobj.c b/gdb/testsuite/gdb.base/tls-multiobj.c
new file mode 100644
index 0000000..10e67da
--- /dev/null
+++ b/gdb/testsuite/gdb.base/tls-multiobj.c
@@ -0,0 +1,89 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 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 <http://www.gnu.org/licenses/>. */
+
+__thread int tls_main_tbss_1;
+__thread int tls_main_tbss_2;
+__thread int tls_main_tdata_1 = 96;
+__thread int tls_main_tdata_2 = 97;
+
+extern __thread int tls_lib1_tbss_1;
+extern __thread int tls_lib1_tbss_2;
+extern __thread int tls_lib1_tdata_1;
+extern __thread int tls_lib1_tdata_2;
+
+extern __thread int tls_lib2_tbss_1;
+extern __thread int tls_lib2_tbss_2;
+extern __thread int tls_lib2_tdata_1;
+extern __thread int tls_lib2_tdata_2;
+
+extern __thread int tls_lib3_tbss_1;
+extern __thread int tls_lib3_tbss_2;
+extern __thread int tls_lib3_tdata_1;
+extern __thread int tls_lib3_tdata_2;
+
+extern void lib1_func ();
+extern void lib2_func ();
+extern void lib3_func ();
+
+volatile int data;
+
+void
+use_it (int a)
+{
+ data = a;
+}
+
+int
+main (int argc, char **argv)
+{
+ use_it (-1);
+
+ tls_main_tbss_1 = 51; /* main-breakpoint-1 */
+ tls_main_tbss_2 = 52;
+ tls_main_tdata_1 = 53;
+ tls_main_tdata_2 = 54;
+
+ tls_lib1_tbss_1 = 151;
+ tls_lib1_tbss_2 = 152;
+ tls_lib1_tdata_1 = 153;
+ tls_lib1_tdata_2 = 154;
+
+ tls_lib2_tbss_1 = 251;
+ tls_lib2_tbss_2 = 252;
+ tls_lib2_tdata_1 = 253;
+ tls_lib2_tdata_2 = 254;
+
+ tls_lib3_tbss_1 = 351;
+ tls_lib3_tbss_2 = 352;
+ tls_lib3_tdata_1 = 353;
+ tls_lib3_tdata_2 = 354;
+
+ lib1_func ();
+ lib2_func ();
+ lib3_func ();
+
+ /* Attempt to keep variables in the main program from being optimized
+ away. */
+ use_it (tls_main_tbss_1);
+ use_it (tls_main_tbss_2);
+ use_it (tls_main_tdata_1);
+ use_it (tls_main_tdata_2);
+
+ use_it (100); /* main-breakpoint-2 */
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.base/tls-multiobj.exp b/gdb/testsuite/gdb.base/tls-multiobj.exp
new file mode 100644
index 0000000..97acb33
--- /dev/null
+++ b/gdb/testsuite/gdb.base/tls-multiobj.exp
@@ -0,0 +1,230 @@
+# Copyright 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.
+
+# Using different compilation/linking scenarios, attempt to access
+# thread-local variables in a non-threaded program using multiple
+# shared objects.
+
+source $srcdir/$subdir/tls-common.exp.tcl
+
+standard_testfile
+
+set lib1src "${srcdir}/${subdir}/${testfile}1.c"
+set lib2src "${srcdir}/${subdir}/${testfile}2.c"
+set lib3src "${srcdir}/${subdir}/${testfile}3.c"
+
+set lib1obj [standard_output_file "${testfile}1-lib.so"]
+set lib2obj [standard_output_file "${testfile}2-lib.so"]
+set lib3obj [standard_output_file "${testfile}3-lib.so"]
+
+proc do_tests {force_internal_tls {do_kfail_tls_access 0}} {
+ clean_restart $::binfile
+ if ![runto_main] {
+ return
+ }
+
+ if $force_internal_tls {
+ gdb_test_no_output "maint set force-internal-tls-address-lookup on"
+ }
+
+ if { $do_kfail_tls_access && [istarget "*-*-linux*"] } {
+ # Turn off do_kfail_tls_access when libthread_db is loaded.
+ # This can happen for the default case when testing x86_64
+ # w/ -m32 using glibc versions 2.34 or newer.
+ gdb_test_multiple "maint check libthread-db" "Check for loaded libthread_db" {
+ -re -wrap "libthread_db integrity checks passed." {
+ set do_kfail_tls_access 0
+ pass $gdb_test_name
+ }
+ -re -wrap "No libthread_db loaded" {
+ pass $gdb_test_name
+ }
+ }
+ # Also turn off do_kfail_tls_access when connected to a
+ # gdbserver and we observe that accessing a TLS variable
+ # works.
+ if [target_is_gdbserver] {
+ gdb_test_multiple "print tls_main_tbss_1" \
+ "Check TLS accessibility when connected to a gdbserver" {
+ -re -wrap "= 0" {
+ set do_kfail_tls_access 0
+ pass $gdb_test_name
+ }
+ -re -wrap "Remote target failed to process qGetTLSAddr request" {
+ pass $gdb_test_name
+ }
+ }
+ }
+ }
+
+ gdb_breakpoint [gdb_get_line_number "main-breakpoint-1"]
+ gdb_continue_to_breakpoint "main-breakpoint-1"
+
+ set t $do_kfail_tls_access
+ set m "tls not available"
+ with_test_prefix "before assignments" {
+ gdb_test_with_kfail "print tls_main_tbss_1" ".* = 0" $t $m
+ gdb_test_with_kfail "print tls_main_tbss_2" ".* = 0" $t $m
+ gdb_test_with_kfail "print tls_main_tdata_1" ".* = 96" $t $m
+ gdb_test_with_kfail "print tls_main_tdata_2" ".* = 97" $t $m
+
+ gdb_test_with_kfail "print tls_lib1_tbss_1" ".* = 0" $t $m
+ gdb_test_with_kfail "print tls_lib1_tbss_2" ".* = 0" $t $m
+ gdb_test_with_kfail "print tls_lib1_tdata_1" ".* = 196" $t $m
+ gdb_test_with_kfail "print tls_lib1_tdata_2" ".* = 197" $t $m
+
+ gdb_test_with_kfail "print tls_lib2_tbss_1" ".* = 0" $t $m
+ gdb_test_with_kfail "print tls_lib2_tbss_2" ".* = 0" $t $m
+ gdb_test_with_kfail "print tls_lib2_tdata_1" ".* = 296" $t $m
+ gdb_test_with_kfail "print tls_lib2_tdata_2" ".* = 297" $t $m
+
+ gdb_test_with_kfail "print tls_lib3_tbss_1" ".* = 0" $t $m
+ gdb_test_with_kfail "print tls_lib3_tbss_2" ".* = 0" $t $m
+ gdb_test_with_kfail "print tls_lib3_tdata_1" ".* = 396" $t $m
+ gdb_test_with_kfail "print tls_lib3_tdata_2" ".* = 397" $t $m
+ }
+
+ gdb_breakpoint [gdb_get_line_number "main-breakpoint-2"]
+ gdb_continue_to_breakpoint "main-breakpoint-2"
+
+ with_test_prefix "after assignments" {
+ gdb_test_with_kfail "print tls_main_tbss_1" ".* = 51" $t $m
+ gdb_test_with_kfail "print tls_main_tbss_2" ".* = 52" $t $m
+ gdb_test_with_kfail "print tls_main_tdata_1" ".* = 53" $t $m
+ gdb_test_with_kfail "print tls_main_tdata_2" ".* = 54" $t $m
+
+ gdb_test_with_kfail "print tls_lib1_tbss_1" ".* = 151" $t $m
+ gdb_test_with_kfail "print tls_lib1_tbss_2" ".* = 152" $t $m
+ gdb_test_with_kfail "print tls_lib1_tdata_1" ".* = 153" $t $m
+ gdb_test_with_kfail "print tls_lib1_tdata_2" ".* = 154" $t $m
+
+ gdb_test_with_kfail "print tls_lib2_tbss_1" ".* = 251" $t $m
+ gdb_test_with_kfail "print tls_lib2_tbss_2" ".* = 252" $t $m
+ gdb_test_with_kfail "print tls_lib2_tdata_1" ".* = 253" $t $m
+ gdb_test_with_kfail "print tls_lib2_tdata_2" ".* = 254" $t $m
+
+ gdb_test_with_kfail "print tls_lib3_tbss_1" ".* = 351" $t $m
+ gdb_test_with_kfail "print tls_lib3_tbss_2" ".* = 352" $t $m
+ gdb_test_with_kfail "print tls_lib3_tdata_1" ".* = 353" $t $m
+ gdb_test_with_kfail "print tls_lib3_tdata_2" ".* = 354" $t $m
+ }
+
+ set corefile ${::binfile}.core
+ set core_supported 0
+ if { ![is_remote host] } {
+ set core_supported [gdb_gcore_cmd $corefile "save corefile"]
+ }
+
+ # Finish test early if no core file was made.
+ if !$core_supported {
+ return
+ }
+
+ clean_restart $::binfile
+
+ set core_loaded [gdb_core_cmd $corefile "load corefile"]
+ if { $core_loaded == -1 } {
+ return
+ }
+
+ with_test_prefix "core file" {
+ if $force_internal_tls {
+ gdb_test_no_output "maint set force-internal-tls-address-lookup on"
+ }
+
+ gdb_test_with_kfail "print tls_main_tbss_1" ".* = 51" $t $m
+ gdb_test_with_kfail "print tls_main_tbss_2" ".* = 52" $t $m
+ gdb_test_with_kfail "print tls_main_tdata_1" ".* = 53" $t $m
+ gdb_test_with_kfail "print tls_main_tdata_2" ".* = 54" $t $m
+
+ gdb_test_with_kfail "print tls_lib1_tbss_1" ".* = 151" $t $m
+ gdb_test_with_kfail "print tls_lib1_tbss_2" ".* = 152" $t $m
+ gdb_test_with_kfail "print tls_lib1_tdata_1" ".* = 153" $t $m
+ gdb_test_with_kfail "print tls_lib1_tdata_2" ".* = 154" $t $m
+
+ gdb_test_with_kfail "print tls_lib2_tbss_1" ".* = 251" $t $m
+ gdb_test_with_kfail "print tls_lib2_tbss_2" ".* = 252" $t $m
+ gdb_test_with_kfail "print tls_lib2_tdata_1" ".* = 253" $t $m
+ gdb_test_with_kfail "print tls_lib2_tdata_2" ".* = 254" $t $m
+
+ gdb_test_with_kfail "print tls_lib3_tbss_1" ".* = 351" $t $m
+ gdb_test_with_kfail "print tls_lib3_tbss_2" ".* = 352" $t $m
+ gdb_test_with_kfail "print tls_lib3_tdata_1" ".* = 353" $t $m
+ gdb_test_with_kfail "print tls_lib3_tdata_2" ".* = 354" $t $m
+ }
+}
+
+if { [gdb_compile_shlib $lib1src $lib1obj {debug}] != "" } {
+ untested "failed to compile shared object"
+ return -1
+}
+if { [gdb_compile_shlib $lib2src $lib2obj {debug}] != "" } {
+ untested "failed to compile shared object"
+ return -1
+}
+if { [gdb_compile_shlib $lib3src $lib3obj {debug}] != "" } {
+ untested "failed to compile shared object"
+ return -1
+}
+
+# Certain linux target architectures implement support for internal
+# TLS lookup which is used when thread stratum support (via
+# libthread_db) is missing or when the linux-only GDB maintenance
+# setting 'force-internal-tls-address-lookup' is 'on'. Thus for some
+# of the testing scenarios, such as statically linked executables,
+# this internal support will be used. Set 'do_kfail_tls_access' to 1
+# for those architectures which don't implement internal tls support.
+if {[istarget *-*-linux*]
+ && ![is_any_target {*}$internal_tls_linux_targets]} {
+ set do_kfail_tls_access 1
+} elseif {[istarget *-*-linux*] && [is_x86_like_target]} {
+ # This covers the case of x86_64 with -m32:
+ set do_kfail_tls_access 1
+} else {
+ set do_kfail_tls_access 0
+}
+
+set binprefix $binfile
+
+with_test_prefix "default" {
+ set binfile $binprefix-default
+ if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable \
+ [list debug shlib=${lib1obj} \
+ shlib=${lib2obj} \
+ shlib=${lib3obj}]] != "" } {
+ untested "failed to compile"
+ } else {
+ foreach_with_prefix force_internal_tls $internal_tls_iters {
+ # Depending on glibc version, it might not be appropriate
+ # for do_kfail_tls_access to be set here. That will be
+ # handled in 'do_tests', disabling it if necessary.
+ #
+ # Specifically, glibc versions 2.34 and later have the
+ # thread library (and libthread_db availability) in
+ # programs not linked against libpthread.so
+ do_tests $force_internal_tls $do_kfail_tls_access
+ }
+ }
+}
+
+with_test_prefix "pthreads" {
+ set binfile $binprefix-pthreads
+ if { [gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable \
+ [list debug shlib=${lib1obj} \
+ shlib=${lib2obj} \
+ shlib=${lib3obj}]] != "" } {
+ untested "failed to compile"
+ } else {
+ foreach_with_prefix force_internal_tls $internal_tls_iters {
+ do_tests $force_internal_tls
+ }
+ }
+}
diff --git a/gdb/testsuite/gdb.base/tls-multiobj1.c b/gdb/testsuite/gdb.base/tls-multiobj1.c
new file mode 100644
index 0000000..86e7222
--- /dev/null
+++ b/gdb/testsuite/gdb.base/tls-multiobj1.c
@@ -0,0 +1,26 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 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 <http://www.gnu.org/licenses/>. */
+
+__thread int tls_lib1_tbss_1;
+__thread int tls_lib1_tbss_2;
+__thread int tls_lib1_tdata_1 = 196;
+__thread int tls_lib1_tdata_2 = 197;
+
+void
+lib1_func ()
+{
+}
diff --git a/gdb/testsuite/gdb.base/tls-multiobj2.c b/gdb/testsuite/gdb.base/tls-multiobj2.c
new file mode 100644
index 0000000..cea0709
--- /dev/null
+++ b/gdb/testsuite/gdb.base/tls-multiobj2.c
@@ -0,0 +1,26 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 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 <http://www.gnu.org/licenses/>. */
+
+__thread int tls_lib2_tbss_1;
+__thread int tls_lib2_tbss_2;
+__thread int tls_lib2_tdata_1 = 296;
+__thread int tls_lib2_tdata_2 = 297;
+
+void
+lib2_func ()
+{
+}
diff --git a/gdb/testsuite/gdb.base/tls-multiobj3.c b/gdb/testsuite/gdb.base/tls-multiobj3.c
new file mode 100644
index 0000000..bb0f239
--- /dev/null
+++ b/gdb/testsuite/gdb.base/tls-multiobj3.c
@@ -0,0 +1,26 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 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 <http://www.gnu.org/licenses/>. */
+
+__thread int tls_lib3_tbss_1;
+__thread int tls_lib3_tbss_2;
+__thread int tls_lib3_tdata_1 = 396;
+__thread int tls_lib3_tdata_2 = 397;
+
+void
+lib3_func ()
+{
+}
diff --git a/gdb/testsuite/gdb.base/tls-nothreads.c b/gdb/testsuite/gdb.base/tls-nothreads.c
new file mode 100644
index 0000000..b3aaa33
--- /dev/null
+++ b/gdb/testsuite/gdb.base/tls-nothreads.c
@@ -0,0 +1,57 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 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 <http://www.gnu.org/licenses/>. */
+
+__thread int tls_tbss_1;
+__thread int tls_tbss_2;
+__thread int tls_tbss_3;
+
+__thread int tls_tdata_1 = 21;
+__thread int tls_tdata_2 = 22;
+__thread int tls_tdata_3 = 23;
+
+volatile int data;
+
+void
+use_it (int a)
+{
+ data = a;
+}
+
+int
+main (int argc, char **argv)
+{
+ use_it (-1);
+
+ tls_tbss_1 = 24; /* main-breakpoint-1 */
+ tls_tbss_2 = 25;
+ tls_tbss_3 = 26;
+
+ tls_tdata_1 = 42;
+ tls_tdata_2 = 43;
+ tls_tdata_3 = 44;
+
+ use_it (tls_tbss_1);
+ use_it (tls_tbss_2);
+ use_it (tls_tbss_3);
+ use_it (tls_tdata_1);
+ use_it (tls_tdata_2);
+ use_it (tls_tdata_3);
+
+ use_it (100); /* main-breakpoint-2 */
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.base/tls-nothreads.exp b/gdb/testsuite/gdb.base/tls-nothreads.exp
new file mode 100644
index 0000000..92a5cd9
--- /dev/null
+++ b/gdb/testsuite/gdb.base/tls-nothreads.exp
@@ -0,0 +1,248 @@
+# Copyright 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.
+
+# Using different compilation/linking scenarios, attempt to access
+# thread-local variables in a non-threaded program. Also test that
+# GDB internal TLS lookup works correctly.
+
+source $srcdir/$subdir/tls-common.exp.tcl
+
+standard_testfile
+
+proc do_tests {force_internal_tls {do_kfail_tls_access 0}} {
+ clean_restart $::binfile
+ if ![runto_main] {
+ return
+ }
+
+ if $force_internal_tls {
+ gdb_test_no_output "maint set force-internal-tls-address-lookup on"
+ }
+
+ if { $do_kfail_tls_access && [istarget "*-*-linux*"] } {
+ # Turn off do_kfail_tls_access when libthread_db is loaded.
+ # This can happen for the default case when testing x86_64
+ # w/ -m32 using glibc versions 2.34 or newer.
+ gdb_test_multiple "maint check libthread-db" "Check for loaded libthread_db" {
+ -re -wrap "libthread_db integrity checks passed." {
+ set do_kfail_tls_access 0
+ pass $gdb_test_name
+ }
+ -re -wrap "No libthread_db loaded" {
+ pass $gdb_test_name
+ }
+ }
+ # Also turn off do_kfail_tls_access when connected to a
+ # gdbserver and we observe that accessing a TLS variable
+ # works.
+ if [target_is_gdbserver] {
+ gdb_test_multiple "print tls_tbss_1" "Check TLS accessibility when connected to a gdbserver" {
+ -re -wrap "= 0" {
+ set do_kfail_tls_access 0
+ pass $gdb_test_name
+ }
+ -re -wrap "Remote target failed to process qGetTLSAddr request" {
+ pass $gdb_test_name
+ }
+ }
+ }
+ }
+
+ gdb_breakpoint [gdb_get_line_number "main-breakpoint-1"]
+ gdb_continue_to_breakpoint "main-breakpoint-1"
+
+ set t $do_kfail_tls_access
+ set m "tls not available"
+ with_test_prefix "before assignments" {
+ gdb_test_with_kfail "print tls_tbss_1" ".* = 0" $t $m
+ gdb_test_with_kfail "print tls_tbss_2" ".* = 0" $t $m
+ gdb_test_with_kfail "print tls_tbss_3" ".* = 0" $t $m
+
+ gdb_test_with_kfail "print tls_tdata_1" ".* = 21" $t $m
+ gdb_test_with_kfail "print tls_tdata_2" ".* = 22" $t $m
+ gdb_test_with_kfail "print tls_tdata_3" ".* = 23" $t $m
+ }
+
+ gdb_breakpoint [gdb_get_line_number "main-breakpoint-2"]
+ gdb_continue_to_breakpoint "main-breakpoint-2"
+
+ with_test_prefix "after assignments" {
+ gdb_test_with_kfail "print tls_tbss_1" ".* = 24" $t $m
+ gdb_test_with_kfail "print tls_tbss_2" ".* = 25" $t $m
+ gdb_test_with_kfail "print tls_tbss_3" ".* = 26" $t $m
+
+ gdb_test_with_kfail "print tls_tdata_1" ".* = 42" $t $m
+ gdb_test_with_kfail "print tls_tdata_2" ".* = 43" $t $m
+ gdb_test_with_kfail "print tls_tdata_3" ".* = 44" $t $m
+ }
+
+ # Make a core file now, but save testing using it until the end
+ # in case core files are not supported.
+ set corefile ${::binfile}.core
+ set core_supported 0
+ if { ![is_remote host] } {
+ set core_supported [gdb_gcore_cmd $corefile "save corefile"]
+ }
+
+ # Now continue to end and see what happens when attempting to
+ # access a TLS variable when the program is no longer running.
+ gdb_continue_to_end
+ with_test_prefix "after exit" {
+ gdb_test "print tls_tbss_1" \
+ "Cannot (?:read|find address of TLS symbol) `tls_tbss_1' without registers"
+ }
+
+ with_test_prefix "stripped" {
+ set binfile_stripped "${::binfile}.stripped"
+ set objcopy [gdb_find_objcopy]
+ set cmd "$objcopy --strip-debug ${::binfile} $binfile_stripped"
+ if ![catch "exec $cmd" cmd_output] {
+ clean_restart $binfile_stripped
+ if ![runto_main] {
+ return
+ }
+
+ if $force_internal_tls {
+ gdb_test_no_output "maint set force-internal-tls-address-lookup on"
+ }
+
+ # While there are no debug (e.g. DWARF) symbols, there
+ # are minimal symbols, so we should be able to place a
+ # breakpoint in use_it and continue to it. Continuing
+ # twice should put us past the assignments, at which point
+ # we can see if the TLS variables are still accessible.
+ gdb_test "break use_it" "Breakpoint 2 at $::hex"
+ gdb_test "continue" "Breakpoint 2, $::hex in use_it.*"
+ gdb_test "continue" "Breakpoint 2, $::hex in use_it.*" "continue 2"
+
+ # Note that a cast has been added in order to avoid the
+ # "...has unknown type; cast it to its declared type"
+ # problem.
+ gdb_test_with_kfail "print (int) tls_tbss_1" ".* = 24" $t $m
+ gdb_test_with_kfail "print (int) tls_tbss_2" ".* = 25" $t $m
+ gdb_test_with_kfail "print (int) tls_tbss_3" ".* = 26" $t $m
+
+ gdb_test_with_kfail "print (int) tls_tdata_1" ".* = 42" $t $m
+ gdb_test_with_kfail "print (int) tls_tdata_2" ".* = 43" $t $m
+ gdb_test_with_kfail "print (int) tls_tdata_3" ".* = 44" $t $m
+
+ # Get rid of the "use_it" breakpoint
+ gdb_test_no_output "del 2"
+
+ # Continue to program exit
+ gdb_continue_to_end
+
+ # TLS variables should not be accessible after program exit
+ # (This case initially caused GDB to crash during development
+ # of GDB-internal TLS lookup support.)
+ with_test_prefix "after exit" {
+ gdb_test "print (int) tls_tbss_1" \
+ "Cannot find address of TLS symbol `tls_tbss_1' without registers"
+ }
+ }
+ }
+
+ # Finish test early if no core file was made.
+ if !$core_supported {
+ return
+ }
+
+ clean_restart $::binfile
+
+ set core_loaded [gdb_core_cmd $corefile "load corefile"]
+ if { $core_loaded == -1 } {
+ return
+ }
+
+ with_test_prefix "core file" {
+ if $force_internal_tls {
+ gdb_test_no_output "maint set force-internal-tls-address-lookup on"
+ }
+
+ gdb_test_with_kfail "print tls_tbss_1" ".* = 24" $t $m
+ gdb_test_with_kfail "print tls_tbss_2" ".* = 25" $t $m
+ gdb_test_with_kfail "print tls_tbss_3" ".* = 26" $t $m
+
+ gdb_test_with_kfail "print tls_tdata_1" ".* = 42" $t $m
+ gdb_test_with_kfail "print tls_tdata_2" ".* = 43" $t $m
+ gdb_test_with_kfail "print tls_tdata_3" ".* = 44" $t $m
+ }
+}
+
+# Certain linux target architectures implement support for internal
+# TLS lookup which is used when thread stratum support (via
+# libthread_db) is missing or when the linux-only GDB maintenance
+# setting 'force-internal-tls-address-lookup' is 'on'. Thus for some
+# of the testing scenarios, such as statically linked executables,
+# this internal support will be used. Set 'do_kfail_tls_access' to 1
+# for those architectures which don't implement internal TLS support.
+if {[istarget *-*-linux*]
+ && ![is_any_target {*}$internal_tls_linux_targets]} {
+ set do_kfail_tls_access 1
+} elseif {[istarget *-*-linux*] && [is_x86_like_target]} {
+ # This covers the case of x86_64 with -m32:
+ set do_kfail_tls_access 1
+} else {
+ set do_kfail_tls_access 0
+}
+
+set binprefix $binfile
+
+with_test_prefix "default" {
+ set binfile $binprefix-default
+ if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+ untested "failed to compile"
+ } else {
+ foreach_with_prefix force_internal_tls $internal_tls_iters {
+ # Depending on glibc version, it might not be appropriate
+ # for do_kfail_tls_access to be set here. That will be
+ # handled in 'do_tests', disabling it if necessary.
+ #
+ # Specifically, glibc versions 2.34 and later have the
+ # thread library (and libthread_db availability) in
+ # programs not linked against libpthread.so
+ do_tests $force_internal_tls $do_kfail_tls_access
+ }
+ }
+}
+
+with_test_prefix "static" {
+ set binfile $binprefix-static
+ if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug "additional_flags=-static"}] != "" } {
+ untested "failed to compile"
+ } else {
+ foreach_with_prefix force_internal_tls $internal_tls_iters {
+ do_tests $force_internal_tls $do_kfail_tls_access
+ }
+ }
+}
+
+with_test_prefix "pthreads" {
+ set binfile $binprefix-pthreads
+ if { [gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+ untested "failed to compile"
+ } else {
+ foreach_with_prefix force_internal_tls $internal_tls_iters {
+ do_tests $force_internal_tls
+ }
+ }
+}
+
+with_test_prefix "pthreads-static" {
+ set binfile $binprefix-pthreads-static
+ if { [gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug "additional_flags=-static"}] != "" } {
+ untested "failed to compile"
+ } else {
+ foreach_with_prefix force_internal_tls $internal_tls_iters {
+ do_tests $force_internal_tls $do_kfail_tls_access
+ }
+ }
+}
diff --git a/gdb/testsuite/gdb.python/gdb_leak_detector.py b/gdb/testsuite/gdb.python/gdb_leak_detector.py
index b0f6d47..8f74b67 100644
--- a/gdb/testsuite/gdb.python/gdb_leak_detector.py
+++ b/gdb/testsuite/gdb.python/gdb_leak_detector.py
@@ -19,6 +19,7 @@
import os
import tracemalloc
+
import gdb
diff --git a/gdb/testsuite/gdb.python/py-inferior-leak.py b/gdb/testsuite/gdb.python/py-inferior-leak.py
index 38f33c3..f764688 100644
--- a/gdb/testsuite/gdb.python/py-inferior-leak.py
+++ b/gdb/testsuite/gdb.python/py-inferior-leak.py
@@ -14,6 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
+
import gdb_leak_detector
diff --git a/gdb/testsuite/gdb.server/no-thread-db.exp b/gdb/testsuite/gdb.server/no-thread-db.exp
index cc24708..9fd2090 100644
--- a/gdb/testsuite/gdb.server/no-thread-db.exp
+++ b/gdb/testsuite/gdb.server/no-thread-db.exp
@@ -57,6 +57,8 @@ gdb_breakpoint ${srcfile}:[gdb_get_line_number "after tls assignment"]
gdb_continue_to_breakpoint "after tls assignment"
# Printing a tls variable should fail gracefully without a libthread_db.
+# Alternately, the correct answer might be printed due GDB's internal
+# TLS support for some targets.
set re_exec "\[^\r\n\]*[file tail $binfile]"
gdb_test "print foo" \
- "Cannot find thread-local storage for Thread \[^,\]+, executable file $re_exec:\[\r\n\]+Remote target failed to process qGetTLSAddr request"
+ "= 1|(?:Cannot find thread-local storage for Thread \[^,\]+, executable file $re_exec:\[\r\n\]+Remote target failed to process qGetTLSAddr request)"
diff --git a/gdb/testsuite/gdb.threads/tls.exp b/gdb/testsuite/gdb.threads/tls.exp
index 1bc5df2..73fada7 100644
--- a/gdb/testsuite/gdb.threads/tls.exp
+++ b/gdb/testsuite/gdb.threads/tls.exp
@@ -159,7 +159,7 @@ gdb_test_multiple "print a_thread_local" "" {
-re -wrap "Cannot find thread-local variables on this target" {
kfail "gdb/25807" $gdb_test_name
}
- -re -wrap "Cannot read .a_thread_local. without registers" {
+ -re -wrap "Cannot (?:read|find address of TLS symbol) .a_thread_local. without registers" {
pass $gdb_test_name
}
}
diff --git a/gdb/unittests/remote-arg-selftests.c b/gdb/unittests/remote-arg-selftests.c
new file mode 100644
index 0000000..70f8a39
--- /dev/null
+++ b/gdb/unittests/remote-arg-selftests.c
@@ -0,0 +1,166 @@
+/* Self tests for GDB's argument splitting and merging.
+
+ Copyright (C) 2023-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 "defs.h"
+#include "gdbsupport/selftest.h"
+#include "gdbsupport/buildargv.h"
+#include "gdbsupport/common-inferior.h"
+#include "gdbsupport/remote-args.h"
+#include "gdbsupport/gdb_argv_vec.h"
+
+namespace selftests {
+namespace remote_args_tests {
+
+/* The data needed to perform a single remote argument test. */
+struct arg_test_desc
+{
+ /* The original inferior argument string. */
+ std::string input;
+
+ /* The individual arguments once they have been split. */
+ std::vector<std::string> split;
+
+ /* The new inferior argument string, created by joining SPLIT. */
+ std::string joined;
+};
+
+/* The list of tests. */
+arg_test_desc desc[] = {
+ { "abc", { "abc" }, "abc" },
+ { "a b c", { "a", "b", "c" }, "a b c" },
+ { "\"a b\" 'c d'", { "a b", "c d" }, "a\\ b c\\ d" },
+ { "\\' \\\"", { "'", "\"" }, "\\' \\\"" },
+ { "'\\'", { "\\" }, "\\\\" },
+ { "\"\\\\\" \"\\\\\\\"\"", { "\\", "\\\"" }, "\\\\ \\\\\\\"" },
+ { "\\ \" \" ' '", { " ", " ", " "}, "\\ \\ \\ " },
+ { "\"'\"", { "'" }, "\\'" },
+ { "'\"' '\\\"'", { "\"", "\\\"" } , "\\\" \\\\\\\""},
+ { "\"first arg\" \"\" \"third-arg\" \"'\" \"\\\"\" \"\\\\\\\"\" \" \" \"\"",
+ { "first arg", "", "third-arg", "'", "\"", "\\\""," ", "" },
+ "first\\ arg '' third-arg \\' \\\" \\\\\\\" \\ ''"},
+ { "\"\\a\" \"\\&\" \"\\#\" \"\\<\" \"\\^\"",
+ { "\\a", "\\&", "\\#" , "\\<" , "\\^"},
+ "\\\\a \\\\\\& \\\\\\# \\\\\\< \\\\\\^" },
+ { "1 '\n' 3", { "1", "\n", "3" }, "1 '\n' 3" },
+};
+
+/* Run the remote argument passing self tests. */
+
+static void
+self_test ()
+{
+ int failure_count = 0;
+ for (const auto &d : desc)
+ {
+ if (run_verbose ())
+ {
+ if (&d != &desc[0])
+ debug_printf ("------------------------------\n");
+ debug_printf ("Input (%s)\n", d.input.c_str ());
+ }
+
+ /* Split argument string into individual arguments. */
+ std::vector<std::string> split_args = gdb::remote_args::split (d.input);
+
+ if (run_verbose ())
+ {
+ debug_printf ("Split:\n");
+
+ size_t len = std::max (split_args.size (), d.split.size ());
+ for (size_t i = 0; i < len; ++i)
+ {
+ const char *got = "N/A";
+ const char *expected = got;
+
+ if (i < split_args.size ())
+ got = split_args[i].c_str ();
+
+ if (i < d.split.size ())
+ expected = d.split[i].c_str ();
+
+ debug_printf (" got (%s), expected (%s)\n", got, expected);
+ }
+ }
+
+ if (split_args != d.split)
+ {
+ ++failure_count;
+ if (run_verbose ())
+ debug_printf ("FAIL\n");
+ continue;
+ }
+
+ /* Now join the arguments. */
+ gdb::argv_vec split_args_c_str;
+ for (const std::string &s : split_args)
+ split_args_c_str.push_back (xstrdup (s.c_str ()));
+ std::string joined_args
+ = gdb::remote_args::join (split_args_c_str.get ());
+
+ if (run_verbose ())
+ debug_printf ("Joined (%s), expected (%s)\n",
+ joined_args.c_str (), d.joined.c_str ());
+
+ if (joined_args != d.joined)
+ {
+ ++failure_count;
+ if (run_verbose ())
+ debug_printf ("FAIL\n");
+ continue;
+ }
+
+ /* The contents of JOINED_ARGS will not be identical to D.INPUT.
+ There are multiple ways that an argument can be escaped, and our
+ join function just picks one. However, if we split JOINED_ARGS
+ again then each individual argument should be the same as those in
+ SPLIT_ARGS. So test that next. */
+ std::vector<std::string> split_args_v2
+ = gdb::remote_args::split (joined_args);
+
+ if (split_args_v2 != split_args)
+ {
+ ++failure_count;
+ if (run_verbose ())
+ {
+ debug_printf ("Re-split:\n");
+ for (const auto &a : split_args_v2)
+ debug_printf (" got (%s)\n", a.c_str ());
+ debug_printf ("FAIL\n");
+ }
+ continue;
+ }
+
+ if (run_verbose ())
+ debug_printf ("PASS\n");
+ }
+
+ SELF_CHECK (failure_count == 0);
+}
+
+} /* namespace remote_args_tests */
+} /* namespace selftests */
+
+void _initialize_remote_arg_selftests ();
+
+void
+_initialize_remote_arg_selftests ()
+{
+ selftests::register_test ("remote-args",
+ selftests::remote_args_tests::self_test);
+}