diff options
Diffstat (limited to 'src/ldso/dlerror.c')
-rw-r--r-- | src/ldso/dlerror.c | 44 |
1 files changed, 28 insertions, 16 deletions
diff --git a/src/ldso/dlerror.c b/src/ldso/dlerror.c index 3fcc7779..dae0f3a9 100644 --- a/src/ldso/dlerror.c +++ b/src/ldso/dlerror.c @@ -3,7 +3,12 @@ #include <stdarg.h> #include "pthread_impl.h" #include "dynlink.h" -#include "lock.h" +#include "atomic.h" + +#define malloc __libc_malloc +#define calloc __libc_calloc +#define realloc __libc_realloc +#define free __libc_free char *dlerror() { @@ -17,30 +22,37 @@ char *dlerror() return s; } -static volatile int freebuf_queue_lock[1]; -static void **freebuf_queue; +/* Atomic singly-linked list, used to store list of thread-local dlerror + * buffers for deferred free. They cannot be freed at thread exit time + * because, by the time it's known they can be freed, the exiting thread + * is in a highly restrictive context where it cannot call (even the + * libc-internal) free. It also can't take locks; thus the atomic list. */ + +static void *volatile freebuf_queue; void __dl_thread_cleanup(void) { pthread_t self = __pthread_self(); - if (self->dlerror_buf && self->dlerror_buf != (void *)-1) { - LOCK(freebuf_queue_lock); - void **p = (void **)self->dlerror_buf; - *p = freebuf_queue; - freebuf_queue = p; - UNLOCK(freebuf_queue_lock); - } + if (!self->dlerror_buf || self->dlerror_buf == (void *)-1) + return; + void *h; + do { + h = freebuf_queue; + *(void **)self->dlerror_buf = h; + } while (a_cas_p(&freebuf_queue, h, self->dlerror_buf) != h); } hidden void __dl_vseterr(const char *fmt, va_list ap) { - LOCK(freebuf_queue_lock); - while (freebuf_queue) { - void **p = freebuf_queue; - freebuf_queue = *p; - free(p); + void **q; + do q = freebuf_queue; + while (q && a_cas_p(&freebuf_queue, q, 0) != q); + + while (q) { + void **p = *q; + free(q); + q = p; } - UNLOCK(freebuf_queue_lock); va_list ap2; va_copy(ap2, ap); |