diff options
-rw-r--r-- | ldso/include/dl-hash.h | 7 | ||||
-rw-r--r-- | ldso/include/ldsodefs.h | 2 | ||||
-rw-r--r-- | ldso/ldso/dl-elf.c | 94 | ||||
-rw-r--r-- | ldso/ldso/dl-tls.c | 207 | ||||
-rw-r--r-- | ldso/ldso/ldso.c | 91 |
5 files changed, 372 insertions, 29 deletions
diff --git a/ldso/include/dl-hash.h b/ldso/include/dl-hash.h index a0e0130f9..a7ef5c014 100644 --- a/ldso/include/dl-hash.h +++ b/ldso/include/dl-hash.h @@ -121,9 +121,10 @@ static inline int _dl_symbol(char * name) #define LD_ERROR_NOTDYN 5 #define LD_ERROR_MMAP_FAILED 6 #define LD_ERROR_NODYNAMIC 7 -#define LD_WRONG_RELOCS 8 -#define LD_BAD_HANDLE 9 -#define LD_NO_SYMBOL 10 +#define LD_ERROR_TLS_FAILED 8 +#define LD_WRONG_RELOCS 9 +#define LD_BAD_HANDLE 10 +#define LD_NO_SYMBOL 11 diff --git a/ldso/include/ldsodefs.h b/ldso/include/ldsodefs.h index 4b8b0e3d3..08fd7ae09 100644 --- a/ldso/include/ldsodefs.h +++ b/ldso/include/ldsodefs.h @@ -88,6 +88,8 @@ EXTERN size_t _dl_tls_static_size; EXTERN size_t _dl_tls_static_used; /* Alignment requirement of the static TLS block. */ EXTERN size_t _dl_tls_static_align; +/* Function pointer for catching TLS errors. */ +EXTERN void **(*_dl_error_catch_tsd) (void) __attribute__ ((const)); /* Number of additional entries in the slotinfo array of each slotinfo list element. A large number makes it almost certain take we never diff --git a/ldso/ldso/dl-elf.c b/ldso/ldso/dl-elf.c index 28b3094d1..d6962acde 100644 --- a/ldso/ldso/dl-elf.c +++ b/ldso/ldso/dl-elf.c @@ -432,6 +432,87 @@ struct elf_resolve *_dl_load_elf_shared_library(int secure, maxvma = ppnt->p_vaddr + ppnt->p_memsz; } } + + if (ppnt->p_type == PT_TLS) + { +#if USE_TLS + if (ppnt->p_memsz == 0) + /* Nothing to do for an empty segment. */ + continue; + + tpnt->l_tls_blocksize = ppnt->p_memsz; + tpnt->l_tls_align = ppnt->p_align; + if (ppnt->p_align == 0) + tpnt->l_tls_firstbyte_offset = 0; + else + tpnt->l_tls_firstbyte_offset = ppnt->p_vaddr & + (ppnt->p_align - 1); + tpnt->l_tls_initimage_size = ppnt->p_filesz; + /* Since we don't know the load address yet only store the + offset. We will adjust it later. */ + tpnt->l_tls_initimage = (void *) ppnt->p_vaddr; + + /* If _dl_tls_dtv_slotinfo_list == NULL, then ldso.c did + not set up TLS data structures, so don't use them now. */ + if (__builtin_expect (_dl_tls_dtv_slotinfo_list != NULL, 1)) + { + /* Assign the next available module ID. */ + tpnt->l_tls_modid = _dl_next_tls_modid (); + continue; + } + +# ifdef SHARED + if (tpnt->prev == NULL) + /* We are loading the executable itself when the dynamic linker + was executed directly. The setup will happen later. */ + continue; + + /* In a static binary there is no way to tell if we dynamically + loaded libpthread. */ + if (_dl_error_catch_tsd == &_dl_initial_error_catch_tsd) +# endif + { + /* We have not yet loaded libpthread. + We can do the TLS setup right now! */ + + void *tcb; + + /* The first call allocates TLS bookkeeping data structures. + Then we allocate the TCB for the initial thread. */ + if (__builtin_expect (_dl_tls_setup (), 0) + || __builtin_expect ((tcb = _dl_allocate_tls(NULL)) == NULL, 0)) + { + _dl_dprintf(2, "%s: '%s' cannot allocate TLS data structures for initial thread\n", _dl_progname, libname); + goto tls_failed; /* I'm using a goto, so shoot me. */ + } + + /* Now we install the TCB in the thread register. */ + if (__builtin_expect (TLS_INIT_TP (tcb, 0) == NULL, 1)) + { + /* Now we are all good. */ + tpnt->l_tls_modid = ++_dl_tls_max_dtv_idx; + continue; + } + + /* The kernel is too old or somesuch. */ + _dl_dprintf(2, "%s: '%s' unknown TLS error\n", _dl_progname, libname); + _dl_deallocate_tls (tcb, 1); + } +tls_failed: +#else + /* + * Yup, the user was an idiot and tried to sneak in a library with + * TLS in it and we don't support it. Let's fall on our own sword + * and scream at the luser while we die. + */ + _dl_dprintf(2, "%s: '%s' library contains unsupported TLS\n", + _dl_progname, libname); +#endif + _dl_internal_error_number = LD_ERROR_TLS_FAILED; + _dl_close(infile); + _dl_munmap(header, _dl_pagesize); + return NULL; + } ppnt++; }; @@ -585,6 +666,12 @@ struct elf_resolve *_dl_load_elf_shared_library(int secure, tpnt->ppnt = (ElfW(Phdr) *)(intptr_t) (tpnt->loadaddr + epnt->e_phoff); tpnt->n_phent = epnt->e_phnum; +#if USE_TLS + /* Adjust the address of the TLS initialization image. */ + if (tpnt->l_tls_initimage != NULL) + tpnt->l_tls_initimage = (char *) tpnt->l_tls_initimage + tpnt->loadaddr; +#endif + /* * Add this object into the symbol chain */ @@ -681,6 +768,13 @@ int _dl_fixup(struct dyn_elf *rpnt, int now_flag) tpnt->dynamic_info[DT_PLTRELSZ]); } } + +#ifdef USE_TLS + /* Add object to slot information data if necessasy. */ + if (tpnt->l_tls_blocksize != 0 && tls_init_tp_called) + _dl_add_to_slotinfo ((struct link_map *) tpnt); +#endif + return goof; } diff --git a/ldso/ldso/dl-tls.c b/ldso/ldso/dl-tls.c index 666a9319c..32db117c1 100644 --- a/ldso/ldso/dl-tls.c +++ b/ldso/ldso/dl-tls.c @@ -26,15 +26,78 @@ * SUCH DAMAGE. */ +/* + * The big TODO list: + * + * - Environment variables LD_TRACE_LOADED_OBJECTS and LD_PRELOAD + * need to be supported. + * - Config option LDSO_PRELOAD_FILE_SUPPORT needs to be supported. + * - Support TLS information for 'ldd' command. + * + */ + #include <tls.h> #include <dl-tls.h> #include <ldsodefs.h> -#define calloc(a, b) NULL -#define malloc(a) NULL -#define realloc(a, b) NULL -#define free(a) -#define _dl_memalign(a, b) NULL +void *(*_dl_calloc_function) (size_t __nmemb, size_t __size) = NULL; +void *(*_dl_realloc_function) (void *__ptr, size_t __size) = NULL; +void *(*_dl_memalign_function) (size_t __boundary, size_t __size) = NULL; +void (*_dl_free_function) (void *__ptr) = NULL; + +void *_dl_memalign (size_t __boundary, size_t __size); + + +void * +_dl_calloc (size_t __nmemb, size_t __size) +{ + void *result; + size_t size = (__size * __nmemb); + + if (_dl_calloc_function) { +#if 1 + _dl_debug_early("Calling libc version\n"); +#endif + return (*_dl_calloc_function) (__nmemb, __size); + } + + _dl_debug_early("allocating memory and zeroing\n"); + if (__nmemb && __size != (size / __nmemb)) { + _dl_dprintf(2, "%si:%i: unaligned structures\n", + __FUNCTION__, __LINE__); + _dl_exit(1); + } + if ((result = _dl_malloc(__size)) != NULL) { + _dl_memset(result, 0, size); + } + return result; +} + +void * +_dl_realloc (void * __ptr, size_t __size) +{ + _dl_debug_early("NOT IMPLEMENTED PROPERLY!!!\n"); + if (_dl_realloc_function) { +#if 1 + _dl_debug_early("Calling libc version\n"); +#endif + return (*_dl_realloc_function) (__ptr, __size); + } + return NULL; +} + +void +_dl_free (void *__ptr) +{ + _dl_debug_early("NOT IMPLEMENTED PROPERLY!!!\n"); + if (_dl_free_function) { +#if 1 + _dl_debug_early("Calling libc version\n"); +#endif + (*_dl_free_function) (__ptr); + } +} + /* The __tls_get_addr function has two basic forms which differ in the arguments. The IA-64 form takes two parameters, the module ID and @@ -62,6 +125,15 @@ /* Value used for dtv entries for which the allocation is delayed. */ #define TLS_DTV_UNALLOCATED ((void *) -1l) +/* Thread control block pointer. */ +void *tcbp = NULL; + +#define _dl_fatal_printf(fmt, args...) \ +{ \ + do { \ + _dl_dprintf(2, "%s:%i: " fmt, __FUNCTION__, __LINE__, ## args); \ + } while (1); \ +} /* Taken from glibc/elf/dl-reloc.c */ #define CHECK_STATIC_TLS(sym_map) \ @@ -114,11 +186,7 @@ static void __attribute__ ((__noreturn__)) oom (void) { - do { - _dl_dprintf (_dl_debug_file, - "cannot allocate thread-local memory: ABORT\n"); - _dl_exit (127); - } while (1); + _dl_fatal_printf("cannot allocate thread-local memory: ABORT\n"); } size_t @@ -338,7 +406,7 @@ _dl_tls_setup (void) const size_t nelem = 2 + TLS_SLOTINFO_SURPLUS; _dl_tls_dtv_slotinfo_list - = calloc (1, (sizeof (struct dtv_slotinfo_list) + = _dl_calloc (1, (sizeof (struct dtv_slotinfo_list) + nelem * sizeof (struct dtv_slotinfo))); if (_dl_tls_dtv_slotinfo_list == NULL) return -1; @@ -367,7 +435,11 @@ allocate_dtv (void *result) initial set of modules. This should avoid in most cases expansions of the dtv. */ dtv_length = _dl_tls_max_dtv_idx + DTV_SURPLUS; +#ifndef __UCLIBC__ dtv = calloc (dtv_length + 2, sizeof (dtv_t)); +#else + dtv = _dl_calloc (dtv_length + 2, sizeof (dtv_t)); +#endif if (dtv != NULL) { /* This is the initial length of the dtv. */ @@ -435,7 +507,7 @@ _dl_allocate_tls_storage (void) result = allocate_dtv (result); if (result == NULL) - free (allocated); + _dl_free (allocated); } return result; @@ -545,11 +617,11 @@ _dl_deallocate_tls (void *tcb, bool dealloc_tcb) for (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); + _dl_free (dtv[1 + cnt].pointer.val); /* The array starts with dtv[-1]. */ if (dtv != _dl_initial_dtv) - free (dtv - 1); + _dl_free (dtv - 1); if (dealloc_tcb) { @@ -561,7 +633,7 @@ _dl_deallocate_tls (void *tcb, bool dealloc_tcb) tcb -= (TLS_PRE_TCB_SIZE + _dl_tls_static_align - 1) & ~(_dl_tls_static_align - 1); # endif - free (tcb); + _dl_free (tcb); } } rtld_hidden_def (_dl_deallocate_tls) @@ -655,7 +727,7 @@ _dl_update_slotinfo (unsigned long int req_modid) if (! dtv[total + cnt].pointer.is_static && dtv[total + cnt].pointer.val != TLS_DTV_UNALLOCATED) { - free (dtv[total + cnt].pointer.val); + _dl_free (dtv[total + cnt].pointer.val); dtv[total + cnt].pointer.val = TLS_DTV_UNALLOCATED; } @@ -681,14 +753,14 @@ _dl_update_slotinfo (unsigned long int req_modid) malloc instead of the real malloc. We can't free it, we have to abandon the old storage. */ - newp = malloc ((2 + newsize) * sizeof (dtv_t)); + newp = _dl_malloc ((2 + newsize) * sizeof (dtv_t)); if (newp == NULL) oom (); _dl_memcpy (newp, &dtv[-1], oldsize * sizeof (dtv_t)); } else { - newp = realloc (&dtv[-1], + newp = _dl_realloc (&dtv[-1], (2 + newsize) * sizeof (dtv_t)); if (newp == NULL) oom (); @@ -718,7 +790,7 @@ _dl_update_slotinfo (unsigned long int req_modid) 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); + _dl_free (dtv[modid].pointer.val); /* This module is loaded dynamically- We defer memory allocation. */ @@ -812,7 +884,7 @@ _dl_add_to_slotinfo (struct link_map *l) assert (idx == 0); listp = prevp->next = (struct dtv_slotinfo_list *) - malloc (sizeof (struct dtv_slotinfo_list) + _dl_malloc (sizeof (struct dtv_slotinfo_list) + TLS_SLOTINFO_SURPLUS * sizeof (struct dtv_slotinfo)); if (listp == NULL) { @@ -841,3 +913,98 @@ _dl_add_to_slotinfo (struct link_map *l) listp->slotinfo[idx].map = l; listp->slotinfo[idx].gen = _dl_tls_generation + 1; } + +/* Taken from glibc/elf/rtld.c */ +static bool tls_init_tp_called; + +/* _dl_error_catch_tsd points to this for the single-threaded case. + It's reset by the thread library for multithreaded programs. */ +void ** __attribute__ ((const)) +_dl_initial_error_catch_tsd (void) +{ + static void *data; + return &data; +} + +static void * +init_tls (void) +{ + /* Number of elements in the static TLS block. */ + _dl_tls_static_nelem = _dl_tls_max_dtv_idx; + + /* Do not do this twice. The audit interface might have required + the DTV interfaces to be set up early. */ + if (_dl_initial_dtv != NULL) + return NULL; + + /* Allocate the array which contains the information about the + dtv slots. We allocate a few entries more than needed to + avoid the need for reallocation. */ + size_t nelem = _dl_tls_max_dtv_idx + 1 + TLS_SLOTINFO_SURPLUS; + + /* Allocate. */ + _dl_tls_dtv_slotinfo_list = (struct dtv_slotinfo_list *) + _dl_calloc (sizeof (struct dtv_slotinfo_list) + + nelem * sizeof (struct dtv_slotinfo), 1); + /* No need to check the return value. If memory allocation failed + the program would have been terminated. */ + + struct dtv_slotinfo *slotinfo = _dl_tls_dtv_slotinfo_list->slotinfo; + _dl_tls_dtv_slotinfo_list->len = nelem; + _dl_tls_dtv_slotinfo_list->next = NULL; + + /* Fill in the information from the loaded modules. No namespace + but the base one can be filled at this time. */ +#ifndef __UCLIBC__ + assert (_dl_ns[LM_ID_BASE + 1]._ns_loaded == NULL); + int i = 0; + struct link_map *l; + for (l = _dl_ns[LM_ID_BASE]._ns_loaded; l != NULL; l = l->l_next) + if (l->l_tls_blocksize != 0) + { + /* This is a module with TLS data. Store the map reference. + The generation counter is zero. */ + slotinfo[i].map = l; + /* slotinfo[i].gen = 0; */ + ++i; + } +#else + int i = 0; + struct link_map *l; + for (l = (struct link_map *) _dl_loaded_modules; l != NULL; l = l->l_next) + if (l->l_tls_blocksize != 0) + { + /* This is a module with TLS data. Store the map reference. + The generation counter is zero. */ + slotinfo[i].map = l; + /* slotinfo[i].gen = 0; */ + ++i; + } +#endif + assert (i == _dl_tls_max_dtv_idx); + + /* Compute the TLS offsets for the various blocks. */ + _dl_determine_tlsoffset (); + + /* Construct the static TLS block and the dtv for the initial + thread. For some platforms this will include allocating memory + for the thread descriptor. The memory for the TLS block will + never be freed. It should be allocated accordingly. The dtv + array can be changed if dynamic loading requires it. */ + void *tcbp = _dl_allocate_tls_storage (); + if (tcbp == NULL) + _dl_fatal_printf ("\ncannot allocate TLS data structures for initial thread"); + + /* Store for detection of the special case by __tls_get_addr + so it knows not to pass this dtv to the normal realloc. */ + _dl_initial_dtv = GET_DTV (tcbp); + + /* And finally install it for the main thread. If ld.so itself uses + TLS we know the thread pointer was initialized earlier. */ + const char *lossage = TLS_INIT_TP (tcbp, USE___THREAD); + if (__builtin_expect (lossage != NULL, 0)) + _dl_fatal_printf ("cannot set up thread-local storage: %s\n", lossage); + tls_init_tp_called = true; + + return tcbp; +} diff --git a/ldso/ldso/ldso.c b/ldso/ldso/ldso.c index 17022faff..4467313dd 100644 --- a/ldso/ldso/ldso.c +++ b/ldso/ldso/ldso.c @@ -228,11 +228,21 @@ void _dl_get_ready_to_run(struct elf_resolve *tpnt, unsigned long load_addr, "app_tpnt->loadaddr=%x\n", app_tpnt->loadaddr); } +#if USE_TLS + /* + * Adjust the address of the TLS initialization image in case + * the executable is actually an ET_DYN object. + */ + if (app_tpnt->l_tls_initimage != NULL) + app_tpnt->l_tls_initimage = + (char *) app_tpnt->l_tls_initimage + app_tpnt->loadaddr; +#endif + /* - * This adds another loop in the non-NPTL case, but we have to - * catch the stupid user who tries to run a binary with TLS data - * in it. For NPTL, we fill in the TLS data for the application - * like we are supposed to. + * This adds another loop, but we have to catch the stupid user who + * tries to run a binary with TLS data and the linker does not support + * it. Otherwise, we fill in the TLS data for the application like we + * are supposed to. */ { int i; @@ -263,12 +273,24 @@ void _dl_get_ready_to_run(struct elf_resolve *tpnt, unsigned long load_addr, } break; #else - _dl_dprintf(_dl_debug_file, "Program uses TLS, but ld-uClibc.so does not support it!\n"); - _dl_exit(1); + _dl_fatal_printf("Program uses TLS, but ld-uClibc.so does not support it!\n"); #endif } } +#if USE_TLS + /* We do not initialize any of the TLS functionality unless any of the + * initial modules uses TLS. This makes dynamic loading of modules with + * TLS impossible, but to support it requires either eagerly doing setup + * now or lazily doing it later. Doing it now makes us incompatible with + * an old kernel that can't perform TLS_INIT_TP, even if no TLS is ever + * used. Trying to do it lazily is too hairy to try when there could be + * multiple threads (from a non-TLS-using libpthread). */ + bool was_tls_init_tp_called = tls_init_tp_called; + if (tcbp == NULL) + tcbp = init_tls (); +#endif + /* * This is used by gdb to locate the chain of shared libraries that are * currently loaded. @@ -817,6 +839,36 @@ void _dl_get_ready_to_run(struct elf_resolve *tpnt, unsigned long load_addr, _dl_malloc_function = (void* (*)(size_t)) (intptr_t) _dl_find_hash("malloc", _dl_symbol_tables, NULL, ELF_RTYPE_CLASS_PLT); +#if USE_TLS + /* Find the real functions and make ldso functions use them from now on */ + _dl_calloc_function = (void* (*)(size_t, size_t)) (intptr_t) + _dl_find_hash("calloc", _dl_symbol_tables, NULL, ELF_RTYPE_CLASS_PLT); + _dl_realloc_function = (void* (*)(void *, size_t)) (intptr_t) + _dl_find_hash("recalloc", _dl_symbol_tables, NULL, ELF_RTYPE_CLASS_PLT); + _dl_free_function = (void (*)(void *)) (intptr_t) + _dl_find_hash("free", _dl_symbol_tables, NULL, ELF_RTYPE_CLASS_PLT); + _dl_memalign_function = (void* (*)(size_t, size_t)) (intptr_t) + _dl_find_hash("memalign", _dl_symbol_tables, NULL, ELF_RTYPE_CLASS_PLT); + + if (!was_tls_init_tp_called && _dl_tls_max_dtv_idx > 0) + ++_dl_tls_generation; + + /* Now that we have completed relocation, the initializer data + for the TLS blocks has its final values and we can copy them + into the main thread's TLS area, which we allocated above. */ + _dl_allocate_tls_init (tcbp); + + /* And finally install it for the main thread. If ld.so itself uses + TLS we know the thread pointer was initialized earlier. */ + if (! tls_init_tp_called) + { + const char *lossage = TLS_INIT_TP (tcbp, USE___THREAD); + if (__builtin_expect (lossage != NULL, 0)) + _dl_fatal_printf ("cannot set up thread-local storage: %s\n", + lossage); + } +#endif + /* Notify the debugger that all objects are now mapped in. */ _dl_debug_addr->r_state = RT_CONSISTENT; _dl_debug_state(); @@ -902,5 +954,32 @@ void *_dl_malloc(int size) return retval; } +#if USE_TLS +void * _dl_memalign (size_t __boundary, size_t __size) +{ + void *result; + int i = 0; + size_t delta; + size_t rounded = 0; + + if (_dl_memalign_function) { +#if 1 + _dl_debug_early("Calling libc version\n"); +#endif + return (*_dl_memalign_function) (__boundary, __size); + } + + _dl_debug_early("allocating aligned memory\n"); + while (rounded < __boundary) { + rounded = (1 << i++); + } + delta = (((size_t) _dl_malloc_addr + __size) % rounded); + if ((result = _dl_malloc(rounded - delta)) == NULL) + return result; + result = _dl_malloc(__size); + return result; +} +#endif + #include "dl-hash.c" #include "dl-elf.c" |