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 |