From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id bAWDOQJ8smoNxzIAWB0awg (envelope-from ) for ; Tue, 22 Sep 2026 09:00:50 -0400 Authentication-Results: simark.ca; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20251104 header.b=I/fShO0m; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id D51291E01F; Tue, 22 Sep 2026 09:00:50 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-5.4 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FROM,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [IPv6:2620:52:6:3111::32]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id 496331E01F for ; Tue, 22 Sep 2026 09:00:49 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 6DCEA4B9DB52 for ; Tue, 22 Sep 2026 13:00:47 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 6DCEA4B9DB52 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20251104 header.b=I/fShO0m Received: from mail-qk2-x10.google.com (mail-qk2-x10.google.com [IPv6:2607:f8b0:4864:34::10]) by sourceware.org (Postfix) with ESMTPS id 036C44BA2E03 for ; Tue, 22 Sep 2026 13:00:16 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 036C44BA2E03 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=gmail.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 036C44BA2E03 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:34::10 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1790082017; cv=none; b=TeS8ifWD7n1REXEWXJXUkqYHgikQ1c4zI2o2InYQR1oT/RuNRYeLk15yuIZYIpRVYSgTPFaF8olfyalnHhwHk4/UwBTHsptUGr85y9agJH4LuS604reITOWUJO7vrxOxGmnAZ+y9m75FDNa8bjhaG3o84DMp60zXwBJ6OuSgvik= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1790082017; c=relaxed/simple; bh=ah58IqJQyL0OX4XpkoGTt2tXsomZ1DCTe1hBtMiQPwo=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=DOSbi8Zo9mA8StEXYOniKgai+Qx9uV/T/rClG9yt6ySNfFuVw/DlZzzu0tfcQE8rhUTQzSo9G4guzyoGaX8EZLHUEdOO24DFHepHN9wsiqrNozAhQfVh3mVTs9AnPyC6ZDSkrzjRq5ZSoAScdPYKzyaU9yDjf6Lk66RZgqrq5DQ= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20251104 header.b=I/fShO0m DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 036C44BA2E03 Received: by mail-qk2-x10.google.com with SMTP id af79cd13be357-93910ca5aa8so383324485a.3 for ; Tue, 22 Sep 2026 06:00:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1790082016; x=1790686816; darn=sourceware.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=IDvHehSU9HI8ctJpZGcdCB2sZyT/ZFG2go/z/DZFvzM=; b=I/fShO0m8KVp0jYCR4kK/yfRE0omgg9VKJhJOboERC7S8vJjzU6g8ieJnQM/aWHEdg B+wVlohkvzZ7i0U94VxwQmqKnckOK2voLzuDxJBuPF1/7mJrIJXiUS5Fy8jt88LCkYZJ 5UPI9XRUdyp2nR7R+spMeBnAv/BnGhL1uuJp7fOiEVfXpOwYUJfR+sGenbNup4u2JJDZ X2kCgRJ0sjUjHhNiC6cXn89zB4hiqG4DIZ12jwZy7uxa/YxeOXWXreAAlzrD+Ggsw8iW 6cFZzP3c6EHoNil5R8us4KXb24LIGtX2dwB/uEvOdn++bQ+lk8m6P7HHE+cqI5Sm043y jErw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790082016; x=1790686816; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=IDvHehSU9HI8ctJpZGcdCB2sZyT/ZFG2go/z/DZFvzM=; b=x9HnVwUcF+q85NCNSUnTo0DACdp9kPPIOlBBlXDtcA4x5QbO6R8Og58bVOzZD+5Zqm PpJWfXEyUsdejX8TI4Ua7wGpeiFC89T47PPO6acEU68EGYjPY3UhQxSP6JOBTVFI9mDZ TxP/V6xNVxz6/al7aZdFg0v/MzkCAcHjpWKYDAu/n/8vSD7uKP+ip6ckn3JO/tq6Au+Z HcCvDJQyQGItlrvi4B2Fh5a++J5C6bnPWb7OcB2y0ExxbKbcswnSi6IhAHyl2oOTVyoi 6QtilwzyyaBRCeM7R7A1XVycW1dpdxbtcVz6218wL83TOJ+yJDhue3u7TJCdkzY9Mc/1 fjVQ== X-Gm-Message-State: AFuF++khberKzyAWDVmgp9oM1XUbxhNoMyJo4oNnW3MFkr1uWhHfyXy3 xtYg4E3D6GCS0IxYOEQa5/LRvwk+nB3IIsLsEtegKE450JkSnE+He2ps X-Gm-Gg: AYBFou0xy2LgNx4Q68eykuXNOJ9ibSH/MOiYpiSi5tdgx8rvpQnF3w3prQ4dyT9xkqT CIPqs2jxjL0msU/dFNcWgQ2u/laUUX/tgAuPvJhaL4BtJSOuTBV3WVo+I/lSnJBlYK2wZFw+WnD 6CWVEL5Zya/fBW/OOk+ONbKPFyfUXL6hpvpD/jKOFqzkA4l9ZfENn9wsXV0dp6zuV6EQX9yA9Ai 372lnyG5lC34Yz5yL8Rr7YMm0MPI6G78l8CRANgN5V9blJ7nQSs4u12w4w+uiK+GmgC8FuDif6n 71AAuHOAUcl0i080G7DeUx/O4CY7vmriBbPT5NrpFGrTQEqVgb6V+p0Y258zrOPwPOul/11wfaf k+1ZSsGa5VS/BKjInJgXRqNk71XpTq+CqNXyUoYK4x8vtrMDdvlSURGZ2lARurnjWbRE+fwMt/N Rxy/TxVDBCwGo8y0AV4WSqyk2zKqerdr9iopUKo6bJvq6fAHhgjYsfX0asq4T9RjF/D9/7olXiP OHlc89RpR+CazGSTtmZMQUhhM+LiuPF0L/rCA== X-Received: by 2002:a05:620a:6890:b0:93b:d79b:9a5c with SMTP id af79cd13be357-93c15f4d6f4mr535992485a.61.1790082015597; Tue, 22 Sep 2026 06:00:15 -0700 (PDT) Received: from dhcp-9-123-8-113.bl1-in.ibm.com ([129.41.58.1]) by smtp.gmail.com with ESMTPSA id af79cd13be357-93c1d131c61sm135787285a.7.2026.09.22.06.00.12 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Tue, 22 Sep 2026 06:00:15 -0700 (PDT) From: Aditya Vidyadhar Kamath 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, Aditya Vidyadhar Kamath Subject: [PATCH v5 2/2] Add TLS variable support for shared libraries on AIX Date: Tue, 22 Sep 2026 18:30:02 +0530 Message-ID: <20260922130001.65051-2-akamath996@gmail.com> X-Mailer: git-send-email 2.43.0 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org From: Aditya Vidyadhar Kamath 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::key + 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 (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, + ""); + return tls_base + offset; } static void -- 2.51.2