Rewrite for better scalability and to avoid deadlocks.
authordrepper <drepper>
Thu, 26 Jul 2001 00:23:57 +0000 (00:23 +0000)
committerdrepper <drepper>
Thu, 26 Jul 2001 00:23:57 +0000 (00:23 +0000)
sysdeps/ia64/dl-fptr.c

index 588bb9d..c31de86 100644 (file)
@@ -1,5 +1,5 @@
-/* Unmap a loaded object.  IA-64 version.
-   Copyright (C) 1999, 2000 Free Software Foundation, Inc.
+/* Manage function descriptors.  IA-64 version.
+   Copyright (C) 1999, 2000, 2001 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
@@ -17,6 +17,7 @@
    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
    02111-1307 USA.  */
 
+#include <ia64intrin.h>
 #include <unistd.h>
 #include <string.h>
 #include <sys/param.h>
 #include <dl-machine.h>
 #ifdef _LIBC_REENTRANT
 # include <pt-machine.h>
-
-static int __ia64_fptr_lock = 0;
+# include <signal.h>
+# include <time.h>
 #endif
 
-/* Because ld.so is now versioned, these functions can be in their own
-   file; no relocations need to be done to call them.  Of course, if
-   ld.so is not versioned...  */
-#if 0
-#ifndef DO_VERSIONING
-# error "This will not work with versioning turned off, sorry."
-#endif
+Elf64_Addr __ia64_boot_fptr_table[IA64_BOOT_FPTR_TABLE_LEN];
+
+static struct local
+  {
+    struct ia64_fdesc_table *root;
+    struct ia64_fdesc *free_list;
+    unsigned int npages;               /* # of pages to allocate */
+#ifdef _LIBC_REENTRANT
+    volatile int lock;
+    sigset_t full_sigset;
 #endif
+    /* the next to members MUST be consecutive! */
+    struct ia64_fdesc_table boot_table;
+    struct ia64_fdesc boot_fdescs[1024];
+  }
+local =
+  {
+    root: &local.boot_table,
+    npages: 2,
+    boot_table:
+      {
+       len: sizeof (local.boot_fdescs) / sizeof (local.boot_fdescs[0]),
+       first_unused: 0
+      }
+  };
 
-#ifdef MAP_ANON
-/* The fd is not examined when using MAP_ANON.  */
-#define ANONFD -1
+/* Locking is tricky: we may get a signal while holding the lock and
+   the signal handler may end up calling into the dynamic loader
+   again.  Also, if a real-time process spins on the lock, a
+   non-realtime process may never get the chance to release it's lock,
+   unless the realtime process relinquishes the CPU from time to time.
+   Hence we (a) block signals before acquiring the lock and (b) do a
+   nanosleep() when we detect prolongued contention.  */
+#ifdef _LIBC_REENTRANT
+# define lock(l)                                               \
+{                                                              \
+  sigset_t _saved_set;                                         \
+  int i = 10000;                                               \
+  if (!__sigismember (&(l)->full_sigset, SIGINT))              \
+    __sigfillset (&(l)->full_sigset);                          \
+                                                               \
+  while (testandset ((int *) &(l)->lock))                      \
+    {                                                          \
+      struct timespec ts;                                      \
+      if (i > 0)                                               \
+       {                                                       \
+         --i;                                                  \
+         continue;                                             \
+       }                                                       \
+      ts.tv_sec = 0;                                           \
+      ts.tv_nsec = 1*1000*1000;                                        \
+      __nanosleep (&ts, NULL);                                 \
+    }                                                          \
+  __sigprocmask (SIG_BLOCK, &(l)->full_sigset, &_saved_set);
+# define unlock(l)                                             \
+  __sigprocmask (SIG_SETMASK, &_saved_set, NULL);              \
+  (l)->lock = 0;                                               \
+}
 #else
-extern int _dl_zerofd;
-#define ANONFD _dl_zerofd
+# define lock(l)
+# define unlock(l)
 #endif
 
+/* Create a new fdesc table and return a pointer to the first fdesc
+   entry.  The fdesc lock must have been acquired already.  */
 
-/* ld.so currently has 14 FPTR relocs, we take 256 and use them for
-   the relocs for the dynamic app itself.  */
-struct ia64_fptr __boot_ldso_fptr[IA64_BOOT_FPTR_SIZE];
-struct ia64_fptr *__fptr_root = NULL;
-struct ia64_fptr *__fptr_next = __boot_ldso_fptr;
-static struct ia64_fptr *__fptr_free = NULL;
-int __fptr_count = IA64_BOOT_FPTR_SIZE;
-
-Elf64_Addr
-__ia64_make_fptr (const struct link_map *sym_map, Elf64_Addr value,
-                 struct ia64_fptr **root, struct ia64_fptr *mem)
+static struct ia64_fdesc *
+new_fdesc_table (struct local *l)
 {
-  struct ia64_fptr **loc;
-  struct ia64_fptr *f;
+  size_t size = l->npages * _dl_pagesize;
+  struct ia64_fdesc_table *new_table;
+  struct ia64_fdesc *fdesc;
 
-#ifdef _LIBC_REENTRANT
-  /* Make sure we are alone. We don't need a lock during bootstrap. */
-  if (mem == NULL)
-    while (testandset (&__ia64_fptr_lock));
-#endif
+  l->npages += l->npages;
+  new_table = __mmap (0, size, PROT_READ | PROT_WRITE,
+                     MAP_ANON | MAP_PRIVATE, -1, 0);
+  if (new_table == MAP_FAILED)
+    _dl_signal_error (errno, NULL, "cannot map pages for fdesc table");
 
-  /* Search the sorted linked list for an existing entry for this
-     symbol.  */
-  loc = root;
-  f = *loc;
-  while (f != NULL && f->func <= value)
-    {
-      if (f->func == value)
-       goto found;
-      loc = &f->next;
-      f = *loc;
-    }
+  new_table->len = (size - sizeof (*new_table)) / sizeof (struct ia64_fdesc);
+  fdesc = &new_table->fdesc[0];
+  new_table->first_unused = 1;
+  new_table->next = l->root;
+  l->root = new_table;
+  return fdesc;
+}
 
-  /* Not found.  Create a new one.  */
-  if (mem != NULL)
-    f = mem;
-  else if (__fptr_free != NULL)
-    {
-      f = __fptr_free;
-      __fptr_free = f->next;
-    }
-  else
+static Elf64_Addr
+make_fdesc (Elf64_Addr ip, Elf64_Addr gp)
+{
+  struct ia64_fdesc *fdesc = NULL;
+  struct ia64_fdesc_table *t;
+  unsigned int old;
+  struct local *l;
+
+  asm ("addl %0 = @gprel (local), gp" : "=r" (l));
+
+  t = l->root;
+  while (1)
     {
-      if (__fptr_count == 0)
+      old = t->first_unused;
+      if (old >= t->len)
+       break;
+      else if (__sync_bool_compare_and_swap (&t->first_unused, old, old + 1))
        {
-#ifndef MAP_ANON
-# define MAP_ANON 0
-         if (_dl_zerofd == -1)
-           {
-             _dl_zerofd = _dl_sysdep_open_zero_fill ();
-             if (_dl_zerofd == -1)
-               {
-                 __close (fd);
-                 _dl_signal_error (errno, NULL,
-                                   "cannot open zero fill device");
-               }
-           }
-#endif
-
-         __fptr_next = __mmap (0, _dl_pagesize, PROT_READ | PROT_WRITE,
-                               MAP_ANON | MAP_PRIVATE, ANONFD, 0);
-         if (__fptr_next == MAP_FAILED)
-           _dl_signal_error(errno, NULL, "cannot map page for fptr");
-         __fptr_count = _dl_pagesize / sizeof (struct ia64_fptr);
+         fdesc = &t->fdesc[old];
+         goto install;
        }
-      f = __fptr_next++;
-      __fptr_count--;
     }
 
-  f->func = value;
-  /* GOT has already been relocated in elf_get_dynamic_info - don't
-     try to relocate it again.  */
-  f->gp = sym_map->l_info[DT_PLTGOT]->d_un.d_ptr;
-  f->next = *loc;
-  *loc = f;
+  lock (l);
+  {
+    if (l->free_list)
+      {
+       fdesc = l->free_list;           /* get it from free-list */
+       l->free_list = (struct ia64_fdesc *) fdesc->ip;
+      }
+    else
+      fdesc = new_fdesc_table (l);     /* create new fdesc table */
+  }
+  unlock (l);
 
-found:
-#ifdef _LIBC_REENTRANT
-  /* Release the lock.  */
-  if (mem == NULL)
-    __ia64_fptr_lock = 0;
+ install:
+  fdesc->ip = ip;
+  fdesc->gp = gp;
+
+  return (Elf64_Addr) fdesc;
+}
+
+static inline Elf64_Addr *
+make_fptr_table (struct link_map *map)
+{
+  const Elf64_Sym *symtab = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
+  const char *strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);
+  Elf64_Addr *fptr_table;
+  size_t size;
+  size_t len;
+
+  /* XXX Apparently the only way to find out the size of the dynamic
+     symbol section is to assume that the string table follows right
+     afterwards...  */
+  len = ((strtab - (char *) symtab) / map->l_info[DT_SYMENT]->d_un.d_val);
+  size = ((len * sizeof (fptr_table[0]) + _dl_pagesize - 1) & -_dl_pagesize);
+  /* XXX We don't support here in the moment systems without MAP_ANON.
+     There probably are none for IA-64.  In case this is proven wrong
+     we will have to open /dev/null here and use the file descriptor
+     instead of the hard-coded -1.  */
+  fptr_table = __mmap (NULL, size, PROT_READ | PROT_WRITE,
+                      MAP_ANON | MAP_PRIVATE, -1, 0);
+  if (fptr_table == MAP_FAILED)
+    _dl_signal_error (errno, NULL, "cannot map pages for fptr table");
+
+  map->l_mach.fptr_table_len = len;
+  map->l_mach.fptr_table = fptr_table;
+  return fptr_table;
+}
+
+Elf64_Addr
+__ia64_make_fptr (struct link_map *map, const Elf64_Sym *sym, Elf64_Addr ip)
+{
+  Elf64_Addr *ftab = map->l_mach.fptr_table;
+  const Elf64_Sym *symtab;
+  Elf_Symndx symidx;
+
+  if (__builtin_expect (!map->l_mach.fptr_table, 0))
+    ftab = make_fptr_table (map);
+
+  symtab = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
+  symidx = sym - symtab;
+
+  if (symidx >= map->l_mach.fptr_table_len)
+    _dl_signal_error (0, NULL,
+                     "internal error: symidx out of range of fptr table");
+
+  if (!ftab[symidx])
+    {
+      /* GOT has already been relocated in elf_get_dynamic_info -
+        don't try to relocate it again.  */
+      ftab[symidx] = make_fdesc (ip, map->l_info[DT_PLTGOT]->d_un.d_ptr);
+#if 0
+      {
+       const char *strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);
+       struct local *l;
+
+       asm ("addl %0 = @gprel (local), gp" : "=r" (l));
+       if (l->root != &l->boot_table || l->boot_table.first_unused > 20)
+         _dl_debug_printf ("created fdesc symbol `%s' at %lx\n",
+                           strtab + sym->st_name, ftab[symidx]);
+      }
 #endif
+    }
 
-  return (Elf64_Addr) f;
+  return ftab[symidx];
 }
 
 void
 _dl_unmap (struct link_map *map)
 {
-  struct ia64_fptr **floc;
-  struct ia64_fptr *f;
-  struct ia64_fptr **lloc;
-  struct ia64_fptr *l;
+  Elf64_Addr *ftab = map->l_mach.fptr_table;
+  struct ia64_fdesc *head = NULL, *tail = NULL;
+  size_t i;
 
   __munmap ((void *) map->l_map_start, map->l_map_end - map->l_map_start);
 
-#ifdef _LIBC_REENTRANT
-  /* Make sure we are alone.  */
-  while (testandset (&__ia64_fptr_lock));
-#endif
+  if (!ftab)
+    return;
 
-  /* Search the sorted linked list for the first entry for this object.  */
-  floc = &__fptr_root;
-  f = *floc;
-  while (f != NULL && f->func < map->l_map_start)
+  /* String together the fdesc structures that are being freed.  */
+  for (i = 0; i < map->l_mach.fptr_table_len; ++i)
     {
-      floc = &f->next;
-      f = *floc;
-    }
-
-  /* We found one.  */
-  if (f != NULL && f->func < map->l_map_end)
-    {
-      /* Get the last entry.  */
-      lloc = floc;
-      l = f;
-      while (l && l->func < map->l_map_end)
+      if (ftab[i])
        {
-         lloc = &l->next;
-         l = *lloc;
+         *(struct ia64_fdesc **) ftab[i] = head;
+         head = (struct ia64_fdesc *) ftab[i];
+         if (!tail)
+           tail = head;
        }
+    }
 
-      /* Updated FPTR.  */
-      *floc = l;
-
-      /* Prepend them to the free list.  */
-      *lloc = __fptr_free;
-      __fptr_free = f;
+  /* Prepend the new list to the free_list: */
+  if (tail)
+    {
+      lock (&local);
+      {
+       *(struct ia64_fdesc **) tail = local.free_list;
+       local.free_list = head;
+      }
+      unlock (&local);
     }
 
-#ifdef _LIBC_REENTRANT
-  /* Release the lock.   */
-  __ia64_fptr_lock = 0;
-#endif
+  __munmap (ftab,
+           map->l_mach.fptr_table_len * sizeof (map->l_mach.fptr_table[0]));
+  map->l_mach.fptr_table = NULL;
 }
 
 Elf64_Addr
 _dl_lookup_address (const void *address)
 {
   Elf64_Addr addr = (Elf64_Addr) address;
-  struct ia64_fptr *f;
-
-#ifdef _LIBC_REENTRANT
-  /* Make sure we are alone.  */
-  while (testandset (&__ia64_fptr_lock));
-#endif
-
-  for (f = __fptr_root; f != NULL; f = f->next)
-    if (f == address)
-      {
-       addr = f->func;
-       break;
-      }
-
-#ifdef _LIBC_REENTRANT
-  /* Release the lock.   */
-  __ia64_fptr_lock = 0;
-#endif
+  struct ia64_fdesc_table *t;
+  unsigned long int i;
 
+  for (t = local.root; t != NULL; t = t->next)
+    {
+      i = (struct ia64_fdesc *) addr - &t->fdesc[0];
+      if (i < t->first_unused && addr == (Elf64_Addr) &t->fdesc[i])
+       {
+         addr = t->fdesc[i].ip;
+         break;
+       }
+    }
   return addr;
 }