blob: 060a728a28cf34474897e865bef7b87ee09b1f59 [file] [log] [blame]
// 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
#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()) {
Guard<Mutex> guard{&lock_};
if (is_shutdown_) {
if (HasValidChannelLocked()) {
// 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_};
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) {
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.).
info.tid = exception->thread()->get_koid(); = 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) {
message->mutable_handles()[0] = exception_handle.release();
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) {
// 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) {
return ZX_ERR_NEXT;
return status;