| // 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 "garnet/lib/debug_ipc/helper/message_loop_zircon.h" |
| |
| #include <lib/fdio/io.h> |
| #include <lib/fdio/unsafe.h> |
| #include <lib/zx/handle.h> |
| #include <lib/zx/job.h> |
| #include <lib/zx/process.h> |
| #include <zircon/syscalls/exception.h> |
| |
| #include "garnet/lib/debug_ipc/helper/fd_watcher.h" |
| #include "garnet/lib/debug_ipc/helper/socket_watcher.h" |
| #include "garnet/lib/debug_ipc/helper/zircon_exception_watcher.h" |
| #include "garnet/public/lib/fxl/logging.h" |
| |
| namespace debug_ipc { |
| |
| namespace { |
| |
| // This signal on the task_event_ indicates there is work to do. |
| constexpr uint32_t kTaskSignal = ZX_USER_SIGNAL_0; |
| |
| // 0 is an invalid ID for watchers, so is safe to use here. |
| constexpr uint64_t kTaskSignalKey = 0; |
| |
| thread_local MessageLoopZircon* current_message_loop_zircon = nullptr; |
| |
| } // namespace |
| |
| // Everything in this class must be simple and copyable since we copy this |
| // structure for every call (to avoid locking problems). |
| struct MessageLoopZircon::WatchInfo { |
| WatchType type = WatchType::kFdio; |
| |
| // FDIO-specific watcher parameters. |
| int fd = -1; |
| fdio_t* fdio = nullptr; |
| FDWatcher* fd_watcher = nullptr; |
| zx_handle_t fd_handle = ZX_HANDLE_INVALID; |
| |
| // Socket-specific parameters. |
| SocketWatcher* socket_watcher = nullptr; |
| zx_handle_t socket_handle = ZX_HANDLE_INVALID; |
| |
| // Task-exception-specific parameters, can be of job or process type. |
| ZirconExceptionWatcher* exception_watcher = nullptr; |
| zx_koid_t task_koid = 0; |
| zx_handle_t task_handle = ZX_HANDLE_INVALID; |
| }; |
| |
| MessageLoopZircon::MessageLoopZircon() { |
| zx::port::create(0, &port_); |
| |
| zx::event::create(0, &task_event_); |
| task_event_.wait_async(port_, kTaskSignalKey, kTaskSignal, |
| ZX_WAIT_ASYNC_REPEATING); |
| } |
| |
| MessageLoopZircon::~MessageLoopZircon() { |
| FXL_DCHECK(Current() != this); // Cleanup should have been called. |
| } |
| |
| void MessageLoopZircon::Init() { |
| MessageLoop::Init(); |
| |
| FXL_DCHECK(!current_message_loop_zircon); |
| current_message_loop_zircon = this; |
| } |
| |
| void MessageLoopZircon::Cleanup() { |
| FXL_DCHECK(current_message_loop_zircon == this); |
| current_message_loop_zircon = nullptr; |
| |
| MessageLoop::Cleanup(); |
| } |
| |
| // static |
| MessageLoopZircon* MessageLoopZircon::Current() { |
| return current_message_loop_zircon; |
| } |
| |
| MessageLoop::WatchHandle MessageLoopZircon::WatchFD(WatchMode mode, int fd, |
| FDWatcher* watcher) { |
| WatchInfo info; |
| info.type = WatchType::kFdio; |
| info.fd_watcher = watcher; |
| info.fd = fd; |
| info.fdio = fdio_unsafe_fd_to_io(fd); |
| if (!info.fdio) |
| return WatchHandle(); |
| |
| uint32_t events = 0; |
| switch (mode) { |
| case WatchMode::kRead: |
| events = POLLIN; |
| break; |
| case WatchMode::kWrite: |
| events = POLLOUT; |
| break; |
| case WatchMode::kReadWrite: |
| events = POLLIN | POLLOUT; |
| break; |
| } |
| |
| zx_signals_t signals = ZX_SIGNAL_NONE; |
| fdio_unsafe_wait_begin(info.fdio, events, &info.fd_handle, &signals); |
| if (info.fd_handle == ZX_HANDLE_INVALID) |
| return WatchHandle(); |
| |
| int watch_id; |
| { |
| std::lock_guard<std::mutex> guard(mutex_); |
| |
| watch_id = next_watch_id_; |
| next_watch_id_++; |
| if (zx_object_wait_async(info.fd_handle, port_.get(), |
| static_cast<uint64_t>(watch_id), signals, |
| ZX_WAIT_ASYNC_REPEATING) != ZX_OK) |
| return WatchHandle(); |
| |
| watches_[watch_id] = info; |
| } |
| return WatchHandle(this, watch_id); |
| } |
| |
| MessageLoop::WatchHandle MessageLoopZircon::WatchSocket( |
| WatchMode mode, zx_handle_t socket_handle, SocketWatcher* watcher) { |
| WatchInfo info; |
| info.type = WatchType::kSocket; |
| info.socket_watcher = watcher; |
| info.socket_handle = socket_handle; |
| |
| int watch_id; |
| { |
| std::lock_guard<std::mutex> guard(mutex_); |
| |
| watch_id = next_watch_id_; |
| next_watch_id_++; |
| |
| if (mode == WatchMode::kRead || mode == WatchMode::kReadWrite) { |
| zx_status_t status = |
| zx_object_wait_async(socket_handle, port_.get(), watch_id, |
| ZX_SOCKET_READABLE, ZX_WAIT_ASYNC_REPEATING); |
| if (status != ZX_OK) |
| return WatchHandle(); |
| } |
| |
| if (mode == WatchMode::kWrite || mode == WatchMode::kReadWrite) { |
| zx_status_t status = |
| zx_object_wait_async(socket_handle, port_.get(), watch_id, |
| ZX_SOCKET_WRITABLE, ZX_WAIT_ASYNC_REPEATING); |
| if (status != ZX_OK) |
| return WatchHandle(); |
| } |
| |
| watches_[watch_id] = info; |
| } |
| return WatchHandle(this, watch_id); |
| } |
| |
| MessageLoop::WatchHandle MessageLoopZircon::WatchProcessExceptions( |
| zx_handle_t process_handle, zx_koid_t process_koid, |
| ZirconExceptionWatcher* watcher) { |
| WatchInfo info; |
| info.type = WatchType::kProcessExceptions; |
| info.exception_watcher = watcher; |
| info.task_koid = process_koid; |
| info.task_handle = process_handle; |
| |
| int watch_id; |
| { |
| std::lock_guard<std::mutex> guard(mutex_); |
| |
| watch_id = next_watch_id_; |
| next_watch_id_++; |
| |
| // Bind to the exception port. |
| zx_status_t status = zx_task_bind_exception_port( |
| process_handle, port_.get(), watch_id, ZX_EXCEPTION_PORT_DEBUGGER); |
| if (status != ZX_OK) |
| return WatchHandle(); |
| |
| // Also watch for process termination. |
| status = |
| zx_object_wait_async(process_handle, port_.get(), watch_id, |
| ZX_PROCESS_TERMINATED, ZX_WAIT_ASYNC_REPEATING); |
| if (status != ZX_OK) |
| return WatchHandle(); |
| |
| watches_[watch_id] = info; |
| } |
| return WatchHandle(this, watch_id); |
| } |
| |
| MessageLoop::WatchHandle MessageLoopZircon::WatchJobExceptions( |
| zx_handle_t job_handle, zx_koid_t job_koid, |
| ZirconExceptionWatcher* watcher) { |
| WatchInfo info; |
| info.type = WatchType::kJobExceptions; |
| info.exception_watcher = watcher; |
| info.task_koid = job_koid; |
| info.task_handle = job_handle; |
| |
| int watch_id; |
| { |
| std::lock_guard<std::mutex> guard(mutex_); |
| |
| watch_id = next_watch_id_; |
| next_watch_id_++; |
| |
| // Bind to the exception port. |
| zx_status_t status = zx_task_bind_exception_port( |
| job_handle, port_.get(), watch_id, ZX_EXCEPTION_PORT_DEBUGGER); |
| if (status != ZX_OK) |
| return WatchHandle(); |
| |
| watches_[watch_id] = info; |
| } |
| return WatchHandle(this, watch_id); |
| } |
| |
| zx_status_t MessageLoopZircon::ResumeFromException(zx::thread& thread, |
| uint32_t options) { |
| return thread.resume_from_exception(port_, options); |
| } |
| |
| bool MessageLoopZircon::CheckAndProcessPendingTasks() { |
| std::lock_guard<std::mutex> guard(mutex_); |
| // Do a C++ task. |
| if (ProcessPendingTask()) { |
| SetHasTasks(); // Enqueue another task signal. |
| return true; |
| } |
| return false; |
| } |
| |
| void MessageLoopZircon::HandleException(zx_port_packet_t packet) { |
| WatchInfo* watch_info = nullptr; |
| { |
| // Some event being watched. |
| std::lock_guard<std::mutex> guard(mutex_); |
| auto found = watches_.find(packet.key); |
| if (found == watches_.end()) { |
| FXL_NOTREACHED(); |
| return; |
| } |
| watch_info = &found->second; |
| } |
| |
| // Dispatch the watch callback outside of the lock. This depends on the |
| // stability of the WatchInfo pointer in the map (std::map is stable across |
| // updates) and the watch not getting unregistered from another thread |
| // asynchronously (which the API requires and is enforced by a DCHECK in |
| // the StopWatching impl). |
| switch (watch_info->type) { |
| case WatchType::kFdio: |
| OnFdioSignal(packet.key, *watch_info, packet); |
| break; |
| case WatchType::kProcessExceptions: |
| OnProcessException(*watch_info, packet); |
| break; |
| case WatchType::kJobExceptions: |
| OnJobException(*watch_info, packet); |
| break; |
| case WatchType::kSocket: |
| OnSocketSignal(packet.key, *watch_info, packet); |
| break; |
| default: |
| FXL_NOTREACHED(); |
| } |
| } |
| |
| void MessageLoopZircon::RunUntilTimeout(zx::duration timeout) { |
| // Init should have been called. |
| FXL_DCHECK(Current() == this); |
| zx_port_packet_t packet; |
| zx_status_t status = port_.wait(zx::deadline_after(timeout), &packet); |
| FXL_DCHECK(status == ZX_OK || status == ZX_ERR_TIMED_OUT); |
| if (status == ZX_OK) { |
| HandleException(packet); |
| } |
| } |
| |
| // Previously, the approach was to first look for C++ tasks and when handled |
| // look for WatchHandle work and finally wait for an event. This worked because |
| // handle events didn't post C++ tasks. |
| // |
| // But some tests do post tasks on handle events. Because C++ tasks are signaled |
| // by explicitly signaling an zx::event, without manually checking, the C++ |
| // tasks will never be checked and we would get blocked until a watch handled |
| // is triggered. |
| // |
| // In order to handle the events properly, we need to check for C++ tasks before |
| // and *after* handling watch handle events. This way we always process C++ |
| // tasks before handle events and will get signaled if one of them posted a new |
| // task. |
| void MessageLoopZircon::RunImpl() { |
| // Init should have been called. |
| FXL_DCHECK(Current() == this); |
| |
| zx_port_packet_t packet; |
| while (!should_quit() && port_.wait(zx::time::infinite(), &packet) == ZX_OK) { |
| // We check first for pending C++ tasks. If an event was handled, it will |
| // signal the associated zx::event in order to trigger the port once more |
| // (this is the way we process an enqueued event). If there is no enqueued |
| // event, we won't trigger the event and go back to wait on the port. |
| if (packet.key == kTaskSignalKey) { |
| CheckAndProcessPendingTasks(); |
| continue; |
| } |
| |
| // If it wasn't a task, we check for what kind of exception it was and |
| // handle it. |
| HandleException(packet); |
| |
| // The exception handling could have added more pending work, so we have to |
| // re-check in order to correctly signal for new work. |
| CheckAndProcessPendingTasks(); |
| } |
| } |
| |
| void MessageLoopZircon::StopWatching(int id) { |
| // The dispatch code for watch callbacks requires this be called on the |
| // same thread as the message loop is. |
| FXL_DCHECK(Current() == this); |
| |
| std::lock_guard<std::mutex> guard(mutex_); |
| |
| auto found = watches_.find(id); |
| if (found == watches_.end()) { |
| FXL_NOTREACHED(); |
| return; |
| } |
| WatchInfo& info = found->second; |
| |
| switch (info.type) { |
| case WatchType::kFdio: |
| port_.cancel(*zx::unowned_handle(info.fd_handle), |
| static_cast<uint64_t>(id)); |
| break; |
| case WatchType::kProcessExceptions: { |
| zx::unowned_process process(info.task_handle); |
| // Binding an invalid port will detach from the exception port. |
| process->bind_exception_port(zx::port(), 0, ZX_EXCEPTION_PORT_DEBUGGER); |
| // Stop watching for process events. |
| port_.cancel(*process, id); |
| break; |
| } |
| case WatchType::kJobExceptions: { |
| zx::unowned_job job(info.task_handle); |
| // Binding an invalid port will detach from the exception port. |
| job->bind_exception_port(zx::port(), 0, ZX_EXCEPTION_PORT_DEBUGGER); |
| // Stop watching for job events. |
| port_.cancel(*job, id); |
| break; |
| } |
| case WatchType::kSocket: |
| port_.cancel(*zx::unowned_handle(info.socket_handle), id); |
| break; |
| default: |
| FXL_NOTREACHED(); |
| break; |
| } |
| watches_.erase(found); |
| } |
| |
| void MessageLoopZircon::SetHasTasks() { task_event_.signal(0, kTaskSignal); } |
| |
| void MessageLoopZircon::OnFdioSignal(int watch_id, const WatchInfo& info, |
| const zx_port_packet_t& packet) { |
| uint32_t events = 0; |
| fdio_unsafe_wait_end(info.fdio, packet.signal.observed, &events); |
| |
| if ((events & POLLERR) || (events & POLLHUP) || (events & POLLNVAL) || |
| (events & POLLRDHUP)) { |
| info.fd_watcher->OnFDError(info.fd); |
| |
| // Don't dispatch any other notifications when there's an error. Zircon |
| // seems to set readable and writable on error even if there's nothing |
| // there. |
| return; |
| } |
| |
| // Since notifications can cause the watcher to be removed, this flag tracks |
| // if anything has been issued and therefore we should re-check the watcher |
| // registration before dereferencing anything. |
| bool sent_notification = false; |
| |
| if (events & POLLIN) { |
| info.fd_watcher->OnFDReadable(info.fd); |
| sent_notification = true; |
| } |
| |
| if (events & POLLOUT) { |
| if (sent_notification) { |
| std::lock_guard<std::mutex> guard(mutex_); |
| if (watches_.find(packet.key) == watches_.end()) |
| return; |
| } |
| info.fd_watcher->OnFDWritable(info.fd); |
| sent_notification = true; |
| } |
| } |
| |
| void MessageLoopZircon::OnProcessException(const WatchInfo& info, |
| const zx_port_packet_t& packet) { |
| if (ZX_PKT_IS_EXCEPTION(packet.type)) { |
| // All debug exceptions. |
| switch (packet.type) { |
| case ZX_EXCP_THREAD_STARTING: |
| info.exception_watcher->OnThreadStarting(info.task_koid, |
| packet.exception.tid); |
| break; |
| case ZX_EXCP_THREAD_EXITING: |
| info.exception_watcher->OnThreadExiting(info.task_koid, |
| packet.exception.tid); |
| break; |
| case ZX_EXCP_GENERAL: |
| case ZX_EXCP_FATAL_PAGE_FAULT: |
| case ZX_EXCP_UNDEFINED_INSTRUCTION: |
| case ZX_EXCP_SW_BREAKPOINT: |
| case ZX_EXCP_HW_BREAKPOINT: |
| case ZX_EXCP_UNALIGNED_ACCESS: |
| case ZX_EXCP_POLICY_ERROR: |
| info.exception_watcher->OnException(info.task_koid, |
| packet.exception.tid, packet.type); |
| break; |
| default: |
| FXL_NOTREACHED(); |
| } |
| } else if (ZX_PKT_IS_SIGNAL_REP(packet.type) && |
| packet.signal.observed & ZX_PROCESS_TERMINATED) { |
| // This type of watcher also gets process terminated signals. |
| info.exception_watcher->OnProcessTerminated(info.task_koid); |
| } else { |
| FXL_NOTREACHED(); |
| } |
| } |
| |
| void MessageLoopZircon::OnJobException(const WatchInfo& info, |
| const zx_port_packet_t& packet) { |
| if (ZX_PKT_IS_EXCEPTION(packet.type)) { |
| // All debug exceptions. |
| switch (packet.type) { |
| case ZX_EXCP_PROCESS_STARTING: |
| info.exception_watcher->OnProcessStarting( |
| info.task_koid, packet.exception.pid, packet.exception.tid); |
| break; |
| default: |
| FXL_NOTREACHED(); |
| } |
| } else { |
| FXL_NOTREACHED(); |
| } |
| } |
| |
| void MessageLoopZircon::OnSocketSignal(int watch_id, const WatchInfo& info, |
| const zx_port_packet_t& packet) { |
| if (!ZX_PKT_IS_SIGNAL_REP(packet.type)) |
| return; |
| |
| // Dispatch readable signal. |
| if (packet.signal.observed & ZX_SOCKET_READABLE) |
| info.socket_watcher->OnSocketReadable(info.socket_handle); |
| |
| // When signaling both readable and writable, make sure the readable handler |
| // didn't remove the watch. |
| if ((packet.signal.observed & ZX_SOCKET_READABLE) && |
| (packet.signal.observed & ZX_SOCKET_WRITABLE)) { |
| std::lock_guard<std::mutex> guard(mutex_); |
| if (watches_.find(packet.key) == watches_.end()) |
| return; |
| } |
| |
| // Dispatch writable signal. |
| if (packet.signal.observed & ZX_SOCKET_WRITABLE) |
| info.socket_watcher->OnSocketWritable(info.socket_handle); |
| } |
| |
| const char* MessageLoopZircon::WatchTypeToString(WatchType type) { |
| switch (type) { |
| case WatchType::kFdio: |
| return "FDIO"; |
| case WatchType::kJobExceptions: |
| return "Job"; |
| case WatchType::kProcessExceptions: |
| return "Process"; |
| case WatchType::kSocket: |
| return "Socket"; |
| } |
| FXL_NOTREACHED(); |
| return ""; |
| } |
| |
| } // namespace debug_ipc |