Skip to content

Commit

Permalink
[NFC][sanitizer] Add Debug utility to print thread history (#111948)
Browse files Browse the repository at this point in the history
For #111949
  • Loading branch information
vitalybuka authored Oct 11, 2024
1 parent e1cff8b commit aa44f59
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 0 deletions.
1 change: 1 addition & 0 deletions compiler-rt/lib/sanitizer_common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
72 changes: 72 additions & 0 deletions compiler-rt/lib/sanitizer_common/sanitizer_thread_history.cpp
Original file line number Diff line number Diff line change
@@ -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 &registry, InternalScopedString &out) {
ThreadRegistryLock l(&registry);
// Stack traces are largest part of printout and they often the same for
// multiple threads, so we will deduplicate them.
InternalMmapVector<const ThreadContextBase *> stacks;

registry.RunCallbackForEachThreadLocked(
[](ThreadContextBase *context, void *arg) {
static_cast<decltype(&stacks)>(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
24 changes: 24 additions & 0 deletions compiler-rt/lib/sanitizer_common/sanitizer_thread_history.h
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,19 @@
//===----------------------------------------------------------------------===//
#include "sanitizer_common/sanitizer_thread_registry.h"

#include <iostream>
#include <vector>

#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;
Expand Down Expand Up @@ -239,4 +247,56 @@ TEST(SanitizerCommon, ThreadRegistryThreadedTest) {
ThreadedTestRegistry(&registry);
}

TEST(SanitizerCommon, PrintThreadHistory) {
ThreadRegistry registry(GetThreadContext<TestThreadContext>,
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",
"<empty stack>",
"",
"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

0 comments on commit aa44f59

Please sign in to comment.