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| parameter allowing this function to not block indefinitely. | |
97 mach_msg_return_t MachMessageServer(MachMessageServerInterface* interface, | |
98 mach_port_t receive_port, | |
99 mach_msg_options_t options, | |
100 bool persistent, | |
101 bool nonblocking, | |
102 mach_msg_timeout_t timeout) { | |
103 options &= ~(MACH_RCV_MSG | MACH_SEND_MSG); | |
104 | |
105 mach_msg_options_t timeout_options = MACH_RCV_TIMEOUT | MACH_SEND_TIMEOUT | | |
106 MACH_RCV_INTERRUPT | MACH_SEND_INTERRUPT; | |
107 | |
108 uint64_t deadline; | |
109 if (nonblocking) { | |
110 options |= timeout_options; | |
111 deadline = 0; | |
112 } else if (timeout != MACH_MSG_TIMEOUT_NONE) { | |
113 options |= timeout_options; | |
114 deadline = NanosecondTicks() + | |
115 static_cast<uint64_t>(timeout) * kNanosecondsPerMillisecond; | |
116 } else { | |
117 options &= ~timeout_options; | |
118 deadline = 0; | |
119 } | |
120 | |
121 mach_msg_size_t trailer_alloc = REQUESTED_TRAILER_SIZE(options); | |
122 mach_msg_size_t max_request_size = interface->MachMessageServerRequestSize(); | |
123 mach_msg_size_t request_alloc = round_page(max_request_size + trailer_alloc); | |
124 mach_msg_size_t request_size = (options & MACH_RCV_LARGE) | |
125 ? request_alloc | |
126 : max_request_size + trailer_alloc; | |
127 | |
128 mach_msg_size_t max_reply_size = interface->MachMessageServerReplySize(); | |
129 mach_msg_size_t reply_alloc = round_page( | |
130 (options & MACH_SEND_TRAILER) ? (max_reply_size + MAX_TRAILER_SIZE) | |
131 : max_reply_size); | |
132 | |
133 mach_port_t self = mach_task_self(); | |
134 | |
135 kern_return_t kr; | |
136 | |
137 do { | |
138 mach_msg_size_t this_request_alloc = request_alloc; | |
139 mach_msg_size_t this_request_size = request_size; | |
140 | |
141 base::mac::ScopedMachVM request_scoper; | |
142 mach_msg_header_t* request_header = NULL; | |
143 | |
144 while (!request_scoper.address()) { | |
145 vm_address_t request_addr; | |
146 kr = vm_allocate(self, | |
147 &request_addr, | |
148 this_request_alloc, | |
149 VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG)); | |
150 if (kr != KERN_SUCCESS) { | |
151 return kr; | |
152 } | |
153 base::mac::ScopedMachVM trial_request_scoper(request_addr, | |
154 this_request_alloc); | |
155 request_header = reinterpret_cast<mach_msg_header_t*>(request_addr); | |
156 | |
157 do { | |
158 // If |options| contains MACH_RCV_INTERRUPT, retry mach_msg in a loop | |
159 // when it returns MACH_RCV_INTERRUPTED to recompute |remaining_ms| | |
160 // rather than allowing mach_msg to retry using the original timeout | |
161 // value. See 10.9.4 xnu-2422.110.17/libsyscall/mach/mach_msg.c | |
162 // mach_msg(). | |
163 mach_msg_timeout_t remaining_ms; | |
164 if (!TimerRunning(deadline, &remaining_ms)) { | |
165 return MACH_RCV_TIMED_OUT; | |
166 } | |
167 | |
168 kr = mach_msg(request_header, | |
169 options | MACH_RCV_MSG, | |
170 0, | |
171 this_request_size, | |
172 receive_port, | |
173 remaining_ms, | |
174 MACH_PORT_NULL); | |
175 } while (kr == MACH_RCV_INTERRUPTED); | |
176 | |
177 if (kr == MACH_MSG_SUCCESS) { | |
178 request_scoper.swap(trial_request_scoper); | |
179 } else if (kr == MACH_RCV_TOO_LARGE && options & MACH_RCV_LARGE) { | |
180 this_request_size = | |
181 round_page(request_header->msgh_size + trailer_alloc); | |
182 this_request_alloc = this_request_size; | |
183 } else { | |
184 return kr; | |
185 } | |
186 } | |
187 | |
188 vm_address_t reply_addr; | |
189 kr = vm_allocate(self, | |
190 &reply_addr, | |
191 reply_alloc, | |
192 VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG)); | |
193 if (kr != KERN_SUCCESS) { | |
194 return kr; | |
195 } | |
196 | |
197 base::mac::ScopedMachVM reply_scoper(reply_addr, reply_alloc); | |
198 | |
199 mach_msg_header_t* reply_header = | |
200 reinterpret_cast<mach_msg_header_t*>(reply_addr); | |
201 bool destroy_complex_request = false; | |
202 interface->MachMessageServerFunction( | |
203 request_header, reply_header, &destroy_complex_request); | |
204 | |
205 if (!(reply_header->msgh_bits & MACH_MSGH_BITS_COMPLEX)) { | |
206 mig_reply_error_t* reply_mig = | |
207 reinterpret_cast<mig_reply_error_t*>(reply_header); | |
208 if (reply_mig->RetCode == MIG_NO_REPLY) { | |
209 reply_header->msgh_remote_port = MACH_PORT_NULL; | |
210 } else if (reply_mig->RetCode != KERN_SUCCESS && | |
211 request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) { | |
212 destroy_complex_request = true; | |
213 } | |
214 } | |
215 | |
216 if (destroy_complex_request && | |
217 request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) { | |
218 request_header->msgh_remote_port = MACH_PORT_NULL; | |
219 mach_msg_destroy(request_header); | |
220 } | |
221 | |
222 if (reply_header->msgh_remote_port != MACH_PORT_NULL) { | |
223 mach_msg_option_t send_options = | |
224 options | MACH_SEND_MSG | | |
225 (MACH_MSGH_BITS_REMOTE(reply_header->msgh_bits) == | |
226 MACH_MSG_TYPE_MOVE_SEND_ONCE | |
227 ? 0 | |
228 : MACH_SEND_TIMEOUT); | |
Robert Sesek
2014/09/08 16:28:47
Why is SEND_ONCE a gate for SEND_TIMEOUT?
| |
229 | |
230 bool running; | |
231 do { | |
232 // If |options| contains MACH_SEND_INTERRUPT, retry mach_msg in a loop | |
233 // when it returns MACH_SEND_INTERRUPTED to recompute |remaining_ms| | |
234 // rather than allowing mach_msg to retry using the original timeout | |
235 // value. See 10.9.4 xnu-2422.110.17/libsyscall/mach/mach_msg.c | |
236 // mach_msg(). | |
237 mach_msg_timeout_t remaining_ms; | |
238 running = TimerRunning(deadline, &remaining_ms); | |
239 if (!running) { | |
240 // Don’t return just yet. If the timer ran out in between the time the | |
241 // request was received and now, at least try to send the response. | |
242 remaining_ms = 0; | |
243 } | |
244 | |
245 kr = mach_msg(reply_header, | |
246 send_options, | |
247 reply_header->msgh_size, | |
248 0, | |
249 MACH_PORT_NULL, | |
250 remaining_ms, | |
251 MACH_PORT_NULL); | |
252 } while (kr == MACH_SEND_INTERRUPTED); | |
253 | |
254 if (kr != KERN_SUCCESS) { | |
255 if (kr == MACH_SEND_INVALID_DEST || kr == MACH_SEND_TIMED_OUT) { | |
256 mach_msg_destroy(reply_header); | |
257 } | |
258 return kr; | |
259 } | |
260 | |
261 if (!running) { | |
262 // The reply message was sent successfuly, so act as though the deadline | |
263 // was reached before or during the subsequent receive operation when in | |
264 // persistent mode, and just return success when not in persistent mode. | |
265 return persistent ? MACH_RCV_TIMED_OUT : kr; | |
266 } | |
267 } | |
268 } while (persistent); | |
269 | |
270 return kr; | |
271 } | |
272 | |
273 } // namespace crashpad | |
OLD | NEW |