Skip to content

Commit d3182d7

Browse files
vitalybukaDanielCChen
authored andcommitted
[NFC][sanitizer] Add Debug utility to print thread history (llvm#111948)
For llvm#111949
1 parent afa866c commit d3182d7

File tree

4 files changed

+157
-0
lines changed

4 files changed

+157
-0
lines changed

compiler-rt/lib/sanitizer_common/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ set(SANITIZER_SYMBOLIZER_SOURCES
9494
sanitizer_symbolizer_report.cpp
9595
sanitizer_symbolizer_report_fuchsia.cpp
9696
sanitizer_symbolizer_win.cpp
97+
sanitizer_thread_history.cpp
9798
sanitizer_unwind_linux_libcdep.cpp
9899
sanitizer_unwind_fuchsia.cpp
99100
sanitizer_unwind_win.cpp
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//===-- sanitizer_thread_history.cpp --------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "sanitizer_thread_history.h"
10+
11+
#include "sanitizer_stackdepot.h"
12+
namespace __sanitizer {
13+
14+
void PrintThreadHistory(ThreadRegistry &registry, InternalScopedString &out) {
15+
ThreadRegistryLock l(&registry);
16+
// Stack traces are largest part of printout and they often the same for
17+
// multiple threads, so we will deduplicate them.
18+
InternalMmapVector<const ThreadContextBase *> stacks;
19+
20+
registry.RunCallbackForEachThreadLocked(
21+
[](ThreadContextBase *context, void *arg) {
22+
static_cast<decltype(&stacks)>(arg)->push_back(context);
23+
},
24+
&stacks);
25+
26+
Sort(stacks.data(), stacks.size(),
27+
[](const ThreadContextBase *a, const ThreadContextBase *b) {
28+
if (a->stack_id < b->stack_id)
29+
return true;
30+
if (a->stack_id > b->stack_id)
31+
return false;
32+
return a->unique_id < b->unique_id;
33+
});
34+
35+
auto describe_thread = [&](const ThreadContextBase *context) {
36+
if (!context) {
37+
out.Append("T-1");
38+
return;
39+
}
40+
out.AppendF("T%llu/%llu", context->unique_id, context->os_id);
41+
if (internal_strlen(context->name))
42+
out.AppendF(" (%s)", context->name);
43+
};
44+
45+
auto get_parent =
46+
[&](const ThreadContextBase *context) -> const ThreadContextBase * {
47+
if (!context)
48+
return nullptr;
49+
ThreadContextBase *parent = registry.GetThreadLocked(context->parent_tid);
50+
if (!parent)
51+
return nullptr;
52+
if (parent->unique_id >= context->unique_id)
53+
return nullptr;
54+
return parent;
55+
};
56+
57+
const ThreadContextBase *prev = nullptr;
58+
for (const ThreadContextBase *context : stacks) {
59+
if (prev && prev->stack_id != context->stack_id)
60+
StackDepotGet(prev->stack_id).PrintTo(&out);
61+
prev = context;
62+
out.Append("Thread ");
63+
describe_thread(context);
64+
out.Append(" was created by ");
65+
describe_thread(get_parent(context));
66+
out.Append("\n");
67+
}
68+
if (prev)
69+
StackDepotGet(prev->stack_id).PrintTo(&out);
70+
}
71+
72+
} // namespace __sanitizer
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//===-- sanitizer_thread_history.h ------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// Utility to print thread histroy from ThreadRegistry.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef SANITIZER_THREAD_HISTORY_H
14+
#define SANITIZER_THREAD_HISTORY_H
15+
16+
#include "sanitizer_thread_registry.h"
17+
18+
namespace __sanitizer {
19+
20+
void PrintThreadHistory(ThreadRegistry& registry, InternalScopedString& out);
21+
22+
} // namespace __sanitizer
23+
24+
#endif // SANITIZER_THREAD_HISTORY_H

compiler-rt/lib/sanitizer_common/tests/sanitizer_thread_registry_test.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,19 @@
1111
//===----------------------------------------------------------------------===//
1212
#include "sanitizer_common/sanitizer_thread_registry.h"
1313

14+
#include <iostream>
1415
#include <vector>
1516

17+
#include "gmock/gmock.h"
1618
#include "gtest/gtest.h"
19+
#include "sanitizer_common/sanitizer_common.h"
20+
#include "sanitizer_common/sanitizer_stackdepot.h"
21+
#include "sanitizer_common/sanitizer_stacktrace.h"
22+
#include "sanitizer_common/sanitizer_thread_history.h"
1723
#include "sanitizer_pthread_wrappers.h"
1824

25+
using testing::HasSubstr;
26+
1927
namespace __sanitizer {
2028

2129
static Mutex tctx_allocator_lock;
@@ -239,4 +247,56 @@ TEST(SanitizerCommon, ThreadRegistryThreadedTest) {
239247
ThreadedTestRegistry(&registry);
240248
}
241249

250+
TEST(SanitizerCommon, PrintThreadHistory) {
251+
ThreadRegistry registry(GetThreadContext<TestThreadContext>,
252+
kThreadsPerShard * kNumShards + 1, 10, 0);
253+
254+
UNINITIALIZED BufferedStackTrace stack1;
255+
stack1.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), nullptr, false,
256+
/*max_depth=*/1);
257+
258+
UNINITIALIZED BufferedStackTrace stack2;
259+
stack2.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), nullptr, false,
260+
/*max_depth=*/1);
261+
262+
EXPECT_EQ(0U, registry.CreateThread(0, true, -1, 0, nullptr));
263+
for (int i = 0; i < 5; i++) {
264+
registry.CreateThread(0, true, 0, StackDepotPut(stack1), nullptr);
265+
registry.CreateThread(0, true, 0, StackDepotPut(stack2), nullptr);
266+
}
267+
268+
InternalScopedString out;
269+
PrintThreadHistory(registry, out);
270+
271+
std::string substrings[] = {
272+
"Thread T0/0 was created by T-1",
273+
"<empty stack>",
274+
"",
275+
"Thread T1/0 was created by T0/0",
276+
"Thread T3/0 was created by T0/0",
277+
"Thread T5/0 was created by T0/0",
278+
"Thread T7/0 was created by T0/0",
279+
"Thread T9/0 was created by T0/0",
280+
"#0 0x",
281+
"",
282+
"Thread T2/0 was created by T0/0",
283+
"Thread T4/0 was created by T0/0",
284+
"Thread T6/0 was created by T0/0",
285+
"Thread T8/0 was created by T0/0",
286+
"Thread T10/0 was created by T0/0",
287+
"#0 0x",
288+
"",
289+
};
290+
291+
std::stringstream ss(out.data());
292+
std::string line;
293+
294+
for (auto substr : substrings) {
295+
std::getline(ss, line);
296+
EXPECT_THAT(line, HasSubstr(substr)) << line;
297+
}
298+
299+
EXPECT_FALSE(std::getline(ss, line)) << "Unmatched line: " << line;
300+
}
301+
242302
} // namespace __sanitizer

0 commit comments

Comments
 (0)