| // 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> |
| |
| #include "src/developer/debug/debug_agent/module_list.h" |
| #include "src/developer/debug/debug_agent/zircon_process_handle.h" |
| #include "src/developer/debug/debug_agent/zircon_thread_handle.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. |
| std::unique_ptr<ThreadHandle> 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. |
| std::unique_lock<std::mutex> lock(data->mutex); |
| data->thread_ready = true; |
| data->thread_ready_cv.notify_one(); |
| |
| // Block until the backtrace is done being completed. |
| 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 handle; |
| zx::thread::self()->duplicate(ZX_RIGHT_SAME_RIGHTS, &handle); |
| |
| // Here we use fake koids for the process and thread because those aren't necessary for the test. |
| data->thread = std::make_unique<ZirconThreadHandle>(std::move(handle)); |
| |
| // 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. |
| std::unique_ptr<SuspendHandle> SyncSuspendThread(ThreadHandle& thread) { |
| auto suspend_handle = thread.Suspend(); |
| |
| // Need long timeout when running on shared bots on QEMU. |
| zx_signals_t observed = 0; |
| zx_status_t status = thread.GetNativeHandle().wait_one( |
| ZX_THREAD_SUSPENDED, zx::deadline_after(zx::sec(10)), &observed); |
| EXPECT_TRUE(observed & ZX_THREAD_SUSPENDED); |
| if (status != ZX_OK) |
| return nullptr; |
| |
| return suspend_handle; |
| } |
| |
| bool FrameHasRegister(const debug_ipc::StackFrame& frame, debug::RegisterID id) { |
| for (const auto& reg : frame.regs) { |
| if (reg.id == id) |
| return true; |
| } |
| return false; |
| } |
| |
| // Returns true if the given stack frame has values for the thread-specific registers for the |
| // current platform. This does not validate the actual values. |
| bool FrameHasThreadRegisters(const debug_ipc::StackFrame& frame) { |
| #if defined(__x86_64__) |
| return FrameHasRegister(frame, debug::RegisterID::kX64_fsbase) && |
| FrameHasRegister(frame, debug::RegisterID::kX64_gsbase); |
| #elif defined(__aarch64__) |
| return FrameHasRegister(frame, debug::RegisterID::kARMv8_tpidr); |
| #else |
| #error Write for your platform |
| #endif |
| } |
| |
| void DoUnwindTest() { |
| zx::process handle; |
| zx::process::self()->duplicate(ZX_RIGHT_SAME_RIGHTS, &handle); |
| ZirconProcessHandle process(std::move(handle)); |
| |
| 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. |
| auto suspend = SyncSuspendThread(*data.thread); |
| |
| // Get the registers for the unwinder. |
| std::optional<GeneralRegisters> regs = data.thread->GetGeneralRegisters(); |
| ASSERT_TRUE(regs); |
| |
| // Find the module information. |
| ModuleList modules; |
| modules.Update(process); |
| |
| // Do the unwinding. |
| zx_status_t status = UnwindStack(process, modules, *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 each one should have |
| // "a bunch" of registers. |
| ASSERT_TRUE(stack.size() >= 3) << "Only got " << stack.size() << " stack entries"; |
| |
| // Don't check the bottom stack frame because it sometimes has weird initial state. |
| for (size_t i = 0; i < stack.size() - 1; i++) { |
| EXPECT_TRUE(stack[i].ip != 0); |
| EXPECT_TRUE(stack[i].regs.size() >= 8) |
| << "Only got " << stack[i].regs.size() << " regs for frame " << i; |
| |
| // Each stack frame should have the thread-specific registers that don't change across frames. |
| EXPECT_TRUE(FrameHasThreadRegisters(stack[i])); |
| } |
| |
| // 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(); |
| } |
| |
| TEST(Unwind, Fuchsia) { |
| SetUnwinderType(UnwinderType::kFuchsia); |
| DoUnwindTest(); |
| } |
| |
| } // namespace debug_agent |