| // Copyright 2026 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/fit/defer.h> | |
| #include <lib/zx/job.h> | |
| #include <lib/zx/process.h> | |
| #include <lib/zx/socket.h> | |
| #include <lib/zx/thread.h> | |
| #include <zircon/syscalls/debug.h> | |
| #include <zircon/syscalls/exception.h> | |
| #include <gtest/gtest.h> | |
| #include "src/developer/debug/shared/channel_watcher.h" | |
| #include "src/developer/debug/shared/platform_message_loop.h" | |
| #include "src/developer/debug/shared/socket_watcher.h" | |
| #include "src/developer/debug/shared/zircon_exception_watcher.h" | |
| namespace debug { | |
| namespace { | |
| class ReadableWatcher : public ChannelWatcher, public SocketWatcher { | |
| public: | |
| explicit ReadableWatcher(MessageLoop* loop) : loop_(loop) {} | |
| void OnSocketReadable(zx_handle_t socket_handle) override { loop_->QuitNow(); } | |
| void OnChannelReadable(zx_handle_t channel_handle) override { | |
| char buf[6] = {'\0'}; | |
| uint32_t actual_bytes = 0; | |
| // We could get two Readable notifications, once when the sender writes to the channel, and then | |
| // again when the channel is closed. The first will have the "Hello" message, which will be | |
| // consumed. The second can come when the channel is closed, but there may or may not be any | |
| // data left in the channel. | |
| zx_channel_read(channel_handle, 0, &buf, nullptr, 5, 0, &actual_bytes, nullptr); | |
| // We haven't received any data before this, so we should have gotten something from the | |
| // channel here. The next time this function is called should not have anything else from the | |
| // sender. | |
| if (actual_bytes > 0) { | |
| ASSERT_EQ(actual_bytes, 5u); | |
| ASSERT_STREQ(buf, "Hello"); | |
| have_gotten_channel_data_ = true; | |
| } | |
| } | |
| void OnPeerClosed(zx_handle_t) override { | |
| // We should have always drained the channel data before acknowledging the PEER_CLOSED event. | |
| ASSERT_TRUE(have_gotten_channel_data_); | |
| loop_->QuitNow(); | |
| } | |
| private: | |
| bool have_gotten_channel_data_ = false; | |
| MessageLoop* loop_; | |
| }; | |
| } // namespace | |
| TEST(MessageLoop, ZirconSocket) { | |
| zx::socket sender, receiver; | |
| ASSERT_EQ(ZX_OK, zx::socket::create(ZX_SOCKET_STREAM, &sender, &receiver)); | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| // Scope everything to before MessageLoop::Cleanup(). | |
| { | |
| ReadableWatcher watcher(&loop); | |
| MessageLoop::WatchHandle watch_handle; | |
| ASSERT_EQ(ZX_OK, loop.WatchSocket(MessageLoop::WatchMode::kRead, receiver.get(), &watcher, | |
| &watch_handle)); | |
| ASSERT_TRUE(watch_handle.watching()); | |
| // Enqueue a task that should cause receiver to become readable. | |
| loop.PostTask(FROM_HERE, [&sender]() { sender.write(0, "Hello", 5, nullptr); }); | |
| // This will quit on success because the OnSocketReadable callback called | |
| // QuitNow, or hang forever on failure. | |
| // TODO(brettw) add a timeout when timers are supported in the message loop. | |
| loop.Run(); | |
| } | |
| loop.Cleanup(); | |
| } | |
| TEST(MessageLoop, ZirconChannel) { | |
| zx::channel sender, receiver; | |
| ASSERT_EQ(ZX_OK, zx::channel::create(0, &sender, &receiver)); | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| // Scope everything to before MessageLoop::Cleanup(). | |
| { | |
| ReadableWatcher watcher(&loop); | |
| MessageLoop::WatchHandle watch_handle; | |
| ASSERT_EQ(ZX_OK, loop.WatchChannel(receiver.get(), &watcher, &watch_handle)); | |
| ASSERT_TRUE(watch_handle.watching()); | |
| // Send a message across the channel. | |
| loop.PostTask(FROM_HERE, [&sender]() { sender.write(0, "Hello", 5, nullptr, 0); }); | |
| // Close the channel to signal we're done. | |
| loop.PostTask(FROM_HERE, [&sender]() { sender.reset(); }); | |
| // This will quit on success because the OnChannelReadable callback called | |
| // QuitNow, or hang forever on failure. | |
| loop.Run(); | |
| } | |
| loop.Cleanup(); | |
| } | |
| TEST(MessageLoop, DrainChannelBeforeClose) { | |
| zx::channel sender, receiver; | |
| ASSERT_EQ(ZX_OK, zx::channel::create(0, &sender, &receiver)); | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| // Scope everything to before MessageLoop::Cleanup(). | |
| { | |
| ReadableWatcher watcher(&loop); | |
| MessageLoop::WatchHandle watch_handle; | |
| ASSERT_EQ(ZX_OK, loop.WatchChannel(receiver.get(), &watcher, &watch_handle)); | |
| ASSERT_TRUE(watch_handle.watching()); | |
| // Synchronously send a message across the channel, then immediately hang up. The reader should | |
| // always drain the channel before acknowledging the peer closed event. | |
| sender.write(0, "Hello", 5, nullptr, 0); | |
| sender.reset(); | |
| // This will quit on success because the OnChannelReadable callback called | |
| // QuitNow, or hang forever on failure. | |
| loop.Run(); | |
| } | |
| loop.Cleanup(); | |
| } | |
| extern "C" void CrashingThreadEntryPoint(uintptr_t, uintptr_t) { __builtin_trap(); } | |
| class ExceptionWatcherForTest : public ZirconExceptionWatcher { | |
| public: | |
| ExceptionWatcherForTest(MessageLoop* loop, MessageLoop::WatchHandle* watch_handle, | |
| zx::thread* thread) | |
| : loop_(loop), watch_handle_(watch_handle), thread_(thread) {} | |
| void OnException(zx::exception exception_token, zx_exception_info_t exception_info) override { | |
| called_ = true; | |
| // Delete this WatchHandle while handling the exception. | |
| watch_handle_->StopWatching(); | |
| uint32_t state = ZX_EXCEPTION_STATE_HANDLED; | |
| exception_token.set_property(ZX_PROP_EXCEPTION_STATE, &state, sizeof(state)); | |
| // Set the thread's instruction pointer to zx_thread_exit, this is to prevent the thread from | |
| // resuming from us handling the exception and immediately faulting again without us catching it | |
| // and causing crash output to be surfaced in syslog output. | |
| zx_thread_state_general_regs_t regs; | |
| if (thread_->read_state(ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs)) == ZX_OK) { | |
| #if defined(__x86_64__) | |
| regs.rip = reinterpret_cast<uintptr_t>(&zx_thread_exit); | |
| #elif defined(__aarch64__) || defined(__riscv) | |
| regs.pc = reinterpret_cast<uintptr_t>(&zx_thread_exit); | |
| #endif | |
| thread_->write_state(ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs)); | |
| } | |
| loop_->QuitNow(); | |
| } | |
| bool called() const { return called_; } | |
| private: | |
| MessageLoop* loop_; | |
| MessageLoop::WatchHandle* watch_handle_; | |
| zx::thread* thread_; | |
| zx::suspend_token suspend_token_; | |
| bool called_ = false; | |
| }; | |
| TEST(MessageLoop, StopWatchingInExceptionCallback) { | |
| PlatformMessageLoop loop; | |
| std::string error_message; | |
| ASSERT_TRUE(loop.Init(&error_message)) << error_message; | |
| { | |
| MessageLoop::WatchHandle watch_handle; | |
| zx::thread thread; | |
| ExceptionWatcherForTest watcher(&loop, &watch_handle, &thread); | |
| zx_info_handle_basic_t info; | |
| ASSERT_EQ(ZX_OK, zx::process::self()->get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), | |
| nullptr, nullptr)); | |
| PlatformMessageLoop::WatchProcessConfig config; | |
| config.process_name = "test_process"; | |
| config.process_handle = zx::process::self()->get(); | |
| config.process_koid = info.koid; | |
| config.watcher = &watcher; | |
| config.claim_exception_channel = true; | |
| ASSERT_EQ(ZX_OK, loop.WatchProcessExceptions(config, &watch_handle)); | |
| ASSERT_TRUE(watch_handle.watching()); | |
| ASSERT_EQ(ZX_OK, zx::thread::create(*zx::process::self(), "crasher", 7, 0, &thread)); | |
| alignas(16) static uint8_t stack[8192]; | |
| uintptr_t stack_top = reinterpret_cast<uintptr_t>(stack + sizeof(stack)); | |
| ASSERT_EQ(ZX_OK, thread.start(reinterpret_cast<uintptr_t>(&CrashingThreadEntryPoint), stack_top, | |
| 0, 0)); | |
| loop.Run(); | |
| EXPECT_TRUE(watcher.called()); | |
| } | |
| loop.Cleanup(); | |
| } | |
| } // namespace debug |