diff options
Diffstat (limited to 'src/rtld/dl-tls.c')
-rw-r--r-- | src/rtld/dl-tls.c | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/src/rtld/dl-tls.c b/src/rtld/dl-tls.c new file mode 100644 index 0000000..8b972ff --- /dev/null +++ b/src/rtld/dl-tls.c @@ -0,0 +1,287 @@ +/* Copyright (C) 2011 Wind River Systems, Inc. + + Code reorganized from original code bearing the following copyright: + Copyright (C) 2003 MontaVista Software, Inc. + Written by Daniel Jacobowitz <drow@mvista.com>, 2003 + + Code updated by Mark Hatle <mark.hatle@windriver.com>, 2011 + to sync to eglibc 2.13 tls behavior... + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +/* glibc 2.22: elf/dl-tls.c */ + +/* Thread-local storage handling in the ELF dynamic linker. Generic version. + Copyright (C) 2002-2015 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 + <http://www.gnu.org/licenses/>. */ + +#include <config.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "rtld.h" + +/* Assign TLS offsets for every loaded library. This code is taken + almost directly from glibc! */ + +#define roundup(x, y) ((((x) + ((y) - 1)) / (y)) * (y)) + +/* The following function needs to mimic the _dl_determine_tlsoffset in eglibc */ +void +rtld_determine_tlsoffsets (int e_machine, struct r_scope_elem *search_list) +{ + uint64_t modid = 1; + + uint64_t i; + + /* skip max_align */ + uint64_t freetop = 0; + uint64_t freebottom = 0; + + /* This comes from each architecture's ABI. If TLS_TCB_AT_TP, then + set offset to -1; if TLS_DTV_AT_TP, then set offset to + TLS_TCB_SIZE. */ + + int tls_tcb_at_tp = 0; + int tls_dtv_at_tp = 0; + uint64_t tls_tcb_size; + + switch (e_machine) + { + case EM_X86_64: + tls_tcb_at_tp = 1; + tls_tcb_size = -1; + break; + + case EM_386: + tls_tcb_at_tp = 1; + tls_tcb_size = -1; + break; + + case EM_SH: + tls_dtv_at_tp = 1; + tls_tcb_size = 8; + break; + + case EM_PPC: + tls_dtv_at_tp = 1; + tls_tcb_size = 0; + break; + + case EM_PPC64: + tls_dtv_at_tp = 1; + tls_tcb_size = 0; + break; + + case EM_ARM: + tls_dtv_at_tp = 1; + tls_tcb_size = 8; + break; + + case EM_AARCH64: + tls_dtv_at_tp = 1; + tls_tcb_size = 16; + break; + + case EM_MIPS: + tls_dtv_at_tp = 1; + tls_tcb_size = 0; + break; + + case EM_SPARC: + case EM_SPARC32PLUS: + tls_tcb_at_tp = 1; + tls_tcb_size = -1; + break; + + case EM_SPARCV9: + tls_tcb_at_tp = 1; + tls_tcb_size = -1; + break; + + case EM_ALTERA_NIOS2: + tls_dtv_at_tp = 1; + tls_tcb_size = 0; + break; + + default: + /* Hope there's no TLS! */ + for (i = 0; i < search_list->r_nlist; i++) + { + struct link_map *map = search_list->r_list[i]; + + if (map->l_tls_blocksize > 0) + _dl_signal_error(0, map->l_name, NULL, "cannot handle TLS data"); + } + + return; + } + + /* eglibc 2.20: elf/dl-tls.c: _dl_determine_tlsoffset (void) */ + /* Determining the offset of the various parts of the static TLS + block has several dependencies. In addition we have to work + around bugs in some toolchains. + + Each TLS block from the objects available at link time has a size + and an alignment requirement. The GNU ld computes the alignment + requirements for the data at the positions *in the file*, though. + I.e, it is not simply possible to allocate a block with the size + of the TLS program header entry. The data is layed out assuming + that the first byte of the TLS block fulfills + + p_vaddr mod p_align == &TLS_BLOCK mod p_align + + This means we have to add artificial padding at the beginning of + the TLS block. These bytes are never used for the TLS data in + this module but the first byte allocated must be aligned + according to mod p_align == 0 so that the first byte of the TLS + block is aligned according to p_vaddr mod p_align. This is ugly + and the linker can help by computing the offsets in the TLS block + assuming the first byte of the TLS block is aligned according to + p_align. + + The extra space which might be allocated before the first byte of + the TLS block need not go unused. The code below tries to use + that memory for the next TLS block. This can work if the total + memory requirement for the next TLS block is smaller than the + gap. */ + + /* Loop over the loaded DSOs. We use the symbol search order; this + should be the same as glibc's ordering, which traverses l_next. + It's somewhat important that we use both the same ordering to + assign module IDs and the same algorithm to assign offsets, + because the prelinker will resolve all relocations using these + offsets... and then glibc will recalculate them. Future dynamic + relocations in any loaded modules will use glibc's values. Also + if we take too much space here, glibc won't allocate enough + static TLS area to hold it. */ + +/* #if TLS_TCB_AT_TP */ if (tls_tcb_at_tp == 1) { + /* We simply start with zero. */ + uint64_t offset = 0; + + for (i = 0; i < search_list->r_nlist; i++) + { + struct link_map *map = search_list->r_list[i]; + + uint64_t firstbyte = (-map->l_tls_firstbyte_offset + & (map->l_tls_align - 1)); + uint64_t off; + + /* elf/rtld.c would have caused us to skip this block.. so emulate this */ + if (map->l_tls_blocksize == 0) + continue; + + /* allocate_tls_init would nomrally Increment the module id */ + map->l_tls_modid = modid++; + + if (freebottom - freetop >= map->l_tls_blocksize) + { + off = roundup (freetop + map->l_tls_blocksize + - firstbyte, map->l_tls_align) + + firstbyte; + if (off <= freebottom) + { + freetop = off; + + /* XXX For some architectures we perhaps should store the + negative offset. */ + map->l_tls_offset = off; + continue; + } + } + + off = roundup (offset + map->l_tls_blocksize - firstbyte, + map->l_tls_align) + firstbyte; + if (off > offset + map->l_tls_blocksize + + (freebottom - freetop)) + { + freetop = offset; + freebottom = off - map->l_tls_blocksize; + } + offset = off; + + /* XXX For some architectures we perhaps should store the + negative offset. */ + map->l_tls_offset = off; + } +/* #elif TLS_DTV_AT_TP */ } else if (tls_dtv_at_tp == 1) { + /* The TLS blocks start right after the TCB. */ + uint64_t offset = tls_tcb_size; + + for (i = 0; i < search_list->r_nlist; i++) + { + struct link_map *map = search_list->r_list[i]; + + uint64_t firstbyte = (-map->l_tls_firstbyte_offset + & (map->l_tls_align - 1)); + uint64_t off; + + /* elf/rtld.c would have caused us to skip this block.. so emulate this */ + if (map->l_tls_blocksize == 0) + continue; + + /* allocate_tls_init would nomrally Increment the module id */ + map->l_tls_modid = modid++; + + if (map->l_tls_blocksize <= freetop - freebottom) + { + off = roundup (freebottom, map->l_tls_align); + if (off - freebottom < firstbyte) + off += map->l_tls_align; + if (off + map->l_tls_blocksize - firstbyte <= freetop) + { + map->l_tls_offset = off - firstbyte; + freebottom = (off + map->l_tls_blocksize + - firstbyte); + continue; + } + } + + off = roundup (offset, map->l_tls_align); + if (off - offset < firstbyte) + off += map->l_tls_align; + + map->l_tls_offset = off - firstbyte; + if (off - firstbyte - offset > freetop - freebottom) + { + freebottom = offset; + freetop = off - firstbyte; + } + + offset = off + map->l_tls_blocksize - firstbyte; + } +/* #else */ } else { + /* Should never happen... */ + _dl_signal_error(0, NULL, NULL, "Neither TLS_TCB_AT_TP nor TLS_DTV_AT_TP is defined for this architecture"); +/* #endif */ } +} |