aboutsummaryrefslogtreecommitdiffstats
path: root/src/rtld/dl-tls.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/rtld/dl-tls.c')
-rw-r--r--src/rtld/dl-tls.c287
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 */ }
+}