/* 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 , 2003 Code updated by Mark Hatle , 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. */ #include #include #include #include #include #include #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_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; default: /* Hope there's no TLS! */ for (i = 0; i < search_list->r_nlist; i++) { struct ldlibs_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; } /* from eglibc 2.13: libc/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 == 1) { /* We simply start with zero. */ uint64_t offset = 0; for (i = 0; i < search_list->r_nlist; i++) { struct ldlibs_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; 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; map->l_tls_offset = off; } } else if (tls_dtv_at_tp == 1) { uint64_t offset = tls_tcb_size; for (i = 0; i < search_list->r_nlist; i++) { struct ldlibs_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 { /* Should never happen... */ _dl_signal_error(0, NULL, NULL, "Neither TLS_TCB_AT_TP nor TLS_DTV_AT_TP is defined for this architecture"); } }