| // Copyright 2019 The Fuchsia Authors | |
| // | |
| // Use of this source code is governed by a MIT-style | |
| // license that can be found in the LICENSE file or at | |
| // https://opensource.org/licenses/MIT | |
| #include "object/exceptionate.h" | |
| #include <zircon/errors.h> | |
| #include <zircon/syscalls/exception.h> | |
| #include <object/exception_dispatcher.h> | |
| #include <object/handle.h> | |
| #include <object/message_packet.h> | |
| #include <object/process_dispatcher.h> | |
| #include <object/thread_dispatcher.h> | |
| Exceptionate::Exceptionate(uint32_t type) : type_(type) {} | |
| Exceptionate::~Exceptionate() { Shutdown(); } | |
| zx_status_t Exceptionate::SetChannel(KernelHandle<ChannelDispatcher> channel_handle, | |
| zx_rights_t thread_rights, zx_rights_t process_rights) { | |
| if (!channel_handle.dispatcher()) { | |
| return ZX_ERR_INVALID_ARGS; | |
| } | |
| Guard<Mutex> guard{&lock_}; | |
| if (is_shutdown_) { | |
| return ZX_ERR_BAD_STATE; | |
| } | |
| if (HasValidChannelLocked()) { | |
| return ZX_ERR_ALREADY_BOUND; | |
| } | |
| // At this point we're certain that either there is no channel or it's a | |
| // dead channel with no peer (since channel endpoints can't re-open) so we | |
| // can overwrite it. | |
| channel_handle_ = ktl::move(channel_handle); | |
| thread_rights_ = thread_rights; | |
| process_rights_ = process_rights; | |
| return ZX_OK; | |
| } | |
| void Exceptionate::Shutdown() { | |
| Guard<Mutex> guard{&lock_}; | |
| channel_handle_.reset(); | |
| is_shutdown_ = true; | |
| } | |
| bool Exceptionate::HasValidChannel() const { | |
| Guard<Mutex> guard{&lock_}; | |
| return HasValidChannelLocked(); | |
| } | |
| bool Exceptionate::HasValidChannelLocked() const { | |
| return channel_handle_.dispatcher() && !channel_handle_.dispatcher()->PeerHasClosed(); | |
| } | |
| zx_status_t Exceptionate::SendException(const fbl::RefPtr<ExceptionDispatcher>& exception) { | |
| DEBUG_ASSERT(exception); | |
| Guard<Mutex> guard{&lock_}; | |
| if (!channel_handle_.dispatcher()) { | |
| return ZX_ERR_NEXT; | |
| } | |
| zx_exception_info_t info{}; | |
| // Since info will be copied to a usermode process make sure it's safe to to be copied (no | |
| // internal padding, trivially copyable, etc.). | |
| static_assert(internal::is_copy_allowed<decltype(info)>::value); | |
| info.tid = exception->thread()->get_koid(); | |
| info.pid = exception->thread()->process()->get_koid(); | |
| info.type = exception->exception_type(); | |
| MessagePacketPtr message; | |
| zx_status_t status = | |
| MessagePacket::Create(reinterpret_cast<const char*>(&info), sizeof(info), 1, &message); | |
| if (status != ZX_OK) { | |
| return status; | |
| } | |
| // It's OK if the function fails after this point, all exception sending | |
| // funnels through here so the task rights will get overwritten next time | |
| // we try to send it. | |
| // | |
| // This is safe to do because we know that an ExceptionDispatcher only goes | |
| // to one handler at a time, so we'll never change the task rights while | |
| // the exception is out in userspace. | |
| exception->SetTaskRights(thread_rights_, process_rights_); | |
| HandleOwner exception_handle(Handle::Make(exception, ExceptionDispatcher::default_rights())); | |
| if (!exception_handle) { | |
| return ZX_ERR_NO_MEMORY; | |
| } | |
| message->mutable_handles()[0] = exception_handle.release(); | |
| message->set_owns_handles(true); | |
| status = channel_handle_.dispatcher()->Write(ZX_KOID_INVALID, ktl::move(message)); | |
| // If sending failed for any reason, the exception handle never made it to | |
| // userspace and has now gone out of scope, triggering on_zero_handles(), | |
| // so we need to reset the exception. | |
| if (status != ZX_OK) { | |
| exception->DiscardHandleClose(); | |
| } | |
| // ZX_ERR_PEER_CLOSED just indicates that there's no longer an endpoint | |
| // to receive exceptions, simplify things for callers by collapsing this | |
| // into the ZX_ERR_NEXT case since it means the same thing. | |
| if (status == ZX_ERR_PEER_CLOSED) { | |
| channel_handle_.reset(); | |
| return ZX_ERR_NEXT; | |
| } | |
| return status; | |
| } |