Back to index

glibc  2.9
dl-fptr.c
Go to the documentation of this file.
00001 /* Manage function descriptors.  Generic version.
00002    Copyright (C) 1999-2004, 2006 Free Software Foundation, Inc.
00003    This file is part of the GNU C Library.
00004 
00005    The GNU C Library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Lesser General Public
00007    License as published by the Free Software Foundation; either
00008    version 2.1 of the License, or (at your option) any later version.
00009 
00010    The GNU C Library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Lesser General Public License for more details.
00014 
00015    You should have received a copy of the GNU Lesser General Public
00016    License along with the GNU C Library; if not, write to the Free
00017    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
00018    02111-1307 USA.  */
00019 
00020 #include <libintl.h>
00021 #include <unistd.h>
00022 #include <string.h>
00023 #include <sys/param.h>
00024 #include <sys/mman.h>
00025 #include <link.h>
00026 #include <ldsodefs.h>
00027 #include <elf/dynamic-link.h>
00028 #include <dl-fptr.h>
00029 #include <atomic.h>
00030 
00031 #ifndef ELF_MACHINE_BOOT_FPTR_TABLE_LEN
00032 /* ELF_MACHINE_BOOT_FPTR_TABLE_LEN should be greater than the number of
00033    dynamic symbols in ld.so.  */
00034 # define ELF_MACHINE_BOOT_FPTR_TABLE_LEN 256
00035 #endif
00036 
00037 #ifndef ELF_MACHINE_LOAD_ADDRESS
00038 # error "ELF_MACHINE_LOAD_ADDRESS is not defined."
00039 #endif
00040 
00041 #ifndef COMPARE_AND_SWAP
00042 # define COMPARE_AND_SWAP(ptr, old, new) \
00043   (catomic_compare_and_exchange_bool_acq (ptr, new, old) == 0)
00044 #endif
00045 
00046 ElfW(Addr) _dl_boot_fptr_table [ELF_MACHINE_BOOT_FPTR_TABLE_LEN];
00047 
00048 static struct local
00049   {
00050     struct fdesc_table *root;
00051     struct fdesc *free_list;
00052     unsigned int npages;           /* # of pages to allocate */
00053     /* the next to members MUST be consecutive! */
00054     struct fdesc_table boot_table;
00055     struct fdesc boot_fdescs[1024];
00056   }
00057 local =
00058   {
00059     .root = &local.boot_table,
00060     .npages = 2,
00061     .boot_table =
00062       {
00063        .len = sizeof (local.boot_fdescs) / sizeof (local.boot_fdescs[0]),
00064        .first_unused = 0
00065       }
00066   };
00067 
00068 /* Create a new fdesc table and return a pointer to the first fdesc
00069    entry.  The fdesc lock must have been acquired already.  */
00070 
00071 static struct fdesc_table *
00072 new_fdesc_table (struct local *l, size_t *size)
00073 {
00074   size_t old_npages = l->npages;
00075   size_t new_npages = old_npages + old_npages;
00076   struct fdesc_table *new_table;
00077 
00078   /* If someone has just created a new table, we return NULL to tell
00079      the caller to use the new table.  */
00080   if (! COMPARE_AND_SWAP (&l->npages, old_npages, new_npages))
00081     return (struct fdesc_table *) NULL;
00082 
00083   *size = old_npages * GLRO(dl_pagesize);
00084   new_table = __mmap (NULL, *size,
00085                     PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
00086   if (new_table == MAP_FAILED)
00087     _dl_signal_error (errno, NULL, NULL,
00088                     N_("cannot map pages for fdesc table"));
00089 
00090   new_table->len
00091     = (*size - sizeof (*new_table)) / sizeof (struct fdesc);
00092   new_table->first_unused = 1;
00093   return new_table;
00094 }
00095 
00096 
00097 static ElfW(Addr)
00098 make_fdesc (ElfW(Addr) ip, ElfW(Addr) gp)
00099 {
00100   struct fdesc *fdesc = NULL;
00101   struct fdesc_table *root;
00102   unsigned int old;
00103   struct local *l;
00104 
00105   ELF_MACHINE_LOAD_ADDRESS (l, local);
00106 
00107  retry:
00108   root = l->root;
00109   while (1)
00110     {
00111       old = root->first_unused;
00112       if (old >= root->len)
00113        break;
00114       else if (COMPARE_AND_SWAP (&root->first_unused, old, old + 1))
00115        {
00116          fdesc = &root->fdesc[old];
00117          goto install;
00118        }
00119     }
00120 
00121   if (l->free_list)
00122     {
00123       /* Get it from free-list.  */
00124       do
00125        {
00126          fdesc = l->free_list;
00127          if (fdesc == NULL)
00128            goto retry;
00129        }
00130       while (! COMPARE_AND_SWAP ((ElfW(Addr) *) &l->free_list,
00131                              (ElfW(Addr)) fdesc, fdesc->ip));
00132     }
00133   else
00134     {
00135       /* Create a new fdesc table.  */
00136       size_t size;
00137       struct fdesc_table *new_table = new_fdesc_table (l, &size);
00138 
00139       if (new_table == NULL)
00140        goto retry;
00141 
00142       new_table->next = root;
00143       if (! COMPARE_AND_SWAP ((ElfW(Addr) *) &l->root,
00144                            (ElfW(Addr)) root,
00145                            (ElfW(Addr)) new_table))
00146        {
00147          /* Someone has just installed a new table. Return NULL to
00148             tell the caller to use the new table.  */
00149          __munmap (new_table, size);
00150          goto retry;
00151        }
00152 
00153       /* Note that the first entry was reserved while allocating the
00154         memory for the new page.  */
00155       fdesc = &new_table->fdesc[0];
00156     }
00157 
00158  install:
00159   fdesc->ip = ip;
00160   fdesc->gp = gp;
00161 
00162   return (ElfW(Addr)) fdesc;
00163 }
00164 
00165 
00166 static inline ElfW(Addr) * __attribute__ ((always_inline))
00167 make_fptr_table (struct link_map *map)
00168 {
00169   const ElfW(Sym) *symtab
00170     = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
00171   const char *strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);
00172   ElfW(Addr) *fptr_table;
00173   size_t size;
00174   size_t len;
00175 
00176   /* XXX Apparently the only way to find out the size of the dynamic
00177      symbol section is to assume that the string table follows right
00178      afterwards...  */
00179   len = ((strtab - (char *) symtab)
00180         / map->l_info[DT_SYMENT]->d_un.d_val);
00181   size = ((len * sizeof (fptr_table[0]) + GLRO(dl_pagesize) - 1)
00182          & -GLRO(dl_pagesize));
00183   /* XXX We don't support here in the moment systems without MAP_ANON.
00184      There probably are none for IA-64.  In case this is proven wrong
00185      we will have to open /dev/null here and use the file descriptor
00186      instead of the hard-coded -1.  */
00187   fptr_table = __mmap (NULL, size,
00188                      PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE,
00189                      -1, 0);
00190   if (fptr_table == MAP_FAILED)
00191     _dl_signal_error (errno, NULL, NULL,
00192                     N_("cannot map pages for fptr table"));
00193 
00194   if (COMPARE_AND_SWAP ((ElfW(Addr) *) &map->l_mach.fptr_table,
00195                      (ElfW(Addr)) NULL, (ElfW(Addr)) fptr_table))
00196     map->l_mach.fptr_table_len = len;
00197   else
00198     __munmap (fptr_table, len * sizeof (fptr_table[0]));
00199 
00200   return map->l_mach.fptr_table;
00201 }
00202 
00203 
00204 ElfW(Addr)
00205 _dl_make_fptr (struct link_map *map, const ElfW(Sym) *sym,
00206               ElfW(Addr) ip)
00207 {
00208   ElfW(Addr) *ftab = map->l_mach.fptr_table;
00209   const ElfW(Sym) *symtab;
00210   Elf_Symndx symidx;
00211   struct local *l;
00212 
00213   if (__builtin_expect (ftab == NULL, 0))
00214     ftab = make_fptr_table (map);
00215 
00216   symtab = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
00217   symidx = sym - symtab;
00218 
00219   if (symidx >= map->l_mach.fptr_table_len)
00220     _dl_signal_error (0, NULL, NULL,
00221                     N_("internal error: symidx out of range of fptr table"));
00222 
00223   while (ftab[symidx] == 0)
00224     {
00225       /* GOT has already been relocated in elf_get_dynamic_info -
00226         don't try to relocate it again.  */
00227       ElfW(Addr) fdesc
00228        = make_fdesc (ip, map->l_info[DT_PLTGOT]->d_un.d_ptr);
00229 
00230       if (__builtin_expect (COMPARE_AND_SWAP (&ftab[symidx], (ElfW(Addr)) NULL,
00231                                          fdesc), 1))
00232        {
00233          /* Noone has updated the entry and the new function
00234             descriptor has been installed.  */
00235 #if 0
00236          const char *strtab
00237            = (const void *) D_PTR (map, l_info[DT_STRTAB]);
00238 
00239          ELF_MACHINE_LOAD_ADDRESS (l, local);
00240          if (l->root != &l->boot_table
00241              || l->boot_table.first_unused > 20)
00242            _dl_debug_printf ("created fdesc symbol `%s' at %lx\n",
00243                            strtab + sym->st_name, ftab[symidx]);
00244 #endif
00245          break;
00246        }
00247       else
00248        {
00249          /* We created a duplicated function descriptor. We put it on
00250             free-list.  */
00251          struct fdesc *f = (struct fdesc *) fdesc;
00252 
00253          ELF_MACHINE_LOAD_ADDRESS (l, local);
00254 
00255          do
00256            f->ip = (ElfW(Addr)) l->free_list;
00257          while (! COMPARE_AND_SWAP ((ElfW(Addr) *) &l->free_list,
00258                                  f->ip, fdesc));
00259        }
00260     }
00261 
00262   return ftab[symidx];
00263 }
00264 
00265 
00266 void
00267 _dl_unmap (struct link_map *map)
00268 {
00269   ElfW(Addr) *ftab = map->l_mach.fptr_table;
00270   struct fdesc *head = NULL, *tail = NULL;
00271   size_t i;
00272 
00273   __munmap ((void *) map->l_map_start,
00274            map->l_map_end - map->l_map_start);
00275 
00276   if (ftab == NULL)
00277     return;
00278 
00279   /* String together the fdesc structures that are being freed.  */
00280   for (i = 0; i < map->l_mach.fptr_table_len; ++i)
00281     {
00282       if (ftab[i])
00283        {
00284          *(struct fdesc **) ftab[i] = head;
00285          head = (struct fdesc *) ftab[i];
00286          if (tail == NULL)
00287            tail = head;
00288        }
00289     }
00290 
00291   /* Prepend the new list to the free_list: */
00292   if (tail)
00293     do
00294       tail->ip = (ElfW(Addr)) local.free_list;
00295     while (! COMPARE_AND_SWAP ((ElfW(Addr) *) &local.free_list,
00296                             tail->ip, (ElfW(Addr)) head));
00297 
00298   __munmap (ftab, (map->l_mach.fptr_table_len
00299                  * sizeof (map->l_mach.fptr_table[0])));
00300 
00301   map->l_mach.fptr_table = NULL;
00302 }
00303 
00304 
00305 ElfW(Addr)
00306 _dl_lookup_address (const void *address)
00307 {
00308   ElfW(Addr) addr = (ElfW(Addr)) address;
00309   struct fdesc_table *t;
00310   unsigned long int i;
00311 
00312   for (t = local.root; t != NULL; t = t->next)
00313     {
00314       i = (struct fdesc *) addr - &t->fdesc[0];
00315       if (i < t->first_unused && addr == (ElfW(Addr)) &t->fdesc[i])
00316        {
00317          addr = t->fdesc[i].ip;
00318          break;
00319        }
00320     }
00321 
00322   return addr;
00323 }