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