* [PATCH v5 2/2] Add TLS variable support for shared libraries on AIX
@ 2026-09-22 13:00 Aditya Vidyadhar Kamath
0 siblings, 0 replies; only message in thread
From: Aditya Vidyadhar Kamath @ 2026-09-22 13:00 UTC (permalink / raw)
To: ulrich.weigand, simon.marchi, tom
Cc: gdb-patches, Aditya.Kamath1, sangamesh.swamy, Aditya Vidyadhar Kamath
From: Aditya Vidyadhar Kamath <aditya.kamath1@ibm.com>
On 64-bit AIX, GDB previously could not read thread-local variables
defined in shared libraries. This patch implements the full lookup
chain for all four XCOFF TLS models.
Module-id resolution for shared libraries uses two strategies:
1. R_TLSML (local-dynamic): there is exactly one R_TLSML reloc per
module, and the loader always writes that module's module-id into
the corresponding TOC slot. Read it from the inferior and return it.
2. R_TLSM (global-dynamic): each R_TLSM reloc in any loaded module's
.loader section references the library that exports the named TLS
symbol. Scan R_TLSM relocs across all loaded objfiles and for each,
check whether the library of interest exports the referenced symbol
name. If so, read the TOC slot value that is the library's
module-id. Matching is done by symbol name so two libraries that
both define a TLS variable at the same within-module offset are
distinguished correctly.
The module-id is cached per-objfile so the .loader scan is paid only
once per shared library.
Module-id 0 is a valid assignment for initial-exec libraries and is not
treated as "not yet allocated". The main executable is distinguished
from a module-id-0 library by returning the sentinel XCOFF_MODID_MAIN_EXE
(UINT64_MAX) from fetch_tls_load_module_address.
Address computation uses three cases:
- XCOFF_MODID_MAIN_EXE: local-exec. The XCOFF symbol value is the
signed TP-relative offset directly. So address = tp + offset.
- module-id 0: initial-exec merged segment. The AIX loader packs all
initial-exec modules into one contiguous TLS block and adjusts each
variable's TP-relative offset accordingly, so the link-time XCOFF
symbol value may not equal the runtime offset. GDB scans the .loader
sections of all loaded modules for an R_TLS or R_TLS_LD slot that
belongs to the owning shared library. Two checks prevent a false
match against a different module that happens to have a variable at
the same intra-module offset: (a) the loader symbol must be exported
by the owning library (name check), and (b) l_value must equal the
static symbol offset (value check, unique within one module). If no
adjusted slot is found the static offset is used as-is, which is
correct for R_TLS_IE variables where the linker wrote the final
absolute TP-relative offset at link time.
- module-id n > 0: global-dynamic. Walk the per-thread thread vector:
tls_base = thread_vector[n]; address = tls_base + offset.
---
gdb/rs6000-aix-tdep.c | 529 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 510 insertions(+), 19 deletions(-)
diff --git a/gdb/rs6000-aix-tdep.c b/gdb/rs6000-aix-tdep.c
index 76763f08e08..406707e2721 100644
--- a/gdb/rs6000-aix-tdep.c
+++ b/gdb/rs6000-aix-tdep.c
@@ -40,6 +40,10 @@
#include "trad-frame.h"
#include "frame-unwind.h"
#include "inferior.h"
+#include "coff/internal.h"
+#include "libcoff.h"
+#include "coff/xcoff.h"
+#include "libxcoff.h"
/* If the kernel has to deliver a signal, it pushes a sigcontext
structure on the stack and then calls the signal handler, passing
@@ -69,6 +73,44 @@
/* Minimum possible text address in AIX. */
#define AIX_TEXT_SEGMENT_BASE 0x10000000
+/* XCOFF TLS relocation types used in the .loader section (low byte of
+ internal_ldrel.l_rtype).
+
+ R_TLS_LE (0x23): local-exec. The linker resolves the offset at link
+ time so nothing needs to happen at runtime; no TOC slot is created.
+
+ R_TLS_IE (0x21): initial-exec. The loader writes the signed TP-relative
+ offset directly into the TOC slot. To get the variable address just do
+ TP + toc_value.
+
+ R_TLS (0x20): global-dynamic. The TOC slot holds the variable's offset
+ within the owning module's TLS block. Eight bytes before it in the TOC
+ is an R_TLSM slot containing the module-id.
+ - If module-id is 0, the module was linked into the initial-exec TLS
+ segment. The loader has already adjusted the slot to an absolute
+ TP-relative offset, so TP + toc_value gives the right answer.
+ - If module-id is non-zero, the module was opened dynamically. Look up
+ the block base in the thread vector and add the slot value:
+ tls_base = thread_vec[module-id]
+ address = tls_base + toc_value.
+
+ R_TLS_LD (0x22): local-dynamic. Same idea as R_TLS but the module-id
+ comes from a single per-module R_TLSML slot shared by all local-dynamic
+ variables in that module.
+
+ R_TLSM (0x24): paired with R_TLS; holds the module-id for that variable.
+ Zero means the module is in the initial-exec segment.
+
+ R_TLSML (0x25): paired with R_TLS_LD; holds the module-id for the whole
+ module. Zero has the same initial-exec meaning as R_TLSM. */
+
+#define XCOFF_R_TLS 0x20
+#define XCOFF_R_TLS_IE 0x21
+#define XCOFF_R_TLS_LD 0x22
+#define XCOFF_R_TLS_LE 0x23
+#define XCOFF_R_TLSM 0x24
+#define XCOFF_R_TLSML 0x25
+
struct rs6000_aix_reg_vrreg_offset
{
int vr0_offset;
@@ -84,6 +126,26 @@ static struct rs6000_aix_reg_vrreg_offset rs6000_aix_vrreg_offset =
560 /* vrsave_offset */
};
+/* Special sentinel used instead of a real module-id to mark the main
+ executable. Real AIX module-ids are small integers, so UINT64_MAX will
+ never clash with one. */
+#define XCOFF_MODID_MAIN_EXE ((CORE_ADDR) UINT64_MAX)
+
+/* Cached TLS module-id for a shared library.
+ We look this up once and store it here so we don't have to re-scan
+ the .loader section on every TLS access.
+ Note that mod_id == 0 is a perfectly valid id (initial-exec libraries
+ get it), so we track whether the lookup has happened separately. */
+
+struct aix_tls_objfile_data
+{
+ bool resolved = false;
+ uint64_t mod_id = 0;
+};
+
+static const registry<objfile>::key<aix_tls_objfile_data>
+ aix_tls_objfile_data_key;
+
static int
rs6000_aix_get_vrreg_offset (ppc_gdbarch_tdep *tdep,
const struct rs6000_aix_reg_vrreg_offset *offsets,
@@ -1356,29 +1418,382 @@ rs6000_aix_core_xfer_shared_libraries_aix (struct gdbarch *gdbarch,
offset, len, 0);
}
-/* For AIX, use the rs6000_aix_fetch_tls_load_module_address gdbarch method. */
+/* Read an 8-byte TOC slot from the inferior at RUNTIME_ADDR.
+ Returns the value as a uint64_t, or throws on a memory error. */
+
+static uint64_t
+rs6000_aix_read_tls_slot (CORE_ADDR runtime_addr, const char *objfile_name_str)
+{
+ gdb_byte buf[8];
+ if (target_read_memory (runtime_addr, buf, sizeof buf) != 0)
+ throw_error (TLS_GENERIC_ERROR,
+ _("Cannot resolve TLS for \"%s\": "
+ "failed to read TLS TOC slot at %s from inferior"),
+ objfile_name_str,
+ core_addr_to_string (runtime_addr));
+ return (uint64_t) extract_unsigned_integer (buf, sizeof buf, BFD_ENDIAN_BIG);
+}
+
+/* Return the base address of MOD_ID's per-thread TLS block.
+ On 64-bit AIX, r13 (tp) points to a word that contains the thread-vector
+ pointer. The thread vector is just an array of pointers indexed by
+ module-id, so thread_vec[mod_id] gives us the block base we want.
+ Throws if the storage hasn't been set up yet. */
+
+static CORE_ADDR
+rs6000_aix_thread_vec_lookup (ULONGEST tp, uint64_t mod_id,
+ const char *objfile_name_str)
+{
+ gdb_byte buf[8];
+ if (target_read_memory ((CORE_ADDR) tp, buf, sizeof buf) != 0)
+ throw_error (TLS_GENERIC_ERROR,
+ _("Cannot resolve TLS for \"%s\": "
+ "failed to read thread vector pointer at tp=%s"),
+ objfile_name_str,
+ core_addr_to_string ((CORE_ADDR) tp));
+
+ CORE_ADDR thread_vec_ptr
+ = (CORE_ADDR) extract_unsigned_integer (buf, sizeof buf, BFD_ENDIAN_BIG);
+
+ if (thread_vec_ptr == 0)
+ throw_error (TLS_NOT_ALLOCATED_YET_ERROR,
+ _("TLS storage not yet allocated for \"%s\""),
+ objfile_name_str);
+
+ CORE_ADDR entry_addr = thread_vec_ptr + mod_id * sizeof (uint64_t);
+ if (target_read_memory (entry_addr, buf, sizeof buf) != 0)
+ throw_error (TLS_GENERIC_ERROR,
+ _("Cannot resolve TLS for \"%s\": "
+ "failed to read thread_vector[%s] at %s"),
+ objfile_name_str,
+ pulongest (mod_id),
+ core_addr_to_string (entry_addr));
+
+ CORE_ADDR tls_base
+ = (CORE_ADDR) extract_unsigned_integer (buf, sizeof buf, BFD_ENDIAN_BIG);
+
+ if (tls_base == 0)
+ throw_error (TLS_NOT_ALLOCATED_YET_ERROR,
+ _("TLS storage not yet allocated for \"%s\""
+ " (thread_vector[%s] == 0)"),
+ objfile_name_str, pulongest (mod_id));
+
+ return tls_base;
+}
+
+/* Return true if OBJFILE has a minimal symbol with the given NAME.
+ This is how we tell which library owns an R_TLSM slot - the library
+ that exports the symbol the reloc names is the one that gets the id. */
+
+static bool
+rs6000_aix_objfile_exports_symbol (struct objfile *objfile, const char *name)
+{
+ for (minimal_symbol *msym : objfile->msymbols ())
+ if (strcmp (msym->linkage_name (), name) == 0)
+ return true;
+ return false;
+}
+
+/* Figure out the AIX TLS module-id for OBJ.
+ The module-id is the index the runtime uses to find OBJ's TLS block
+ in the per-thread thread vector.
+
+ We try two ways to find it:
+
+ First, look for an R_TLSML reloc inside OBJ's own .loader section.
+ The loader writes the module's own id into that slot, so if we find
+ one we can just read it out of the inferior.
+
+ If there is no R_TLSML (e.g. the library only has global-dynamic
+ variables), scan every loaded module's .loader section for R_TLSM
+ relocs. Each R_TLSM names the library that exports the TLS symbol,
+ so if we find one whose symbol is exported by OBJ, the value in
+ that slot is OBJ's module-id.
+
+ Returns the module-id on success, throws on failure. */
+
+static uint64_t
+rs6000_aix_find_module_id (struct objfile *obj)
+{
+ const char *oname = objfile_name (obj);
+ bfd *abfd = obj->obfd.get ();
+
+ /* Try the R_TLSML slot inside the library itself first. */
+ {
+ asection *loader_sec = bfd_get_section_by_name (abfd, ".loader");
+ if (loader_sec != nullptr)
+ {
+ bfd_size_type loader_size = bfd_section_size (loader_sec);
+ gdb::byte_vector loader_buf (loader_size);
+ if (bfd_get_section_contents (abfd, loader_sec, loader_buf.data (),
+ 0, loader_size))
+ {
+ struct internal_ldhdr ldhdr;
+ bfd_xcoff_swap_ldhdr_in (abfd, loader_buf.data (), &ldhdr);
+ bfd_vma reloc_start = bfd_xcoff_loader_reloc_offset (abfd, &ldhdr);
+ bfd_size_type relsz = bfd_xcoff_ldrelsz (abfd);
+ CORE_ADDR data_slide = obj->data_section_offset ();
+
+ const gdb_byte *rp = loader_buf.data () + reloc_start;
+ for (size_t i = 0; i < ldhdr.l_nreloc; i++, rp += relsz)
+ {
+ struct internal_ldrel ldrel;
+ bfd_xcoff_swap_ldrel_in (abfd, rp, &ldrel);
+ if ((ldrel.l_rtype & 0xff) != XCOFF_R_TLSML)
+ continue;
+
+ /* The loader fills this slot with the module's own id.
+ Zero is valid here - it means initial-exec. */
+ CORE_ADDR slot_addr = ldrel.l_vaddr + data_slide;
+ uint64_t mod_id = rs6000_aix_read_tls_slot (slot_addr, oname);
+ return mod_id;
+ }
+ }
+ }
+ }
+
+ /* No R_TLSML found. Walk all loaded modules looking for an R_TLSM
+ that references a symbol exported by OBJ. */
+ for (struct objfile &candidate : current_program_space->objfiles ())
+ {
+ if (candidate.obfd.get () == nullptr)
+ continue;
+
+ bfd *cabfd = candidate.obfd.get ();
+ asection *loader_sec = bfd_get_section_by_name (cabfd, ".loader");
+ if (loader_sec == nullptr)
+ continue;
+
+ bfd_size_type loader_size = bfd_section_size (loader_sec);
+ gdb::byte_vector loader_buf (loader_size);
+ if (!bfd_get_section_contents (cabfd, loader_sec, loader_buf.data (),
+ 0, loader_size))
+ continue;
+
+ struct internal_ldhdr ldhdr;
+ bfd_xcoff_swap_ldhdr_in (cabfd, loader_buf.data (), &ldhdr);
+ bfd_vma sym_start = bfd_xcoff_loader_symbol_offset (cabfd, &ldhdr);
+ bfd_size_type symsz = bfd_xcoff_ldsymsz (cabfd);
+ bfd_vma reloc_start = bfd_xcoff_loader_reloc_offset (cabfd, &ldhdr);
+ bfd_size_type relsz = bfd_xcoff_ldrelsz (cabfd);
+ CORE_ADDR data_slide = candidate.data_section_offset ();
+ /* Symbol names longer than SYMNMLEN are stored in the string table. */
+ const char *strtab
+ = (ldhdr.l_stoff < loader_size
+ ? (const char *) loader_buf.data () + ldhdr.l_stoff
+ : nullptr);
+
+ const gdb_byte *rp = loader_buf.data () + reloc_start;
+ for (size_t i = 0; i < ldhdr.l_nreloc; i++, rp += relsz)
+ {
+ struct internal_ldrel ldrel;
+ bfd_xcoff_swap_ldrel_in (cabfd, rp, &ldrel);
+ if ((ldrel.l_rtype & 0xff) != XCOFF_R_TLSM)
+ continue;
+
+ /* l_symndx 0-2 are reserved for .text/.data/.bss; real symbols
+ start at index 3. */
+ if (ldrel.l_symndx < 3
+ || (bfd_vma)(ldrel.l_symndx - 3) >= ldhdr.l_nsyms)
+ continue;
+
+ bfd_vma ldsym_off
+ = sym_start + (bfd_vma)(ldrel.l_symndx - 3) * symsz;
+ if (ldsym_off + symsz > loader_size)
+ continue;
+
+ struct internal_ldsym ldsym;
+ bfd_xcoff_swap_ldsym_in (cabfd,
+ loader_buf.data () + ldsym_off, &ldsym);
+
+ /* Short names are stored inline; longer ones use the string table. */
+ char nambuf[SYMNMLEN + 1];
+ const char *sym_name;
+ if (ldsym._l._l_l._l_zeroes != 0)
+ {
+ memcpy (nambuf, ldsym._l._l_name, SYMNMLEN);
+ nambuf[SYMNMLEN] = '\0';
+ sym_name = nambuf;
+ }
+ else if (strtab != nullptr
+ && ldsym._l._l_l._l_offset < ldhdr.l_stlen)
+ sym_name = strtab + ldsym._l._l_l._l_offset;
+ else
+ continue;
+
+ /* If OBJ exports this symbol the loader put OBJ's module-id
+ in this slot. Take the first match we find. */
+ if (!rs6000_aix_objfile_exports_symbol (obj, sym_name))
+ continue;
+
+ /* Zero is a real module-id (initial-exec), not an error. */
+ CORE_ADDR slot_addr = ldrel.l_vaddr + data_slide;
+ uint64_t mod_id = rs6000_aix_read_tls_slot (slot_addr, oname);
+ return mod_id;
+ }
+ }
+
+ throw_error (TLS_GENERIC_ERROR,
+ _("Cannot resolve TLS for \"%s\": "
+ "no R_TLSML or R_TLSM relocation found for this module"),
+ oname);
+}
+
+/* gdbarch hook: return the "load module address" for TLS lookups.
+ For the main executable we return XCOFF_MODID_MAIN_EXE so the
+ resolution code can tell it apart from a shared library with module-id 0.
+ For shared libraries we scan the .loader section to find the module-id,
+ cache it, and return it. */
static CORE_ADDR
rs6000_aix_fetch_tls_load_module_address (struct objfile *objfile)
{
- /* TLS variables from shared libraries cannot be directly fetched
- via the thread pointer if they were loaded by dlopen(). */
- if (objfile->flags & OBJF_SHARED)
+ /* Main executable uses local-exec; the symbol value is already the
+ TP-relative offset so no scan is needed. */
+ if (!(objfile->flags & OBJF_SHARED))
+ return XCOFF_MODID_MAIN_EXE;
+
+ if (objfile->obfd.get () == nullptr)
throw_error (TLS_GENERIC_ERROR,
- _("TLS lookup via thread pointer is not supported for "
- "shared library \"%s\"; full DTV-based lookup is not "
- "yet implemented for AIX"),
- objfile_name (objfile));
+ _("Cannot resolve TLS for \"%s\": no BFD"),
+ objfile_name (objfile));
- return 0;
+ /* Use the cached id if we've seen this library before. */
+ aix_tls_objfile_data &cache
+ = aix_tls_objfile_data_key.try_emplace (objfile);
+ if (cache.resolved)
+ return (CORE_ADDR) cache.mod_id;
+
+ /* First time for this library; go find the module-id. */
+ uint64_t mod_id = rs6000_aix_find_module_id (objfile);
+
+ cache.mod_id = mod_id;
+ cache.resolved = true;
+
+ return (CORE_ADDR) mod_id;
+}
+
+/* For initial-exec libraries (module-id 0) the loader merges all such
+ modules into one TLS block and adjusts each variable's offset, so the
+ static XCOFF symbol value is not necessarily the right TP-relative offset
+ at runtime.
+ This function scans ABFD's .loader section to find the TOC slot for the
+ variable and reads the actual runtime offset from the inferior.
+ TARGET_OBJFILE is the library that defines the variable; we use it to
+ filter out slots from other modules that happen to have a variable at the
+ same within-module offset (name check + value check).
+ Returns true and stores the result in *RUNTIME_OFFSET on success. */
+
+static bool
+rs6000_aix_find_initial_exec_tls_offset (bfd *abfd,
+ CORE_ADDR data_slide,
+ struct objfile *target_objfile,
+ CORE_ADDR static_offset,
+ int64_t *runtime_offset)
+{
+ if (abfd == nullptr)
+ return false;
+
+ asection *loader_sec = bfd_get_section_by_name (abfd, ".loader");
+ if (loader_sec == nullptr)
+ return false;
+
+ bfd_size_type loader_size = bfd_section_size (loader_sec);
+ gdb::byte_vector loader_buf (loader_size);
+ if (!bfd_get_section_contents (abfd, loader_sec, loader_buf.data (),
+ 0, loader_size))
+ return false;
+
+ struct internal_ldhdr ldhdr;
+ bfd_xcoff_swap_ldhdr_in (abfd, loader_buf.data (), &ldhdr);
+
+ bfd_vma sym_start = bfd_xcoff_loader_symbol_offset (abfd, &ldhdr);
+ bfd_size_type symsz = bfd_xcoff_ldsymsz (abfd);
+ bfd_vma reloc_start = bfd_xcoff_loader_reloc_offset (abfd, &ldhdr);
+ bfd_size_type relsz = bfd_xcoff_ldrelsz (abfd);
+
+ /* Long symbol names live in the string table at l_stoff. */
+ const char *strtab
+ = (ldhdr.l_stoff < loader_size
+ ? (const char *) loader_buf.data () + ldhdr.l_stoff
+ : nullptr);
+
+ const gdb_byte *rp = loader_buf.data () + reloc_start;
+ for (size_t i = 0; i < ldhdr.l_nreloc; i++, rp += relsz)
+ {
+ struct internal_ldrel ldrel;
+ bfd_xcoff_swap_ldrel_in (abfd, rp, &ldrel);
+
+ int rtype = ldrel.l_rtype & 0xff;
+ /* We only care about R_TLS and R_TLS_LD; those are the slots that
+ hold the per-variable offset the loader adjusts for initial-exec. */
+ if (rtype != XCOFF_R_TLS && rtype != XCOFF_R_TLS_LD)
+ continue;
+
+ /* Indices 0-2 are reserved for .text/.data/.bss, not real symbols. */
+ if (ldrel.l_symndx < 3
+ || (bfd_vma)(ldrel.l_symndx - 3) >= ldhdr.l_nsyms)
+ continue;
+
+ bfd_vma ldsym_off
+ = sym_start + (bfd_vma)(ldrel.l_symndx - 3) * symsz;
+ if (ldsym_off + symsz > loader_size)
+ continue;
+
+ struct internal_ldsym ldsym;
+ bfd_xcoff_swap_ldsym_in (abfd, loader_buf.data () + ldsym_off, &ldsym);
+
+ /* Short names are inline; anything longer lives in the string table. */
+ char nambuf[SYMNMLEN + 1];
+ const char *sym_name;
+ if (ldsym._l._l_l._l_zeroes != 0)
+ {
+ memcpy (nambuf, ldsym._l._l_name, SYMNMLEN);
+ nambuf[SYMNMLEN] = '\0';
+ sym_name = nambuf;
+ }
+ else if (strtab != nullptr && ldsym._l._l_l._l_offset < ldhdr.l_stlen)
+ sym_name = strtab + ldsym._l._l_l._l_offset;
+ else
+ continue;
+
+ /* Make sure this symbol actually comes from the library we're looking
+ at. Two libraries can have a TLS variable at the same offset within
+ their respective modules, so checking the name is essential. */
+ if (!rs6000_aix_objfile_exports_symbol (target_objfile, sym_name))
+ continue;
+
+ /* l_value is the variable's offset within the module at link time.
+ No two variables in the same module share this, so it picks out
+ exactly the slot we want. */
+ if ((CORE_ADDR)(int64_t) ldsym.l_value != static_offset)
+ continue;
+
+ /* Found the right slot - read what the loader put there. */
+ CORE_ADDR slot_addr = ldrel.l_vaddr + data_slide;
+ gdb_byte buf[8];
+ if (target_read_memory (slot_addr, buf, sizeof buf) != 0)
+ return false;
+
+ *runtime_offset
+ = (int64_t) extract_unsigned_integer (buf, sizeof buf, BFD_ENDIAN_BIG);
+ return true;
+ }
+
+ return false;
}
-/* Implement the get_thread_local_address gdbarch method for AIX.
+/* gdbarch hook: compute the address of a TLS variable for a given thread.
+ On 64-bit AIX the thread pointer lives in r13.
- On 64-bit AIX the thread pointer (TP) is in R13. For variables in
- the main executable (lm_addr == 0) the XCOFF symbol value is a
- signed TP-relative offset baked in at link time:
- address = tp + (int64_t) offset */
+ lm_addr is whatever fetch_tls_load_module_address returned:
+ XCOFF_MODID_MAIN_EXE - main executable (local-exec). The XCOFF symbol
+ value is already the signed TP-relative offset; just add it to tp.
+ 0 - initial-exec shared library. The static symbol value may have been
+ shifted when the loader packed all initial-exec modules into one TLS
+ block, so we scan the .loader sections to find the real runtime offset.
+ n > 0 - dynamically loaded library. Walk the thread vector to get the
+ module's TLS block base, then add the variable's within-module offset. */
static CORE_ADDR
rs6000_aix_get_thread_local_address (struct gdbarch *gdbarch, ptid_t ptid,
@@ -1386,7 +1801,7 @@ rs6000_aix_get_thread_local_address (struct gdbarch *gdbarch, ptid_t ptid,
{
ppc_gdbarch_tdep *tdep = gdbarch_tdep<ppc_gdbarch_tdep> (gdbarch);
- /* Only 64-bit AIX is supported. */
+ /* We only handle 64-bit for now. */
if (tdep->wordsize != 8)
throw_error (TLS_GENERIC_ERROR,
_("TLS lookup not yet supported for 32-bit AIX"));
@@ -1401,12 +1816,88 @@ rs6000_aix_get_thread_local_address (struct gdbarch *gdbarch, ptid_t ptid,
throw_error (TLS_GENERIC_ERROR,
_("Unable to fetch thread pointer for TLS lookup"));
- /* local-exec: XCOFF symbol value is a signed TP-relative offset. */
- if (lm_addr == 0)
+ /* Main executable: symbol value is the TP-relative offset, nothing more
+ to do. */
+ if (lm_addr == XCOFF_MODID_MAIN_EXE)
return tp + (CORE_ADDR)(int64_t) offset;
- throw_error (TLS_GENERIC_ERROR,
- _("TLS in shared libraries not yet supported on AIX"));
+ /* Initial-exec shared library (module-id 0).
+ The loader merges all initial-exec modules into one TLS block and
+ adjusts each variable's offset, so the static XCOFF symbol value may
+ not be right. We scan all loaded .loader sections to find the slot
+ the loader filled with the real runtime offset. We check both the
+ symbol name (to avoid confusing two libraries that share an offset)
+ and l_value (to pick the exact variable within the library).
+ If nothing turns up we fall back to the static value, which is correct
+ for R_TLS_IE variables since the linker already computed the final
+ offset there. */
+ if (lm_addr == 0)
+ {
+ /* Find which shared library has module-id 0 so we can pass it as
+ the name filter when scanning other modules' .loader sections. */
+ struct objfile *own_objfile = nullptr;
+ for (struct objfile &candidate : current_program_space->objfiles ())
+ {
+ if (!(candidate.flags & OBJF_SHARED))
+ continue;
+ aix_tls_objfile_data *d = aix_tls_objfile_data_key.get (&candidate);
+ if (d != nullptr && d->resolved && d->mod_id == 0)
+ {
+ own_objfile = &candidate;
+ break;
+ }
+ }
+
+ /* Check the owning library first. If that misses, try everything
+ else - the main executable often has R_TLS or R_TLS_IE slots
+ for variables that are defined in a shared library. */
+ int64_t runtime_tprel = 0;
+ bool found = false;
+
+ if (own_objfile != nullptr && own_objfile->obfd.get () != nullptr)
+ found = rs6000_aix_find_initial_exec_tls_offset (
+ own_objfile->obfd.get (),
+ own_objfile->data_section_offset (),
+ own_objfile, offset, &runtime_tprel);
+
+ if (!found)
+ {
+ for (struct objfile &candidate : current_program_space->objfiles ())
+ {
+ if (&candidate == own_objfile)
+ continue;
+ if (candidate.obfd.get () == nullptr)
+ continue;
+ /* If we couldn't identify the owning library, use the
+ candidate as its own name filter. */
+ struct objfile *name_filter
+ = (own_objfile != nullptr) ? own_objfile : &candidate;
+ if (rs6000_aix_find_initial_exec_tls_offset (
+ candidate.obfd.get (),
+ candidate.data_section_offset (),
+ name_filter, offset, &runtime_tprel))
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (found)
+ return tp + (CORE_ADDR) runtime_tprel;
+
+ /* Didn't find a loader-adjusted slot. Fall back to the static
+ XCOFF value - correct for R_TLS_IE where the linker wrote the
+ final offset. */
+ return tp + (CORE_ADDR)(int64_t) offset;
+ }
+
+ /* Dynamically loaded library: find the TLS block via the thread vector
+ and add the variable's within-module offset. */
+ uint64_t mod_id = (uint64_t) lm_addr;
+ CORE_ADDR tls_base = rs6000_aix_thread_vec_lookup (tp, mod_id,
+ "<shared library>");
+ return tls_base + offset;
}
static void
--
2.51.2
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-09-22 13:00 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-22 13:00 [PATCH v5 2/2] Add TLS variable support for shared libraries on AIX Aditya Vidyadhar Kamath
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox