| // 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/lib/debug/backtrace-request.h" | |
| #include <lib/zx/channel.h> | |
| #include <lib/zx/event.h> | |
| #include <lib/zx/exception.h> | |
| #include <lib/zx/thread.h> | |
| #include <atomic> | |
| #include <condition_variable> | |
| #include <mutex> | |
| #include <thread> | |
| #include <zxtest/zxtest.h> | |
| #include "src/lib/debug/backtrace-request-utils.h" | |
| namespace { | |
| constexpr zx_signals_t kChannelReadySignal = ZX_USER_SIGNAL_0; | |
| constexpr zx_signals_t kBacktraceReturnedSignal = ZX_USER_SIGNAL_1; | |
| TEST(BacktraceRequest, RequestAndResume) { | |
| zx::event event; | |
| ASSERT_OK(zx::event::create(0, &event)); | |
| zx::channel exception_channel; | |
| std::thread thread([&] { | |
| // Attach an exception handler so we can resume the request thread | |
| // locally without going to up the system crash service. | |
| ASSERT_OK(zx::thread::self()->create_exception_channel(0, &exception_channel)); | |
| ASSERT_OK(event.signal(0, kChannelReadySignal)); | |
| // Request the backtrace, then once it returns flip the signal to prove | |
| // we got control back at the right place. | |
| backtrace_request_current_thread(); | |
| ASSERT_OK(event.signal(0, kBacktraceReturnedSignal)); | |
| }); | |
| ASSERT_OK(event.wait_one(kChannelReadySignal, zx::time::infinite(), nullptr)); | |
| // Pull out the exception and all the state we need. | |
| zx_exception_info_t info; | |
| zx::exception exception; | |
| zx::thread exception_thread; | |
| zx_thread_state_general_regs_t regs; | |
| ASSERT_OK(exception_channel.wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), nullptr)); | |
| ASSERT_OK(exception_channel.read(0, &info, exception.reset_and_get_address(), sizeof(info), 1, | |
| nullptr, nullptr)); | |
| ASSERT_OK(exception.get_thread(&exception_thread)); | |
| ASSERT_OK(exception_thread.read_state(ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs))); | |
| // Make sure this is a backtrace and clean it up. | |
| ASSERT_TRUE(is_backtrace_request(info.type, ®s)); | |
| ASSERT_TRUE(is_backtrace_request_current_thread(®s)); | |
| ASSERT_OK(cleanup_backtrace_request(exception_thread.get(), ®s)); | |
| // Resume the thread, it should pick up where it left off. | |
| uint32_t handled = ZX_EXCEPTION_STATE_HANDLED; | |
| ASSERT_OK(exception.set_property(ZX_PROP_EXCEPTION_STATE, &handled, sizeof(handled))); | |
| exception.reset(); | |
| ASSERT_OK(event.wait_one(kBacktraceReturnedSignal, zx::time::infinite(), nullptr)); | |
| thread.join(); | |
| } | |
| TEST(BacktraceRequest, RequestAndResumeSpecificThread) { | |
| zx::event event; | |
| ASSERT_OK(zx::event::create(0, &event)); | |
| zx::channel exception_channel; | |
| std::thread thread([&] { | |
| ASSERT_OK(zx::thread::self()->create_exception_channel(0, &exception_channel)); | |
| zx_info_handle_basic_t basic_info; | |
| ASSERT_OK(zx::thread::self()->get_info(ZX_INFO_HANDLE_BASIC, &basic_info, sizeof(basic_info), | |
| nullptr, nullptr)); | |
| uint64_t my_koid = basic_info.koid; | |
| ASSERT_OK(event.signal(0, kChannelReadySignal)); | |
| backtrace_request_thread(my_koid); | |
| ASSERT_OK(event.signal(0, kBacktraceReturnedSignal)); | |
| }); | |
| ASSERT_OK(event.wait_one(kChannelReadySignal, zx::time::infinite(), nullptr)); | |
| zx_exception_info_t info; | |
| zx::exception exception; | |
| zx::thread exception_thread; | |
| zx_thread_state_general_regs_t regs; | |
| ASSERT_OK(exception_channel.wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), nullptr)); | |
| ASSERT_OK(exception_channel.read(0, &info, exception.reset_and_get_address(), sizeof(info), 1, | |
| nullptr, nullptr)); | |
| ASSERT_OK(exception.get_thread(&exception_thread)); | |
| ASSERT_OK(exception_thread.read_state(ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs))); | |
| ASSERT_TRUE(is_backtrace_request(info.type, ®s)); | |
| ASSERT_TRUE(is_backtrace_request_specific_thread(®s)); | |
| zx_info_handle_basic_t basic_info; | |
| ASSERT_OK(exception_thread.get_info(ZX_INFO_HANDLE_BASIC, &basic_info, sizeof(basic_info), | |
| nullptr, nullptr)); | |
| ASSERT_EQ(get_backtrace_request_koid(®s), basic_info.koid); | |
| ASSERT_OK(cleanup_backtrace_request(exception_thread.get(), ®s)); | |
| uint32_t handled = ZX_EXCEPTION_STATE_HANDLED; | |
| ASSERT_OK(exception.set_property(ZX_PROP_EXCEPTION_STATE, &handled, sizeof(handled))); | |
| exception.reset(); | |
| ASSERT_OK(event.wait_one(kBacktraceReturnedSignal, zx::time::infinite(), nullptr)); | |
| thread.join(); | |
| } | |
| TEST(BacktraceRequest, RequestAndResumeManyThreads) { | |
| // We only care that at least one thread triggers it before creating the main thread. | |
| constexpr zx_signals_t kWaitThreadReady = ZX_USER_SIGNAL_2; | |
| constexpr zx_signals_t kTestDoneSignal = ZX_USER_SIGNAL_3; | |
| zx::event event; | |
| ASSERT_OK(zx::event::create(0, &event)); | |
| // Create 5 threads that will wait until the test is done. | |
| constexpr int kWaitThreadCount = 5; | |
| std::thread wait_threads[kWaitThreadCount]; | |
| for (int i = 0; i < kWaitThreadCount; i++) { | |
| wait_threads[i] = std::thread([&]() { | |
| // Signal we're ready and wait for the test to be done. | |
| // It doesn't matter that the signal is already asserted when another thread comes. | |
| ASSERT_OK(event.signal(0, kWaitThreadReady)); | |
| ASSERT_OK(event.wait_one(kTestDoneSignal, zx::time::infinite(), nullptr)); | |
| }); | |
| } | |
| // Wait for one of the wait threads to be ready. | |
| ASSERT_OK(event.wait_one(kWaitThreadReady, zx::time::infinite(), nullptr)); | |
| zx::channel exception_channel; | |
| std::thread thread([&] { | |
| // Attach an exception handler so we can resume the request thread | |
| // locally without going to up the system crash service. | |
| ASSERT_OK(zx::thread::self()->create_exception_channel(0, &exception_channel)); | |
| ASSERT_OK(event.signal(0, kChannelReadySignal)); | |
| // Request the backtrace, then once it returns flip the signal to prove | |
| // we got control back at the right place. | |
| backtrace_request_all_threads(); | |
| ASSERT_OK(event.signal(0, kBacktraceReturnedSignal)); | |
| }); | |
| ASSERT_OK(event.wait_one(kChannelReadySignal, zx::time::infinite(), nullptr)); | |
| // Pull out the exception and all the state we need. | |
| zx_exception_info_t info; | |
| zx::exception exception; | |
| zx::thread exception_thread; | |
| zx_thread_state_general_regs_t regs; | |
| ASSERT_OK(exception_channel.wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), nullptr)); | |
| ASSERT_OK(exception_channel.read(0, &info, exception.reset_and_get_address(), sizeof(info), 1, | |
| nullptr, nullptr)); | |
| ASSERT_OK(exception.get_thread(&exception_thread)); | |
| ASSERT_OK(exception_thread.read_state(ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs))); | |
| // Make sure this is a backtrace and clean it up. | |
| ASSERT_TRUE(is_backtrace_request(info.type, ®s)); | |
| ASSERT_FALSE(is_backtrace_request_current_thread(®s)); | |
| ASSERT_OK(cleanup_backtrace_request(exception_thread.get(), ®s)); | |
| // Resume the thread, it should pick up where it left off. | |
| uint32_t handled = ZX_EXCEPTION_STATE_HANDLED; | |
| ASSERT_OK(exception.set_property(ZX_PROP_EXCEPTION_STATE, &handled, sizeof(handled))); | |
| exception.reset(); | |
| ASSERT_OK(event.wait_one(kBacktraceReturnedSignal, zx::time::infinite(), nullptr)); | |
| thread.join(); | |
| // Tell all the other threads we're done. | |
| ASSERT_OK(event.signal(0, kTestDoneSignal)); | |
| for (int i = 0; i < kWaitThreadCount; i++) { | |
| wait_threads[i].join(); | |
| } | |
| } | |
| TEST(BacktraceRequest, IgnoreNormalException) { | |
| zx::event event; | |
| ASSERT_OK(zx::event::create(0, &event)); | |
| zx::channel exception_channel; | |
| const void* exit_address = nullptr; | |
| std::thread thread([&] { | |
| exit_address = &&exit; // clang and gcc extension to obtain address of a label. | |
| ASSERT_OK(zx::thread::self()->create_exception_channel(0, &exception_channel)); | |
| ASSERT_OK(event.signal(0, kChannelReadySignal)); | |
| // Cause a non-breakpoint machine exception. The `asm goto` tells the | |
| // compiler that the code can jump to the exit: label below. The test code | |
| // below will mutate the PC to that label when it catches the exception. | |
| #if defined(__x86_64__) | |
| __asm__ goto("ud2" : : : : exit); | |
| #elif defined(__aarch64__) | |
| __asm__ goto("mov x0, xzr; brk #1" : : : "x0" : exit); | |
| #elif defined(__riscv) | |
| __asm__ goto("unimp" : : : : exit); | |
| #else | |
| #error "what machine?" | |
| #endif | |
| ADD_FAILURE("This should never be reached"); | |
| exit:; | |
| }); | |
| ASSERT_OK(event.wait_one(kChannelReadySignal, zx::time::infinite(), nullptr)); | |
| zx_exception_info_t info; | |
| zx::exception exception; | |
| zx::thread exception_thread; | |
| zx_thread_state_general_regs_t regs; | |
| ASSERT_OK(exception_channel.wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), nullptr)); | |
| ASSERT_OK(exception_channel.read(0, &info, exception.reset_and_get_address(), sizeof(info), 1, | |
| nullptr, nullptr)); | |
| ASSERT_OK(exception.get_thread(&exception_thread)); | |
| ASSERT_OK(exception_thread.read_state(ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs))); | |
| ASSERT_FALSE(is_backtrace_request(info.type, ®s)); | |
| // Move the program counter past the exception and resume. The thread should exit and | |
| // clean up normally. | |
| #if defined(__x86_64__) | |
| regs.rip = reinterpret_cast<size_t>(exit_address); | |
| #elif defined(__aarch64__) | |
| regs.pc = reinterpret_cast<size_t>(exit_address); | |
| #elif defined(__riscv) | |
| regs.pc = reinterpret_cast<size_t>(exit_address); | |
| #else | |
| #error "what machine?" | |
| #endif | |
| ASSERT_OK(exception_thread.write_state(ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs))); | |
| uint32_t handled = ZX_EXCEPTION_STATE_HANDLED; | |
| ASSERT_OK(exception.set_property(ZX_PROP_EXCEPTION_STATE, &handled, sizeof(handled))); | |
| exception.reset(); | |
| thread.join(); | |
| } | |
| } // namespace |