Просмотр исходного кода

ldso: serialize dl_iterate_phdr to fix concurrent unwind hang on SMP

dl_iterate_phdr walked the loaded-module list and invoked the callback
without locking. glibc holds dl_load_write_lock across the whole
iteration, and callers rely on it: libgcc's _Unwind_IteratePhdrCallback
keeps a static global LRU cache (frame_hdr_cache) that it re-links on
every lookup without locking itself, assuming dl_iterate_phdr serializes
the callbacks.

Without that, several threads unwinding at once (e.g. many threads
cancelling, as in NPTL tst-cancel10) corrupt the cache into a cyclic
list and the unwinder spins forever -> hang. This can only happen on
real SMP systems: it needs genuine concurrency plus the libgcc
PT_GNU_EH_FRAME unwind path. It is not arch specific.

Take a recursive mutex around the callback loop, matching glibc.

Signed-off-by: Ramin Moussavi <ramin.moussavi@yacoub.de>
ramin 1 месяц назад
Родитель
Сommit
e636c23654
1 измененных файлов с 12 добавлено и 0 удалено
  1. 12 0
      libc/misc/elf/dl-iterate-phdr.c

+ 12 - 0
libc/misc/elf/dl-iterate-phdr.c

@@ -14,10 +14,20 @@
 
 #include <link.h>
 #include <ldso.h>
+#include <bits/uClibc_mutex.h>
 
 /* we want this in libc but nowhere else */
 #ifdef __USE_GNU
 
+/* Serialize the callback invocations.  glibc holds the loader's
+   dl_load_write_lock across dl_iterate_phdr; some callers (notably the
+   libgcc unwinder's _Unwind_IteratePhdrCallback) rely on that and mutate
+   shared state from the callback without locking themselves.  Without
+   serialization, concurrent unwinds (e.g. many threads cancelling at once)
+   corrupt that state and spin.  */
+__UCLIBC_MUTEX_STATIC(_dl_iterate_phdr_lock,
+		      PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP);
+
 static int
 __dl_iterate_phdr (int (*callback) (struct dl_phdr_info *info, size_t size, void *data), void *data)
 {
@@ -26,6 +36,7 @@ __dl_iterate_phdr (int (*callback) (struct dl_phdr_info *info, size_t size, void
 	struct elf_resolve *l;
 	struct dl_phdr_info info;
 
+	__UCLIBC_MUTEX_CONDITIONAL_LOCK(_dl_iterate_phdr_lock, 1);
 	for (l = _dl_loaded_modules; l != NULL; l = l->next) {
 		info.dlpi_addr = l->loadaddr;
 		info.dlpi_name = l->libname;
@@ -49,6 +60,7 @@ __dl_iterate_phdr (int (*callback) (struct dl_phdr_info *info, size_t size, void
 		if (ret)
 			break;
 	}
+	__UCLIBC_MUTEX_CONDITIONAL_UNLOCK(_dl_iterate_phdr_lock, 1);
 #endif
 	return ret;
 }