Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(516)

Unified Diff: util/mach/mach_message_server.cc

Issue 544393002: Add MachMessageServer and its test (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@master
Patch Set: Address review feedback Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « util/mach/mach_message_server.h ('k') | util/mach/mach_message_server_test.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: util/mach/mach_message_server.cc
diff --git a/util/mach/mach_message_server.cc b/util/mach/mach_message_server.cc
new file mode 100644
index 0000000000000000000000000000000000000000..dfaaa9742485be5fba5f5750425d4eece73f777d
--- /dev/null
+++ b/util/mach/mach_message_server.cc
@@ -0,0 +1,282 @@
+// 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 <mach/mach_time.h>
+
+#include <limits>
+
+#include "base/mac/mach_logging.h"
+#include "base/mac/scoped_mach_vm.h"
+
+namespace crashpad {
+
+namespace {
+
+mach_timebase_info_data_t* TimebaseInternal() {
+ mach_timebase_info_data_t* timebase_info = new mach_timebase_info_data_t;
+ kern_return_t kr = mach_timebase_info(timebase_info);
+ MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_timebase_info";
+ return timebase_info;
+}
+
+mach_timebase_info_data_t* Timebase() {
+ static mach_timebase_info_data_t* timebase_info = TimebaseInternal();
+ return timebase_info;
+}
+
+uint64_t NanosecondTicks() {
+ uint64_t absolute_time = mach_absolute_time();
+ mach_timebase_info_data_t* timebase_info = Timebase();
+ return absolute_time * timebase_info->numer / timebase_info->denom;
+}
+
+const int kNanosecondsPerMillisecond = 1E6;
+
+// TimerRunning determines whether |deadline| has passed. If |deadline| is in
+// the future, |*remaining_ms| is set to the number of milliseconds remaining,
+// which will always be a positive value, and this function returns true. If
+// |deadline| is zero (indicating that no timer is in effect), |*remaining_ms|
+// is set to zero and this function returns true. Otherwise, this function
+// returns false. |deadline| is specified on the same time base as is returned
+// by NanosecondTicks().
+bool TimerRunning(uint64_t deadline, mach_msg_timeout_t* remaining_ms) {
+ if (!deadline) {
+ *remaining_ms = MACH_MSG_TIMEOUT_NONE;
+ return true;
+ }
+
+ uint64_t now = NanosecondTicks();
+
+ if (now >= deadline) {
+ return false;
+ }
+
+ uint64_t remaining = deadline - now;
+
+ // Round to the nearest millisecond, taking care not to overflow.
+ const int kHalfMillisecondInNanoseconds = kNanosecondsPerMillisecond / 2;
+ mach_msg_timeout_t remaining_mach;
+ if (remaining <=
+ std::numeric_limits<uint64_t>::max() - kHalfMillisecondInNanoseconds) {
+ remaining_mach = (remaining + kHalfMillisecondInNanoseconds) /
+ kNanosecondsPerMillisecond;
+ } else {
+ remaining_mach = remaining / kNanosecondsPerMillisecond;
+ }
+
+ if (remaining_mach == MACH_MSG_TIMEOUT_NONE) {
+ return false;
+ }
+
+ *remaining_ms = remaining_mach;
+ return true;
+}
+
+} // namespace
+
+// This implementation is based on 10.9.4
+// xnu-2422.110.17/libsyscall/mach/mach_msg.c mach_msg_server_once(), but
+// adapted to local style using scopers, replacing the server callback function
+// and |max_size| parameter with a C++ interface, and with the addition of the
+// the |persistent| parameter allowing this function to serve as a stand-in for
+// mach_msg_server(), the |nonblocking| parameter to control blocking directly,
+// and the |timeout_ms| parameter 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,
+ Nonblocking nonblocking,
+ mach_msg_timeout_t timeout_ms) {
+ options &= ~(MACH_RCV_MSG | MACH_SEND_MSG);
+
+ mach_msg_options_t timeout_options = MACH_RCV_TIMEOUT | MACH_SEND_TIMEOUT |
+ MACH_RCV_INTERRUPT | MACH_SEND_INTERRUPT;
+
+ uint64_t deadline;
+ if (nonblocking == kNonblocking) {
+ options |= timeout_options;
+ deadline = 0;
+ } else if (timeout_ms != MACH_MSG_TIMEOUT_NONE) {
+ options |= timeout_options;
+ deadline = NanosecondTicks() +
+ static_cast<uint64_t>(timeout_ms) * kNanosecondsPerMillisecond;
+ } else {
+ options &= ~timeout_options;
+ deadline = 0;
+ }
+
+ mach_msg_size_t trailer_alloc = REQUESTED_TRAILER_SIZE(options);
+ mach_msg_size_t max_request_size = interface->MachMessageServerRequestSize();
+ mach_msg_size_t request_alloc = round_page(max_request_size + trailer_alloc);
+ mach_msg_size_t request_size = (options & MACH_RCV_LARGE)
+ ? request_alloc
+ : max_request_size + trailer_alloc;
+
+ mach_msg_size_t max_reply_size = interface->MachMessageServerReplySize();
+ mach_msg_size_t reply_alloc = round_page(
+ (options & MACH_SEND_TRAILER) ? (max_reply_size + MAX_TRAILER_SIZE)
+ : max_reply_size);
+
+ mach_port_t self = mach_task_self();
+
+ kern_return_t kr;
+
+ do {
+ mach_msg_size_t this_request_alloc = request_alloc;
+ mach_msg_size_t this_request_size = request_size;
+
+ base::mac::ScopedMachVM request_scoper;
+ mach_msg_header_t* request_header = NULL;
+
+ while (!request_scoper.address()) {
+ vm_address_t request_addr;
+ kr = vm_allocate(self,
+ &request_addr,
+ this_request_alloc,
+ VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG));
+ if (kr != KERN_SUCCESS) {
+ return kr;
+ }
+ base::mac::ScopedMachVM trial_request_scoper(request_addr,
+ this_request_alloc);
+ request_header = reinterpret_cast<mach_msg_header_t*>(request_addr);
+
+ do {
+ // If |options| contains MACH_RCV_INTERRUPT, retry mach_msg() in a loop
+ // when it returns MACH_RCV_INTERRUPTED to recompute |remaining_ms|
+ // rather than allowing mach_msg() to retry using the original timeout
+ // value. See 10.9.4 xnu-2422.110.17/libsyscall/mach/mach_msg.c
+ // mach_msg().
+ mach_msg_timeout_t remaining_ms;
+ if (!TimerRunning(deadline, &remaining_ms)) {
+ return MACH_RCV_TIMED_OUT;
+ }
+
+ kr = mach_msg(request_header,
+ options | MACH_RCV_MSG,
+ 0,
+ this_request_size,
+ receive_port,
+ remaining_ms,
+ MACH_PORT_NULL);
+ } while (kr == MACH_RCV_INTERRUPTED);
+
+ if (kr == MACH_MSG_SUCCESS) {
+ request_scoper.swap(trial_request_scoper);
+ } else if (kr == MACH_RCV_TOO_LARGE && options & MACH_RCV_LARGE) {
+ this_request_size =
+ round_page(request_header->msgh_size + trailer_alloc);
+ this_request_alloc = this_request_size;
+ } else {
+ return kr;
+ }
+ }
+
+ vm_address_t reply_addr;
+ kr = vm_allocate(self,
+ &reply_addr,
+ reply_alloc,
+ VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG));
+ if (kr != KERN_SUCCESS) {
+ return kr;
+ }
+
+ base::mac::ScopedMachVM reply_scoper(reply_addr, reply_alloc);
+
+ mach_msg_header_t* reply_header =
+ reinterpret_cast<mach_msg_header_t*>(reply_addr);
+ bool destroy_complex_request = false;
+ interface->MachMessageServerFunction(
+ request_header, reply_header, &destroy_complex_request);
+
+ if (!(reply_header->msgh_bits & MACH_MSGH_BITS_COMPLEX)) {
+ 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) {
+ // If the reply port right is a send-once right, the send won’t block even
+ // if the remote side isn’t waiting for a message. No timeout is used,
+ // which keeps the communication on the kernel’s fast path. If the reply
+ // port right is a send right, MACH_SEND_TIMEOUT is used to avoid blocking
+ // indefinitely. This duplicates the logic in 10.9.4
+ // xnu-2422.110.17/libsyscall/mach/mach_msg.c mach_msg_server_once().
+ mach_msg_option_t send_options =
+ options | MACH_SEND_MSG |
+ (MACH_MSGH_BITS_REMOTE(reply_header->msgh_bits) ==
+ MACH_MSG_TYPE_MOVE_SEND_ONCE
+ ? 0
+ : MACH_SEND_TIMEOUT);
+
+ bool running;
+ do {
+ // If |options| contains MACH_SEND_INTERRUPT, retry mach_msg() in a loop
+ // when it returns MACH_SEND_INTERRUPTED to recompute |remaining_ms|
+ // rather than allowing mach_msg() to retry using the original timeout
+ // value. See 10.9.4 xnu-2422.110.17/libsyscall/mach/mach_msg.c
+ // mach_msg().
+ mach_msg_timeout_t remaining_ms;
+ running = TimerRunning(deadline, &remaining_ms);
+ if (!running) {
+ // Don’t return just yet. If the timer ran out in between the time the
+ // request was received and now, at least try to send the response.
+ remaining_ms = 0;
+ }
+
+ kr = mach_msg(reply_header,
+ send_options,
+ reply_header->msgh_size,
+ 0,
+ MACH_PORT_NULL,
+ remaining_ms,
+ MACH_PORT_NULL);
+ } while (kr == MACH_SEND_INTERRUPTED);
+
+ if (kr != KERN_SUCCESS) {
+ if (kr == MACH_SEND_INVALID_DEST || kr == MACH_SEND_TIMED_OUT) {
+ mach_msg_destroy(reply_header);
+ }
+ return kr;
+ }
+
+ if (!running) {
+ // The reply message was sent successfuly, so act as though the deadline
+ // was reached before or during the subsequent receive operation when in
+ // persistent mode, and just return success when not in persistent mode.
+ return (persistent == kPersistent) ? MACH_RCV_TIMED_OUT : kr;
+ }
+ }
+ } while (persistent == kPersistent);
+
+ return kr;
+}
+
+} // namespace crashpad
« no previous file with comments | « util/mach/mach_message_server.h ('k') | util/mach/mach_message_server_test.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698