blob: 0700c0efb05008c6c11714474434090c5e2e2dbd [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 <lib/zx/event.h>
#include <lib/zx/exception.h>
#include <lib/zx/process.h>
#include <lib/zx/thread.h>
#include <stdio.h>
#include <string.h>
#include <zircon/syscalls/exception.h>
#include <zircon/threads.h>
#include <memory>
#include <inspector/inspector.h>
#include <zxtest/zxtest.h>
#include "src/lib/debug/backtrace-request.h"
namespace {
#if defined(__aarch64__)
#define BKPT_PC_ADVANCE 4
#elif defined(__riscv)
// Note this assumes `c.ebreak`, which is what `ebreak` produces when C is
// enabled, and thus what `__builtin_debugtrap()` thus will produce.
#define BKPT_PC_ADVANCE 2
#else
#define BKPT_PC_ADVANCE 0
#endif
// Test utilities ----------------------------------------------------------------------------------
constexpr int kLoopThreadCount = 5;
struct ThreadContext {
zx::unowned<zx::thread> thread;
zx::channel exception_channel;
// NOTE: Not all events are used by all tests.
zx::event loop_threads_ready[kLoopThreadCount]; // Set then all the looping threads are ready.
zx::event crash_thread_ready; // Set when the crash thread is about to crash.
zx::event test_done; // Set when test is done.
};
struct LoopThreadContext {
int index;
ThreadContext* context;
};
ThreadContext SetupThreadContext() {
ThreadContext context;
for (int i = 0; i < kLoopThreadCount; i++) {
zx::event::create(0, &context.loop_threads_ready[i]);
}
zx::event::create(0, &context.crash_thread_ready);
zx::event::create(0, &context.test_done);
return context;
}
struct ExceptionReport {
zx::exception exception;
zx_exception_info_t info;
zx_thread_state_general_regs_t regs;
};
ExceptionReport WaitForException(ThreadContext* context) {
ExceptionReport report;
// We wait for the exception.
context->exception_channel.wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), nullptr);
context->exception_channel.read(0, &report.info, report.exception.reset_and_get_address(),
sizeof(zx_exception_info_t), 1, nullptr, nullptr);
context->thread->read_state(ZX_THREAD_STATE_GENERAL_REGS, &report.regs, sizeof(report.regs));
return report;
}
std::string GetProcessName() {
// Search for the process name.
char process_name[ZX_MAX_NAME_LEN];
zx::process::self()->get_property(ZX_PROP_NAME, process_name, sizeof(process_name));
return process_name;
}
void ResumeException(ThreadContext* context, ExceptionReport* report) {
#if BKPT_PC_ADVANCE > 0
// Skip past the breakpoint instruction.
// Otherwise the breakpoint will trigger again.
report->regs.pc += BKPT_PC_ADVANCE;
ASSERT_OK(context->thread->write_state(ZX_THREAD_STATE_GENERAL_REGS, &report->regs,
sizeof(report->regs)));
#endif
uint32_t handled = ZX_EXCEPTION_STATE_HANDLED;
ASSERT_OK(report->exception.set_property(ZX_PROP_EXCEPTION_STATE, &handled, sizeof(handled)));
report->exception.reset();
}
// Thread Functions --------------------------------------------------------------------------------
int LoopThread(void* user) {
LoopThreadContext* context = reinterpret_cast<LoopThreadContext*>(user);
// Tell the test that this thread is running.
context->context->loop_threads_ready[context->index].signal(0, ZX_USER_SIGNAL_0);
// Wait until the test tells us we're ready.
context->context->test_done.wait_one(ZX_USER_SIGNAL_0, zx::time::infinite(), 0);
return 0;
}
// Define the crashing function in assembly so it can use specialized CFI
// that constitutes a regression test for unwinder bugs.
extern "C" void CrashingFunction();
__asm__(
".globl CrashingFunction\n"
".type CrashingFunction, %function\n"
"CrashingFunction:\n"
".cfi_startproc\n"
"nop\n"
#if defined(__aarch64__)
".cfi_return_column 30\n"
// This has the effect of the default same_value rule, but via
// a val_expression rule to test the unwinder's val_expression support.
// DW_CFA_val_expression, regno 30, BLOCK(DW_OP_breg30 0)
".cfi_escape 0x16, 30, 2, 0x8e, 0\n"
"brk 0\n"
#elif defined(__riscv)
".cfi_return_column 1\n"
// This has the effect of the default same_value rule, but via
// a val_expression rule to test the unwinder's val_expression support.
// DW_CFA_val_expression, regno 1, BLOCK(DW_OP_breg1 0)
".cfi_escape 0x16, 1, 2, 0x71, 0\n"
".option push\n"
".option arch, +c\n"
"c.ebreak\n"
".option pop\n"
#elif defined(__x86_64__)
".cfi_return_column 16\n"
// DW_CFA_val_expression, regno 16, BLOCK(DW_OP_breg16 0)
".cfi_escape 0x16, 16, 2, 0x80, 0\n"
"int3\n"
#else
#error Not supported on this platform.
#endif
"ret\n"
".cfi_endproc\n"
".size CrashingFunction, . - CrashingFunction");
int CrashThreadFunction(void* user) {
ThreadContext* context = reinterpret_cast<ThreadContext*>(user);
zx_status_t status;
// Bind the exception channel for the caller.
status = zx::thread::self()->create_exception_channel(0, &context->exception_channel);
if (status != ZX_OK) {
printf("Could not get exception channel: %s.\n", zx_status_get_string(status));
return 1;
}
// Let them know we're ready.
status = context->crash_thread_ready.signal(0, ZX_USER_SIGNAL_0);
if (status != ZX_OK) {
printf("Could not signal thread ready: %s.\n", zx_status_get_string(status));
return 1;
}
CrashingFunction();
return 0;
}
} // namespace
// Tests -------------------------------------------------------------------------------------------
TEST(Inspector, PrintDebugInfoForOneThread) {
constexpr char kThreadName[] = "main-test-thread";
ThreadContext context = SetupThreadContext();
thrd_t c_thread;
ASSERT_EQ(thrd_create_with_name(&c_thread, CrashThreadFunction, &context, kThreadName), 0);
context.thread = zx::unowned<zx::thread>(thrd_get_zx_handle(c_thread));
// Wait for the thread to tell us we're ready.
ASSERT_OK(context.crash_thread_ready.wait_one(ZX_USER_SIGNAL_0, zx::time::infinite(), nullptr));
ExceptionReport report = WaitForException(&context);
// Create a temporary file to hold the output of the inspector call.
constexpr size_t kBufSize = 1024 * 1024;
auto buf = std::make_unique<char[]>(kBufSize + 1);
buf[kBufSize] = '\0';
FILE* inspect_out = fmemopen(buf.get(), kBufSize, "r+");
ASSERT_NE(inspect_out, nullptr);
inspector_print_debug_info(inspect_out, zx_process_self(), thrd_get_zx_handle(c_thread));
fclose(inspect_out);
std::string inspector_output(buf.get());
ASSERT_FALSE(inspector_output.empty());
ASSERT_NE(inspector_output.find(GetProcessName()), std::string::npos);
ASSERT_NE(inspector_output.find(kThreadName), std::string::npos);
ASSERT_NE(inspector_output.find("sw breakpoint"), std::string::npos);
ResumeException(&context, &report);
// Join the exception thread.
int res = -1;
ASSERT_EQ(thrd_join(c_thread, &res), thrd_success);
ASSERT_EQ(res, 0);
}
TEST(Inspector, PrintDebugInfoForManyThreads) {
ThreadContext context = SetupThreadContext();
// Create threads that will loop until the signal is off.
thrd_t loop_threads[kLoopThreadCount];
std::string loop_thread_names[kLoopThreadCount];
LoopThreadContext loop_thread_contexts[kLoopThreadCount];
for (int i = 0; i < kLoopThreadCount; i++) {
LoopThreadContext* loop_context = loop_thread_contexts + i;
loop_context->index = i;
loop_context->context = &context;
char buf[128];
snprintf(buf, sizeof(buf), "loop_thread_%d", i);
ASSERT_EQ(thrd_create_with_name(loop_threads + i, LoopThread, loop_context, buf), 0);
loop_thread_names[i] = buf;
}
// Wait until all the loop threads are done.
for (int i = 0; i < kLoopThreadCount; i++) {
context.loop_threads_ready[i].wait_one(ZX_USER_SIGNAL_0, zx::time::infinite(), 0);
}
// Create the main crash thread.
constexpr char kCrashThread1[] = "crash-thread";
thrd_t c_thread;
ASSERT_EQ(thrd_create_with_name(&c_thread, CrashThreadFunction, &context, kCrashThread1), 0);
context.thread = zx::unowned<zx::thread>(thrd_get_zx_handle(c_thread));
// Wait for the thread to tell us we're ready.
ASSERT_OK(context.crash_thread_ready.wait_one(ZX_USER_SIGNAL_0, zx::time::infinite(), nullptr));
ExceptionReport report = WaitForException(&context);
// Create a temporary file to hold the output of the inspector call.
constexpr size_t kBufSize = 1024 * 1024;
auto buf = std::make_unique<char[]>(kBufSize + 1);
buf[kBufSize] = '\0';
FILE* inspect_out = fmemopen(buf.get(), kBufSize, "r+");
ASSERT_NE(inspect_out, nullptr);
inspector_print_debug_info_for_all_threads(inspect_out, zx_process_self());
fclose(inspect_out);
std::string inspector_output(buf.get());
ASSERT_FALSE(inspector_output.empty());
// Search for the main thread. It should appear first.
ASSERT_NE(inspector_output.find(GetProcessName()), std::string::npos);
size_t pos = std::string::npos;
pos = inspector_output.find(kCrashThread1);
size_t inspector_pos = pos;
ASSERT_NE(pos, std::string::npos);
ASSERT_EQ(inspector_output.find(kCrashThread1, pos + 1), std::string::npos);
// Exception should only appear once.
pos = inspector_output.find("sw breakpoint");
ASSERT_NE(pos, std::string::npos);
ASSERT_EQ(inspector_output.find("sw breakpoint", pos + 1), std::string::npos);
// Each name should only appear once.
for (int i = 0; i < kLoopThreadCount; i++) {
pos = inspector_output.find(loop_thread_names[i]);
ASSERT_NE(pos, std::string::npos, "%s not found.", loop_thread_names[i].c_str());
ASSERT_EQ(inspector_output.find(loop_thread_names[i], pos + 1), std::string::npos,
"%s found twice.", loop_thread_names[i].c_str());
// Exception should always appear first.
ASSERT_LT(inspector_pos, pos);
}
ResumeException(&context, &report);
// Join the exception thread.
int res = -1;
ASSERT_EQ(thrd_join(c_thread, &res), thrd_success);
ASSERT_EQ(res, 0);
// Tell the loop threads we're done.
context.test_done.signal(0, ZX_USER_SIGNAL_0);
for (int i = 0; i < kLoopThreadCount; i++) {
res = -1;
ASSERT_EQ(thrd_join(loop_threads[i], &res), thrd_success);
ASSERT_EQ(res, 0);
}
}
// Run tests ---------------------------------------------------------------------------------------
int main(int argc, char* argv[]) { RUN_ALL_TESTS(argc, argv); }