crashpad/util/mach/mach_message_server.cc
Mark Mentovai c874958fd0 MachMessageServer: eliminate argument redundancy.
MachMessageServer::Run()’s distinct |nonblocking| parameter is removed.
The information it formerly conveyed is now implied by the |timeout_ms|
parameter, which can accept two special values,
kMachMessageTimeoutNonblocking and kMachMessageTimeoutWaitIndefinitely.

TEST=client_test, snapshot_test, util_test
R=rsesek@chromium.org

Review URL: https://codereview.chromium.org/777993002
2014-12-10 11:11:21 -05:00

281 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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/mach_message_server.h"
#include <string.h>
#include <limits>
#include "base/logging.h"
#include "base/mac/mach_logging.h"
#include "base/mac/scoped_mach_vm.h"
#include "util/mach/mach_message.h"
namespace crashpad {
namespace {
//! \brief Manages a dynamically-allocated buffer to be used for Mach messaging.
class MachMessageBuffer {
public:
MachMessageBuffer() : vm_() {}
~MachMessageBuffer() {}
//! \return A pointer to the buffer.
mach_msg_header_t* Header() const {
return reinterpret_cast<mach_msg_header_t*>(vm_.address());
}
//! \brief Ensures that this object has a buffer of exactly \a size bytes
//! available.
//!
//! If the existing buffer is a different size, it will be reallocated without
//! copying any of the old buffers contents to the new buffer. The contents
//! of the buffer are unspecified after this call, even if no reallocation is
//! performed.
kern_return_t Reallocate(vm_size_t size) {
// This test uses == instead of > so that a large reallocation to receive a
// large message doesnt cause permanent memory bloat for the duration of
// a MachMessageServer::Run() loop.
if (size != vm_.size()) {
// reset() first, so that two allocations dont exist simultaneously.
vm_.reset();
if (size) {
vm_address_t address;
kern_return_t kr =
vm_allocate(mach_task_self(),
&address,
size,
VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG));
if (kr != KERN_SUCCESS) {
return kr;
}
vm_.reset(address, size);
}
}
#if !defined(NDEBUG)
// Regardless of whether the allocation was changed, scribble over the
// memory to make sure that nothing relies on zero-initialization or stale
// contents.
memset(Header(), 0x66, size);
#endif
return KERN_SUCCESS;
}
private:
base::mac::ScopedMachVM vm_;
DISALLOW_COPY_AND_ASSIGN(MachMessageBuffer);
};
// Wraps MachMessageWithDeadline(), using a MachMessageBuffer argument which
// will be resized to |receive_size| (after being page-rounded). MACH_RCV_MSG
// is always combined into |options|.
mach_msg_return_t MachMessageAllocateReceive(MachMessageBuffer* request,
mach_msg_option_t options,
mach_msg_size_t receive_size,
mach_port_name_t receive_port,
MachMessageDeadline deadline,
mach_port_name_t notify_port,
bool run_even_if_expired) {
mach_msg_size_t request_alloc = round_page(receive_size);
kern_return_t kr = request->Reallocate(request_alloc);
if (kr != KERN_SUCCESS) {
return kr;
}
return MachMessageWithDeadline(request->Header(),
options | MACH_RCV_MSG,
receive_size,
receive_port,
deadline,
notify_port,
run_even_if_expired);
}
} // namespace
// This method implements a server similar to 10.9.4
// xnu-2422.110.17/libsyscall/mach/mach_msg.c mach_msg_server_once(). The server
// callback function and |max_size| parameter have been replaced with a C++
// interface. The |persistent| parameter has been added, allowing this method to
// serve as a stand-in for mach_msg_server(). The |timeout_ms| parameter has
// been added, allowing this function to not block indefinitely.
//
// static
mach_msg_return_t MachMessageServer::Run(Interface* interface,
mach_port_t receive_port,
mach_msg_options_t options,
Persistent persistent,
ReceiveLarge receive_large,
mach_msg_timeout_t timeout_ms) {
options &= ~(MACH_RCV_MSG | MACH_SEND_MSG);
const MachMessageDeadline deadline =
MachMessageDeadlineFromTimeout(timeout_ms);
if (receive_large == kReceiveLargeResize) {
options |= MACH_RCV_LARGE;
} else {
options &= ~MACH_RCV_LARGE;
}
const mach_msg_size_t trailer_alloc = REQUESTED_TRAILER_SIZE(options);
const mach_msg_size_t expected_receive_size =
round_msg(interface->MachMessageServerRequestSize()) + trailer_alloc;
const mach_msg_size_t request_size = (receive_large == kReceiveLargeResize)
? round_page(expected_receive_size)
: expected_receive_size;
DCHECK_GE(request_size, sizeof(mach_msg_empty_rcv_t));
// mach_msg_server() and mach_msg_server_once() would consider whether
// |options| contains MACH_SEND_TRAILER and include MAX_TRAILER_SIZE in this
// computation if it does, but that option is ineffective on OS X.
const mach_msg_size_t reply_size = interface->MachMessageServerReplySize();
DCHECK_GE(reply_size, sizeof(mach_msg_empty_send_t));
const mach_msg_size_t reply_alloc = round_page(reply_size);
MachMessageBuffer request;
MachMessageBuffer reply;
bool received_any_request = false;
bool retry;
kern_return_t kr;
do {
retry = false;
kr = MachMessageAllocateReceive(&request,
options,
request_size,
receive_port,
deadline,
MACH_PORT_NULL,
!received_any_request);
if (kr == MACH_RCV_TOO_LARGE) {
switch (receive_large) {
case kReceiveLargeError:
break;
case kReceiveLargeIgnore:
// Try again, even in one-shot mode. The caller is expecting this
// method to take action on the first message in the queue, and has
// indicated that they want large messages to be ignored. The
// alternatives, which might involve returning MACH_MSG_SUCCESS,
// MACH_RCV_TIMED_OUT, or MACH_RCV_TOO_LARGE to a caller that
// specified one-shot behavior, all seem less correct than retrying.
MACH_LOG(WARNING, kr) << "mach_msg: ignoring large message";
retry = true;
continue;
case kReceiveLargeResize: {
mach_msg_size_t this_request_size = round_page(
round_msg(request.Header()->msgh_size) + trailer_alloc);
DCHECK_GT(this_request_size, request_size);
kr = MachMessageAllocateReceive(&request,
options & ~MACH_RCV_LARGE,
this_request_size,
receive_port,
deadline,
MACH_PORT_NULL,
!received_any_request);
break;
}
}
}
if (kr != MACH_MSG_SUCCESS) {
return kr;
}
received_any_request = true;
kr = reply.Reallocate(reply_alloc);
if (kr != KERN_SUCCESS) {
return kr;
}
mach_msg_header_t* request_header = request.Header();
mach_msg_header_t* reply_header = reply.Header();
bool destroy_complex_request = false;
interface->MachMessageServerFunction(
request_header, reply_header, &destroy_complex_request);
if (!(reply_header->msgh_bits & MACH_MSGH_BITS_COMPLEX)) {
// This only works if the reply message is not complex, because otherwise,
// the location of the RetCode field is not known. It should be possible
// to locate the RetCode field by looking beyond the descriptors in a
// complex reply message, but this is not currently done. This behavior
// has not proven itself necessary in practice, and its not done by
// mach_msg_server() or mach_msg_server_once() either.
mig_reply_error_t* reply_mig =
reinterpret_cast<mig_reply_error_t*>(reply_header);
if (reply_mig->RetCode == MIG_NO_REPLY) {
reply_header->msgh_remote_port = MACH_PORT_NULL;
} else if (reply_mig->RetCode != KERN_SUCCESS &&
request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) {
destroy_complex_request = true;
}
}
if (destroy_complex_request &&
request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) {
request_header->msgh_remote_port = MACH_PORT_NULL;
mach_msg_destroy(request_header);
}
if (reply_header->msgh_remote_port != MACH_PORT_NULL) {
// Avoid blocking indefinitely. This duplicates the logic in 10.9.5
// xnu-2422.115.4/libsyscall/mach/mach_msg.c mach_msg_server_once(),
// although the special provision for sending to a send-once right is not
// made, because kernel keeps sends to a send-once right on the fast path
// without considering the user-specified timeout. See 10.9.5
// xnu-2422.115.4/osfmk/ipc/ipc_mqueue.c ipc_mqueue_send().
const MachMessageDeadline send_deadline =
deadline == kMachMessageDeadlineWaitIndefinitely
? kMachMessageDeadlineNonblocking
: deadline;
kr = MachMessageWithDeadline(reply_header,
options | MACH_SEND_MSG,
0,
MACH_PORT_NULL,
send_deadline,
MACH_PORT_NULL,
true);
if (kr != MACH_MSG_SUCCESS) {
if (kr == MACH_SEND_INVALID_DEST ||
kr == MACH_SEND_TIMED_OUT ||
kr == MACH_SEND_INTERRUPTED) {
mach_msg_destroy(reply_header);
}
return kr;
}
}
} while (persistent == kPersistent || retry);
return kr;
}
} // namespace crashpad