diff options
Diffstat (limited to 'elf/dl-tls.c')
-rw-r--r-- | elf/dl-tls.c | 213 |
1 files changed, 105 insertions, 108 deletions
diff --git a/elf/dl-tls.c b/elf/dl-tls.c index ed13fd950b..c87caf13d6 100644 --- a/elf/dl-tls.c +++ b/elf/dl-tls.c @@ -1,5 +1,5 @@ /* Thread-local storage handling in the ELF dynamic linker. Generic version. - Copyright (C) 2002-2016 Free Software Foundation, Inc. + Copyright (C) 2002-2018 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 @@ -44,7 +44,6 @@ oom (void) size_t -internal_function _dl_next_tls_modid (void) { size_t result; @@ -105,7 +104,6 @@ _dl_next_tls_modid (void) size_t -internal_function _dl_count_modids (void) { /* It is rare that we have gaps; see elf/dl-open.c (_dl_open) where @@ -133,7 +131,6 @@ _dl_count_modids (void) #ifdef SHARED void -internal_function _dl_determine_tlsoffset (void) { size_t max_align = TLS_TCB_ALIGN; @@ -274,42 +271,9 @@ _dl_determine_tlsoffset (void) /* The alignment requirement for the static TLS block. */ GL(dl_tls_static_align) = max_align; } - - -/* This is called only when the data structure setup was skipped at startup, - when there was no need for it then. Now we have dynamically loaded - something needing TLS, or libpthread needs it. */ -int -internal_function -_dl_tls_setup (void) -{ - assert (GL(dl_tls_dtv_slotinfo_list) == NULL); - assert (GL(dl_tls_max_dtv_idx) == 0); - - const size_t nelem = 2 + TLS_SLOTINFO_SURPLUS; - - GL(dl_tls_dtv_slotinfo_list) - = calloc (1, (sizeof (struct dtv_slotinfo_list) - + nelem * sizeof (struct dtv_slotinfo))); - if (GL(dl_tls_dtv_slotinfo_list) == NULL) - return -1; - - GL(dl_tls_dtv_slotinfo_list)->len = nelem; - - /* Number of elements in the static TLS block. It can't be zero - because of various assumptions. The one element is null. */ - GL(dl_tls_static_nelem) = GL(dl_tls_max_dtv_idx) = 1; - - /* This initializes more variables for us. */ - _dl_determine_tlsoffset (); - - return 0; -} -rtld_hidden_def (_dl_tls_setup) -#endif +#endif /* SHARED */ static void * -internal_function allocate_dtv (void *result) { dtv_t *dtv; @@ -340,16 +304,30 @@ allocate_dtv (void *result) /* Get size and alignment requirements of the static TLS block. */ void -internal_function _dl_get_tls_static_info (size_t *sizep, size_t *alignp) { *sizep = GL(dl_tls_static_size); *alignp = GL(dl_tls_static_align); } +/* Derive the location of the pointer to the start of the original + allocation (before alignment) from the pointer to the TCB. */ +static inline void ** +tcb_to_pointer_to_free_location (void *tcb) +{ +#if TLS_TCB_AT_TP + /* The TCB follows the TLS blocks, and the pointer to the front + follows the TCB. */ + void **original_pointer_location = tcb + TLS_TCB_SIZE; +#elif TLS_DTV_AT_TP + /* The TCB comes first, preceded by the pre-TCB, and the pointer is + before that. */ + void **original_pointer_location = tcb - TLS_PRE_TCB_SIZE - sizeof (void *); +#endif + return original_pointer_location; +} void * -internal_function _dl_allocate_tls_storage (void) { void *result; @@ -359,39 +337,50 @@ _dl_allocate_tls_storage (void) /* Memory layout is: [ TLS_PRE_TCB_SIZE ] [ TLS_TCB_SIZE ] [ TLS blocks ] ^ This should be returned. */ - size += (TLS_PRE_TCB_SIZE + GL(dl_tls_static_align) - 1) - & ~(GL(dl_tls_static_align) - 1); + size += TLS_PRE_TCB_SIZE; #endif - /* Allocate a correctly aligned chunk of memory. */ - result = __libc_memalign (GL(dl_tls_static_align), size); - if (__builtin_expect (result != NULL, 1)) - { - /* Allocate the DTV. */ - void *allocated = result; + /* Perform the allocation. Reserve space for the required alignment + and the pointer to the original allocation. */ + size_t alignment = GL(dl_tls_static_align); + void *allocated = malloc (size + alignment + sizeof (void *)); + if (__glibc_unlikely (allocated == NULL)) + return NULL; + /* Perform alignment and allocate the DTV. */ #if TLS_TCB_AT_TP - /* The TCB follows the TLS blocks. */ - result = (char *) result + 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); + /* The TCB follows the TLS blocks, which determine the alignment. + (TCB alignment requirements have been taken into account when + calculating GL(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 - result = (char *) result + size - GL(dl_tls_static_size); - - /* 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 ((char *) result - TLS_PRE_TCB_SIZE, '\0', - TLS_PRE_TCB_SIZE + TLS_TCB_SIZE); + /* 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 - result = allocate_dtv (result); - if (result == NULL) - free (allocated); - } + /* Record the value of the original pointer for later + deallocation. */ + *tcb_to_pointer_to_free_location (result) = allocated; + result = allocate_dtv (result); + if (result == NULL) + free (allocated); return result; } @@ -444,7 +433,6 @@ _dl_resize_dtv (dtv_t *dtv) void * -internal_function _dl_allocate_tls_init (void *result) { if (result == NULL) @@ -494,7 +482,7 @@ _dl_allocate_tls_init (void *result) maxgen = MAX (maxgen, listp->slotinfo[cnt].gen); dtv[map->l_tls_modid].pointer.val = TLS_DTV_UNALLOCATED; - dtv[map->l_tls_modid].pointer.is_static = false; + dtv[map->l_tls_modid].pointer.to_free = NULL; if (map->l_tls_offset == NO_TLS_OFFSET || map->l_tls_offset == FORCED_DYNAMIC_TLS_OFFSET) @@ -511,6 +499,10 @@ _dl_allocate_tls_init (void *result) # error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined" #endif + /* Set up the DTV entry. The simplified __tls_get_addr that + some platforms use in static programs requires it. */ + dtv[map->l_tls_modid].pointer.val = dest; + /* Copy the initialization image and clear the BSS part. */ memset (__mempcpy (dest, map->l_tls_initimage, map->l_tls_initimage_size), '\0', @@ -533,7 +525,6 @@ _dl_allocate_tls_init (void *result) rtld_hidden_def (_dl_allocate_tls_init) void * -internal_function _dl_allocate_tls (void *mem) { return _dl_allocate_tls_init (mem == NULL @@ -544,33 +535,20 @@ rtld_hidden_def (_dl_allocate_tls) void -internal_function _dl_deallocate_tls (void *tcb, bool dealloc_tcb) { dtv_t *dtv = GET_DTV (tcb); /* We need to free the memory allocated for non-static TLS. */ for (size_t cnt = 0; cnt < dtv[-1].counter; ++cnt) - if (! dtv[1 + cnt].pointer.is_static - && dtv[1 + cnt].pointer.val != TLS_DTV_UNALLOCATED) - free (dtv[1 + cnt].pointer.val); + free (dtv[1 + cnt].pointer.to_free); /* The array starts with dtv[-1]. */ if (dtv != GL(dl_initial_dtv)) free (dtv - 1); if (dealloc_tcb) - { -#if TLS_TCB_AT_TP - /* The TCB follows the TLS blocks. Back up to free the whole block. */ - tcb -= GL(dl_tls_static_size) - TLS_TCB_SIZE; -#elif TLS_DTV_AT_TP - /* Back up the TLS_PRE_TCB_SIZE bytes. */ - tcb -= (TLS_PRE_TCB_SIZE + GL(dl_tls_static_align) - 1) - & ~(GL(dl_tls_static_align) - 1); -#endif - free (tcb); - } + free (*tcb_to_pointer_to_free_location (tcb)); } rtld_hidden_def (_dl_deallocate_tls) @@ -594,21 +572,49 @@ rtld_hidden_def (_dl_deallocate_tls) # define GET_ADDR_OFFSET ti->ti_offset # endif +/* Allocate one DTV entry. */ +static struct dtv_pointer +allocate_dtv_entry (size_t alignment, size_t size) +{ + if (powerof2 (alignment) && alignment <= _Alignof (max_align_t)) + { + /* The alignment is supported by malloc. */ + void *ptr = malloc (size); + return (struct dtv_pointer) { ptr, ptr }; + } + + /* Emulate memalign to by manually aligning a pointer returned by + malloc. First compute the size with an overflow check. */ + size_t alloc_size = size + alignment; + if (alloc_size < size) + return (struct dtv_pointer) {}; -static void * + /* Perform the allocation. This is the pointer we need to free + later. */ + void *start = malloc (alloc_size); + if (start == NULL) + return (struct dtv_pointer) {}; + + /* Find the aligned position within the larger allocation. */ + void *aligned = (void *) roundup ((uintptr_t) start, alignment); + + return (struct dtv_pointer) { .val = aligned, .to_free = start }; +} + +static struct dtv_pointer allocate_and_init (struct link_map *map) { - void *newp; - - newp = __libc_memalign (map->l_tls_align, map->l_tls_blocksize); - if (newp == NULL) + struct dtv_pointer result = allocate_dtv_entry + (map->l_tls_align, map->l_tls_blocksize); + if (result.val == NULL) oom (); /* Initialize the memory. */ - memset (__mempcpy (newp, map->l_tls_initimage, map->l_tls_initimage_size), + memset (__mempcpy (result.val, map->l_tls_initimage, + map->l_tls_initimage_size), '\0', map->l_tls_blocksize - map->l_tls_initimage_size); - return newp; + return result; } @@ -678,12 +684,9 @@ _dl_update_slotinfo (unsigned long int req_modid) { /* If this modid was used at some point the memory might still be allocated. */ - if (! dtv[total + cnt].pointer.is_static - && (dtv[total + cnt].pointer.val - != TLS_DTV_UNALLOCATED)) - free (dtv[total + cnt].pointer.val); + free (dtv[total + cnt].pointer.to_free); dtv[total + cnt].pointer.val = TLS_DTV_UNALLOCATED; - dtv[total + cnt].pointer.is_static = false; + dtv[total + cnt].pointer.to_free = NULL; } continue; @@ -708,16 +711,9 @@ _dl_update_slotinfo (unsigned long int req_modid) dtv entry free it. */ /* XXX Ideally we will at some point create a memory pool. */ - if (! dtv[modid].pointer.is_static - && dtv[modid].pointer.val != TLS_DTV_UNALLOCATED) - /* Note that free is called for NULL is well. We - deallocate even if it is this dtv entry we are - supposed to load. The reason is that we call - memalign and not malloc. */ - free (dtv[modid].pointer.val); - + free (dtv[modid].pointer.to_free); dtv[modid].pointer.val = TLS_DTV_UNALLOCATED; - dtv[modid].pointer.is_static = false; + dtv[modid].pointer.to_free = NULL; if (modid == req_modid) the_map = map; @@ -780,7 +776,7 @@ tls_get_addr_tail (GET_ADDR_ARGS, dtv_t *dtv, struct link_map *the_map) #endif __rtld_lock_unlock_recursive (GL(dl_load_lock)); - dtv[GET_ADDR_MODULE].pointer.is_static = true; + dtv[GET_ADDR_MODULE].pointer.to_free = NULL; dtv[GET_ADDR_MODULE].pointer.val = p; return (char *) p + GET_ADDR_OFFSET; @@ -788,10 +784,11 @@ tls_get_addr_tail (GET_ADDR_ARGS, dtv_t *dtv, struct link_map *the_map) else __rtld_lock_unlock_recursive (GL(dl_load_lock)); } - void *p = dtv[GET_ADDR_MODULE].pointer.val = allocate_and_init (the_map); - assert (!dtv[GET_ADDR_MODULE].pointer.is_static); + struct dtv_pointer result = allocate_and_init (the_map); + dtv[GET_ADDR_MODULE].pointer = result; + assert (result.to_free != NULL); - return (char *) p + GET_ADDR_OFFSET; + return (char *) result.val + GET_ADDR_OFFSET; } |