[PATCH v2 2/3] Consolidate TLS block allocation for static binaries with ld.so
Adhemerval Zanella Netto
adhemerval.zanella@linaro.org
Tue Jan 14 19:16:27 GMT 2025
On 13/01/25 09:33, Florian Weimer wrote:
> Use the same code to compute the TLS block size and its alignment.
> The code in elf/dl-tls.c is linked in anyway for all binaries
> due to the reference to _dl_tls_static_surplus_init.
>
> It is not possible to call _dl_allocate_tls_storage directly
> because malloc is not available in the static case. (The
> dynamic linker uses the minimal malloc at this stage.) Therefore,
> split _dl_tls_block_size_with_pre and _dl_tls_block_align from
> _dl_allocate_tls_storage, and call those new functions from
> __libc_setup_tls.
>
> This fixes extra TLS allocation for the static case, and apparently
> some pre-existing bugs as well (the independent recomputation of
> TLS block sizes in init_static_tls looks rather suspect).
>
> Fixes commit 0e411c5d3098982d67cd2d7a233eaa6c977a1869 ("Add generic
> 'extra TLS'").
It is not clear which change exactly fixes the 0e411c5d30989, because
consolidation suggests that functionality won't changed.
In any case LGTM, a minor suggestion below.
> ---
> csu/libc-tls.c | 193 ++++++++-------------------------------
> elf/dl-tls.c | 50 ++--------
> elf/dl-tls_block_align.h | 70 ++++++++++++++
> 3 files changed, 114 insertions(+), 199 deletions(-)
> create mode 100644 elf/dl-tls_block_align.h
>
> diff --git a/csu/libc-tls.c b/csu/libc-tls.c
> index 5ffebc6fc2..ea59a46efe 100644
> --- a/csu/libc-tls.c
> +++ b/csu/libc-tls.c
> @@ -28,6 +28,7 @@
> #include <pthreadP.h>
> #include <dl-call_tls_init_tp.h>
> #include <dl-extra_tls.h>
> +#include <elf/dl-tls_block_align.h>
>
> #ifdef SHARED
> #error makefile bug, this file is for static only
> @@ -74,7 +75,7 @@ TLS_INIT_HELPER
> #endif
>
> static void
> -init_slotinfo (void)
> +init_slotinfo (struct link_map *main_map)
> {
> /* Create the slotinfo list. Note that the type of static_slotinfo
> has effectively a zero-length array, so we cannot use the size of
> @@ -82,39 +83,26 @@ init_slotinfo (void)
> static_slotinfo.len = array_length (_dl_static_dtv);
> /* static_slotinfo.next = NULL; -- Already zero. */
>
> + main_map->l_tls_modid = 1;
> + static_slotinfo.slotinfo[1].map = main_map;
> + main_map->l_tls_in_slotinfo = 1;
> +
> /* The slotinfo list. Will be extended by the code doing dynamic
> linking. */
> GL(dl_tls_max_dtv_idx) = 1;
> GL(dl_tls_dtv_slotinfo_list) = &static_slotinfo;
> }
>
> -static void
> -init_static_tls (size_t memsz, size_t align)
> -{
> - /* That is the size of the TLS memory for this object. */
> - GL(dl_tls_static_size) = roundup (memsz + GLRO(dl_tls_static_surplus),
> - TCB_ALIGNMENT);
> -#if TLS_TCB_AT_TP
> - GL(dl_tls_static_size) += TLS_TCB_SIZE;
> -#endif
> - GL(dl_tls_static_used) = memsz;
> - /* The alignment requirement for the static TLS block. */
> - GL(dl_tls_static_align) = align;
> - /* Number of elements in the static TLS block. */
> - GL(dl_tls_static_nelem) = GL(dl_tls_max_dtv_idx);
> -}
> -
> +/* Perform TLS setup for statically linked binaries. Similar to
> + init_tls in elf/rtld.c. */
> void
> __libc_setup_tls (void)
> {
> - void *tlsblock;
> size_t memsz = 0;
> size_t filesz = 0;
> void *initimage = NULL;
> size_t align = 0;
> - size_t tls_blocks_size = 0;
> size_t max_align = TCB_ALIGNMENT;
> - size_t tcb_offset;
> const ElfW(Phdr) *phdr;
>
> struct link_map *main_map = GL(dl_ns)[LM_ID_BASE]._ns_loaded;
> @@ -132,152 +120,45 @@ __libc_setup_tls (void)
> align = phdr->p_align;
> if (phdr->p_align > max_align)
> max_align = phdr->p_align;
> +
> + main_map->l_tls_align = align;
> + main_map->l_tls_blocksize = memsz;
> + main_map->l_tls_initimage = initimage;
> + main_map->l_tls_initimage_size = filesz;
> + init_slotinfo (main_map);
> break;
> }
>
> + /* Number of elements in the static TLS block. */
> + GL(dl_tls_static_nelem) = GL(dl_tls_max_dtv_idx);
> +
> /* Calculate the size of the static TLS surplus, with 0 auditors. */
> _dl_tls_static_surplus_init (0);
>
> - /* Extra TLS block for internal usage to append at the end of the TLS blocks
> - (in allocation order). The address at which the block is allocated must
> - be aligned to 'extra_tls_align'. The size of the block as returned by
> - '_dl_extra_tls_get_size ()' is always a multiple of the aligment.
> -
> - On Linux systems this is where the rseq area will be allocated. On other
> - systems it is currently unused and both values will be '0'. */
> - size_t extra_tls_size = _dl_extra_tls_get_size ();
> - size_t extra_tls_align = _dl_extra_tls_get_align ();
> -
> - /* Increase the maximum alignment with the extra TLS alignment requirements
> - if necessary. */
> - max_align = MAX (max_align, extra_tls_align);
> -
> - /* We have to set up the TCB block which also (possibly) contains
> - 'errno'. Therefore we avoid 'malloc' which might touch 'errno'.
> - Instead we use 'sbrk' which would only uses 'errno' if it fails.
> - In this case we are right away out of memory and the user gets
> - what she/he deserves. */
> -#if TLS_TCB_AT_TP
> - /* In this layout the TLS blocks are located before the thread pointer. */
> -
> - /* Record the size of the combined TLS blocks.
> -
> - First reserve space for 'memsz' while respecting both its alignment
> - requirements and those of the extra TLS blocks. Then add the size of
> - the extra TLS block. Both values respect the extra TLS alignment
> - requirements and so does the resulting size and the offset that will
> - be derived from it. */
> - tls_blocks_size = roundup (memsz, MAX (align, extra_tls_align) ?: 1)
> - + extra_tls_size;
> -
> - /* Record the extra TLS block offset from the thread pointer.
> -
> - With TLS_TCB_AT_TP the TLS blocks are allocated before the thread pointer
> - in reverse order. Our block is added last which results in it being the
> - first in the static TLS block, thus record the most negative offset.
> + /* Calculate the TLS block size. */
> + _dl_determine_tlsoffset ();
>
> - The alignment requirements of the pointer resulting from this offset and
> - the thread pointer are enforced by 'max_align' which is used to align the
> - tcb_offset. */
> - _dl_extra_tls_set_offset (-tls_blocks_size);
> -
> - /* Align the TCB offset to the maximum alignment, as
> - _dl_allocate_tls_storage (in elf/dl-tls.c) does using __libc_memalign
> - and dl_tls_static_align. */
> - tcb_offset = roundup (tls_blocks_size + GLRO(dl_tls_static_surplus), max_align);
> - tlsblock = _dl_early_allocate (tcb_offset + TLS_INIT_TCB_SIZE + max_align);
> - if (tlsblock == NULL)
> - _startup_fatal_tls_error ();
> -#elif TLS_DTV_AT_TP
> - /* In this layout the TLS blocks are located after the thread pointer. */
> -
> - /* Record the tcb_offset including the aligment requirements of 'memsz'
> - that comes after it. */
> - tcb_offset = roundup (TLS_INIT_TCB_SIZE, align ?: 1);
> -
> - /* Record the size of the combined TLS blocks.
> -
> - First reserve space for TLS_INIT_TCB_SIZE and 'memsz' while respecting
> - both its alignment requirements and those of the extra TLS blocks. Then
> - add the size of the extra TLS block. Both values respect the extra TLS
> - alignment requirements and so does the resulting size and the offset that
> - will be derived from it. */
> - tls_blocks_size = roundup (TLS_INIT_TCB_SIZE + memsz,
> - MAX (align, extra_tls_align) ?: 1) + extra_tls_size;
> -
> - /* Record the extra TLS block offset from the thread pointer.
> -
> - With TLS_DTV_AT_TP the TLS blocks are allocated after the thread pointer in
> - order. Our block is added last which results in it being the last in the
> - static TLS block, thus record the offset as the size of the static TLS
> - block minus the size of our block.
> -
> - On some architectures the TLS blocks are offset from the thread pointer,
> - include this offset in the extra TLS block offset.
> -
> - The alignment requirements of the pointer resulting from this offset and
> - the thread pointer are enforced by 'max_align' which is used to align the
> - tcb_offset. */
> - _dl_extra_tls_set_offset (tls_blocks_size - extra_tls_size - TLS_TP_OFFSET);
> -
> - tlsblock = _dl_early_allocate (tls_blocks_size + max_align
> - + TLS_PRE_TCB_SIZE
> - + GLRO(dl_tls_static_surplus));
> - if (tlsblock == NULL)
> - _startup_fatal_tls_error ();
> - tlsblock += TLS_PRE_TCB_SIZE;
> -#else
> - /* In case a model with a different layout for the TCB and DTV
> - is defined add another #elif here and in the following #ifs. */
> -# error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
> -#endif
> -
> - /* Align the TLS block. */
> - tlsblock = (void *) (((uintptr_t) tlsblock + max_align - 1)
> - & ~(max_align - 1));
> + /* See _dl_allocate_tls_storage in elf/dl-tls.c. */
> + void *tcbp;
> + {
> + size_t size = _dl_tls_block_size_with_pre ();
> + void *allocated = _dl_early_allocate (size + GLRO (dl_tls_static_align));
> + if (allocated == NULL)
> + _startup_fatal_tls_error ();
> + tcbp = _dl_tls_block_align (size, allocated);
> + }
>
> /* Initialize the dtv. [0] is the length, [1] the generation counter. */
> - _dl_static_dtv[0].counter = (sizeof (_dl_static_dtv) / sizeof (_dl_static_dtv[0])) - 2;
> - // _dl_static_dtv[1].counter = 0; would be needed if not already done
> -
> - /* Initialize the TLS block. */
> -#if TLS_TCB_AT_TP
> - _dl_static_dtv[2].pointer.val = ((char *) tlsblock + tcb_offset
> - - roundup (memsz, align ?: 1));
> - main_map->l_tls_offset = roundup (memsz, align ?: 1);
> -#elif TLS_DTV_AT_TP
> - _dl_static_dtv[2].pointer.val = (char *) tlsblock + tcb_offset;
> - main_map->l_tls_offset = tcb_offset;
> -#else
> -# error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
> -#endif
> - _dl_static_dtv[2].pointer.to_free = NULL;
> - /* sbrk gives us zero'd memory, so we don't need to clear the remainder. */
> - memcpy (_dl_static_dtv[2].pointer.val, initimage, filesz);
> -
> - /* Install the pointer to the dtv. */
> + _dl_static_dtv[0].counter = (sizeof (_dl_static_dtv)
> + / sizeof (_dl_static_dtv[0])) - 2;
Maybe array_length() here?
>
> - /* Initialize the thread pointer. */
> -#if TLS_TCB_AT_TP
> - INSTALL_DTV ((char *) tlsblock + tcb_offset, _dl_static_dtv);
> + /* Install the pointer to the DTV. See allocate_dtv in elf/dl-tls.c. */
> + INSTALL_DTV (tcbp, _dl_static_dtv);
>
> - call_tls_init_tp ((char *) tlsblock + tcb_offset);
> -#elif TLS_DTV_AT_TP
> - INSTALL_DTV (tlsblock, _dl_static_dtv);
> - call_tls_init_tp (tlsblock);
> -#endif
> -
> - /* Update the executable's link map with enough information to make
> - the TLS routines happy. */
> - main_map->l_tls_align = align;
> - main_map->l_tls_blocksize = memsz;
> - main_map->l_tls_initimage = initimage;
> - main_map->l_tls_initimage_size = filesz;
> - main_map->l_tls_modid = 1;
> -
> - init_slotinfo ();
> - /* static_slotinfo.slotinfo[1].gen = 0; -- Already zero. */
> - static_slotinfo.slotinfo[1].map = main_map;
> + /* _dl_allocate_tls_init uses recursive locking and the TCB, so this
> + has to come first. */
> + call_tls_init_tp (tcbp);
>
> - init_static_tls (tls_blocks_size, MAX (TCB_ALIGNMENT, max_align));
> + /* Initialize the TLS image for the allocated TCB. */
> + _dl_allocate_tls_init (tcbp, true);
> }
> diff --git a/elf/dl-tls.c b/elf/dl-tls.c
> index 647deaf6d0..8306a39e8d 100644
> --- a/elf/dl-tls.c
> +++ b/elf/dl-tls.c
> @@ -28,6 +28,7 @@
> #include <tls.h>
> #include <dl-tls.h>
> #include <ldsodefs.h>
> +#include <dl-tls_block_align.h>
>
> #if PTHREAD_IN_LIBC
> # include <list.h>
> @@ -237,7 +238,6 @@ _dl_count_modids (void)
> }
>
>
> -#ifdef SHARED
> void
> _dl_determine_tlsoffset (void)
> {
> @@ -446,7 +446,6 @@ _dl_determine_tlsoffset (void)
> /* The alignment requirement for the static TLS block. */
> GLRO (dl_tls_static_align) = max_align;
> }
> -#endif /* SHARED */
>
> static void *
> allocate_dtv (void *result)
> @@ -508,55 +507,20 @@ tcb_to_pointer_to_free_location (void *tcb)
> void *
> _dl_allocate_tls_storage (void)
> {
> - void *result;
> - size_t size = GLRO (dl_tls_static_size);
> -
> -#if TLS_DTV_AT_TP
> - /* Memory layout is:
> - [ TLS_PRE_TCB_SIZE ] [ TLS_TCB_SIZE ] [ TLS blocks ]
> - ^ This should be returned. */
> - size += TLS_PRE_TCB_SIZE;
> -#endif
> -
> - /* Reserve space for the required alignment and the pointer to the
> - original allocation. */
> - size_t alignment = GLRO (dl_tls_static_align);
> + size_t size = _dl_tls_block_size_with_pre ();
>
> - /* Perform the allocation. */
> + /* Perform the allocation. Reserve space for alignment storage of
> + the pointer that will have to be freed. */
> _dl_tls_allocate_begin ();
> - void *allocated = malloc (size + alignment + sizeof (void *));
> + void *allocated = malloc (size + GLRO (dl_tls_static_align)
> + + sizeof (void *));
> if (__glibc_unlikely (allocated == NULL))
> {
> _dl_tls_allocate_end ();
> return NULL;
> }
>
> - /* Perform alignment and allocate the DTV. */
> -#if TLS_TCB_AT_TP
> - /* The TCB follows the TLS blocks, which determine the alignment.
> - (TCB alignment requirements have been taken into account when
> - calculating GLRO (dl_tls_static_align).) */
> - void *aligned = (void *) roundup ((uintptr_t) allocated, alignment);
> - result = aligned + size - TLS_TCB_SIZE;
> -
> - /* Clear the TCB data structure. We can't ask the caller (i.e.
> - libpthread) to do it, because we will initialize the DTV et al. */
> - memset (result, '\0', TLS_TCB_SIZE);
> -#elif TLS_DTV_AT_TP
> - /* Pre-TCB and TCB come before the TLS blocks. The layout computed
> - in _dl_determine_tlsoffset assumes that the TCB is aligned to the
> - TLS block alignment, and not just the TLS blocks after it. This
> - can leave an unused alignment gap between the TCB and the TLS
> - blocks. */
> - result = (void *) roundup
> - (sizeof (void *) + TLS_PRE_TCB_SIZE + (uintptr_t) allocated,
> - alignment);
> -
> - /* Clear the TCB data structure and TLS_PRE_TCB_SIZE bytes before
> - it. We can't ask the caller (i.e. libpthread) to do it, because
> - we will initialize the DTV et al. */
> - memset (result - TLS_PRE_TCB_SIZE, '\0', TLS_PRE_TCB_SIZE + TLS_TCB_SIZE);
> -#endif
> + void *result = _dl_tls_block_align (size, allocated);
>
> /* Record the value of the original pointer for later
> deallocation. */
> diff --git a/elf/dl-tls_block_align.h b/elf/dl-tls_block_align.h
> new file mode 100644
> index 0000000000..82016f3bb5
> --- /dev/null
> +++ b/elf/dl-tls_block_align.h
> @@ -0,0 +1,70 @@
> +/* Computation of TLS block size and its alignment.
> + Copyright (C) 2002-2025 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + modify it under the terms of the GNU Lesser General Public
> + License as published by the Free Software Foundation; either
> + version 2.1 of the License, or (at your option) any later version.
> +
> + The GNU C Library 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
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +/* Compute the size of the TLS block for memory allocation. */
> +static size_t
> +_dl_tls_block_size_with_pre (void)
> +{
> + size_t size = GLRO (dl_tls_static_size);
> +
> +#if TLS_DTV_AT_TP
> + /* Memory layout is:
> + [ TLS_PRE_TCB_SIZE ] [ TLS_TCB_SIZE ] [ TLS blocks ]
> + ^ This should be returned. */
> + size += TLS_PRE_TCB_SIZE;
> +#endif
> + return size;
> +}
> +
> +/* SIZE must be the value returned by _dl_tls_block_size_with_pre.
> + ALLOCATED must point to at least SIZE + GLRO (dl_tls_static_align)
> + bytes. */
> +static void *
> +_dl_tls_block_align (size_t size, void *allocated)
> +{
> + void *result;
> + size_t alignment = GLRO (dl_tls_static_align);
> +
> +#if TLS_TCB_AT_TP
> + /* The TCB follows the TLS blocks, which determine the alignment.
> + (TCB alignment requirements have been taken into account when
> + calculating GLRO (dl_tls_static_align).) */
> + void *aligned = (void *) roundup ((uintptr_t) allocated, alignment);
> + result = aligned + size - TLS_TCB_SIZE;
> +
> + /* Clear the TCB data structure. We can't ask the caller (i.e.
> + libpthread) to do it, because we will initialize the DTV et al. */
> + memset (result, '\0', TLS_TCB_SIZE);
> +#elif TLS_DTV_AT_TP
> + /* Pre-TCB and TCB come before the TLS blocks. The layout computed
> + in _dl_determine_tlsoffset assumes that the TCB is aligned to the
> + TLS block alignment, and not just the TLS blocks after it. This
> + can leave an unused alignment gap between the TCB and the TLS
> + blocks. */
> + result = (void *) roundup
> + (sizeof (void *) + TLS_PRE_TCB_SIZE + (uintptr_t) allocated,
> + alignment);
> +
> + /* Clear the TCB data structure and TLS_PRE_TCB_SIZE bytes before
> + it. We can't ask the caller (i.e. libpthread) to do it, because
> + we will initialize the DTV et al. */
> + memset (result - TLS_PRE_TCB_SIZE, '\0', TLS_PRE_TCB_SIZE + TLS_TCB_SIZE);
> +#endif
> +
> + return result;
> +}
More information about the Libc-alpha
mailing list