OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Crashpad Authors. All rights reserved. |
| 2 // |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 // you may not use this file except in compliance with the License. |
| 5 // You may obtain a copy of the License at |
| 6 // |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 // |
| 9 // Unless required by applicable law or agreed to in writing, software |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 // See the License for the specific language governing permissions and |
| 13 // limitations under the License. |
| 14 |
| 15 #include "util/mach/mach_message_server.h" |
| 16 |
| 17 #include <mach/mach_time.h> |
| 18 |
| 19 #include <limits> |
| 20 |
| 21 #include "base/mac/mach_logging.h" |
| 22 #include "base/mac/scoped_mach_vm.h" |
| 23 |
| 24 namespace crashpad { |
| 25 |
| 26 namespace { |
| 27 |
| 28 mach_timebase_info_data_t* TimebaseInternal() { |
| 29 mach_timebase_info_data_t* timebase_info = new mach_timebase_info_data_t; |
| 30 kern_return_t kr = mach_timebase_info(timebase_info); |
| 31 MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_timebase_info"; |
| 32 return timebase_info; |
| 33 } |
| 34 |
| 35 mach_timebase_info_data_t* Timebase() { |
| 36 static mach_timebase_info_data_t* timebase_info = TimebaseInternal(); |
| 37 return timebase_info; |
| 38 } |
| 39 |
| 40 uint64_t NanosecondTicks() { |
| 41 uint64_t absolute_time = mach_absolute_time(); |
| 42 mach_timebase_info_data_t* timebase_info = Timebase(); |
| 43 return absolute_time * timebase_info->numer / timebase_info->denom; |
| 44 } |
| 45 |
| 46 const int kNanosecondsPerMillisecond = 1E6; |
| 47 |
| 48 // TimerRunning determines whether |deadline| has passed. If |deadline| is in |
| 49 // the future, |*remaining_ms| is set to the number of milliseconds remaining, |
| 50 // which will always be a positive value, and this function returns true. If |
| 51 // |deadline| is zero (indicating that no timer is in effect), |*remaining_ms| |
| 52 // is set to zero and this function returns true. Otherwise, this function |
| 53 // returns false. |deadline| is specified on the same time base as is returned |
| 54 // by NanosecondTicks(). |
| 55 bool TimerRunning(uint64_t deadline, mach_msg_timeout_t* remaining_ms) { |
| 56 if (!deadline) { |
| 57 *remaining_ms = MACH_MSG_TIMEOUT_NONE; |
| 58 return true; |
| 59 } |
| 60 |
| 61 uint64_t now = NanosecondTicks(); |
| 62 |
| 63 if (now >= deadline) { |
| 64 return false; |
| 65 } |
| 66 |
| 67 uint64_t remaining = deadline - now; |
| 68 |
| 69 // Round to the nearest millisecond, taking care not to overflow. |
| 70 const int kHalfMillisecondInNanoseconds = kNanosecondsPerMillisecond / 2; |
| 71 mach_msg_timeout_t remaining_mach; |
| 72 if (remaining <= |
| 73 std::numeric_limits<uint64_t>::max() - kHalfMillisecondInNanoseconds) { |
| 74 remaining_mach = (remaining + kHalfMillisecondInNanoseconds) / |
| 75 kNanosecondsPerMillisecond; |
| 76 } else { |
| 77 remaining_mach = remaining / kNanosecondsPerMillisecond; |
| 78 } |
| 79 |
| 80 if (remaining_mach == MACH_MSG_TIMEOUT_NONE) { |
| 81 return false; |
| 82 } |
| 83 |
| 84 *remaining_ms = remaining_mach; |
| 85 return true; |
| 86 } |
| 87 |
| 88 } // namespace |
| 89 |
| 90 // This implementation is based on 10.9.4 |
| 91 // xnu-2422.110.17/libsyscall/mach/mach_msg.c mach_msg_server_once(), but |
| 92 // adapted to local style using scopers, replacing the server callback function |
| 93 // and |max_size| parameter with a C++ interface, and with the addition of the |
| 94 // the |persistent| parameter allowing this function to serve as a stand-in for |
| 95 // mach_msg_server(), the |nonblocking| parameter to control blocking directly, |
| 96 // and the |timeout_ms| parameter allowing this function to not block |
| 97 // indefinitely. |
| 98 // |
| 99 // static |
| 100 mach_msg_return_t MachMessageServer::Run(Interface* interface, |
| 101 mach_port_t receive_port, |
| 102 mach_msg_options_t options, |
| 103 Persistent persistent, |
| 104 Nonblocking nonblocking, |
| 105 mach_msg_timeout_t timeout_ms) { |
| 106 options &= ~(MACH_RCV_MSG | MACH_SEND_MSG); |
| 107 |
| 108 mach_msg_options_t timeout_options = MACH_RCV_TIMEOUT | MACH_SEND_TIMEOUT | |
| 109 MACH_RCV_INTERRUPT | MACH_SEND_INTERRUPT; |
| 110 |
| 111 uint64_t deadline; |
| 112 if (nonblocking == kNonblocking) { |
| 113 options |= timeout_options; |
| 114 deadline = 0; |
| 115 } else if (timeout_ms != MACH_MSG_TIMEOUT_NONE) { |
| 116 options |= timeout_options; |
| 117 deadline = NanosecondTicks() + |
| 118 static_cast<uint64_t>(timeout_ms) * kNanosecondsPerMillisecond; |
| 119 } else { |
| 120 options &= ~timeout_options; |
| 121 deadline = 0; |
| 122 } |
| 123 |
| 124 mach_msg_size_t trailer_alloc = REQUESTED_TRAILER_SIZE(options); |
| 125 mach_msg_size_t max_request_size = interface->MachMessageServerRequestSize(); |
| 126 mach_msg_size_t request_alloc = round_page(max_request_size + trailer_alloc); |
| 127 mach_msg_size_t request_size = (options & MACH_RCV_LARGE) |
| 128 ? request_alloc |
| 129 : max_request_size + trailer_alloc; |
| 130 |
| 131 mach_msg_size_t max_reply_size = interface->MachMessageServerReplySize(); |
| 132 mach_msg_size_t reply_alloc = round_page( |
| 133 (options & MACH_SEND_TRAILER) ? (max_reply_size + MAX_TRAILER_SIZE) |
| 134 : max_reply_size); |
| 135 |
| 136 mach_port_t self = mach_task_self(); |
| 137 |
| 138 kern_return_t kr; |
| 139 |
| 140 do { |
| 141 mach_msg_size_t this_request_alloc = request_alloc; |
| 142 mach_msg_size_t this_request_size = request_size; |
| 143 |
| 144 base::mac::ScopedMachVM request_scoper; |
| 145 mach_msg_header_t* request_header = NULL; |
| 146 |
| 147 while (!request_scoper.address()) { |
| 148 vm_address_t request_addr; |
| 149 kr = vm_allocate(self, |
| 150 &request_addr, |
| 151 this_request_alloc, |
| 152 VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG)); |
| 153 if (kr != KERN_SUCCESS) { |
| 154 return kr; |
| 155 } |
| 156 base::mac::ScopedMachVM trial_request_scoper(request_addr, |
| 157 this_request_alloc); |
| 158 request_header = reinterpret_cast<mach_msg_header_t*>(request_addr); |
| 159 |
| 160 do { |
| 161 // If |options| contains MACH_RCV_INTERRUPT, retry mach_msg() in a loop |
| 162 // when it returns MACH_RCV_INTERRUPTED to recompute |remaining_ms| |
| 163 // rather than allowing mach_msg() to retry using the original timeout |
| 164 // value. See 10.9.4 xnu-2422.110.17/libsyscall/mach/mach_msg.c |
| 165 // mach_msg(). |
| 166 mach_msg_timeout_t remaining_ms; |
| 167 if (!TimerRunning(deadline, &remaining_ms)) { |
| 168 return MACH_RCV_TIMED_OUT; |
| 169 } |
| 170 |
| 171 kr = mach_msg(request_header, |
| 172 options | MACH_RCV_MSG, |
| 173 0, |
| 174 this_request_size, |
| 175 receive_port, |
| 176 remaining_ms, |
| 177 MACH_PORT_NULL); |
| 178 } while (kr == MACH_RCV_INTERRUPTED); |
| 179 |
| 180 if (kr == MACH_MSG_SUCCESS) { |
| 181 request_scoper.swap(trial_request_scoper); |
| 182 } else if (kr == MACH_RCV_TOO_LARGE && options & MACH_RCV_LARGE) { |
| 183 this_request_size = |
| 184 round_page(request_header->msgh_size + trailer_alloc); |
| 185 this_request_alloc = this_request_size; |
| 186 } else { |
| 187 return kr; |
| 188 } |
| 189 } |
| 190 |
| 191 vm_address_t reply_addr; |
| 192 kr = vm_allocate(self, |
| 193 &reply_addr, |
| 194 reply_alloc, |
| 195 VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG)); |
| 196 if (kr != KERN_SUCCESS) { |
| 197 return kr; |
| 198 } |
| 199 |
| 200 base::mac::ScopedMachVM reply_scoper(reply_addr, reply_alloc); |
| 201 |
| 202 mach_msg_header_t* reply_header = |
| 203 reinterpret_cast<mach_msg_header_t*>(reply_addr); |
| 204 bool destroy_complex_request = false; |
| 205 interface->MachMessageServerFunction( |
| 206 request_header, reply_header, &destroy_complex_request); |
| 207 |
| 208 if (!(reply_header->msgh_bits & MACH_MSGH_BITS_COMPLEX)) { |
| 209 mig_reply_error_t* reply_mig = |
| 210 reinterpret_cast<mig_reply_error_t*>(reply_header); |
| 211 if (reply_mig->RetCode == MIG_NO_REPLY) { |
| 212 reply_header->msgh_remote_port = MACH_PORT_NULL; |
| 213 } else if (reply_mig->RetCode != KERN_SUCCESS && |
| 214 request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) { |
| 215 destroy_complex_request = true; |
| 216 } |
| 217 } |
| 218 |
| 219 if (destroy_complex_request && |
| 220 request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) { |
| 221 request_header->msgh_remote_port = MACH_PORT_NULL; |
| 222 mach_msg_destroy(request_header); |
| 223 } |
| 224 |
| 225 if (reply_header->msgh_remote_port != MACH_PORT_NULL) { |
| 226 // If the reply port right is a send-once right, the send won’t block even |
| 227 // if the remote side isn’t waiting for a message. No timeout is used, |
| 228 // which keeps the communication on the kernel’s fast path. If the reply |
| 229 // port right is a send right, MACH_SEND_TIMEOUT is used to avoid blocking |
| 230 // indefinitely. This duplicates the logic in 10.9.4 |
| 231 // xnu-2422.110.17/libsyscall/mach/mach_msg.c mach_msg_server_once(). |
| 232 mach_msg_option_t send_options = |
| 233 options | MACH_SEND_MSG | |
| 234 (MACH_MSGH_BITS_REMOTE(reply_header->msgh_bits) == |
| 235 MACH_MSG_TYPE_MOVE_SEND_ONCE |
| 236 ? 0 |
| 237 : MACH_SEND_TIMEOUT); |
| 238 |
| 239 bool running; |
| 240 do { |
| 241 // If |options| contains MACH_SEND_INTERRUPT, retry mach_msg() in a loop |
| 242 // when it returns MACH_SEND_INTERRUPTED to recompute |remaining_ms| |
| 243 // rather than allowing mach_msg() to retry using the original timeout |
| 244 // value. See 10.9.4 xnu-2422.110.17/libsyscall/mach/mach_msg.c |
| 245 // mach_msg(). |
| 246 mach_msg_timeout_t remaining_ms; |
| 247 running = TimerRunning(deadline, &remaining_ms); |
| 248 if (!running) { |
| 249 // Don’t return just yet. If the timer ran out in between the time the |
| 250 // request was received and now, at least try to send the response. |
| 251 remaining_ms = 0; |
| 252 } |
| 253 |
| 254 kr = mach_msg(reply_header, |
| 255 send_options, |
| 256 reply_header->msgh_size, |
| 257 0, |
| 258 MACH_PORT_NULL, |
| 259 remaining_ms, |
| 260 MACH_PORT_NULL); |
| 261 } while (kr == MACH_SEND_INTERRUPTED); |
| 262 |
| 263 if (kr != KERN_SUCCESS) { |
| 264 if (kr == MACH_SEND_INVALID_DEST || kr == MACH_SEND_TIMED_OUT) { |
| 265 mach_msg_destroy(reply_header); |
| 266 } |
| 267 return kr; |
| 268 } |
| 269 |
| 270 if (!running) { |
| 271 // The reply message was sent successfuly, so act as though the deadline |
| 272 // was reached before or during the subsequent receive operation when in |
| 273 // persistent mode, and just return success when not in persistent mode. |
| 274 return (persistent == kPersistent) ? MACH_RCV_TIMED_OUT : kr; |
| 275 } |
| 276 } |
| 277 } while (persistent == kPersistent); |
| 278 |
| 279 return kr; |
| 280 } |
| 281 |
| 282 } // namespace crashpad |
OLD | NEW |