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 | |
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" |
Niels Möller | 198cf00 | 2019-05-10 12:29:13 +0200 | [diff] [blame] | 22 | #include "rtc_base/string_utils.h" |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 23 | #include "rtc_base/strings/string_builder.h" |
Markus Handell | 85585f4 | 2020-07-08 23:04:37 +0200 | [diff] [blame] | 24 | #include "rtc_base/synchronization/mutex.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() |
Markus Handell | 85585f4 | 2020-07-08 23:04:37 +0200 | [diff] [blame] | 121 | : mutex_lock_(std::make_unique<MutexLock>(&mutex_)) {} |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 122 | |
| 123 | private: |
Markus Handell | 85585f4 | 2020-07-08 23:04:37 +0200 | [diff] [blame] | 124 | void Deadlock() override { MutexLock lock(&mutex_); } |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 125 | |
Markus Handell | 85585f4 | 2020-07-08 23:04:37 +0200 | [diff] [blame] | 126 | void Release() override { mutex_lock_.reset(); } |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 127 | |
Markus Handell | 85585f4 | 2020-07-08 23:04:37 +0200 | [diff] [blame] | 128 | Mutex mutex_; |
| 129 | std::unique_ptr<MutexLock> mutex_lock_; |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 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 | |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 156 | void TestStacktrace(std::unique_ptr<DeadlockInterface> deadlock_impl) { |
| 157 | // Set params that will be sent to other thread. |
| 158 | ThreadParams params; |
| 159 | params.deadlock_impl = deadlock_impl.get(); |
| 160 | |
| 161 | // Spawn thread. |
Markus Handell | ad5037b | 2021-05-07 15:02:36 +0200 | [diff] [blame] | 162 | auto thread = rtc::PlatformThread::SpawnJoinable( |
| 163 | [¶ms] { |
| 164 | params.tid = gettid(); |
| 165 | params.deadlock_region_start_address = |
| 166 | GetCurrentRelativeExecutionAddress(); |
| 167 | params.deadlock_start_event.Set(); |
| 168 | params.deadlock_impl->Deadlock(); |
| 169 | params.deadlock_region_end_address = |
| 170 | GetCurrentRelativeExecutionAddress(); |
| 171 | params.deadlock_done_event.Set(); |
| 172 | }, |
| 173 | "StacktraceTest"); |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 174 | |
Karl Wiberg | 0611a15 | 2019-03-18 11:37:48 +0100 | [diff] [blame] | 175 | // Wait until the thread has entered the deadlock region, and take a very |
| 176 | // brief nap to give it time to reach the actual deadlock. |
| 177 | params.deadlock_start_event.Wait(); |
| 178 | SleepMs(1); |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 179 | |
| 180 | // Acquire the stack trace of the thread which should now be deadlocking. |
| 181 | std::vector<StackTraceElement> stack_trace = GetStackTrace(params.tid); |
| 182 | |
| 183 | // Release the deadlock so that the thread can continue. |
| 184 | deadlock_impl->Release(); |
| 185 | |
| 186 | // Wait until the thread has left the deadlock. |
| 187 | params.deadlock_done_event.Wait(rtc::Event::kForever); |
| 188 | |
| 189 | // Assert that the stack trace contains the deadlock region. |
| 190 | EXPECT_TRUE(StackTraceContainsRange(stack_trace, |
| 191 | params.deadlock_region_start_address, |
| 192 | params.deadlock_region_end_address)) |
| 193 | << "Deadlock region: [" |
| 194 | << rtc::ToHex(params.deadlock_region_start_address) << ", " |
| 195 | << rtc::ToHex(params.deadlock_region_end_address) |
| 196 | << "] not contained in: " << StackTraceToString(stack_trace); |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 197 | } |
| 198 | |
Karl Wiberg | 9841703 | 2019-03-24 19:12:40 +0100 | [diff] [blame] | 199 | class LookoutLogSink final : public rtc::LogSink { |
| 200 | public: |
| 201 | explicit LookoutLogSink(std::string look_for) |
| 202 | : look_for_(std::move(look_for)) {} |
| 203 | void OnLogMessage(const std::string& message) override { |
| 204 | if (message.find(look_for_) != std::string::npos) { |
| 205 | when_found_.Set(); |
| 206 | } |
| 207 | } |
| 208 | rtc::Event& WhenFound() { return when_found_; } |
| 209 | |
| 210 | private: |
| 211 | const std::string look_for_; |
| 212 | rtc::Event when_found_; |
| 213 | }; |
| 214 | |
Karl Wiberg | 0611a15 | 2019-03-18 11:37:48 +0100 | [diff] [blame] | 215 | } // namespace |
| 216 | |
Karl Wiberg | c130d42 | 2019-02-28 17:06:54 +0100 | [diff] [blame] | 217 | TEST(Stacktrace, TestCurrentThread) { |
| 218 | const uint32_t start_addr = GetCurrentRelativeExecutionAddress(); |
| 219 | const std::vector<StackTraceElement> stack_trace = GetStackTrace(); |
| 220 | const uint32_t end_addr = GetCurrentRelativeExecutionAddress(); |
| 221 | EXPECT_TRUE(StackTraceContainsRange(stack_trace, start_addr, end_addr)) |
| 222 | << "Caller region: [" << rtc::ToHex(start_addr) << ", " |
| 223 | << rtc::ToHex(end_addr) |
| 224 | << "] not contained in: " << StackTraceToString(stack_trace); |
| 225 | } |
| 226 | |
Mirko Bonadei | f5ea3b9 | 2021-11-15 15:41:28 +0100 | [diff] [blame^] | 227 | // TODO(bugs.webrtc.org/13383): Re-enable once stack unwinding with |
| 228 | // compiler-rt/libunwind works on Android arm64. |
| 229 | #ifdef WEBRTC_ARCH_ARM64 |
| 230 | #define MAYBE_TestSpinLock DISABLED_TestSpinLock |
| 231 | #else |
| 232 | #define MAYBE_TestSpinLock TestSpinLock |
| 233 | #endif |
| 234 | TEST(Stacktrace, MAYBE_TestSpinLock) { |
Mirko Bonadei | 317a1f0 | 2019-09-17 17:06:18 +0200 | [diff] [blame] | 235 | TestStacktrace(std::make_unique<SpinDeadlock>()); |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 236 | } |
| 237 | |
Mirko Bonadei | f5ea3b9 | 2021-11-15 15:41:28 +0100 | [diff] [blame^] | 238 | // TODO(bugs.webrtc.org/13383): Re-enable once stack unwinding with |
| 239 | // compiler-rt/libunwind works on Android arm64. |
| 240 | #ifdef WEBRTC_ARCH_ARM64 |
| 241 | #define MAYBE_TestSleep DISABLED_TestSleep |
| 242 | #else |
| 243 | #define MAYBE_TestSleep TestSleep |
| 244 | #endif |
| 245 | TEST(Stacktrace, MAYBE_TestSleep) { |
Mirko Bonadei | 317a1f0 | 2019-09-17 17:06:18 +0200 | [diff] [blame] | 246 | TestStacktrace(std::make_unique<SleepDeadlock>()); |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 247 | } |
| 248 | |
| 249 | // Stack traces originating from kernel space does not include user space stack |
| 250 | // traces for ARM 32. |
| 251 | #ifdef WEBRTC_ARCH_ARM64 |
Karl Wiberg | 9841703 | 2019-03-24 19:12:40 +0100 | [diff] [blame] | 252 | |
Mirko Bonadei | f5ea3b9 | 2021-11-15 15:41:28 +0100 | [diff] [blame^] | 253 | // TODO(bugs.webrtc.org/13383): Re-enable once stack unwinding with |
| 254 | // compiler-rt/libunwind works on Android arm64. |
| 255 | TEST(Stacktrace, DISABLED_TestRtcEvent) { |
Mirko Bonadei | 317a1f0 | 2019-09-17 17:06:18 +0200 | [diff] [blame] | 256 | TestStacktrace(std::make_unique<RtcEventDeadlock>()); |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 257 | } |
| 258 | |
Mirko Bonadei | f5ea3b9 | 2021-11-15 15:41:28 +0100 | [diff] [blame^] | 259 | // TODO(bugs.webrtc.org/13383): Re-enable once stack unwinding with |
| 260 | // compiler-rt/libunwind works on Android arm64. |
| 261 | TEST(Stacktrace, DISABLED_TestRtcCriticalSection) { |
Mirko Bonadei | 317a1f0 | 2019-09-17 17:06:18 +0200 | [diff] [blame] | 262 | TestStacktrace(std::make_unique<RtcCriticalSectionDeadlock>()); |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 263 | } |
Karl Wiberg | 9841703 | 2019-03-24 19:12:40 +0100 | [diff] [blame] | 264 | |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 265 | #endif |
| 266 | |
Karl Wiberg | 9841703 | 2019-03-24 19:12:40 +0100 | [diff] [blame] | 267 | TEST(Stacktrace, TestRtcEventDeadlockDetection) { |
| 268 | // Start looking for the expected log output. |
| 269 | LookoutLogSink sink(/*look_for=*/"Probable deadlock"); |
| 270 | rtc::LogMessage::AddLogToStream(&sink, rtc::LS_WARNING); |
| 271 | |
| 272 | // Start a thread that waits for an event. |
| 273 | rtc::Event ev; |
Markus Handell | ad5037b | 2021-05-07 15:02:36 +0200 | [diff] [blame] | 274 | auto thread = rtc::PlatformThread::SpawnJoinable( |
| 275 | [&ev] { ev.Wait(rtc::Event::kForever); }, |
| 276 | "TestRtcEventDeadlockDetection"); |
Karl Wiberg | 9841703 | 2019-03-24 19:12:40 +0100 | [diff] [blame] | 277 | |
| 278 | // The message should appear after 3 sec. We'll wait up to 10 sec in an |
| 279 | // attempt to not be flaky. |
| 280 | EXPECT_TRUE(sink.WhenFound().Wait(10000)); |
| 281 | |
| 282 | // Unblock the thread and shut it down. |
| 283 | ev.Set(); |
Markus Handell | ad5037b | 2021-05-07 15:02:36 +0200 | [diff] [blame] | 284 | thread.Finalize(); |
Karl Wiberg | 9841703 | 2019-03-24 19:12:40 +0100 | [diff] [blame] | 285 | rtc::LogMessage::RemoveLogToStream(&sink); |
| 286 | } |
| 287 | |
Magnus Jedvert | 7510e4a | 2019-01-20 11:46:32 +0100 | [diff] [blame] | 288 | } // namespace test |
| 289 | } // namespace webrtc |