Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2019 The WebRTC project authors. All Rights Reserved. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license |
| 5 | * that can be found in the LICENSE file in the root of the source |
| 6 | * tree. An additional intellectual property rights grant can be found |
| 7 | * in the file PATENTS. All contributing project authors may |
| 8 | * be found in the AUTHORS file in the root of the source tree. |
| 9 | */ |
| 10 | |
| 11 | #include "sdk/android/native_api/stacktrace/stacktrace.h" |
Karl Wiberg | 0611a15 | 2019-03-18 11:37:48 +0100 | [diff] [blame] | 12 | |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 13 | #include <dlfcn.h> |
Mirko Bonadei | 317a1f0 | 2019-09-17 17:06:18 +0200 | [diff] [blame] | 14 | |
Karl Wiberg | 0611a15 | 2019-03-18 11:37:48 +0100 | [diff] [blame] | 15 | #include <atomic> |
Mirko Bonadei | 317a1f0 | 2019-09-17 17:06:18 +0200 | [diff] [blame] | 16 | #include <memory> |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 17 | #include <vector> |
Karl Wiberg | 0611a15 | 2019-03-18 11:37:48 +0100 | [diff] [blame] | 18 | |
Steve Anton | f380284 | 2019-01-24 19:07:40 -0800 | [diff] [blame] | 19 | #include "rtc_base/critical_section.h" |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 20 | #include "rtc_base/event.h" |
| 21 | #include "rtc_base/logging.h" |
| 22 | #include "rtc_base/platform_thread.h" |
Niels Möller | 198cf00 | 2019-05-10 12:29:13 +0200 | [diff] [blame] | 23 | #include "rtc_base/string_utils.h" |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 24 | #include "rtc_base/strings/string_builder.h" |
Karl Wiberg | 0611a15 | 2019-03-18 11:37:48 +0100 | [diff] [blame] | 25 | #include "rtc_base/system/inline.h" |
| 26 | #include "system_wrappers/include/sleep.h" |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 27 | #include "test/gtest.h" |
| 28 | |
| 29 | namespace webrtc { |
| 30 | namespace test { |
| 31 | |
Karl Wiberg | 0611a15 | 2019-03-18 11:37:48 +0100 | [diff] [blame] | 32 | namespace { |
| 33 | |
| 34 | // A simple atomic spin event. Implemented with std::atomic_flag, since the C++ |
| 35 | // standard guarantees that that type is implemented with actual atomic |
| 36 | // instructions (as opposed to e.g. with a mutex). Uses sequentially consistent |
| 37 | // memory order since this is a test, where simplicity trumps performance. |
| 38 | class SimpleSpinEvent { |
| 39 | public: |
| 40 | // Initialize the event to its blocked state. |
| 41 | SimpleSpinEvent() { |
| 42 | static_cast<void>(blocked_.test_and_set(std::memory_order_seq_cst)); |
| 43 | } |
| 44 | |
| 45 | // Busy-wait for the event to become unblocked, and block it behind us as we |
| 46 | // leave. |
| 47 | void Wait() { |
| 48 | bool was_blocked; |
| 49 | do { |
| 50 | // Check if the event was blocked, and set it to blocked. |
| 51 | was_blocked = blocked_.test_and_set(std::memory_order_seq_cst); |
| 52 | } while (was_blocked); |
| 53 | } |
| 54 | |
| 55 | // Unblock the event. |
| 56 | void Set() { blocked_.clear(std::memory_order_seq_cst); } |
| 57 | |
| 58 | private: |
| 59 | std::atomic_flag blocked_; |
| 60 | }; |
| 61 | |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 62 | // Returns the execution address relative to the .so base address. This matches |
| 63 | // the addresses we get from GetStacktrace(). |
Karl Wiberg | 0611a15 | 2019-03-18 11:37:48 +0100 | [diff] [blame] | 64 | RTC_NO_INLINE uint32_t GetCurrentRelativeExecutionAddress() { |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 65 | void* pc = __builtin_return_address(0); |
| 66 | Dl_info dl_info = {}; |
| 67 | const bool success = dladdr(pc, &dl_info); |
| 68 | EXPECT_TRUE(success); |
| 69 | return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(pc) - |
| 70 | reinterpret_cast<uintptr_t>(dl_info.dli_fbase)); |
| 71 | } |
| 72 | |
| 73 | // Returns true if any of the stack trace element is inside the specified |
| 74 | // region. |
| 75 | bool StackTraceContainsRange(const std::vector<StackTraceElement>& stack_trace, |
| 76 | uintptr_t pc_low, |
| 77 | uintptr_t pc_high) { |
| 78 | for (const StackTraceElement& stack_trace_element : stack_trace) { |
| 79 | if (pc_low <= stack_trace_element.relative_address && |
| 80 | pc_high >= stack_trace_element.relative_address) { |
| 81 | return true; |
| 82 | } |
| 83 | } |
| 84 | return false; |
| 85 | } |
| 86 | |
| 87 | class DeadlockInterface { |
| 88 | public: |
| 89 | virtual ~DeadlockInterface() {} |
| 90 | |
| 91 | // This function should deadlock until Release() is called. |
| 92 | virtual void Deadlock() = 0; |
| 93 | |
| 94 | // This function should release the thread stuck in Deadlock(). |
| 95 | virtual void Release() = 0; |
| 96 | }; |
| 97 | |
| 98 | struct ThreadParams { |
| 99 | volatile int tid; |
| 100 | // Signaled when the deadlock region is entered. |
Karl Wiberg | 0611a15 | 2019-03-18 11:37:48 +0100 | [diff] [blame] | 101 | SimpleSpinEvent deadlock_start_event; |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 102 | DeadlockInterface* volatile deadlock_impl; |
| 103 | // Defines an address range within the deadlock will occur. |
| 104 | volatile uint32_t deadlock_region_start_address; |
| 105 | volatile uint32_t deadlock_region_end_address; |
| 106 | // Signaled when the deadlock is done. |
| 107 | rtc::Event deadlock_done_event; |
| 108 | }; |
| 109 | |
| 110 | class RtcEventDeadlock : public DeadlockInterface { |
| 111 | private: |
| 112 | void Deadlock() override { event.Wait(rtc::Event::kForever); } |
| 113 | void Release() override { event.Set(); } |
| 114 | |
| 115 | rtc::Event event; |
| 116 | }; |
| 117 | |
| 118 | class RtcCriticalSectionDeadlock : public DeadlockInterface { |
| 119 | public: |
| 120 | RtcCriticalSectionDeadlock() |
Mirko Bonadei | 317a1f0 | 2019-09-17 17:06:18 +0200 | [diff] [blame] | 121 | : critscope_(std::make_unique<rtc::CritScope>(&crit_)) {} |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 122 | |
| 123 | private: |
| 124 | void Deadlock() override { rtc::CritScope lock(&crit_); } |
| 125 | |
| 126 | void Release() override { critscope_.reset(); } |
| 127 | |
| 128 | rtc::CriticalSection crit_; |
| 129 | std::unique_ptr<rtc::CritScope> critscope_; |
| 130 | }; |
| 131 | |
| 132 | class SpinDeadlock : public DeadlockInterface { |
| 133 | public: |
| 134 | SpinDeadlock() : is_deadlocked_(true) {} |
| 135 | |
| 136 | private: |
| 137 | void Deadlock() override { |
| 138 | while (is_deadlocked_) { |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | void Release() override { is_deadlocked_ = false; } |
| 143 | |
| 144 | std::atomic<bool> is_deadlocked_; |
| 145 | }; |
| 146 | |
| 147 | class SleepDeadlock : public DeadlockInterface { |
| 148 | private: |
| 149 | void Deadlock() override { sleep(1000000); } |
| 150 | |
| 151 | void Release() override { |
| 152 | // The interrupt itself will break free from the sleep. |
| 153 | } |
| 154 | }; |
| 155 | |
| 156 | // This is the function that is exectued by the thread that will deadlock and |
| 157 | // have its stacktrace captured. |
| 158 | void ThreadFunction(void* void_params) { |
| 159 | ThreadParams* params = static_cast<ThreadParams*>(void_params); |
| 160 | params->tid = gettid(); |
| 161 | |
| 162 | params->deadlock_region_start_address = GetCurrentRelativeExecutionAddress(); |
| 163 | params->deadlock_start_event.Set(); |
| 164 | params->deadlock_impl->Deadlock(); |
| 165 | params->deadlock_region_end_address = GetCurrentRelativeExecutionAddress(); |
| 166 | |
| 167 | params->deadlock_done_event.Set(); |
| 168 | } |
| 169 | |
| 170 | void TestStacktrace(std::unique_ptr<DeadlockInterface> deadlock_impl) { |
| 171 | // Set params that will be sent to other thread. |
| 172 | ThreadParams params; |
| 173 | params.deadlock_impl = deadlock_impl.get(); |
| 174 | |
| 175 | // Spawn thread. |
| 176 | rtc::PlatformThread thread(&ThreadFunction, ¶ms, "StacktraceTest"); |
| 177 | thread.Start(); |
| 178 | |
Karl Wiberg | 0611a15 | 2019-03-18 11:37:48 +0100 | [diff] [blame] | 179 | // Wait until the thread has entered the deadlock region, and take a very |
| 180 | // brief nap to give it time to reach the actual deadlock. |
| 181 | params.deadlock_start_event.Wait(); |
| 182 | SleepMs(1); |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 183 | |
| 184 | // Acquire the stack trace of the thread which should now be deadlocking. |
| 185 | std::vector<StackTraceElement> stack_trace = GetStackTrace(params.tid); |
| 186 | |
| 187 | // Release the deadlock so that the thread can continue. |
| 188 | deadlock_impl->Release(); |
| 189 | |
| 190 | // Wait until the thread has left the deadlock. |
| 191 | params.deadlock_done_event.Wait(rtc::Event::kForever); |
| 192 | |
| 193 | // Assert that the stack trace contains the deadlock region. |
| 194 | EXPECT_TRUE(StackTraceContainsRange(stack_trace, |
| 195 | params.deadlock_region_start_address, |
| 196 | params.deadlock_region_end_address)) |
| 197 | << "Deadlock region: [" |
| 198 | << rtc::ToHex(params.deadlock_region_start_address) << ", " |
| 199 | << rtc::ToHex(params.deadlock_region_end_address) |
| 200 | << "] not contained in: " << StackTraceToString(stack_trace); |
| 201 | |
| 202 | thread.Stop(); |
| 203 | } |
| 204 | |
Karl Wiberg | 9841703 | 2019-03-24 19:12:40 +0100 | [diff] [blame] | 205 | class LookoutLogSink final : public rtc::LogSink { |
| 206 | public: |
| 207 | explicit LookoutLogSink(std::string look_for) |
| 208 | : look_for_(std::move(look_for)) {} |
| 209 | void OnLogMessage(const std::string& message) override { |
| 210 | if (message.find(look_for_) != std::string::npos) { |
| 211 | when_found_.Set(); |
| 212 | } |
| 213 | } |
| 214 | rtc::Event& WhenFound() { return when_found_; } |
| 215 | |
| 216 | private: |
| 217 | const std::string look_for_; |
| 218 | rtc::Event when_found_; |
| 219 | }; |
| 220 | |
Karl Wiberg | 0611a15 | 2019-03-18 11:37:48 +0100 | [diff] [blame] | 221 | } // namespace |
| 222 | |
Karl Wiberg | c130d42 | 2019-02-28 17:06:54 +0100 | [diff] [blame] | 223 | TEST(Stacktrace, TestCurrentThread) { |
| 224 | const uint32_t start_addr = GetCurrentRelativeExecutionAddress(); |
| 225 | const std::vector<StackTraceElement> stack_trace = GetStackTrace(); |
| 226 | const uint32_t end_addr = GetCurrentRelativeExecutionAddress(); |
| 227 | EXPECT_TRUE(StackTraceContainsRange(stack_trace, start_addr, end_addr)) |
| 228 | << "Caller region: [" << rtc::ToHex(start_addr) << ", " |
| 229 | << rtc::ToHex(end_addr) |
| 230 | << "] not contained in: " << StackTraceToString(stack_trace); |
| 231 | } |
| 232 | |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 233 | TEST(Stacktrace, TestSpinLock) { |
Mirko Bonadei | 317a1f0 | 2019-09-17 17:06:18 +0200 | [diff] [blame] | 234 | TestStacktrace(std::make_unique<SpinDeadlock>()); |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 235 | } |
| 236 | |
| 237 | TEST(Stacktrace, TestSleep) { |
Mirko Bonadei | 317a1f0 | 2019-09-17 17:06:18 +0200 | [diff] [blame] | 238 | TestStacktrace(std::make_unique<SleepDeadlock>()); |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 239 | } |
| 240 | |
| 241 | // Stack traces originating from kernel space does not include user space stack |
| 242 | // traces for ARM 32. |
| 243 | #ifdef WEBRTC_ARCH_ARM64 |
Karl Wiberg | 9841703 | 2019-03-24 19:12:40 +0100 | [diff] [blame] | 244 | |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 245 | TEST(Stacktrace, TestRtcEvent) { |
Mirko Bonadei | 317a1f0 | 2019-09-17 17:06:18 +0200 | [diff] [blame] | 246 | TestStacktrace(std::make_unique<RtcEventDeadlock>()); |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 247 | } |
| 248 | |
| 249 | TEST(Stacktrace, TestRtcCriticalSection) { |
Mirko Bonadei | 317a1f0 | 2019-09-17 17:06:18 +0200 | [diff] [blame] | 250 | TestStacktrace(std::make_unique<RtcCriticalSectionDeadlock>()); |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 251 | } |
Karl Wiberg | 9841703 | 2019-03-24 19:12:40 +0100 | [diff] [blame] | 252 | |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 253 | #endif |
| 254 | |
Karl Wiberg | 9841703 | 2019-03-24 19:12:40 +0100 | [diff] [blame] | 255 | TEST(Stacktrace, TestRtcEventDeadlockDetection) { |
| 256 | // Start looking for the expected log output. |
| 257 | LookoutLogSink sink(/*look_for=*/"Probable deadlock"); |
| 258 | rtc::LogMessage::AddLogToStream(&sink, rtc::LS_WARNING); |
| 259 | |
| 260 | // Start a thread that waits for an event. |
| 261 | rtc::Event ev; |
| 262 | rtc::PlatformThread thread( |
| 263 | [](void* arg) { |
| 264 | auto* ev = static_cast<rtc::Event*>(arg); |
| 265 | ev->Wait(rtc::Event::kForever); |
| 266 | }, |
| 267 | &ev, "TestRtcEventDeadlockDetection"); |
| 268 | thread.Start(); |
| 269 | |
| 270 | // The message should appear after 3 sec. We'll wait up to 10 sec in an |
| 271 | // attempt to not be flaky. |
| 272 | EXPECT_TRUE(sink.WhenFound().Wait(10000)); |
| 273 | |
| 274 | // Unblock the thread and shut it down. |
| 275 | ev.Set(); |
| 276 | thread.Stop(); |
| 277 | rtc::LogMessage::RemoveLogToStream(&sink); |
| 278 | } |
| 279 | |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 280 | } // namespace test |
| 281 | } // namespace webrtc |