diff --git a/compiler-rt/lib/sanitizer_common/CMakeLists.txt b/compiler-rt/lib/sanitizer_common/CMakeLists.txt index 556a64f301748..09391e4f5f370 100644 --- a/compiler-rt/lib/sanitizer_common/CMakeLists.txt +++ b/compiler-rt/lib/sanitizer_common/CMakeLists.txt @@ -94,6 +94,7 @@ set(SANITIZER_SYMBOLIZER_SOURCES sanitizer_symbolizer_report.cpp sanitizer_symbolizer_report_fuchsia.cpp sanitizer_symbolizer_win.cpp + sanitizer_thread_history.cpp sanitizer_unwind_linux_libcdep.cpp sanitizer_unwind_fuchsia.cpp sanitizer_unwind_win.cpp diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_thread_history.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_thread_history.cpp new file mode 100644 index 0000000000000..0f5bec3ca083e --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/sanitizer_thread_history.cpp @@ -0,0 +1,72 @@ +//===-- sanitizer_thread_history.cpp --------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_thread_history.h" + +#include "sanitizer_stackdepot.h" +namespace __sanitizer { + +void PrintThreadHistory(ThreadRegistry ®istry, InternalScopedString &out) { + ThreadRegistryLock l(®istry); + // Stack traces are largest part of printout and they often the same for + // multiple threads, so we will deduplicate them. + InternalMmapVector stacks; + + registry.RunCallbackForEachThreadLocked( + [](ThreadContextBase *context, void *arg) { + static_cast(arg)->push_back(context); + }, + &stacks); + + Sort(stacks.data(), stacks.size(), + [](const ThreadContextBase *a, const ThreadContextBase *b) { + if (a->stack_id < b->stack_id) + return true; + if (a->stack_id > b->stack_id) + return false; + return a->unique_id < b->unique_id; + }); + + auto describe_thread = [&](const ThreadContextBase *context) { + if (!context) { + out.Append("T-1"); + return; + } + out.AppendF("T%llu/%llu", context->unique_id, context->os_id); + if (internal_strlen(context->name)) + out.AppendF(" (%s)", context->name); + }; + + auto get_parent = + [&](const ThreadContextBase *context) -> const ThreadContextBase * { + if (!context) + return nullptr; + ThreadContextBase *parent = registry.GetThreadLocked(context->parent_tid); + if (!parent) + return nullptr; + if (parent->unique_id >= context->unique_id) + return nullptr; + return parent; + }; + + const ThreadContextBase *prev = nullptr; + for (const ThreadContextBase *context : stacks) { + if (prev && prev->stack_id != context->stack_id) + StackDepotGet(prev->stack_id).PrintTo(&out); + prev = context; + out.Append("Thread "); + describe_thread(context); + out.Append(" was created by "); + describe_thread(get_parent(context)); + out.Append("\n"); + } + if (prev) + StackDepotGet(prev->stack_id).PrintTo(&out); +} + +} // namespace __sanitizer diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_thread_history.h b/compiler-rt/lib/sanitizer_common/sanitizer_thread_history.h new file mode 100644 index 0000000000000..2995f6015fe50 --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/sanitizer_thread_history.h @@ -0,0 +1,24 @@ +//===-- sanitizer_thread_history.h ------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Utility to print thread histroy from ThreadRegistry. +// +//===----------------------------------------------------------------------===// + +#ifndef SANITIZER_THREAD_HISTORY_H +#define SANITIZER_THREAD_HISTORY_H + +#include "sanitizer_thread_registry.h" + +namespace __sanitizer { + +void PrintThreadHistory(ThreadRegistry& registry, InternalScopedString& out); + +} // namespace __sanitizer + +#endif // SANITIZER_THREAD_HISTORY_H diff --git a/compiler-rt/lib/sanitizer_common/tests/sanitizer_thread_registry_test.cpp b/compiler-rt/lib/sanitizer_common/tests/sanitizer_thread_registry_test.cpp index c3cac707f0a0b..6e84ecdfeba68 100644 --- a/compiler-rt/lib/sanitizer_common/tests/sanitizer_thread_registry_test.cpp +++ b/compiler-rt/lib/sanitizer_common/tests/sanitizer_thread_registry_test.cpp @@ -11,11 +11,19 @@ //===----------------------------------------------------------------------===// #include "sanitizer_common/sanitizer_thread_registry.h" +#include #include +#include "gmock/gmock.h" #include "gtest/gtest.h" +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_stackdepot.h" +#include "sanitizer_common/sanitizer_stacktrace.h" +#include "sanitizer_common/sanitizer_thread_history.h" #include "sanitizer_pthread_wrappers.h" +using testing::HasSubstr; + namespace __sanitizer { static Mutex tctx_allocator_lock; @@ -239,4 +247,56 @@ TEST(SanitizerCommon, ThreadRegistryThreadedTest) { ThreadedTestRegistry(®istry); } +TEST(SanitizerCommon, PrintThreadHistory) { + ThreadRegistry registry(GetThreadContext, + kThreadsPerShard * kNumShards + 1, 10, 0); + + UNINITIALIZED BufferedStackTrace stack1; + stack1.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), nullptr, false, + /*max_depth=*/1); + + UNINITIALIZED BufferedStackTrace stack2; + stack2.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), nullptr, false, + /*max_depth=*/1); + + EXPECT_EQ(0U, registry.CreateThread(0, true, -1, 0, nullptr)); + for (int i = 0; i < 5; i++) { + registry.CreateThread(0, true, 0, StackDepotPut(stack1), nullptr); + registry.CreateThread(0, true, 0, StackDepotPut(stack2), nullptr); + } + + InternalScopedString out; + PrintThreadHistory(registry, out); + + std::string substrings[] = { + "Thread T0/0 was created by T-1", + "", + "", + "Thread T1/0 was created by T0/0", + "Thread T3/0 was created by T0/0", + "Thread T5/0 was created by T0/0", + "Thread T7/0 was created by T0/0", + "Thread T9/0 was created by T0/0", + "#0 0x", + "", + "Thread T2/0 was created by T0/0", + "Thread T4/0 was created by T0/0", + "Thread T6/0 was created by T0/0", + "Thread T8/0 was created by T0/0", + "Thread T10/0 was created by T0/0", + "#0 0x", + "", + }; + + std::stringstream ss(out.data()); + std::string line; + + for (auto substr : substrings) { + std::getline(ss, line); + EXPECT_THAT(line, HasSubstr(substr)) << line; + } + + EXPECT_FALSE(std::getline(ss, line)) << "Unmatched line: " << line; +} + } // namespace __sanitizer