about summary refs log tree commit diff
path: root/elf/dl-error.c
diff options
context:
space:
mode:
Diffstat (limited to 'elf/dl-error.c')
-rw-r--r--elf/dl-error.c83
1 files changed, 65 insertions, 18 deletions
diff --git a/elf/dl-error.c b/elf/dl-error.c
index 03292abaf4..cfb6b9ead5 100644
--- a/elf/dl-error.c
+++ b/elf/dl-error.c
@@ -22,6 +22,7 @@
 #include <string.h>
 #include <unistd.h>
 #include <elf/ldsodefs.h>
+#include <bits/libc-lock.h>
 
 /* This structure communicates state between _dl_catch_error and
    _dl_signal_error.  */
@@ -31,14 +32,57 @@ struct catch
     jmp_buf env;		/* longjmp here on error.  */
   };
 
-/* This points to such a structure during a call to _dl_catch_error.
-   During implicit startup and run-time work for needed shared libraries,
-   this is null.  */
+/* Multiple threads at once can use the `_dl_catch_error' function.  The
+   calls can come from the `_dl_map_object_deps', `_dlerror_run', or from
+   any of the libc functionality which loads dynamic objects (NSS, iconv).
+   Therefore we have to be prepared to safe the state in thread-local
+   memory.  `catch' will only be used for the non-threaded case.
+
+   Please note the horrible kludge we have to use to check for the
+   thread functions to be defined.  The problem is that while running
+   ld.so standalone (i.e., before the relocation with the libc symbols
+   available) we do not have a real handling of undefined weak symbols.
+   All symbols are relocated, regardless of the availability.  They are
+   relocated relative to the load address of the dynamic linker.  Adding
+   this start address to zero (the value in the GOT for undefined symbols)
+   leads to an address which is the load address of ld.so.  Once we have
+   relocated with the libc values the value is NULL if the function is
+   not available.  Our "solution" is to regard NULL and the ld.so load
+   address as indicators for unavailable weak symbols.   */
 static struct catch *catch;
 
+#ifdef PIC
+# define tsd_setspecific(data) \
+  if (__libc_internal_tsd_set != (void *) _dl_rtld_map.l_addr		      \
+      && __libc_internal_tsd_set != NULL)				      \
+    __libc_internal_tsd_set (_LIBC_TSD_KEY_DL_ERROR, data);		      \
+  else									      \
+    catch = (data)
+# define tsd_getspecific() \
+  (__libc_internal_tsd_set != (void *) _dl_rtld_map.l_addr		      \
+   && __libc_internal_tsd_set != NULL					      \
+   ? (struct catch *) __libc_internal_tsd_get (_LIBC_TSD_KEY_DL_ERROR)	      \
+   : catch)
+#else
+# define tsd_setspecific(data) \
+  if (__libc_internal_tsd_set != NULL)					      \
+    __libc_internal_tsd_set (_LIBC_TSD_KEY_DL_ERROR, data);		      \
+  else									      \
+    catch = (data)
+# define tsd_getspecific() \
+  (__libc_internal_tsd_set != NULL					      \
+   ? (struct catch *) __libc_internal_tsd_get (_LIBC_TSD_KEY_DL_ERROR)	      \
+   : catch)
+#endif
+
+
 /* This points to a function which is called when an error is
    received.  Unlike the handling of `catch' this function may return.
-   The arguments will be the `errstring' and `objname'.  */
+   The arguments will be the `errstring' and `objname'.
+
+   Since this functionality is not used in normal programs (only in ld.so)
+   we do not care about multi-threaded programs here.  We keep this as a
+   global variable.  */
 static receiver_fct receiver;
 
 
@@ -48,27 +92,30 @@ _dl_signal_error (int errcode,
 		  const char *objname,
 		  const char *errstring)
 {
+  struct catch *lcatch;
+
   if (! errstring)
     errstring = "DYNAMIC LINKER BUG!!!";
 
-  if (catch)
+  lcatch = tsd_getspecific ();
+  if (lcatch != NULL)
     {
       /* We are inside _dl_catch_error.  Return to it.  We have to
 	 duplicate the error string since it might be allocated on the
 	 stack.  */
       size_t objname_len = objname ? strlen (objname) + 2 : 0;
       size_t errstring_len = strlen (errstring) + 1;
-      catch->errstring = malloc (objname_len + errstring_len);
-      if (catch->errstring != NULL)
+      lcatch->errstring = malloc (objname_len + errstring_len);
+      if (lcatch->errstring != NULL)
 	{
 	  if (objname_len > 0)
 	    {
-	      memcpy (catch->errstring, objname, objname_len - 2);
-	      memcpy (catch->errstring + objname_len - 2, ": ", 2);
+	      memcpy (lcatch->errstring, objname, objname_len - 2);
+	      memcpy (lcatch->errstring + objname_len - 2, ": ", 2);
 	    }
-	  memcpy (catch->errstring + objname_len, errstring, errstring_len);
+	  memcpy (lcatch->errstring + objname_len, errstring, errstring_len);
 	}
-      longjmp (catch->env, errcode ?: -1);
+      longjmp (lcatch->env, errcode ?: -1);
     }
   else if (receiver)
     {
@@ -106,19 +153,19 @@ _dl_catch_error (char **errstring,
      inefficient.  So we initialize `c' by hand.  */
   c.errstring = NULL;
 
-  old = catch;
+  old = tsd_getspecific ();
   errcode = setjmp (c.env);
   if (errcode == 0)
     {
-      catch = &c;
+      tsd_setspecific (&c);
       (*operate) (args);
-      catch = old;
+      tsd_setspecific (old);
       *errstring = NULL;
       return 0;
     }
 
   /* We get here only if we longjmp'd out of OPERATE.  */
-  catch = old;
+  tsd_setspecific (old);
   *errstring = c.errstring;
   return errcode == -1 ? 0 : errcode;
 }
@@ -130,15 +177,15 @@ _dl_receive_error (receiver_fct fct, void (*operate) (void *), void *args)
   struct catch *old_catch;
   receiver_fct old_receiver;
 
-  old_catch = catch;
+  old_catch = tsd_getspecific ();
   old_receiver = receiver;
 
   /* Set the new values.  */
-  catch = NULL;
+  tsd_setspecific (NULL);
   receiver = fct;
 
   (*operate) (args);
 
-  catch = old_catch;
+  tsd_setspecific (old_catch);
   receiver = old_receiver;
 }