| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/developer/debug/debug_agent/unwind.h" |
| |
| #include <lib/zx/process.h> |
| #include <lib/zx/suspend_token.h> |
| |
| #include <condition_variable> |
| #include <thread> |
| |
| #include "gtest/gtest.h" |
| |
| namespace debug_agent { |
| |
| namespace { |
| |
| // This would be simpler using a mutex instead of the condition variable since |
| // there are only two threads, but the Clang lock checker gets very upset. |
| struct ThreadData { |
| std::mutex mutex; |
| |
| // Set by thread itself before thread_ready is signaled. |
| // zx::thread::native_handle doesn't seem to do what we want. |
| zx::thread thread; |
| |
| bool thread_ready = false; |
| std::condition_variable thread_ready_cv; |
| |
| bool backtrace_done = false; |
| std::condition_variable backtrace_done_cv; |
| }; |
| |
| void __attribute__((noinline)) ThreadFunc2(ThreadData* data) { |
| // Tell the main thread we're ready for backtrace computation. |
| data->thread_ready = true; |
| data->thread_ready_cv.notify_one(); |
| |
| // Block until the backtrace is done being completed. |
| std::unique_lock<std::mutex> lock(data->mutex); |
| if (!data->backtrace_done) { |
| data->backtrace_done_cv.wait(lock, |
| [data]() { return data->backtrace_done; }); |
| } |
| } |
| |
| void __attribute__((noinline)) ThreadFunc1(ThreadData* data) { |
| // Fill in our thread handle. |
| zx::thread::self()->duplicate(ZX_RIGHT_SAME_RIGHTS, &data->thread); |
| |
| // Put another function on the stack. |
| ThreadFunc2(data); |
| |
| // This doesn't do anything useful but we need some code the compiler can't |
| // remove after the ThreadFunc2 call to ensure the compiler doesn't optimize |
| // out the return. |
| data->thread_ready_cv.notify_one(); |
| } |
| |
| // Synchronously suspends the thread. Returns a valid suspend token on success. |
| zx::suspend_token SyncSuspendThread(zx::thread& thread) { |
| zx::suspend_token token; |
| zx_status_t status = thread.suspend(&token); |
| EXPECT_EQ(ZX_OK, status); |
| |
| // Need long timeout when running on shared bots on QEMU. |
| zx_signals_t observed = 0; |
| status = thread.wait_one(ZX_THREAD_SUSPENDED, |
| zx::deadline_after(zx::sec(10)), &observed); |
| EXPECT_TRUE(observed & ZX_THREAD_SUSPENDED); |
| if (status != ZX_OK) |
| return zx::suspend_token(); |
| |
| return token; |
| } |
| |
| void DoUnwindTest() { |
| ThreadData data; |
| std::thread background(ThreadFunc1, &data); |
| |
| // Wait until the background thread is ready for the backtrace. |
| std::vector<debug_ipc::StackFrame> stack; |
| { |
| std::unique_lock<std::mutex> lock(data.mutex); |
| if (!data.thread_ready) |
| data.thread_ready_cv.wait(lock, [&data]() { return data.thread_ready; }); |
| |
| // Thread query functions require it to be suspended. |
| zx::suspend_token suspend = SyncSuspendThread(data.thread); |
| |
| // Get the registers for the unwinder. |
| zx_thread_state_general_regs regs; |
| zx_status_t status = data.thread.read_state(ZX_THREAD_STATE_GENERAL_REGS, |
| ®s, sizeof(regs)); |
| ASSERT_EQ(ZX_OK, status); |
| |
| // The debug addr is necessary to find the unwind information. |
| uintptr_t debug_addr = 0; |
| status = zx::process::self()->get_property(ZX_PROP_PROCESS_DEBUG_ADDR, |
| &debug_addr, sizeof(debug_addr)); |
| ASSERT_EQ(ZX_OK, status); |
| ASSERT_NE(0u, debug_addr); |
| |
| // Do the unwinding. |
| status = UnwindStack(*zx::process::self(), debug_addr, data.thread, regs, |
| 16, &stack); |
| ASSERT_EQ(ZX_OK, status); |
| |
| data.backtrace_done = true; |
| } |
| |
| // Tell the background thread it can complete. |
| data.backtrace_done_cv.notify_one(); |
| background.join(); |
| |
| // Validate the stack. It's really hard to say what these values will be |
| // without symbols given the few guarantees C++ can provide. But we should |
| // have "several" entries and the first one should have "a bunch" of |
| // registers. |
| ASSERT_TRUE(stack.size() >= 2) << "Only got " << stack.size(); |
| EXPECT_TRUE(stack[0].ip != 0); |
| EXPECT_TRUE(stack[0].regs.size() >= 8); |
| |
| // TODO: It might be nice to write the thread functions in assembly so we can |
| // know what the addresses are supposed to be. |
| } |
| |
| } // namespace |
| |
| TEST(Unwind, Android) { |
| SetUnwinderType(UnwinderType::kAndroid); |
| DoUnwindTest(); |
| } |
| |
| TEST(Unwind, NG) { |
| SetUnwinderType(UnwinderType::kNgUnwind); |
| DoUnwindTest(); |
| } |
| |
| } // namespace debug_agent |