From: Aditya Vidyadhar Kamath <akamath996@gmail.com>
To: ulrich.weigand@de.ibm.com, simon.marchi@polymtl.ca, tom@tromey.com
Cc: gdb-patches@sourceware.org, Aditya.Kamath1@ibm.com,
sangamesh.swamy@in.ibm.com, PRAJWAL.B.MEHENDARKAR@ibm.com
Subject: [PATCH v1 2/2] Add TLS support for shared libraries in AIX
Date: Mon, 3 Aug 2026 16:29:12 +0530 [thread overview]
Message-ID: <20260803105911.74857-2-akamath996@gmail.com> (raw)
From: Aditya Kamath <Aditya.Kamath1@ibm.com>
This patch adds support to debug thread local variables defined in shared libraries in AIX.
Sample debug output of this patch is as below
Thread 3 hit Breakpoint 1, thread_runner (arg=0x2) at tls_main.c:36
36 volatile int bp_here = 0; (void)bp_here;
$3 = 20
$4 = 40
thread 2: my_tls_var=20 lib_tls_var=40
[Thread 1 (tid 101646715) (id 1) exited]
[Thread 515 (tid 88015327) (id 3) exited]
[Inferior 1 (process 21758254) exited normally]
where lib_tls_var=40 is a variable from a thread library.
For initial-exec (R_TLS_IE): read the runtime TP-relative offset from the
.loader section TOC slot written by the AIX loader, returning it with bit 0
set as a sentinel so get_thread_local_address() uses it directly without
re-adding the static symbol value.
For global-dynamic/local-dynamic (R_TLSM): scan the .loader section for the
R_TLSM relocation, read the 8-byte mod_id from the inferior's TOC, and
dereference it as a TP-relative pointer to the per-thread block, then add the
intra-module variable offset.
Local-exec (main executable) continues to use the existing tp + offset path.
This patch implements the same as mentioned above.
---
gdb/rs6000-aix-tdep.c | 205 ++++++++++++++++++++++++++++++++++++------
1 file changed, 179 insertions(+), 26 deletions(-)
diff --git a/gdb/rs6000-aix-tdep.c b/gdb/rs6000-aix-tdep.c
index aa68654ff41..db81c91524c 100644
--- a/gdb/rs6000-aix-tdep.c
+++ b/gdb/rs6000-aix-tdep.c
@@ -40,6 +40,11 @@
#include "trad-frame.h"
#include "frame-unwind.h"
#include "inferior.h"
+#include "bfd.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 +74,9 @@
/* Minimum possible text address in AIX. */
#define AIX_TEXT_SEGMENT_BASE 0x10000000
+/* R_TLSM relocation type stored in the low byte of internal_ldrel.l_rtype. */
+#define XCOFF_R_TLSM 0x24
+
struct rs6000_aix_reg_vrreg_offset
{
int vr0_offset;
@@ -1358,45 +1366,165 @@ rs6000_aix_core_xfer_shared_libraries_aix (struct gdbarch *gdbarch,
/* For AIX, use the rs6000_aix_fetch_tls_load_module_address gdbarch method.
- Thread-local variables accessed via a simple TP-relative offset (the Local
- Exec and Initial Exec TLS models) are only valid for the main executable and
- for shared libraries that were pulled in at program startup. Variables
- belonging to a library loaded later via dlopen() have their storage
- allocated dynamically; their addresses must be resolved through the DTV
- (Dynamic Thread Vector) and cannot be computed with a plain TP offset.
+ AIX 64-bit TLS defines four access models, they are:
+
+ local-exec:
+ The TLS variable belongs to the main executable.
+
+ initial-exec:
+ The TLS variable may be in the main executable or a shared library
+ that was present at program startup (i.e. not dlopen'd).
+
+ local-dynamic:
+ The TLS variable is in the same module but it may be a shared library.
+
+ global-dynamic:
+ The general case for shared libraries. The compiler generates a
+ per-module TOC entry pair:
+ TOC[mod_entry] -- filled with "mod_id" by the R_TLSM loader relocation
+ TOC[off_entry] -- filled with the intra-module variable offset by R_TLS
+ mod_id is a signed TP-relative offset to the per-thread block pointer
+ for this module's TLS storage:
+ per_thread_block_ptr = *(thread_pointer + mod_id)
+ variable_addr = per_thread_block_ptr + xcoff_symbol_value
- Until full DTV-based lookup is implemented, reject any objfile that is a
- shared library (OBJF_SHARED) which has been dlopen'd and only
- allow the main executable (OBJF_MAINLINE). Returning 0 here causes
- rs6000_aix_get_thread_local_address() to be called with lm_addr == 0,
- which it treats as the signal to use the TP-relative path. */
+ In practice, local-exec and initial-exec both resolve via a plain
+ TP-relative offset (lm_addr == 0). local-dynamic and global-dynamic
+ both require the mod_id indirection (lm_addr != 0) and are handled
+ in rs6000_aix_fetch_tls_load_module_address ().
+
+ To obtain mod_id for a shared library we scan the library's loader
+ section for the first R_TLSM relocation, read the virtual address of the
+ TOC entry it targets, apply the objfile's data-section relocation to get
+ the runtime address, and then read the 8-byte mod_id from the inferior. */
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 so return 0. */
+ if (!(objfile->flags & OBJF_SHARED))
+ return 0;
+
+ bfd *abfd = objfile->obfd.get ();
+ if (abfd == 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"),
+ _("Cannot resolve TLS for \"%s\": no BFD"),
objfile_name (objfile));
- return 0;
+ /* Read the .loader section, which contains the header, symbol table,
+ and relocation table for the shared library. */
+ asection *loader_sec = bfd_get_section_by_name (abfd, ".loader");
+ if (loader_sec == nullptr)
+ throw_error (TLS_GENERIC_ERROR,
+ _("Cannot resolve TLS for \"%s\": no .loader section"),
+ objfile_name (objfile));
+
+ 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))
+ throw_error (TLS_GENERIC_ERROR,
+ _("Cannot resolve TLS for \"%s\": cannot read .loader section"),
+ objfile_name (objfile));
+
+ /* Parse the loader header to find the relocation table offset and count. */
+ 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 reloc_size = bfd_xcoff_ldrelsz (abfd);
+
+ if (reloc_start + ldhdr.l_nreloc * reloc_size > loader_size)
+ throw_error (TLS_GENERIC_ERROR,
+ _("Cannot resolve TLS for \"%s\": .loader section truncated"),
+ objfile_name (objfile));
+
+ /* Scan loader relocations for TLS reloc types.
+ l_rtype is a 16-bit field: high byte = reloc size, low byte = reloc type
+
+ R_TLS_IE (0x21) = initial-exec: the TOC slot holds the complete
+ TP-relative address of the variable written at load time by the
+ AIX loader. For shared libraries this value differs from the
+ static XCOFF symbol value. We must read the runtime TOC slot.
+ Bit 0 of lm_addr is set as a sentinel to distinguish this path
+
+ R_TLSM (0x24) = global-dynamic / local-dynamic: the TOC slot holds
+ mod_id, a signed TP-relative offset to the per-thread block pointer.
+ get_thread_local_address() then dereferences it and adds offset. */
+
+ const gdb_byte *reloc_ptr = loader_buf.data () + reloc_start;
+ for (size_t i = 0; i < ldhdr.l_nreloc; i++, reloc_ptr += reloc_size)
+ {
+ struct internal_ldrel ldrel;
+ bfd_xcoff_swap_ldrel_in (abfd, reloc_ptr, &ldrel);
+
+ int rtype = ldrel.l_rtype & 0xff;
+ if (rtype != R_TLS_IE && rtype != XCOFF_R_TLSM)
+ continue;
+
+ /* ldrel.l_vaddr is the static (pre-relocation) address of the TOC
+ entry the loader fills with the TLS value. Apply the .data section
+ relocation offset to get the runtime TOC slot address. */
+ CORE_ADDR toc_addr = ldrel.l_vaddr + objfile->data_section_offset ();
+
+ /* Read the 8-byte value the AIX loader wrote into the TOC slot. */
+ gdb_byte buf[8];
+ if (target_read_memory (toc_addr, buf, sizeof buf) != 0)
+ throw_error (TLS_GENERIC_ERROR,
+ _("Cannot resolve TLS for \"%s\": "
+ "failed to read TLS TOC slot from inferior"),
+ objfile_name (objfile));
+
+ CORE_ADDR toc_val
+ = extract_signed_integer (buf, sizeof buf, BFD_ENDIAN_BIG);
+
+ /* A zero or positive value means the loader has not yet written the
+ TLS offset (storage not yet allocated). */
+ if ((LONGEST) toc_val >= 0)
+ throw_error (TLS_NOT_ALLOCATED_YET_ERROR,
+ _("TLS storage not yet allocated for \"%s\""),
+ objfile_name (objfile));
+
+ if (rtype == R_TLS_IE)
+ {
+ /* initial-exec: return runtime TP-relative offset with bit 0 set
+ so get_thread_local_address() uses it directly without adding
+ the symbol offset (which is the static .tdata offset, not the
+ runtime TP offset for a shared library). */
+ return (CORE_ADDR) toc_val | 1;
+ }
+
+ /* R_TLSM: return mod_id as-is (bit 0 is always 0). */
+ return (CORE_ADDR) toc_val;
+ }
+
+ throw_error (TLS_GENERIC_ERROR,
+ _("Cannot resolve TLS for \"%s\": "
+ "no TLS relocation (R_TLS_IE or R_TLSM) found "
+ "in .loader section"),
+ objfile_name (objfile));
}
/* Use the AIX get_thread_local_address gdbarch function.
- On 64-bit AIX the thread pointer is in R13. For the Local Exec TLS model
- (used by the main executable) the XCOFF symbol value is a signed
- TP-relative offset, so the per-thread address is:
+ On 64-bit AIX the thread pointer is in R13.
+
+ lm_addr == 0 (local-exec, main executable):
+ The XCOFF symbol value is a signed TP-relative offset baked in at
+ link time:
+ address = tp + (int64_t) offset
- address = thread_pointer + (int64_t) offset
+ lm_addr has bit 0 set (initial-exec, shared library):
+ The full runtime TP-relative offset was read from the R_TLS_IE TOC
+ slot by rs6000_aix_fetch_tls_load_module_address(). Do not add the
+ symbol offset again:
+ address = tp + (int64_t)(lm_addr & ~1)
- This only works correctly when the variable's storage is allocated
- statically relative to the thread pointer, which is guaranteed for the
- main executable. */
+ lm_addr != 0 and bit 0 clear (global-dynamic / local-dynamic):
+ lm_addr is mod_id from the R_TLSM TOC slot a signed TP-relative
+ offset to the per-thread block pointer. Dereference it, then add
+ the intra-module symbol offset:
+ address = *(tp + (int64_t) lm_addr) + (int64_t) offset */
static CORE_ADDR
rs6000_aix_get_thread_local_address (struct gdbarch *gdbarch, ptid_t ptid,
@@ -1420,7 +1548,32 @@ 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"));
- return tp + (CORE_ADDR)(int64_t) offset;
+ if (lm_addr == 0)
+ {
+ /* local-exec (main executable): XCOFF symbol value is TP-relative. */
+ return tp + (CORE_ADDR)(int64_t) offset;
+ }
+ else if (lm_addr & 1)
+ {
+ /* initial-exec (shared library): lm_addr holds the full runtime
+ TP-relative offset from the R_TLS_IE TOC slot, with bit 0 set as
+ a sentinel. Strip the sentinel and compute the address directly. */
+ return tp + (CORE_ADDR)(int64_t)(lm_addr & ~(CORE_ADDR)1);
+ }
+ else
+ {
+ /* global-dynamic / local-dynamic: lm_addr is mod_id from R_TLSM.
+ Dereference the per-thread block pointer, then add the symbol offset. */
+ CORE_ADDR region_ptr_addr = tp + (CORE_ADDR)(int64_t) lm_addr;
+ gdb_byte buf[8];
+ if (target_read_memory (region_ptr_addr, buf, sizeof buf) != 0)
+ throw_error (TLS_GENERIC_ERROR,
+ _("Cannot read TLS region pointer for thread-local lookup"));
+
+ CORE_ADDR region_base
+ = extract_unsigned_integer (buf, sizeof buf, BFD_ENDIAN_BIG);
+ return region_base + (CORE_ADDR)(int64_t) offset;
+ }
}
static void
--
2.51.2
next reply other threads:[~2026-08-03 10:59 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-03 10:59 Aditya Vidyadhar Kamath [this message]
2026-08-05 15:16 ` Ulrich Weigand
2026-08-10 10:23 ` Aditya Kamath
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260803105911.74857-2-akamath996@gmail.com \
--to=akamath996@gmail.com \
--cc=Aditya.Kamath1@ibm.com \
--cc=PRAJWAL.B.MEHENDARKAR@ibm.com \
--cc=gdb-patches@sourceware.org \
--cc=sangamesh.swamy@in.ibm.com \
--cc=simon.marchi@polymtl.ca \
--cc=tom@tromey.com \
--cc=ulrich.weigand@de.ibm.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox