Rewrite for better scalability and to avoid deadlocks.
[kopensolaris-gnu/glibc.git] / sysdeps / ia64 / dl-fptr.c
1 /* Manage function descriptors.  IA-64 version.
2    Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, write to the Free
17    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
18    02111-1307 USA.  */
19
20 #include <ia64intrin.h>
21 #include <unistd.h>
22 #include <string.h>
23 #include <sys/param.h>
24 #include <sys/mman.h>
25 #include <link.h>
26 #include <ldsodefs.h>
27 #include <elf/dynamic-link.h>
28 #include <dl-machine.h>
29 #ifdef _LIBC_REENTRANT
30 # include <pt-machine.h>
31 # include <signal.h>
32 # include <time.h>
33 #endif
34
35 Elf64_Addr __ia64_boot_fptr_table[IA64_BOOT_FPTR_TABLE_LEN];
36
37 static struct local
38   {
39     struct ia64_fdesc_table *root;
40     struct ia64_fdesc *free_list;
41     unsigned int npages;                /* # of pages to allocate */
42 #ifdef _LIBC_REENTRANT
43     volatile int lock;
44     sigset_t full_sigset;
45 #endif
46     /* the next to members MUST be consecutive! */
47     struct ia64_fdesc_table boot_table;
48     struct ia64_fdesc boot_fdescs[1024];
49   }
50 local =
51   {
52     root: &local.boot_table,
53     npages: 2,
54     boot_table:
55       {
56         len: sizeof (local.boot_fdescs) / sizeof (local.boot_fdescs[0]),
57         first_unused: 0
58       }
59   };
60
61 /* Locking is tricky: we may get a signal while holding the lock and
62    the signal handler may end up calling into the dynamic loader
63    again.  Also, if a real-time process spins on the lock, a
64    non-realtime process may never get the chance to release it's lock,
65    unless the realtime process relinquishes the CPU from time to time.
66    Hence we (a) block signals before acquiring the lock and (b) do a
67    nanosleep() when we detect prolongued contention.  */
68 #ifdef _LIBC_REENTRANT
69 # define lock(l)                                                \
70 {                                                               \
71   sigset_t _saved_set;                                          \
72   int i = 10000;                                                \
73   if (!__sigismember (&(l)->full_sigset, SIGINT))               \
74     __sigfillset (&(l)->full_sigset);                           \
75                                                                 \
76   while (testandset ((int *) &(l)->lock))                       \
77     {                                                           \
78       struct timespec ts;                                       \
79       if (i > 0)                                                \
80         {                                                       \
81           --i;                                                  \
82           continue;                                             \
83         }                                                       \
84       ts.tv_sec = 0;                                            \
85       ts.tv_nsec = 1*1000*1000;                                 \
86       __nanosleep (&ts, NULL);                                  \
87     }                                                           \
88   __sigprocmask (SIG_BLOCK, &(l)->full_sigset, &_saved_set);
89 # define unlock(l)                                              \
90   __sigprocmask (SIG_SETMASK, &_saved_set, NULL);               \
91   (l)->lock = 0;                                                \
92 }
93 #else
94 # define lock(l)
95 # define unlock(l)
96 #endif
97
98 /* Create a new fdesc table and return a pointer to the first fdesc
99    entry.  The fdesc lock must have been acquired already.  */
100
101 static struct ia64_fdesc *
102 new_fdesc_table (struct local *l)
103 {
104   size_t size = l->npages * _dl_pagesize;
105   struct ia64_fdesc_table *new_table;
106   struct ia64_fdesc *fdesc;
107
108   l->npages += l->npages;
109   new_table = __mmap (0, size, PROT_READ | PROT_WRITE,
110                       MAP_ANON | MAP_PRIVATE, -1, 0);
111   if (new_table == MAP_FAILED)
112     _dl_signal_error (errno, NULL, "cannot map pages for fdesc table");
113
114   new_table->len = (size - sizeof (*new_table)) / sizeof (struct ia64_fdesc);
115   fdesc = &new_table->fdesc[0];
116   new_table->first_unused = 1;
117   new_table->next = l->root;
118   l->root = new_table;
119   return fdesc;
120 }
121
122 static Elf64_Addr
123 make_fdesc (Elf64_Addr ip, Elf64_Addr gp)
124 {
125   struct ia64_fdesc *fdesc = NULL;
126   struct ia64_fdesc_table *t;
127   unsigned int old;
128   struct local *l;
129
130   asm ("addl %0 = @gprel (local), gp" : "=r" (l));
131
132   t = l->root;
133   while (1)
134     {
135       old = t->first_unused;
136       if (old >= t->len)
137         break;
138       else if (__sync_bool_compare_and_swap (&t->first_unused, old, old + 1))
139         {
140           fdesc = &t->fdesc[old];
141           goto install;
142         }
143     }
144
145   lock (l);
146   {
147     if (l->free_list)
148       {
149         fdesc = l->free_list;           /* get it from free-list */
150         l->free_list = (struct ia64_fdesc *) fdesc->ip;
151       }
152     else
153       fdesc = new_fdesc_table (l);      /* create new fdesc table */
154   }
155   unlock (l);
156
157  install:
158   fdesc->ip = ip;
159   fdesc->gp = gp;
160
161   return (Elf64_Addr) fdesc;
162 }
163
164 static inline Elf64_Addr *
165 make_fptr_table (struct link_map *map)
166 {
167   const Elf64_Sym *symtab = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
168   const char *strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);
169   Elf64_Addr *fptr_table;
170   size_t size;
171   size_t len;
172
173   /* XXX Apparently the only way to find out the size of the dynamic
174      symbol section is to assume that the string table follows right
175      afterwards...  */
176   len = ((strtab - (char *) symtab) / map->l_info[DT_SYMENT]->d_un.d_val);
177   size = ((len * sizeof (fptr_table[0]) + _dl_pagesize - 1) & -_dl_pagesize);
178   /* XXX We don't support here in the moment systems without MAP_ANON.
179      There probably are none for IA-64.  In case this is proven wrong
180      we will have to open /dev/null here and use the file descriptor
181      instead of the hard-coded -1.  */
182   fptr_table = __mmap (NULL, size, PROT_READ | PROT_WRITE,
183                        MAP_ANON | MAP_PRIVATE, -1, 0);
184   if (fptr_table == MAP_FAILED)
185     _dl_signal_error (errno, NULL, "cannot map pages for fptr table");
186
187   map->l_mach.fptr_table_len = len;
188   map->l_mach.fptr_table = fptr_table;
189   return fptr_table;
190 }
191
192 Elf64_Addr
193 __ia64_make_fptr (struct link_map *map, const Elf64_Sym *sym, Elf64_Addr ip)
194 {
195   Elf64_Addr *ftab = map->l_mach.fptr_table;
196   const Elf64_Sym *symtab;
197   Elf_Symndx symidx;
198
199   if (__builtin_expect (!map->l_mach.fptr_table, 0))
200     ftab = make_fptr_table (map);
201
202   symtab = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
203   symidx = sym - symtab;
204
205   if (symidx >= map->l_mach.fptr_table_len)
206     _dl_signal_error (0, NULL,
207                       "internal error: symidx out of range of fptr table");
208
209   if (!ftab[symidx])
210     {
211       /* GOT has already been relocated in elf_get_dynamic_info -
212          don't try to relocate it again.  */
213       ftab[symidx] = make_fdesc (ip, map->l_info[DT_PLTGOT]->d_un.d_ptr);
214 #if 0
215       {
216         const char *strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);
217         struct local *l;
218
219         asm ("addl %0 = @gprel (local), gp" : "=r" (l));
220         if (l->root != &l->boot_table || l->boot_table.first_unused > 20)
221           _dl_debug_printf ("created fdesc symbol `%s' at %lx\n",
222                             strtab + sym->st_name, ftab[symidx]);
223       }
224 #endif
225     }
226
227   return ftab[symidx];
228 }
229
230 void
231 _dl_unmap (struct link_map *map)
232 {
233   Elf64_Addr *ftab = map->l_mach.fptr_table;
234   struct ia64_fdesc *head = NULL, *tail = NULL;
235   size_t i;
236
237   __munmap ((void *) map->l_map_start, map->l_map_end - map->l_map_start);
238
239   if (!ftab)
240     return;
241
242   /* String together the fdesc structures that are being freed.  */
243   for (i = 0; i < map->l_mach.fptr_table_len; ++i)
244     {
245       if (ftab[i])
246         {
247           *(struct ia64_fdesc **) ftab[i] = head;
248           head = (struct ia64_fdesc *) ftab[i];
249           if (!tail)
250             tail = head;
251         }
252     }
253
254   /* Prepend the new list to the free_list: */
255   if (tail)
256     {
257       lock (&local);
258       {
259         *(struct ia64_fdesc **) tail = local.free_list;
260         local.free_list = head;
261       }
262       unlock (&local);
263     }
264
265   __munmap (ftab,
266             map->l_mach.fptr_table_len * sizeof (map->l_mach.fptr_table[0]));
267   map->l_mach.fptr_table = NULL;
268 }
269
270 Elf64_Addr
271 _dl_lookup_address (const void *address)
272 {
273   Elf64_Addr addr = (Elf64_Addr) address;
274   struct ia64_fdesc_table *t;
275   unsigned long int i;
276
277   for (t = local.root; t != NULL; t = t->next)
278     {
279       i = (struct ia64_fdesc *) addr - &t->fdesc[0];
280       if (i < t->first_unused && addr == (Elf64_Addr) &t->fdesc[i])
281         {
282           addr = t->fdesc[i].ip;
283           break;
284         }
285     }
286   return addr;
287 }