From 129394ff50ed28a0b85d742c8ae315758bb22582 Mon Sep 17 00:00:00 2001 From: Sanjeet Karan Singh Date: Fri, 31 Mar 2023 20:36:34 -0700 Subject: [PATCH] asan_memory_profile: Fix for deadlock in memory profiler code. Wrapping stopTheWorld in dl_iterate_phdr acquire dl_load lock before calling the function. Acquiring dl_load, allocator and thread registry locks before calling stopTheWorld ensures no other threads are holding that locks, we can safely suspend them and reenter in tracer thread. LockStuffAndStopTheWorld's logic here is same as lsan's implementation of this function. Reviewed By: vitalybuka Differential Revision: https://reviews.llvm.org/D146990 --- compiler-rt/lib/asan/asan_memory_profile.cpp | 44 ++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/compiler-rt/lib/asan/asan_memory_profile.cpp b/compiler-rt/lib/asan/asan_memory_profile.cpp index 4fcd560..c7e3d7b 100644 --- a/compiler-rt/lib/asan/asan_memory_profile.cpp +++ b/compiler-rt/lib/asan/asan_memory_profile.cpp @@ -11,14 +11,18 @@ // This file implements __sanitizer_print_memory_profile. //===----------------------------------------------------------------------===// +#include "asan/asan_allocator.h" +#include "lsan/lsan_common.h" #include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_platform.h" #include "sanitizer_common/sanitizer_stackdepot.h" #include "sanitizer_common/sanitizer_stacktrace.h" #include "sanitizer_common/sanitizer_stoptheworld.h" -#include "lsan/lsan_common.h" -#include "asan/asan_allocator.h" #if CAN_SANITIZE_LEAKS +# if SANITIZER_LINUX || SANITIZER_NETBSD +# include +# endif namespace __asan { @@ -111,6 +115,40 @@ static void MemoryProfileCB(const SuspendedThreadsList &suspended_threads_list, __asan_print_accumulated_stats(); } +struct DoStopTheWorldParam { + StopTheWorldCallback callback; + void *argument; +}; + +static void LockDefStuffAndStopTheWorld(DoStopTheWorldParam *param) { + __lsan::LockThreadRegistry(); + __lsan::LockAllocator(); + __sanitizer::StopTheWorld(param->callback, param->argument); + __lsan::UnlockAllocator(); + __lsan::UnlockThreadRegistry(); +} + +static int LockStuffAndStopTheWorldCallback(struct dl_phdr_info *info, + size_t size, void *data) { + DoStopTheWorldParam *param = reinterpret_cast(data); + LockDefStuffAndStopTheWorld(param); + return 1; +} + +static void LockStuffAndStopTheWorld(StopTheWorldCallback callback, + void *argument) { + DoStopTheWorldParam param = {callback, argument}; + +# if SANITIZER_LINUX || SANITIZER_NETBSD + // For libc dep systems, symbolization uses dl_iterate_phdr, which acquire a + // dl write lock. It could deadlock if the lock is already acquired by one of + // suspended. So calling stopTheWorld inside dl_iterate_phdr, first wait for + // that lock to be released (if acquired) and than suspend all threads + dl_iterate_phdr(LockStuffAndStopTheWorldCallback, ¶m); +# else + LockDefStuffAndStopTheWorld(¶m); +# endif +} } // namespace __asan #endif // CAN_SANITIZE_LEAKS @@ -123,7 +161,7 @@ void __sanitizer_print_memory_profile(uptr top_percent, uptr Arg[2]; Arg[0] = top_percent; Arg[1] = max_number_of_contexts; - __sanitizer::StopTheWorld(__asan::MemoryProfileCB, Arg); + __asan::LockStuffAndStopTheWorld(__asan::MemoryProfileCB, Arg); #endif // CAN_SANITIZE_LEAKS } } // extern "C" -- 2.7.4