aboutsummaryrefslogtreecommitdiff
path: root/dlfcn
diff options
context:
space:
mode:
Diffstat (limited to 'dlfcn')
-rw-r--r--dlfcn/Makefile3
-rw-r--r--dlfcn/Versions6
-rw-r--r--dlfcn/dlerror.c305
-rw-r--r--dlfcn/dlerror.h92
-rw-r--r--dlfcn/dlfreeres.c29
-rw-r--r--dlfcn/libc_dlerror_result.c39
6 files changed, 265 insertions, 209 deletions
diff --git a/dlfcn/Makefile b/dlfcn/Makefile
index d51fd08..994a3af 100644
--- a/dlfcn/Makefile
+++ b/dlfcn/Makefile
@@ -22,9 +22,10 @@ include ../Makeconfig
headers := bits/dlfcn.h dlfcn.h
extra-libs := libdl
libdl-routines := dlopen dlclose dlsym dlvsym dlerror dladdr dladdr1 dlinfo \
- dlmopen dlfcn dlfreeres
+ dlmopen dlfcn
routines := $(patsubst %,s%,$(filter-out dlfcn,$(libdl-routines)))
elide-routines.os := $(routines)
+routines += libc_dlerror_result
extra-libs-others := libdl
diff --git a/dlfcn/Versions b/dlfcn/Versions
index 1df6925..f07cb92 100644
--- a/dlfcn/Versions
+++ b/dlfcn/Versions
@@ -1,3 +1,8 @@
+libc {
+ GLIBC_PRIVATE {
+ __libc_dlerror_result;
+ }
+}
libdl {
GLIBC_2.0 {
dladdr; dlclose; dlerror; dlopen; dlsym;
@@ -13,6 +18,5 @@ libdl {
}
GLIBC_PRIVATE {
_dlfcn_hook;
- __libdl_freeres;
}
}
diff --git a/dlfcn/dlerror.c b/dlfcn/dlerror.c
index 947b7c1..7db70a2 100644
--- a/dlfcn/dlerror.c
+++ b/dlfcn/dlerror.c
@@ -25,6 +25,8 @@
#include <libc-lock.h>
#include <ldsodefs.h>
#include <libc-symbols.h>
+#include <assert.h>
+#include <dlerror.h>
#if !defined SHARED && IS_IN (libdl)
@@ -36,92 +38,75 @@ dlerror (void)
#else
-/* Type for storing results of dynamic loading actions. */
-struct dl_action_result
- {
- int errcode;
- int returned;
- bool malloced;
- const char *objname;
- const char *errstring;
- };
-static struct dl_action_result last_result;
-static struct dl_action_result *static_buf;
-
-/* This is the key for the thread specific memory. */
-static __libc_key_t key;
-__libc_once_define (static, once);
-
-/* Destructor for the thread-specific data. */
-static void init (void);
-static void free_key_mem (void *mem);
-
-
char *
__dlerror (void)
{
- char *buf = NULL;
- struct dl_action_result *result;
-
# ifdef SHARED
if (!rtld_active ())
return _dlfcn_hook->dlerror ();
# endif
- /* If we have not yet initialized the buffer do it now. */
- __libc_once (once, init);
+ struct dl_action_result *result = __libc_dlerror_result;
- /* Get error string. */
- if (static_buf != NULL)
- result = static_buf;
- else
+ /* No libdl function has been called. No error is possible. */
+ if (result == NULL)
+ return NULL;
+
+ /* For an early malloc failure, clear the error flag and return the
+ error message. This marks the error as delivered. */
+ if (result == dl_action_result_malloc_failed)
{
- /* init () has been run and we don't use the static buffer.
- So we have a valid key. */
- result = (struct dl_action_result *) __libc_getspecific (key);
- if (result == NULL)
- result = &last_result;
+ __libc_dlerror_result = NULL;
+ return (char *) "out of memory";
}
- /* Test whether we already returned the string. */
- if (result->returned != 0)
+ /* Placeholder object. This can be observed in a recursive call,
+ e.g. from an ELF constructor. */
+ if (result->errstring == NULL)
+ return NULL;
+
+ /* If we have already reported the error, we can free the result and
+ return NULL. See __libc_dlerror_result_free. */
+ if (result->returned)
{
- /* We can now free the string. */
- if (result->errstring != NULL)
- {
- if (strcmp (result->errstring, "out of memory") != 0)
- free ((char *) result->errstring);
- result->errstring = NULL;
- }
+ __libc_dlerror_result = NULL;
+ dl_action_result_errstring_free (result);
+ free (result);
+ return NULL;
}
- else if (result->errstring != NULL)
- {
- buf = (char *) result->errstring;
- int n;
- if (result->errcode == 0)
- n = __asprintf (&buf, "%s%s%s",
- result->objname,
- result->objname[0] == '\0' ? "" : ": ",
- _(result->errstring));
- else
- n = __asprintf (&buf, "%s%s%s: %s",
- result->objname,
- result->objname[0] == '\0' ? "" : ": ",
- _(result->errstring),
- strerror (result->errcode));
- if (n != -1)
- {
- /* We don't need the error string anymore. */
- if (strcmp (result->errstring, "out of memory") != 0)
- free ((char *) result->errstring);
- result->errstring = buf;
- }
- /* Mark the error as returned. */
- result->returned = 1;
- }
+ assert (result->errstring != NULL);
+
+ /* Create the combined error message. */
+ char *buf;
+ int n;
+ if (result->errcode == 0)
+ n = __asprintf (&buf, "%s%s%s",
+ result->objname,
+ result->objname[0] == '\0' ? "" : ": ",
+ _(result->errstring));
+ else
+ n = __asprintf (&buf, "%s%s%s: %s",
+ result->objname,
+ result->objname[0] == '\0' ? "" : ": ",
+ _(result->errstring),
+ strerror (result->errcode));
- return buf;
+ /* Mark the error as delivered. */
+ result->returned = true;
+
+ if (n >= 0)
+ {
+ /* Replace the error string with the newly allocated one. */
+ dl_action_result_errstring_free (result);
+ result->errstring = buf;
+ result->errstring_source = dl_action_result_errstring_local;
+ return buf;
+ }
+ else
+ /* We could not create the combined error message, so use the
+ existing string as a fallback. */
+ return result->errstring;
}
# ifdef SHARED
strong_alias (__dlerror, dlerror)
@@ -130,130 +115,94 @@ strong_alias (__dlerror, dlerror)
int
_dlerror_run (void (*operate) (void *), void *args)
{
- struct dl_action_result *result;
-
- /* If we have not yet initialized the buffer do it now. */
- __libc_once (once, init);
-
- /* Get error string and number. */
- if (static_buf != NULL)
- result = static_buf;
- else
+ struct dl_action_result *result = __libc_dlerror_result;
+ if (result != NULL)
{
- /* We don't use the static buffer and so we have a key. Use it
- to get the thread-specific buffer. */
- result = __libc_getspecific (key);
- if (result == NULL)
+ if (result == dl_action_result_malloc_failed)
{
- result = (struct dl_action_result *) calloc (1, sizeof (*result));
- if (result == NULL)
- /* We are out of memory. Since this is no really critical
- situation we carry on by using the global variable.
- This might lead to conflicts between the threads but
- they soon all will have memory problems. */
- result = &last_result;
- else
- /* Set the tsd. */
- __libc_setspecific (key, result);
+ /* Clear the previous error. */
+ __libc_dlerror_result = NULL;
+ result = NULL;
+ }
+ else
+ {
+ /* There is an existing object. Free its error string, but
+ keep the object. */
+ dl_action_result_errstring_free (result);
+ /* Mark the object as not containing an error. This ensures
+ that call to dlerror from, for example, an ELF
+ constructor will not notice this result object. */
+ result->errstring = NULL;
}
}
- if (result->errstring != NULL)
- {
- /* Free the error string from the last failed command. This can
- happen if `dlerror' was not run after an error was found. */
- if (result->malloced)
- free ((char *) result->errstring);
- result->errstring = NULL;
- }
-
- result->errcode = GLRO (dl_catch_error) (&result->objname,
- &result->errstring,
- &result->malloced,
- operate, args);
-
- /* If no error we mark that no error string is available. */
- result->returned = result->errstring == NULL;
+ const char *objname;
+ const char *errstring;
+ bool malloced;
+ int errcode = GLRO (dl_catch_error) (&objname, &errstring, &malloced,
+ operate, args);
- return result->errstring != NULL;
-}
+ /* ELF constructors or destructors may have indirectly altered the
+ value of __libc_dlerror_result, therefore reload it. */
+ result = __libc_dlerror_result;
-
-/* Initialize buffers for results. */
-static void
-init (void)
-{
- if (__libc_key_create (&key, free_key_mem))
- /* Creating the key failed. This means something really went
- wrong. In any case use a static buffer which is better than
- nothing. */
- static_buf = &last_result;
-}
-
-
-static void
-check_free (struct dl_action_result *rec)
-{
- if (rec->errstring != NULL
- && strcmp (rec->errstring, "out of memory") != 0)
+ if (errstring == NULL)
{
- /* We can free the string only if the allocation happened in the
- C library used by the dynamic linker. This means, it is
- always the C library in the base namespace. When we're statically
- linked, the dynamic linker is part of the program and so always
- uses the same C library we use here. */
-#ifdef SHARED
- struct link_map *map = NULL;
- Dl_info info;
- if (_dl_addr (check_free, &info, &map, NULL) != 0 && map->l_ns == 0)
-#endif
+ /* There is no error. We no longer need the result object if it
+ does not contain an error. However, a recursive call may
+ have added an error even if this call did not cause it. Keep
+ the other error. */
+ if (result != NULL && result->errstring == NULL)
{
- free ((char *) rec->errstring);
- rec->errstring = NULL;
+ __libc_dlerror_result = NULL;
+ free (result);
}
+ return 0;
}
-}
-
-
-static void
-__attribute__ ((destructor))
-fini (void)
-{
- check_free (&last_result);
-}
-
-
-/* Free the thread specific data, this is done if a thread terminates. */
-static void
-free_key_mem (void *mem)
-{
- check_free ((struct dl_action_result *) mem);
+ else
+ {
+ /* A new error occurred. Check if a result object has to be
+ allocated. */
+ if (result == NULL || result == dl_action_result_malloc_failed)
+ {
+ /* Allocating storage for the error message after the fact
+ is not ideal. But this avoids an infinite recursion in
+ case malloc itself calls libdl functions (without
+ triggering errors). */
+ result = malloc (sizeof (*result));
+ if (result == NULL)
+ {
+ /* Assume that the dlfcn failure was due to a malloc
+ failure, too. */
+ if (malloced)
+ dl_error_free ((char *) errstring);
+ __libc_dlerror_result = dl_action_result_malloc_failed;
+ return 1;
+ }
+ __libc_dlerror_result = result;
+ }
+ else
+ /* Deallocate the existing error message from a recursive
+ call, but reuse the result object. */
+ dl_action_result_errstring_free (result);
+
+ result->errcode = errcode;
+ result->objname = objname;
+ result->errstring = (char *) errstring;
+ result->returned = false;
+ /* In case of an error, the malloced flag indicates whether the
+ error string is constant or not. */
+ if (malloced)
+ result->errstring_source = dl_action_result_errstring_rtld;
+ else
+ result->errstring_source = dl_action_result_errstring_constant;
- free (mem);
- __libc_setspecific (key, NULL);
+ return 1;
+ }
}
# ifdef SHARED
-/* Free the dlerror-related resources. */
-void
-__dlerror_main_freeres (void)
-{
- /* Free the global memory if used. */
- check_free (&last_result);
-
- if (__libc_once_get (once) && static_buf == NULL)
- {
- /* init () has been run and we don't use the static buffer.
- So we have a valid key. */
- void *mem;
- /* Free the TSD memory if used. */
- mem = __libc_getspecific (key);
- if (mem != NULL)
- free_key_mem (mem);
- }
-}
-
struct dlfcn_hook *_dlfcn_hook __attribute__((nocommon));
libdl_hidden_data_def (_dlfcn_hook)
diff --git a/dlfcn/dlerror.h b/dlfcn/dlerror.h
new file mode 100644
index 0000000..cb9a9ce
--- /dev/null
+++ b/dlfcn/dlerror.h
@@ -0,0 +1,92 @@
+/* Memory management for dlerror messages.
+ Copyright (C) 2021 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
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#ifndef _DLERROR_H
+#define _DLERROR_H
+
+#include <dlfcn.h>
+#include <ldsodefs.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/* Source of the errstring member in struct dl_action_result, for
+ finding the right deallocation routine. */
+enum dl_action_result_errstring_source
+ {
+ dl_action_result_errstring_constant, /* String literal, no deallocation. */
+ dl_action_result_errstring_rtld, /* libc in the primary namespace. */
+ dl_action_result_errstring_local, /* libc in the current namespace. */
+ };
+
+struct dl_action_result
+{
+ int errcode;
+ char errstring_source;
+ bool returned;
+ const char *objname;
+ char *errstring;
+};
+
+/* Used to free the errstring member of struct dl_action_result in the
+ dl_action_result_errstring_rtld case. */
+static inline void
+dl_error_free (void *ptr)
+{
+#ifdef SHARED
+ /* In the shared case, ld.so may use a different malloc than this
+ namespace. */
+ GLRO (dl_error_free (ptr));
+#else
+ /* Call the implementation directly. It still has to check for
+ pointers which cannot be freed, so do not call free directly
+ here. */
+ _dl_error_free (ptr);
+#endif
+}
+
+/* Deallocate RESULT->errstring, leaving *RESULT itself allocated. */
+static inline void
+dl_action_result_errstring_free (struct dl_action_result *result)
+{
+ switch (result->errstring_source)
+ {
+ case dl_action_result_errstring_constant:
+ break;
+ case dl_action_result_errstring_rtld:
+ dl_error_free (result->errstring);
+ break;
+ case dl_action_result_errstring_local:
+ free (result->errstring);
+ break;
+ }
+}
+
+/* Stand-in for an error result object whose allocation failed. No
+ precise message can be reported for this, but an error must still
+ be signaled. */
+static struct dl_action_result *const dl_action_result_malloc_failed
+ __attribute__ ((unused)) = (struct dl_action_result *) (intptr_t) -1;
+
+/* Thread-local variable for storing dlfcn failures for subsequent
+ reporting via dlerror. */
+extern __thread struct dl_action_result *__libc_dlerror_result
+ attribute_tls_model_ie;
+void __libc_dlerror_result_free (void) attribute_hidden;
+
+#endif /* _DLERROR_H */
diff --git a/dlfcn/dlfreeres.c b/dlfcn/dlfreeres.c
deleted file mode 100644
index 856b764..0000000
--- a/dlfcn/dlfreeres.c
+++ /dev/null
@@ -1,29 +0,0 @@
-/* Clean up allocated libdl memory on demand.
- Copyright (C) 2018-2021 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
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- The GNU C Library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with the GNU C Library; if not, see
- <https://www.gnu.org/licenses/>. */
-
-#include <set-hooks.h>
-#include <libc-symbols.h>
-#include <dlfcn.h>
-
-/* Free libdl.so resources.
- Note: Caller ensures we are called only once. */
-void
-__libdl_freeres (void)
-{
- call_function_static_weak (__dlerror_main_freeres);
-}
diff --git a/dlfcn/libc_dlerror_result.c b/dlfcn/libc_dlerror_result.c
new file mode 100644
index 0000000..9974718
--- /dev/null
+++ b/dlfcn/libc_dlerror_result.c
@@ -0,0 +1,39 @@
+/* Thread-local variable holding the dlerror result.
+ Copyright (C) 2021 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
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#include <dlerror.h>
+
+/* This pointer is either NULL, dl_action_result_malloc_failed (), or
+ has been allocated using malloc by the namespace that also contains
+ this instance of the thread-local variable. */
+__thread struct dl_action_result *__libc_dlerror_result attribute_tls_model_ie;
+
+/* Called during thread shutdown to free resources. */
+void
+__libc_dlerror_result_free (void)
+{
+ if (__libc_dlerror_result != NULL)
+ {
+ if (__libc_dlerror_result != dl_action_result_malloc_failed)
+ {
+ dl_action_result_errstring_free (__libc_dlerror_result);
+ free (__libc_dlerror_result);
+ }
+ __libc_dlerror_result = NULL;
+ }
+}