blob: efd94edf259458bbf25d33bb5bf6336a243bae09 [file] [log] [blame]
// 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,
&regs, 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