| // Copyright 2018 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/async/cpp/task.h> |
| #include <src/lib/fxl/strings/string_printf.h> |
| #include <lib/zx/channel.h> |
| #include <string.h> |
| |
| #include "garnet/lib/inferior_control/process.h" |
| #include "garnet/lib/inferior_control/test_helper.h" |
| #include "garnet/lib/inferior_control/test_server.h" |
| |
| #include "gtest/gtest.h" |
| |
| namespace inferior_control { |
| namespace { |
| |
| using ProcessTest = TestServer; |
| |
| TEST_F(ProcessTest, Launch) { |
| std::vector<std::string> argv{ |
| kTestHelperPath, |
| }; |
| |
| ASSERT_TRUE(SetupInferior(argv, zx::channel{})); |
| EXPECT_TRUE(RunHelperProgram()); |
| EXPECT_TRUE(Run()); |
| EXPECT_TRUE(TestSuccessfulExit()); |
| } |
| |
| // Test detaching and re-attaching. |
| // To add some determinism, we wait for the main thread to finish starting |
| // before detaching. This ensures we always have processed the main |
| // thread's ZX_EXCP_THREAD_STARTING exception before detaching. |
| // Note: Exceptions are handled in the same thread as server.Run(). |
| |
| class AttachTest : public TestServer { |
| public: |
| AttachTest() = default; |
| |
| void OnThreadStarting(Process* process, Thread* thread, zx_handle_t eport, |
| const zx_exception_context_t& context) override { |
| if (!main_thread_started_) { |
| // Must be the inferior's main thread. |
| main_thread_started_ = true; |
| DoDetachAttach(true); |
| // Do the test twice, once at THREAD_STARTING, prior to seeing the |
| // ld.so breakpoint, and once later after we've gone past it. |
| async::PostTask(message_loop().dispatcher(), |
| [this] { DoDetachAttach(false); }); |
| // Since we detached there's no need to resume the thread, the kernel |
| // will for us when the eport is unbound. |
| } else { |
| // The inferior doesn't have any other threads, but don't assume that. |
| TestServer::OnThreadStarting(process, thread, eport, context); |
| } |
| } |
| |
| void DoDetachAttach(bool thread_starting) { |
| auto inferior = current_process(); |
| |
| if (!thread_starting) { |
| // The inferior will send us a packet. Wait for it so that we know it has |
| // gone past the ld.so breakpoint. |
| zx_signals_t pending; |
| EXPECT_EQ(channel_.wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), |
| &pending), |
| ZX_OK); |
| } |
| |
| // Make a copy of the process handle, we use it to re-attach to shortly. |
| zx::process dup; |
| ASSERT_EQ(inferior->process().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup), |
| ZX_OK); |
| |
| EXPECT_TRUE(inferior->Detach()); |
| EXPECT_FALSE(inferior->IsAttached()); |
| EXPECT_FALSE(inferior->process()); |
| |
| // Sleep a little to hopefully give the inferior a chance to run. |
| // We want it to trip over the ld.so breakpoint if we forgot to remove it. |
| zx::nanosleep(zx::deadline_after(zx::msec(10))); |
| |
| EXPECT_TRUE(inferior->AttachToRunning(std::move(dup))); |
| EXPECT_TRUE(inferior->IsAttached()); |
| EXPECT_TRUE(inferior->process()); |
| |
| // If attaching failed we'll hang since we won't see the inferior exiting. |
| if (!inferior->IsAttached()) { |
| QuitMessageLoop(true); |
| } |
| |
| if (!thread_starting) { |
| // The inferior is waiting for us to close our side of the channel. |
| // We don't need to read the packet it sent us. |
| channel_.reset(); |
| } |
| } |
| |
| void set_channel(zx::channel channel) { channel_ = std::move(channel); } |
| |
| private: |
| bool main_thread_started_ = false; |
| zx::channel channel_; |
| }; |
| |
| TEST_F(AttachTest, Attach) { |
| std::vector<std::string> argv{ |
| kTestHelperPath, |
| "wait-peer-closed", |
| }; |
| |
| zx::channel our_channel, their_channel; |
| ASSERT_EQ(zx::channel::create(0, &our_channel, &their_channel), ZX_OK); |
| ASSERT_TRUE(SetupInferior(argv, std::move(their_channel))); |
| |
| EXPECT_TRUE(RunHelperProgram()); |
| set_channel(std::move(our_channel)); |
| |
| EXPECT_TRUE(Run()); |
| EXPECT_TRUE(TestSuccessfulExit()); |
| } |
| |
| class FindThreadByIdTest : public TestServer { |
| public: |
| FindThreadByIdTest() = default; |
| |
| void OnThreadStarting(Process* process, Thread* thread, zx_handle_t eport, |
| const zx_exception_context_t& context) override { |
| thread_koid_ = thread->id(); |
| Thread* lookup_thread = process->FindThreadById(thread_koid_); |
| if (lookup_thread) { |
| found_thread_by_id_ = true; |
| } |
| TestServer::OnThreadStarting(process, thread, eport, context); |
| } |
| |
| zx_koid_t thread_koid() const { return thread_koid_; } |
| bool found_thread_by_id() const { return found_thread_by_id_; } |
| |
| private: |
| bool found_thread_by_id_ = false; |
| zx_koid_t thread_koid_ = ZX_KOID_INVALID; |
| }; |
| |
| TEST_F(FindThreadByIdTest, FindThreadById) { |
| std::vector<std::string> argv{ |
| kTestHelperPath, |
| }; |
| |
| ASSERT_TRUE(SetupInferior(argv, zx::channel{})); |
| |
| EXPECT_TRUE(RunHelperProgram()); |
| |
| EXPECT_TRUE(Run()); |
| EXPECT_TRUE(TestSuccessfulExit()); |
| EXPECT_TRUE(found_thread_by_id()); |
| Process* process = current_process(); |
| ASSERT_NE(process, nullptr); |
| EXPECT_EQ(process->FindThreadById(thread_koid()), nullptr); |
| } |
| |
| class LdsoBreakpointTest : public TestServer { |
| public: |
| LdsoBreakpointTest() = default; |
| |
| bool dsos_loaded() const { return dsos_loaded_; } |
| bool libc_present() const { return libc_present_; } |
| bool exec_present() const { return exec_present_; } |
| |
| void OnArchitecturalException( |
| Process* process, Thread* thread, zx_handle_t eport, |
| const zx_excp_type_t type, const zx_exception_context_t& context) |
| override { |
| FXL_LOG(INFO) << "Got exception " |
| << debugger_utils::ExceptionNameAsString(type); |
| if (type == ZX_EXCP_SW_BREAKPOINT) { |
| // The shared libraries should have been loaded by now. |
| if (process->DsosLoaded()) { |
| dsos_loaded_ = true; |
| |
| // Libc and the main executable should be present. |
| for (debugger_utils::dsoinfo_t* dso = process->GetDsos(); dso; |
| dso = dso->next) { |
| FXL_VLOG(2) << "Have dso " << dso->name; |
| // The main executable's name might either be recorded as "" or |
| // a potentially clipped version of the path in which case |
| // "inferior_control_tests" should still be present. |
| if (strcmp(dso->name, "") == 0 || |
| strstr(dso->name, kTestHelperDsoName) != nullptr) { |
| exec_present_ = true; |
| } else if (strcmp(dso->name, "libc.so") == 0) { |
| libc_present_ = true; |
| } |
| } |
| |
| // Various state vars describing ld.so state should be set. |
| EXPECT_NE(process->debug_addr_property(), 0u); |
| EXPECT_TRUE(process->ldso_debug_data_has_initialized()); |
| EXPECT_NE(process->ldso_debug_break_addr(), 0u); |
| EXPECT_NE(process->ldso_debug_map_addr(), 0u); |
| } |
| |
| // Terminate the inferior, we don't want the exception propagating to |
| // the system exception handler. We don't check the result here. If it |
| // fails we'll timeout. |
| process->Kill(); |
| } else { |
| EXPECT_TRUE(thread->TryNext(eport)); |
| } |
| } |
| |
| private: |
| bool dsos_loaded_ = false; |
| bool libc_present_ = false; |
| bool exec_present_ = false; |
| }; |
| |
| TEST_F(LdsoBreakpointTest, LdsoBreakpoint) { |
| std::vector<std::string> argv{ |
| kTestHelperPath, |
| "trigger-sw-bkpt", |
| }; |
| |
| zx::channel our_channel, their_channel; |
| ASSERT_EQ(zx::channel::create(0, &our_channel, &their_channel), ZX_OK); |
| ASSERT_TRUE(SetupInferior(argv, std::move(their_channel))); |
| |
| EXPECT_TRUE(RunHelperProgram()); |
| |
| // The inferior is waiting for us to close our side of the channel. |
| our_channel.reset(); |
| |
| EXPECT_TRUE(Run()); |
| EXPECT_TRUE(dsos_loaded()); |
| EXPECT_TRUE(libc_present()); |
| EXPECT_TRUE(exec_present()); |
| } |
| |
| class KillTest : public TestServer { |
| public: |
| KillTest() = default; |
| |
| void OnThreadStarting(Process* process, Thread* thread, zx_handle_t eport, |
| const zx_exception_context_t& context) override { |
| kill_requested_ = process->Kill(); |
| TestServer::OnThreadStarting(process, thread, eport, context); |
| } |
| |
| bool kill_requested() const { return kill_requested_; } |
| |
| void set_channel(zx::channel channel) { channel_ = std::move(channel); } |
| |
| private: |
| bool kill_requested_ = false; |
| zx::channel channel_; |
| }; |
| |
| TEST_F(KillTest, Kill) { |
| std::vector<std::string> argv{ |
| kTestHelperPath, |
| "wait-peer-closed", |
| }; |
| |
| zx::channel our_channel, their_channel; |
| ASSERT_EQ(zx::channel::create(0, &our_channel, &their_channel), ZX_OK); |
| ASSERT_TRUE(SetupInferior(argv, std::move(their_channel))); |
| |
| EXPECT_TRUE(RunHelperProgram()); |
| |
| EXPECT_TRUE(Run()); |
| EXPECT_TRUE(TestFailureExit()); |
| EXPECT_TRUE(kill_requested()); |
| } |
| |
| // Test |RefreshThreads()| when a new thread is created while we're collecting |
| // the list of threads. This is done by detaching and re-attaching with |
| // successive number of new threads, and each time telling |RefreshThreads()| |
| // there's only one thread. |
| |
| class RefreshTest : public TestServer { |
| public: |
| static constexpr size_t kNumIterations = 4; |
| |
| RefreshTest() = default; |
| |
| void OnThreadStarting(Process* process, Thread* thread, zx_handle_t eport, |
| const zx_exception_context_t& context) override { |
| ++num_threads_; |
| FXL_LOG(INFO) << "Thread " << thread->id() << " starting, #threads: " << num_threads_; |
| // If this is the main thread then we don't want to do the test yet, |
| // we need to first proceed past the ld.so breakpoint. |
| // We can't currently catch the ld.so breakpoint, so just count started |
| // threads. |
| if (num_threads_ >= 2) { |
| PostQuitMessageLoop(true); |
| } |
| |
| // Pass on to baseclass method to resume the thread. |
| TestServer::OnThreadStarting(process, thread, eport, context); |
| } |
| |
| private: |
| int num_threads_ = 0; |
| }; |
| |
| TEST_F(RefreshTest, RefreshWithNewThreads) { |
| std::vector<std::string> argv{ |
| kTestHelperPath, |
| "start-n-threads", |
| fxl::StringPrintf("%zu", kNumIterations), |
| }; |
| |
| zx::channel our_channel, their_channel; |
| auto status = zx::channel::create(0, &our_channel, &their_channel); |
| ASSERT_EQ(status, ZX_OK); |
| |
| ASSERT_TRUE(SetupInferior(argv, std::move(their_channel))); |
| |
| EXPECT_TRUE(RunHelperProgram()); |
| |
| Process* inferior = current_process(); |
| |
| // This can't test new threads appearing while we're building the list, |
| // that is tested by the unittest for |GetProcessThreadKoids()|. But we |
| // can exercise |RefreshThreads()|. |
| |
| for (size_t i = 0; i < kNumIterations; ++i) { |
| FXL_VLOG(1) << "Iteration " << i + 1; |
| |
| // This won't return until the new thread is running. |
| EXPECT_TRUE(Run()); |
| |
| // Make a copy of the process handle, we use it to re-attach to shortly. |
| zx::process dup; |
| ASSERT_EQ(inferior->process().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup), |
| ZX_OK); |
| |
| // Detaching and re-attaching will cause us to discard the previously |
| // collected set of threads. |
| EXPECT_TRUE(inferior->Detach()); |
| EXPECT_TRUE(inferior->AttachToRunning(std::move(dup))); |
| |
| inferior->EnsureThreadMapFresh(); |
| // There should be the main thread plus one new thread each iteration. |
| size_t count = 0; |
| inferior->ForEachThread([&count](Thread*) { ++count; }); |
| EXPECT_EQ(count, i + 2); |
| |
| // Reset the quit indicator for the next iteration. Do this before |
| // we allow the inferior to advance and create a new thread. |
| EXPECT_EQ(message_loop().ResetQuit(), ZX_OK); |
| |
| // Send the inferior a packet so that it will continue with the next |
| // iteration. |
| FXL_VLOG(1) << "Advancing to next iteration"; |
| uint64_t packet = kUint64MagicPacketValue; |
| EXPECT_EQ(our_channel.write(0, &packet, sizeof(packet), nullptr, 0), ZX_OK); |
| } |
| |
| // Run the loop one more time to catch the inferior exiting. |
| EXPECT_TRUE(Run()); |
| |
| EXPECT_TRUE(TestSuccessfulExit()); |
| } |
| |
| } // namespace |
| } // namespace inferior_control |