Add NotifyServer and its test.

TEST=util_test NotifyServerTest.*
R=rsesek@chromium.org

Review URL: https://codereview.chromium.org/804633002
This commit is contained in:
Mark Mentovai 2014-12-16 14:10:16 -05:00
parent 4263334db8
commit d78b003ef1
6 changed files with 994 additions and 6 deletions

View File

@ -59,13 +59,10 @@ class ChildPortServer : public MachMessageServer::Interface {
explicit ChildPortServer(Interface* interface);
// MachMessageServer::Interface:
bool MachMessageServerFunction(const mach_msg_header_t* in_header,
mach_msg_header_t* out_header,
bool* destroy_complex_request) override;
std::set<mach_msg_id_t> MachMessageServerRequestIDs() override;
mach_msg_size_t MachMessageServerRequestSize() override;
mach_msg_size_t MachMessageServerReplySize() override;

View File

@ -102,13 +102,10 @@ class UniversalMachExcServer final : public MachMessageServer::Interface {
~UniversalMachExcServer();
// MachMessageServer::Interface:
bool MachMessageServerFunction(const mach_msg_header_t* in_header,
mach_msg_header_t* out_header,
bool* destroy_complex_request) override;
std::set<mach_msg_id_t> MachMessageServerRequestIDs() override;
mach_msg_size_t MachMessageServerRequestSize() override;
mach_msg_size_t MachMessageServerReplySize() override;

245
util/mach/notify_server.cc Normal file
View File

@ -0,0 +1,245 @@
// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "util/mach/notify_server.h"
#include "base/logging.h"
#include "util/mach/notifyServer.h"
#include "util/mach/mach_message.h"
extern "C" {
// These five functions are not used, and are in fact obsoleted by the other
// functionality implemented in this file. The standard MIG-generated
// notify_server() (in notifyServer.c) server dispatch routine usable with the
// standard mach_msg_server() function calls out to this function.
// notify_server() is unused and is replaced by the more flexible NotifyServer,
// but the linker still needs to see these five function definitions.
kern_return_t do_mach_notify_port_deleted(notify_port_t notify,
mach_port_name_t name) {
NOTREACHED();
return KERN_FAILURE;
}
kern_return_t do_mach_notify_port_destroyed(notify_port_t notify,
mach_port_t rights) {
NOTREACHED();
return KERN_FAILURE;
}
kern_return_t do_mach_notify_no_senders(notify_port_t notify,
mach_port_mscount_t mscount) {
NOTREACHED();
return KERN_FAILURE;
}
kern_return_t do_mach_notify_send_once(notify_port_t notify) {
NOTREACHED();
return KERN_FAILURE;
}
kern_return_t do_mach_notify_dead_name(notify_port_t notify,
mach_port_name_t name) {
NOTREACHED();
return KERN_FAILURE;
}
} // extern "C"
namespace {
// The MIG-generated __MIG_check__Request__*() functions are not declared as
// accepting const data, but they could have been because they in fact do not
// modify the data. These wrapper functions are provided to bridge the const gap
// between the code in this file, which is const-correct and treats request
// message data as const, and the generated functions.
kern_return_t MIGCheckRequestMachNotifyPortDeleted(
const __Request__mach_notify_port_deleted_t* in_request) {
using Request = __Request__mach_notify_port_deleted_t;
return __MIG_check__Request__mach_notify_port_deleted_t(
const_cast<Request*>(in_request));
}
kern_return_t MIGCheckRequestMachNotifyPortDestroyed(
const __Request__mach_notify_port_destroyed_t* in_request) {
using Request = __Request__mach_notify_port_destroyed_t;
return __MIG_check__Request__mach_notify_port_destroyed_t(
const_cast<Request*>(in_request));
}
kern_return_t MIGCheckRequestMachNotifyNoSenders(
const __Request__mach_notify_no_senders_t* in_request) {
using Request = __Request__mach_notify_no_senders_t;
return __MIG_check__Request__mach_notify_no_senders_t(
const_cast<Request*>(in_request));
}
kern_return_t MIGCheckRequestMachNotifySendOnce(
const __Request__mach_notify_send_once_t* in_request) {
using Request = __Request__mach_notify_send_once_t;
return __MIG_check__Request__mach_notify_send_once_t(
const_cast<Request*>(in_request));
}
kern_return_t MIGCheckRequestMachNotifyDeadName(
const __Request__mach_notify_dead_name_t* in_request) {
using Request = __Request__mach_notify_dead_name_t;
return __MIG_check__Request__mach_notify_dead_name_t(
const_cast<Request*>(in_request));
}
} // namespace
namespace crashpad {
NotifyServer::NotifyServer(NotifyServer::Interface* interface)
: MachMessageServer::Interface(),
interface_(interface) {
}
bool NotifyServer::MachMessageServerFunction(
const mach_msg_header_t* in_header,
mach_msg_header_t* out_header,
bool* destroy_complex_request) {
PrepareMIGReplyFromRequest(in_header, out_header);
const mach_msg_trailer_t* in_trailer =
MachMessageTrailerFromHeader(in_header);
switch (in_header->msgh_id) {
case MACH_NOTIFY_PORT_DELETED: {
// mach_notify_port_deleted(), do_mach_notify_port_deleted().
using Request = __Request__mach_notify_port_deleted_t;
const Request* in_request = reinterpret_cast<const Request*>(in_header);
kern_return_t kr = MIGCheckRequestMachNotifyPortDeleted(in_request);
if (kr != MACH_MSG_SUCCESS) {
SetMIGReplyError(out_header, kr);
return true;
}
using Reply = __Reply__mach_notify_port_deleted_t;
Reply* out_reply = reinterpret_cast<Reply*>(out_header);
out_reply->RetCode =
interface_->DoMachNotifyPortDeleted(in_header->msgh_local_port,
in_request->name,
in_trailer);
return true;
}
case MACH_NOTIFY_PORT_DESTROYED: {
// mach_notify_port_destroyed(), do_mach_notify_port_destroyed().
using Request = __Request__mach_notify_port_destroyed_t;
const Request* in_request = reinterpret_cast<const Request*>(in_header);
kern_return_t kr = MIGCheckRequestMachNotifyPortDestroyed(in_request);
if (kr != MACH_MSG_SUCCESS) {
SetMIGReplyError(out_header, kr);
return true;
}
using Reply = __Reply__mach_notify_port_destroyed_t;
Reply* out_reply = reinterpret_cast<Reply*>(out_header);
out_reply->RetCode =
interface_->DoMachNotifyPortDestroyed(in_header->msgh_local_port,
in_request->rights.name,
in_trailer,
destroy_complex_request);
return true;
}
case MACH_NOTIFY_NO_SENDERS: {
// mach_notify_no_senders(), do_mach_notify_no_senders().
using Request = __Request__mach_notify_no_senders_t;
const Request* in_request = reinterpret_cast<const Request*>(in_header);
kern_return_t kr = MIGCheckRequestMachNotifyNoSenders(in_request);
if (kr != MACH_MSG_SUCCESS) {
SetMIGReplyError(out_header, kr);
return true;
}
using Reply = __Reply__mach_notify_no_senders_t;
Reply* out_reply = reinterpret_cast<Reply*>(out_header);
out_reply->RetCode =
interface_->DoMachNotifyNoSenders(in_header->msgh_local_port,
in_request->mscount,
in_trailer);
return true;
}
case MACH_NOTIFY_SEND_ONCE: {
// mach_notify_send_once(), do_mach_notify_send_once().
using Request = __Request__mach_notify_send_once_t;
const Request* in_request = reinterpret_cast<const Request*>(in_header);
kern_return_t kr = MIGCheckRequestMachNotifySendOnce(in_request);
if (kr != MACH_MSG_SUCCESS) {
SetMIGReplyError(out_header, kr);
return true;
}
using Reply = __Reply__mach_notify_send_once_t;
Reply* out_reply = reinterpret_cast<Reply*>(out_header);
out_reply->RetCode =
interface_->DoMachNotifySendOnce(in_header->msgh_local_port,
in_trailer);
return true;
}
case MACH_NOTIFY_DEAD_NAME: {
// mach_notify_dead_name(), do_mach_notify_dead_name().
using Request = __Request__mach_notify_dead_name_t;
const Request* in_request = reinterpret_cast<const Request*>(in_header);
kern_return_t kr = MIGCheckRequestMachNotifyDeadName(in_request);
if (kr != MACH_MSG_SUCCESS) {
SetMIGReplyError(out_header, kr);
return true;
}
using Reply = __Reply__mach_notify_dead_name_t;
Reply* out_reply = reinterpret_cast<Reply*>(out_header);
out_reply->RetCode =
interface_->DoMachNotifyDeadName(in_header->msgh_local_port,
in_request->name,
in_trailer);
return true;
}
default: {
SetMIGReplyError(out_header, MIG_BAD_ID);
return false;
}
}
}
std::set<mach_msg_id_t> NotifyServer::MachMessageServerRequestIDs() {
const mach_msg_id_t request_ids[] = {
MACH_NOTIFY_PORT_DELETED,
MACH_NOTIFY_PORT_DESTROYED,
MACH_NOTIFY_NO_SENDERS,
MACH_NOTIFY_SEND_ONCE,
MACH_NOTIFY_DEAD_NAME,
};
return std::set<mach_msg_id_t>(&request_ids[0],
&request_ids[arraysize(request_ids)]);
}
mach_msg_size_t NotifyServer::MachMessageServerRequestSize() {
return sizeof(__RequestUnion__do_notify_subsystem);
}
mach_msg_size_t NotifyServer::MachMessageServerReplySize() {
return sizeof(__ReplyUnion__do_notify_subsystem);
}
} // namespace crashpad

193
util/mach/notify_server.h Normal file
View File

@ -0,0 +1,193 @@
// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CRASHPAD_UTIL_MACH_NOTIFY_SERVER_H_
#define CRASHPAD_UTIL_MACH_NOTIFY_SERVER_H_
#include <mach/mach.h>
#include <set>
#include "base/basictypes.h"
#include "util/mach/mach_message_server.h"
namespace crashpad {
//! \brief A server interface for the `notify` Mach subsystem.
//!
//! The <a
//! href="https://lists.apple.com/archives/darwin-development/2001/Sep/msg00451.html">mach
//! port notifications</a> thread on the <a
//! href="https://lists.apple.com/archives/darwin-development/">darwin-development</a>
//! mailing list (now known as <a
//! href="https://lists.apple.com/mailman/listinfo/darwin-dev">darwin-dev</a>)
//! is good background for the various notification types.
class NotifyServer : public MachMessageServer::Interface {
public:
//! \brief An interface that the different request messages that are a part of
//! the `notify` Mach subsystem can be dispatched to.
class Interface {
public:
//! \brief Handles port-deleted notifications sent by
//! `mach_notify_port_deleted()`.
//!
//! A port-deleted notification is generated when a port with a dead-name
//! notification request is destroyed and the port name becomes available
//! for reuse.
//!
//! This behaves equivalently to a `do_mach_notify_port_deleted()` function
//! used with `notify_server()`.
//!
//! \param[in] notify The Mach port that the notification was sent to.
//! \param[in] name The name that formerly referenced the deleted port. When
//! this method is called, \a name no longer corresponds to the port
//! that has been deleted, and may be reused for another purpose.
//! \param[in] trailer The trailer received with the notification message.
virtual kern_return_t DoMachNotifyPortDeleted(
notify_port_t notify,
mach_port_name_t name,
const mach_msg_trailer_t* trailer) = 0;
//! \brief Handles port-destroyed notifications sent by
//! `mach_notify_port_destroyed()`.
//!
//! A port-destroyed notification is generated when a receive right with a
//! port-destroyed notification request is destroyed. Rather than destroying
//! the receive right, it is transferred via this notifications \a rights
//! parameter.
//!
//! This behaves equivalently to a `do_mach_notify_port_destroyed()`
//! function used with `notify_server()`.
//!
//! \param[in] notify The Mach port that the notification was sent to.
//! \param[in] rights A receive right for the port that would have been
//! destroyed. The callee takes ownership of this port, however, if the
//! callee does not wish to take ownership, it may set \a
//! destroy_request to `true`.
//! \param[in] trailer The trailer received with the notification message.
//! \param[out] destroy_request `true` if the request message is to be
//! destroyed even when this method returns success. See
//! MachMessageServer::Interface.
virtual kern_return_t DoMachNotifyPortDestroyed(
notify_port_t notify,
mach_port_t rights,
const mach_msg_trailer_t* trailer,
bool* destroy_request) = 0;
//! \brief Handles no-senders notifications sent by
//! `mach_notify_no_senders()`.
//!
//! A no-senders notification is generated when a receive right with a
//! no-senders notification request loses its last corresponding send right.
//!
//! This behaves equivalently to a `do_mach_notify_no_senders()` function
//! used with `notify_server()`.
//!
//! \param[in] notify The Mach port that the notification was sent to.
//! \param[in] mscount The value of the sender-less ports make-send count
//! at the time the notification was generated.
//! \param[in] trailer The trailer received with the notification message.
virtual kern_return_t DoMachNotifyNoSenders(
notify_port_t notify,
mach_port_mscount_t mscount,
const mach_msg_trailer_t* trailer) = 0;
//! \brief Handles send-once notifications sent by
//! `mach_notify_send_once()`.
//!
//! A send-once notification is generated when a send-once right is
//! destroyed without being used.
//!
//! This behaves equivalently to a `do_mach_notify_send_once()` function
//! used with `notify_server()`.
//!
//! \param[in] notify The Mach port that the notification was sent to.
//! \param[in] trailer The trailer received with the notification message.
//!
//! \note Unlike the other notifications in the `notify` subsystem,
//! send-once notifications are not generated as a result of a
//! notification request, but are generated any time a send-once right
//! is destroyed rather than being used. The notification is sent via
//! the send-once right to its receiver. These notifications are more
//! useful for clients, not servers. Send-once notifications are
//! normally handled by MIG-generated client routines, which make
//! send-once rights for their reply ports and interpret send-once
//! notifications as a signal that there will be no reply. Although not
//! expected to be primarily useful for servers, this method is provided
//! because send-once notifications are defined as a part of the
//! `notify` subsystem.
virtual kern_return_t DoMachNotifySendOnce(
notify_port_t notify,
const mach_msg_trailer_t* trailer) = 0;
//! \brief Handles dead-name notifications sent by
//! `mach_notify_dead_name()`.
//!
//! A dead-name notification is generated when a port with a dead-name
//! notification request is destroyed and the right becomes a dead name.
//!
//! This behaves equivalently to a `do_mach_notify_dead_name()` function
//! used with `notify_server()`.
//!
//! \param[in] notify The Mach port that the notification was sent to.
//! \param[in] name The dead name. Although this is transferred as a
//! `mach_port_name_t` and not a `mach_port_t`, the callee assumes an
//! additional reference to this port when this method is called. See
//! the note below.
//! \param[in] trailer The trailer received with the notification message.
//!
//! \note When a dead-name notification is generated, the user reference
//! count of the dead name is incremented. A send right with one
//! reference that becomes a dead name will have one dead-name
//! reference, and the dead-name notification will add another dead-name
//! reference, for a total of 2. DoMachNotifyDeadName() implementations
//! must take care to deallocate this extra reference. There is no \a
//! destroy_request parameter to simplify this operation because
//! dead-name notifications carry a port name only (\a name is of type
//! `mach_port_name_t`) without transferring port rights, and are thus
//! not complex Mach messages.
virtual kern_return_t DoMachNotifyDeadName(
notify_port_t notify,
mach_port_name_t name,
const mach_msg_trailer_t* trailer) = 0;
protected:
~Interface() {}
};
//! \brief Constructs an object of this class.
//!
//! \param[in] interface The interface to dispatch requests to. Weak.
explicit NotifyServer(Interface* interface);
// MachMessageServer::Interface:
bool MachMessageServerFunction(const mach_msg_header_t* in_header,
mach_msg_header_t* out_header,
bool* destroy_complex_request) override;
std::set<mach_msg_id_t> MachMessageServerRequestIDs() override;
mach_msg_size_t MachMessageServerRequestSize() override;
mach_msg_size_t MachMessageServerReplySize() override;
private:
Interface* interface_; // weak
DISALLOW_COPY_AND_ASSIGN(NotifyServer);
};
} // namespace crashpad
#endif // CRASHPAD_UTIL_MACH_NOTIFY_SERVER_H_

View File

@ -0,0 +1,550 @@
// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "util/mach/notify_server.h"
#include "base/compiler_specific.h"
#include "base/mac/scoped_mach_port.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "util/mach/mach_extensions.h"
#include "util/mach/mach_message.h"
#include "util/mach/mach_message_server.h"
#include "util/test/mac/mach_errors.h"
namespace crashpad {
namespace test {
namespace {
using testing::AllOf;
using testing::Eq;
using testing::Invoke;
using testing::Ne;
using testing::Pointee;
using testing::ResultOf;
using testing::Return;
using testing::SetArgPointee;
using testing::StrictMock;
using testing::WithArg;
//! \brief Allocates and returns a new receive right.
//!
//! \return The new receive right. On failure, `MACH_PORT_NULL` with a gtest
//! failure added.
mach_port_t NewReceiveRight() {
mach_port_t receive_right;
kern_return_t kr = mach_port_allocate(
mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &receive_right);
if (kr != KERN_SUCCESS) {
EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_allocate");
return MACH_PORT_NULL;
}
return receive_right;
}
//! \brief Adds a send right to an existing receive right.
//!
//! \param[in] receive_right The receive right to add a send right to.
//!
//! \return The send right, which will have the same name as the receive right.
//! On failure, `MACH_PORT_NULL` with a gtest failure added.
mach_port_t SendRightFromReceiveRight(mach_port_t receive_right) {
kern_return_t kr = mach_port_insert_right(
mach_task_self(), receive_right, receive_right, MACH_MSG_TYPE_MAKE_SEND);
if (kr != KERN_SUCCESS) {
EXPECT_EQ(KERN_SUCCESS, kr)
<< MachErrorMessage(kr, "mach_port_insert_right");
return MACH_PORT_NULL;
}
return receive_right;
}
//! \brief Extracts a send-once right from a receive right.
//!
//! \param[in] receive_right The receive right to make a send-once right from.
//!
//! \return The send-once right. On failure, `MACH_PORT_NULL` with a gtest
//! failure added.
mach_port_t SendOnceRightFromReceiveRight(mach_port_t receive_right) {
mach_port_t send_once_right;
mach_msg_type_name_t acquired_type;
kern_return_t kr = mach_port_extract_right(mach_task_self(),
receive_right,
MACH_MSG_TYPE_MAKE_SEND_ONCE,
&send_once_right,
&acquired_type);
if (kr != KERN_SUCCESS) {
EXPECT_EQ(KERN_SUCCESS, kr)
<< MachErrorMessage(kr, "mach_port_extract_right");
return MACH_PORT_NULL;
}
EXPECT_EQ(implicit_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_PORT_SEND_ONCE),
acquired_type);
return send_once_right;
}
//! \brief Deallocates a Mach port by calling `mach_port_deallocate()`.
//!
//! This function exists to adapt `mach_port_deallocate()` to a function that
//! accepts a single argument and has no return value. It can be used with the
//! testing::Invoke() gmock action.
//!
//! On failure, a gtest failure will be added.
void MachPortDeallocate(mach_port_t port) {
kern_return_t kr = mach_port_deallocate(mach_task_self(), port);
EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_deallocate");
}
//! \brief Determines whether a specific right is held for a Mach port.
//!
//! \param[in] port The port to check for a right.
//! \param[in] right The right to check for.
//!
//! \return `true` if \a port has \a right, `false` otherwise. On faliure,
//! `false` with a gtest failure added.
bool IsRight(mach_port_t port, mach_port_type_t right) {
mach_port_type_t type;
kern_return_t kr = mach_port_type(mach_task_self(), port, &type);
if (kr != KERN_SUCCESS) {
EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_type");
return false;
}
return type & right;
}
//! \brief Determines whether a receive right is held for a Mach port.
//!
//! This is a special single-argument form of IsRight() for ease of use in a
//! gmock matcher.
//!
//! \param[in] port The port to check for a receive right.
//!
//! \return `true` if a receive right is held, `false` otherwise. On faliure,
//! `false` with a gtest failure added.
bool IsReceiveRight(mach_port_t port) {
return IsRight(port, MACH_PORT_TYPE_RECEIVE);
}
//! \brief Returns the user reference count for port rights.
//!
//! \param[in] port The port whose user reference count should be returned.
//! \param[in] right The port right to return the user reference count for.
//!
//! \return The user reference count for the specified port and right. On
//! failure, `-1` with a gtest failure added.
mach_port_urefs_t RightRefCount(mach_port_t port, mach_port_right_t right) {
mach_port_urefs_t refs;
kern_return_t kr = mach_port_get_refs(mach_task_self(), port, right, &refs);
if (kr != KERN_SUCCESS) {
EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_get_refs");
return -1;
}
return refs;
}
//! \brief Returns the user reference count for a ports dead-name rights.
//!
//! This is a special single-argument form of RightRefCount() for ease of use in
//! a gmock matcher.
//!
//! \param[in] port The port whose dead-name user reference count should be
//! returned.
//!
//! \return The user reference count for the ports dead-name rights. On
//! failure, `-1` with a gtest failure added.
mach_port_urefs_t DeadNameRightRefCount(mach_port_t port) {
return RightRefCount(port, MACH_PORT_RIGHT_DEAD_NAME);
}
class NotifyServerTestBase : public testing::Test,
public NotifyServer::Interface {
public:
// NotifyServer::Interface:
MOCK_METHOD3(DoMachNotifyPortDeleted,
kern_return_t(notify_port_t notify,
mach_port_name_t name,
const mach_msg_trailer_t* trailer));
MOCK_METHOD4(DoMachNotifyPortDestroyed,
kern_return_t(notify_port_t notify,
mach_port_t rights,
const mach_msg_trailer_t* trailer,
bool* destroy_request));
MOCK_METHOD3(DoMachNotifyNoSenders,
kern_return_t(notify_port_t notify,
mach_port_mscount_t mscount,
const mach_msg_trailer_t* trailer));
MOCK_METHOD2(DoMachNotifySendOnce,
kern_return_t(notify_port_t notify,
const mach_msg_trailer_t* trailer));
MOCK_METHOD3(DoMachNotifyDeadName,
kern_return_t(notify_port_t notify,
mach_port_name_t name,
const mach_msg_trailer_t* trailer));
protected:
NotifyServerTestBase() : testing::Test(), NotifyServer::Interface() {}
~NotifyServerTestBase() override {}
//! \brief Requests a Mach port notification.
//!
//! \a name, \a variant, and \a sync are passed as-is to
//! `mach_port_request_notification()`. The notification will be sent to a
//! send-once right made from ServerPort(). Any previous send right for the
//! notification will be deallocated.
//!
//! \return `true` on success, `false` on failure with a gtest failure added.
bool RequestMachPortNotification(mach_port_t name,
mach_msg_id_t variant,
mach_port_mscount_t sync) {
mach_port_t previous;
kern_return_t kr =
mach_port_request_notification(mach_task_self(),
name,
variant,
sync,
ServerPort(),
MACH_MSG_TYPE_MAKE_SEND_ONCE,
&previous);
if (kr != KERN_SUCCESS) {
EXPECT_EQ(KERN_SUCCESS, kr)
<< MachErrorMessage(kr, "mach_port_request_notification");
return false;
}
base::mac::ScopedMachSendRight previous_owner(previous);
EXPECT_EQ(kMachPortNull, previous);
return true;
}
//! \brief Runs a NotifyServer Mach message server.
//!
//! The server will listen on ServerPort() in persistent nonblocking mode, and
//! dispatch received messages to the appropriate NotifyServer::Interface
//! method. gmock expectations check that the proper method, if any, is called
//! exactly once, and that no undesired methods are called.
//!
//! MachMessageServer::Run() is expected to return `MACH_RCV_TIMED_OUT`,
//! because it runs in persistent nonblocking mode. If it returns anything
//! else, a gtest assertion is added.
void RunServer() {
NotifyServer notify_server(this);
mach_msg_return_t mr =
MachMessageServer::Run(&notify_server,
ServerPort(),
MACH_MSG_OPTION_NONE,
MachMessageServer::kPersistent,
MachMessageServer::kReceiveLargeError,
kMachMessageTimeoutNonblocking);
ASSERT_EQ(MACH_RCV_TIMED_OUT, mr)
<< MachErrorMessage(mr, "MachMessageServer::Run");
}
//! \brief Returns the receive right to be used for the server.
//!
//! This receive right is created lazily on a per-test basis. It is destroyed
//! by TearDown() at the conclusion of each test.
//!
//! \return The server port receive right, creating it if one has not yet been
//! established for the current test. On failure, returns `MACH_PORT_NULL`
//! with a gtest failure added.
mach_port_t ServerPort() {
if (!server_port_) {
server_port_.reset(NewReceiveRight());
}
return server_port_;
}
// testing::Test:
void TearDown() override {
server_port_.reset();
}
private:
base::mac::ScopedMachReceiveRight server_port_;
DISALLOW_COPY_AND_ASSIGN(NotifyServerTestBase);
};
using NotifyServerTest = StrictMock<NotifyServerTestBase>;
TEST_F(NotifyServerTest, Basic) {
NotifyServer server(this);
std::set<mach_msg_id_t> expect_request_ids;
expect_request_ids.insert(MACH_NOTIFY_PORT_DELETED);
expect_request_ids.insert(MACH_NOTIFY_PORT_DESTROYED);
expect_request_ids.insert(MACH_NOTIFY_NO_SENDERS);
expect_request_ids.insert(MACH_NOTIFY_SEND_ONCE);
expect_request_ids.insert(MACH_NOTIFY_DEAD_NAME);
EXPECT_EQ(expect_request_ids, server.MachMessageServerRequestIDs());
// The port-destroyed notification is the largest request message in the
// subsystem. <mach/notify.h> defines the same structure, but with a basic
// trailer, so use offsetof to get the size of the basic structure without any
// trailer.
EXPECT_EQ(offsetof(mach_port_destroyed_notification_t, trailer),
server.MachMessageServerRequestSize());
mig_reply_error_t reply;
EXPECT_EQ(sizeof(reply), server.MachMessageServerReplySize());
}
// When no notifications are requested, nothing should happen.
TEST_F(NotifyServerTest, NoNotification) {
RunServer();
}
// When a send-once right with a dead-name notification request is deallocated,
// a port-deleted notification should be generated.
TEST_F(NotifyServerTest, MachNotifyPortDeleted) {
base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
ASSERT_NE(kMachPortNull, receive_right);
base::mac::ScopedMachSendRight send_once_right(
SendOnceRightFromReceiveRight(receive_right));
ASSERT_NE(kMachPortNull, send_once_right);
ASSERT_TRUE(RequestMachPortNotification(
send_once_right, MACH_NOTIFY_DEAD_NAME, 0));
EXPECT_CALL(*this, DoMachNotifyPortDeleted(ServerPort(),
send_once_right.get(),
Ne(nullptr)))
.WillOnce(Return(MIG_NO_REPLY))
.RetiresOnSaturation();
send_once_right.reset();
RunServer();
}
// When a receive right with a port-destroyed notification request is destroyed,
// a port-destroyed notification should be generated.
TEST_F(NotifyServerTest, MachNotifyPortDestroyed) {
base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
ASSERT_NE(kMachPortNull, receive_right);
ASSERT_TRUE(RequestMachPortNotification(
receive_right, MACH_NOTIFY_PORT_DESTROYED, 0));
EXPECT_CALL(*this, DoMachNotifyPortDestroyed(ServerPort(),
ResultOf(IsReceiveRight, true),
Ne(nullptr),
Pointee(Eq(false))))
.WillOnce(DoAll(SetArgPointee<3>(true), Return(MIG_NO_REPLY)))
.RetiresOnSaturation();
receive_right.reset();
RunServer();
}
// When a receive right with a port-destroyed notification request is not
// destroyed, no port-destroyed notification should be generated.
TEST_F(NotifyServerTest, MachNotifyPortDestroyed_NoNotification) {
base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
ASSERT_NE(kMachPortNull, receive_right);
ASSERT_TRUE(RequestMachPortNotification(
receive_right, MACH_NOTIFY_PORT_DESTROYED, 0));
RunServer();
}
// When a no-senders notification request is registered for a receive right with
// no senders, a no-senders notification should be generated.
TEST_F(NotifyServerTest, MachNotifyNoSenders_NoSendRight) {
base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
ASSERT_NE(kMachPortNull, receive_right);
ASSERT_TRUE(RequestMachPortNotification(
receive_right, MACH_NOTIFY_NO_SENDERS, 0));
EXPECT_CALL(*this, DoMachNotifyNoSenders(ServerPort(), 0, Ne(nullptr)))
.WillOnce(Return(MIG_NO_REPLY))
.RetiresOnSaturation();
RunServer();
}
// When the last send right corresponding to a receive right with a no-senders
// notification request is deallocated, a no-senders notification should be
// generated.
TEST_F(NotifyServerTest, MachNotifyNoSenders_SendRightDeallocated) {
base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
ASSERT_NE(kMachPortNull, receive_right);
base::mac::ScopedMachSendRight send_right(
SendRightFromReceiveRight(receive_right));
ASSERT_NE(kMachPortNull, send_right);
ASSERT_TRUE(RequestMachPortNotification(
receive_right, MACH_NOTIFY_NO_SENDERS, 1));
EXPECT_CALL(*this, DoMachNotifyNoSenders(ServerPort(), 1, Ne(nullptr)))
.WillOnce(Return(MIG_NO_REPLY))
.RetiresOnSaturation();
send_right.reset();
RunServer();
}
// When the a receive right with a no-senders notification request never loses
// all senders, no no-senders notification should be generated.
TEST_F(NotifyServerTest, MachNotifyNoSenders_NoNotification) {
base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
ASSERT_NE(kMachPortNull, receive_right);
base::mac::ScopedMachSendRight send_right_0(
SendRightFromReceiveRight(receive_right));
ASSERT_NE(kMachPortNull, send_right_0);
base::mac::ScopedMachSendRight send_right_1(
SendRightFromReceiveRight(receive_right));
ASSERT_NE(kMachPortNull, send_right_1);
ASSERT_TRUE(RequestMachPortNotification(
receive_right, MACH_NOTIFY_NO_SENDERS, 1));
send_right_1.reset();
RunServer();
EXPECT_EQ(1u, RightRefCount(receive_right, MACH_PORT_RIGHT_RECEIVE));
EXPECT_EQ(1u, RightRefCount(receive_right, MACH_PORT_RIGHT_SEND));
}
// When a send-once right is deallocated without being used, a send-once
// notification notification should be sent via the send-once right.
TEST_F(NotifyServerTest, MachNotifySendOnce_ExplicitDeallocation) {
base::mac::ScopedMachSendRight send_once_right(
SendOnceRightFromReceiveRight(ServerPort()));
ASSERT_NE(kMachPortNull, send_once_right);
EXPECT_CALL(*this, DoMachNotifySendOnce(ServerPort(), Ne(nullptr)))
.WillOnce(Return(MIG_NO_REPLY))
.RetiresOnSaturation();
send_once_right.reset();
RunServer();
}
// When a send-once right is sent to a receiver that never dequeues the message,
// the send-once right is destroyed, and a send-once notification should appear
// on the reply port.
TEST_F(NotifyServerTest, MachNotifySendOnce_ImplicitDeallocation) {
base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
ASSERT_NE(kMachPortNull, receive_right);
mach_msg_empty_send_t message = {};
message.header.msgh_bits =
MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE);
message.header.msgh_size = sizeof(message);
message.header.msgh_remote_port = receive_right;
message.header.msgh_local_port = ServerPort();
mach_msg_return_t mr = mach_msg(&message.header,
MACH_SEND_MSG | MACH_SEND_TIMEOUT,
message.header.msgh_size,
0,
MACH_PORT_NULL,
0,
MACH_PORT_NULL);
ASSERT_EQ(MACH_MSG_SUCCESS, mr) << MachErrorMessage(mr, "mach_msg");
EXPECT_CALL(*this, DoMachNotifySendOnce(ServerPort(), Ne(nullptr)))
.WillOnce(Return(MIG_NO_REPLY))
.RetiresOnSaturation();
receive_right.reset();
RunServer();
}
// When the receive right corresponding to a send-once right with a dead-name
// notification request is destroyed, a dead-name notification should be
// generated.
TEST_F(NotifyServerTest, MachNotifyDeadName) {
base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
ASSERT_NE(kMachPortNull, receive_right);
base::mac::ScopedMachSendRight send_once_right(
SendOnceRightFromReceiveRight(receive_right));
ASSERT_NE(kMachPortNull, send_once_right);
ASSERT_TRUE(RequestMachPortNotification(
send_once_right, MACH_NOTIFY_DEAD_NAME, 0));
// send_once_right becomes a dead name with the send-once rights original
// user reference count of 1, but the dead-name notification increments the
// dead-name reference count, so it becomes 2. Take care to deallocate that
// reference. The original reference is managed by send_once_right_owner.
EXPECT_CALL(*this,
DoMachNotifyDeadName(ServerPort(),
AllOf(send_once_right.get(),
ResultOf(DeadNameRightRefCount, 2)),
Ne(nullptr)))
.WillOnce(DoAll(WithArg<1>(Invoke(MachPortDeallocate)),
Return(MIG_NO_REPLY)))
.RetiresOnSaturation();
receive_right.reset();
RunServer();
EXPECT_TRUE(IsRight(send_once_right, MACH_PORT_TYPE_DEAD_NAME));
EXPECT_EQ(0u, RightRefCount(send_once_right, MACH_PORT_RIGHT_SEND_ONCE));
EXPECT_EQ(1u, DeadNameRightRefCount(send_once_right));
}
// When the receive right corresponding to a send-once right with a dead-name
// notification request is not destroyed, no dead-name notification should be
// generated.
TEST_F(NotifyServerTest, MachNotifyDeadName_NoNotification) {
base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight());
ASSERT_NE(kMachPortNull, receive_right);
base::mac::ScopedMachSendRight send_once_right(
SendOnceRightFromReceiveRight(receive_right));
ASSERT_NE(kMachPortNull, send_once_right);
ASSERT_TRUE(RequestMachPortNotification(
send_once_right, MACH_NOTIFY_DEAD_NAME, 0));
RunServer();
EXPECT_FALSE(IsRight(send_once_right, MACH_PORT_TYPE_DEAD_NAME));
EXPECT_EQ(1u, RightRefCount(send_once_right, MACH_PORT_RIGHT_SEND_ONCE));
EXPECT_EQ(0u, DeadNameRightRefCount(send_once_right));
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -62,6 +62,8 @@
'mach/mach_message.h',
'mach/mach_message_server.cc',
'mach/mach_message_server.h',
'mach/notify_server.cc',
'mach/notify_server.h',
'mach/scoped_task_suspend.cc',
'mach/scoped_task_suspend.h',
'mach/symbolic_constants_mach.cc',
@ -126,12 +128,14 @@
'files': [
'$(SDKROOT)/usr/include/mach/exc.defs',
'$(SDKROOT)/usr/include/mach/mach_exc.defs',
'$(SDKROOT)/usr/include/mach/notify.defs',
],
},
],
'sources': [
'<(INTERMEDIATE_DIR)/util/mach/exc.defs',
'<(INTERMEDIATE_DIR)/util/mach/mach_exc.defs',
'<(INTERMEDIATE_DIR)/util/mach/notify.defs',
],
}, { # else: GENERATOR!="ninja"
# The Xcode generator does copies after rules, so the above trick
@ -140,6 +144,7 @@
'sources': [
'$(SDKROOT)/usr/include/mach/exc.defs',
'$(SDKROOT)/usr/include/mach/mach_exc.defs',
'$(SDKROOT)/usr/include/mach/notify.defs',
],
}],
],
@ -243,6 +248,7 @@
'mach/mach_extensions_test.cc',
'mach/mach_message_server_test.cc',
'mach/mach_message_test.cc',
'mach/notify_server_test.cc',
'mach/scoped_task_suspend_test.cc',
'mach/symbolic_constants_mach_test.cc',
'mach/task_memory_test.cc',