OLD | NEW |
1 // Copyright 2014 The Crashpad Authors. All rights reserved. | 1 // Copyright 2014 The Crashpad Authors. All rights reserved. |
2 // | 2 // |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
4 // you may not use this file except in compliance with 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 | 5 // You may obtain a copy of the License at |
6 // | 6 // |
7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
8 // | 8 // |
9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
13 // limitations under the License. | 13 // limitations under the License. |
14 | 14 |
15 #include "util/mach/mach_message_server.h" | 15 #include "util/mach/mach_message_server.h" |
16 | 16 |
17 #include <limits> | 17 #include <limits> |
18 | 18 |
| 19 #include "base/logging.h" |
19 #include "base/mac/mach_logging.h" | 20 #include "base/mac/mach_logging.h" |
20 #include "base/mac/scoped_mach_vm.h" | 21 #include "base/mac/scoped_mach_vm.h" |
21 #include "util/misc/clock.h" | 22 #include "util/mach/mach_message.h" |
22 | 23 |
23 namespace crashpad { | 24 namespace crashpad { |
24 | 25 |
25 namespace { | 26 namespace { |
26 | 27 |
27 const int kNanosecondsPerMillisecond = 1E6; | 28 //! \brief Manages a dynamically-allocated buffer to be used for Mach messaging. |
| 29 class MachMessageBuffer { |
| 30 public: |
| 31 MachMessageBuffer() : vm_() {} |
28 | 32 |
29 // TimerRunning determines whether |deadline| has passed. If |deadline| is in | 33 ~MachMessageBuffer() {} |
30 // the future, |*remaining_ms| is set to the number of milliseconds remaining, | 34 |
31 // which will always be a positive value, and this function returns true. If | 35 //! \return A pointer to the buffer. |
32 // |deadline| is zero (indicating that no timer is in effect), |*remaining_ms| | 36 mach_msg_header_t* Header() const { |
33 // is set to zero and this function returns true. Otherwise, this function sets | 37 return reinterpret_cast<mach_msg_header_t*>(vm_.address()); |
34 // |*remaining_ms| to zero and returns false. |deadline| is specified on the | |
35 // same time base as is returned by ClockMonotonicNanoseconds(). | |
36 bool TimerRunning(uint64_t deadline, mach_msg_timeout_t* remaining_ms) { | |
37 if (!deadline) { | |
38 *remaining_ms = MACH_MSG_TIMEOUT_NONE; | |
39 return true; | |
40 } | 38 } |
41 | 39 |
42 uint64_t now = ClockMonotonicNanoseconds(); | 40 //! \brief Ensures that this object has a buffer of exactly \a size bytes |
| 41 //! available. |
| 42 //! |
| 43 //! If the existing buffer is a different size, it will be reallocated without |
| 44 //! copying any of the old buffer’s contents to the new buffer. The contents |
| 45 //! of the buffer are unspecified after this call, even if no reallocation is |
| 46 //! made. |
| 47 kern_return_t Reallocate(vm_size_t size) { |
| 48 // This test uses == instead of > so that a large reallocation to receive a |
| 49 // large message doesn’t cause permanent memory bloat for the duration of |
| 50 // a MachMessageServer::Run() loop. |
| 51 if (size == vm_.size()) { |
| 52 return KERN_SUCCESS; |
| 53 } |
43 | 54 |
44 if (now >= deadline) { | 55 // reset() first, so that two allocations don’t exist simultaneously. |
45 *remaining_ms = 0; | 56 vm_.reset(); |
46 return false; | 57 |
| 58 vm_address_t address; |
| 59 kern_return_t kr = |
| 60 vm_allocate(mach_task_self(), |
| 61 &address, |
| 62 size, |
| 63 VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG)); |
| 64 if (kr != KERN_SUCCESS) { |
| 65 return kr; |
| 66 } |
| 67 |
| 68 vm_.reset(address, size); |
| 69 return KERN_SUCCESS; |
47 } | 70 } |
48 | 71 |
49 uint64_t remaining = deadline - now; | 72 private: |
| 73 base::mac::ScopedMachVM vm_; |
50 | 74 |
51 // Round to the nearest millisecond, taking care not to overflow. | 75 DISALLOW_COPY_AND_ASSIGN(MachMessageBuffer); |
52 const int kHalfMillisecondInNanoseconds = kNanosecondsPerMillisecond / 2; | 76 }; |
53 mach_msg_timeout_t remaining_mach; | 77 |
54 if (remaining <= | 78 // Wraps MachMessageWithDeadline(), using a MachMessageBuffer argument which |
55 std::numeric_limits<uint64_t>::max() - kHalfMillisecondInNanoseconds) { | 79 // will be resized to |receive_size| (after being page-rounded). MACH_RCV_MSG |
56 remaining_mach = (remaining + kHalfMillisecondInNanoseconds) / | 80 // is always combined into |options|. |
57 kNanosecondsPerMillisecond; | 81 mach_msg_return_t MachMessageAllocateReceive(MachMessageBuffer* request, |
58 } else { | 82 mach_msg_option_t options, |
59 remaining_mach = remaining / kNanosecondsPerMillisecond; | 83 mach_msg_size_t receive_size, |
| 84 mach_port_name_t receive_port, |
| 85 MachMessageDeadline deadline, |
| 86 mach_port_name_t notify_port, |
| 87 bool run_even_if_expired) { |
| 88 mach_msg_size_t request_alloc = round_page(receive_size); |
| 89 kern_return_t kr = request->Reallocate(request_alloc); |
| 90 if (kr != KERN_SUCCESS) { |
| 91 return kr; |
60 } | 92 } |
61 | 93 |
62 if (remaining_mach == MACH_MSG_TIMEOUT_NONE) { | 94 return MachMessageWithDeadline(request->Header(), |
63 *remaining_ms = 0; | 95 options | MACH_RCV_MSG, |
64 return false; | 96 receive_size, |
65 } | 97 receive_port, |
66 | 98 deadline, |
67 *remaining_ms = remaining_mach; | 99 notify_port, |
68 return true; | 100 run_even_if_expired); |
69 } | 101 } |
70 | 102 |
71 } // namespace | 103 } // namespace |
72 | 104 |
73 // This implementation is based on 10.9.4 | 105 // This implementation is based on 10.9.4 |
74 // xnu-2422.110.17/libsyscall/mach/mach_msg.c mach_msg_server_once(), but | 106 // xnu-2422.110.17/libsyscall/mach/mach_msg.c mach_msg_server_once(), but |
75 // adapted to local style using scopers, replacing the server callback function | 107 // adapted to local style using scopers, replacing the server callback function |
76 // and |max_size| parameter with a C++ interface, and with the addition of the | 108 // and |max_size| parameter with a C++ interface, and with the addition of the |
77 // the |persistent| parameter allowing this function to serve as a stand-in for | 109 // the |persistent| parameter allowing this function to serve as a stand-in for |
78 // mach_msg_server(), the |nonblocking| parameter to control blocking directly, | 110 // mach_msg_server(), the |nonblocking| parameter to control blocking directly, |
79 // and the |timeout_ms| parameter allowing this function to not block | 111 // and the |timeout_ms| parameter allowing this function to not block |
80 // indefinitely. | 112 // indefinitely. |
81 // | 113 // |
82 // static | 114 // static |
83 mach_msg_return_t MachMessageServer::Run(Interface* interface, | 115 mach_msg_return_t MachMessageServer::Run(Interface* interface, |
84 mach_port_t receive_port, | 116 mach_port_t receive_port, |
85 mach_msg_options_t options, | 117 mach_msg_options_t options, |
86 Persistent persistent, | 118 Persistent persistent, |
87 Nonblocking nonblocking, | 119 Nonblocking nonblocking, |
88 ReceiveLarge receive_large, | 120 ReceiveLarge receive_large, |
89 mach_msg_timeout_t timeout_ms) { | 121 mach_msg_timeout_t timeout_ms) { |
90 options &= ~(MACH_RCV_MSG | MACH_SEND_MSG); | 122 options &= ~(MACH_RCV_MSG | MACH_SEND_MSG); |
91 | 123 |
92 mach_msg_options_t timeout_options = MACH_RCV_TIMEOUT | MACH_SEND_TIMEOUT | | 124 MachMessageDeadline deadline; |
93 MACH_RCV_INTERRUPT | MACH_SEND_INTERRUPT; | |
94 | |
95 uint64_t deadline; | |
96 if (nonblocking == kNonblocking) { | 125 if (nonblocking == kNonblocking) { |
97 options |= timeout_options; | 126 deadline = kMachMessageNonblocking; |
98 deadline = 0; | 127 } else if (timeout_ms == MACH_MSG_TIMEOUT_NONE) { |
99 } else if (timeout_ms != MACH_MSG_TIMEOUT_NONE) { | 128 deadline = kMachMessageWaitIndefinitely; |
100 options |= timeout_options; | |
101 deadline = ClockMonotonicNanoseconds() + | |
102 implicit_cast<uint64_t>(timeout_ms) * kNanosecondsPerMillisecond; | |
103 } else { | 129 } else { |
104 options &= ~timeout_options; | 130 deadline = MachMessageDeadlineFromTimeout(timeout_ms); |
105 deadline = 0; | |
106 } | 131 } |
107 | 132 |
108 if (receive_large == kReceiveLargeResize) { | 133 if (receive_large == kReceiveLargeResize) { |
109 options |= MACH_RCV_LARGE; | 134 options |= MACH_RCV_LARGE; |
110 } else { | 135 } else { |
111 options &= ~MACH_RCV_LARGE; | 136 options &= ~MACH_RCV_LARGE; |
112 } | 137 } |
113 | 138 |
114 mach_msg_size_t trailer_alloc = REQUESTED_TRAILER_SIZE(options); | 139 const mach_msg_size_t trailer_alloc = REQUESTED_TRAILER_SIZE(options); |
115 mach_msg_size_t expected_request_size = | 140 const mach_msg_size_t expected_receive_size = |
116 interface->MachMessageServerRequestSize(); | 141 round_msg(interface->MachMessageServerRequestSize()) + trailer_alloc; |
117 mach_msg_size_t request_alloc = | 142 const mach_msg_size_t request_size = (receive_large == kReceiveLargeResize) |
118 round_page(round_msg(expected_request_size) + trailer_alloc); | 143 ? round_page(expected_receive_size) |
119 mach_msg_size_t request_size = | 144 : expected_receive_size; |
120 (receive_large == kReceiveLargeResize) | |
121 ? request_alloc | |
122 : round_msg(expected_request_size) + trailer_alloc; | |
123 | |
124 mach_msg_size_t max_reply_size = interface->MachMessageServerReplySize(); | |
125 | 145 |
126 // mach_msg_server() and mach_msg_server_once() would consider whether | 146 // mach_msg_server() and mach_msg_server_once() would consider whether |
127 // |options| contains MACH_SEND_TRAILER and include MAX_TRAILER_SIZE in this | 147 // |options| contains MACH_SEND_TRAILER and include MAX_TRAILER_SIZE in this |
128 // computation if it does, but that option is ineffective on OS X. | 148 // computation if it does, but that option is ineffective on OS X. |
129 mach_msg_size_t reply_alloc = round_page(max_reply_size); | 149 const mach_msg_size_t reply_alloc = |
| 150 round_page(interface->MachMessageServerReplySize()); |
130 | 151 |
131 base::mac::ScopedMachVM request_scoper; | 152 MachMessageBuffer request; |
132 base::mac::ScopedMachVM reply_scoper; | 153 MachMessageBuffer reply; |
133 bool received_any_request = false; | 154 bool received_any_request = false; |
| 155 bool retry; |
134 | 156 |
135 kern_return_t kr; | 157 kern_return_t kr; |
136 | 158 |
137 do { | 159 do { |
138 mach_msg_size_t this_request_alloc = request_alloc; | 160 retry = false; |
139 mach_msg_size_t this_request_size = request_size; | |
140 | 161 |
141 mach_msg_header_t* request_header = nullptr; | 162 kr = MachMessageAllocateReceive(&request, |
| 163 options, |
| 164 request_size, |
| 165 receive_port, |
| 166 deadline, |
| 167 MACH_PORT_NULL, |
| 168 !received_any_request); |
| 169 if (kr == MACH_RCV_TOO_LARGE) { |
| 170 switch (receive_large) { |
| 171 case kReceiveLargeError: |
| 172 break; |
142 | 173 |
143 do { | 174 case kReceiveLargeIgnore: |
144 // This test uses != instead of < so that a large reallocation to receive | 175 // Try again, even in one-shot mode. The caller is expecting this |
145 // a large message doesn’t cause permanent memory bloat. | 176 // method to take action on the first message in the queue, and has |
146 if (request_scoper.size() != this_request_alloc) { | 177 // indicated that they want large messages to be ignored. The |
147 // reset() first, so that two allocations don’t exist simultaneously. | 178 // alternatives, which might involve returning MACH_MSG_SUCCESS, |
148 request_scoper.reset(); | 179 // MACH_RCV_TIMED_OUT, or MACH_RCV_TOO_LARGE to a caller that |
| 180 // specified one-shot behavior, all seem less correct than retrying. |
| 181 MACH_LOG(WARNING, kr) << "mach_msg: ignoring large message"; |
| 182 retry = true; |
| 183 continue; |
149 | 184 |
150 vm_address_t request_addr; | 185 case kReceiveLargeResize: { |
151 kr = vm_allocate(mach_task_self(), | 186 mach_msg_size_t this_request_size = round_page( |
152 &request_addr, | 187 round_msg(request.Header()->msgh_size) + trailer_alloc); |
153 this_request_alloc, | 188 |
154 VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG)); | 189 kr = MachMessageAllocateReceive(&request, |
155 if (kr != KERN_SUCCESS) { | 190 options & ~MACH_RCV_LARGE, |
156 return kr; | 191 this_request_size, |
| 192 receive_port, |
| 193 deadline, |
| 194 MACH_PORT_NULL, |
| 195 !received_any_request); |
| 196 |
| 197 break; |
157 } | 198 } |
| 199 } |
| 200 } |
158 | 201 |
159 request_scoper.reset(request_addr, this_request_alloc); | 202 if (kr != MACH_MSG_SUCCESS) { |
160 } | 203 return kr; |
161 | 204 } |
162 request_header = | |
163 reinterpret_cast<mach_msg_header_t*>(request_scoper.address()); | |
164 | |
165 do { | |
166 // If |options| contains MACH_RCV_INTERRUPT, retry mach_msg() in a loop | |
167 // when it returns MACH_RCV_INTERRUPTED to recompute |remaining_ms| | |
168 // rather than allowing mach_msg() to retry using the original timeout | |
169 // value. See 10.9.4 xnu-2422.110.17/libsyscall/mach/mach_msg.c | |
170 // mach_msg(). Don’t return early here if nothing has ever been | |
171 // received: this method should always attempt to dequeue at least one | |
172 // message. | |
173 mach_msg_timeout_t remaining_ms; | |
174 if (!TimerRunning(deadline, &remaining_ms) && received_any_request) { | |
175 return MACH_RCV_TIMED_OUT; | |
176 } | |
177 | |
178 kr = mach_msg(request_header, | |
179 options | MACH_RCV_MSG, | |
180 0, | |
181 this_request_size, | |
182 receive_port, | |
183 remaining_ms, | |
184 MACH_PORT_NULL); | |
185 | |
186 if (kr == MACH_RCV_TOO_LARGE && receive_large == kReceiveLargeIgnore) { | |
187 MACH_LOG(WARNING, kr) << "mach_msg: ignoring large message"; | |
188 } | |
189 } while ( | |
190 (kr == MACH_RCV_TOO_LARGE && receive_large == kReceiveLargeIgnore) || | |
191 kr == MACH_RCV_INTERRUPTED); | |
192 | |
193 if (kr == MACH_RCV_TOO_LARGE && receive_large == kReceiveLargeResize) { | |
194 this_request_size = | |
195 round_page(round_msg(request_header->msgh_size) + trailer_alloc); | |
196 this_request_alloc = this_request_size; | |
197 } else if (kr != MACH_MSG_SUCCESS) { | |
198 return kr; | |
199 } | |
200 } while (kr != MACH_MSG_SUCCESS); | |
201 | 205 |
202 received_any_request = true; | 206 received_any_request = true; |
203 | 207 |
204 if (reply_scoper.size() != reply_alloc) { | 208 kr = reply.Reallocate(reply_alloc); |
205 vm_address_t reply_addr; | 209 if (kr != KERN_SUCCESS) { |
206 kr = vm_allocate(mach_task_self(), | 210 return kr; |
207 &reply_addr, | |
208 reply_alloc, | |
209 VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_MACH_MSG)); | |
210 if (kr != KERN_SUCCESS) { | |
211 return kr; | |
212 } | |
213 | |
214 reply_scoper.reset(reply_addr, reply_alloc); | |
215 } | 211 } |
216 | 212 |
217 mach_msg_header_t* reply_header = | 213 mach_msg_header_t* request_header = request.Header(); |
218 reinterpret_cast<mach_msg_header_t*>(reply_scoper.address()); | 214 mach_msg_header_t* reply_header = reply.Header(); |
219 bool destroy_complex_request = false; | 215 bool destroy_complex_request = false; |
220 interface->MachMessageServerFunction( | 216 interface->MachMessageServerFunction( |
221 request_header, reply_header, &destroy_complex_request); | 217 request_header, reply_header, &destroy_complex_request); |
222 | 218 |
223 if (!(reply_header->msgh_bits & MACH_MSGH_BITS_COMPLEX)) { | 219 if (!(reply_header->msgh_bits & MACH_MSGH_BITS_COMPLEX)) { |
224 // This only works if the reply message is not complex, because otherwise, | 220 // This only works if the reply message is not complex, because otherwise, |
225 // the location of the RetCode field is not known. It should be possible | 221 // the location of the RetCode field is not known. It should be possible |
226 // to locate the RetCode field by looking beyond the descriptors in a | 222 // to locate the RetCode field by looking beyond the descriptors in a |
227 // complex reply message, but this is not currently done. This behavior | 223 // complex reply message, but this is not currently done. This behavior |
228 // has not proven itself necessary in practice, and it’s not done by | 224 // has not proven itself necessary in practice, and it’s not done by |
229 // mach_msg_server() or mach_msg_server_once() either. | 225 // mach_msg_server() or mach_msg_server_once() either. |
230 mig_reply_error_t* reply_mig = | 226 mig_reply_error_t* reply_mig = |
231 reinterpret_cast<mig_reply_error_t*>(reply_header); | 227 reinterpret_cast<mig_reply_error_t*>(reply_header); |
232 if (reply_mig->RetCode == MIG_NO_REPLY) { | 228 if (reply_mig->RetCode == MIG_NO_REPLY) { |
233 reply_header->msgh_remote_port = MACH_PORT_NULL; | 229 reply_header->msgh_remote_port = MACH_PORT_NULL; |
234 } else if (reply_mig->RetCode != KERN_SUCCESS && | 230 } else if (reply_mig->RetCode != KERN_SUCCESS && |
235 request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) { | 231 request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) { |
236 destroy_complex_request = true; | 232 destroy_complex_request = true; |
237 } | 233 } |
238 } | 234 } |
239 | 235 |
240 if (destroy_complex_request && | 236 if (destroy_complex_request && |
241 request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) { | 237 request_header->msgh_bits & MACH_MSGH_BITS_COMPLEX) { |
242 request_header->msgh_remote_port = MACH_PORT_NULL; | 238 request_header->msgh_remote_port = MACH_PORT_NULL; |
243 mach_msg_destroy(request_header); | 239 mach_msg_destroy(request_header); |
244 } | 240 } |
245 | 241 |
246 if (reply_header->msgh_remote_port != MACH_PORT_NULL) { | 242 if (reply_header->msgh_remote_port != MACH_PORT_NULL) { |
247 // If the reply port right is a send-once right, the send won’t block even | 243 // Avoid blocking indefinitely. This duplicates the logic in 10.9.5 |
248 // if the remote side isn’t waiting for a message. No timeout is used, | 244 // xnu-2422.115.4/libsyscall/mach/mach_msg.c mach_msg_server_once(), |
249 // which keeps the communication on the kernel’s fast path. If the reply | 245 // although the special provision for sending to a send-once right is not |
250 // port right is a send right, MACH_SEND_TIMEOUT is used to avoid blocking | 246 // made, because kernel keeps sends to a send-once right on the fast path |
251 // indefinitely. This duplicates the logic in 10.9.4 | 247 // without considering the user-specified timeout. See 10.9.5 |
252 // xnu-2422.110.17/libsyscall/mach/mach_msg.c mach_msg_server_once(). | 248 // xnu-2422.115.4/osfmk/ipc/ipc_mqueue.c ipc_mqueue_send(). |
253 mach_msg_option_t send_options = | 249 const MachMessageDeadline send_deadline = |
254 options | MACH_SEND_MSG | | 250 deadline == kMachMessageWaitIndefinitely ? kMachMessageNonblocking |
255 (MACH_MSGH_BITS_REMOTE(reply_header->msgh_bits) == | 251 : deadline; |
256 MACH_MSG_TYPE_MOVE_SEND_ONCE | |
257 ? 0 | |
258 : MACH_SEND_TIMEOUT); | |
259 | 252 |
260 bool running; | 253 kr = MachMessageWithDeadline(reply_header, |
261 do { | 254 options | MACH_SEND_MSG, |
262 // If |options| contains MACH_SEND_INTERRUPT, retry mach_msg() in a loop | 255 0, |
263 // when it returns MACH_SEND_INTERRUPTED to recompute |remaining_ms| | 256 MACH_PORT_NULL, |
264 // rather than allowing mach_msg() to retry using the original timeout | 257 send_deadline, |
265 // value. See 10.9.4 xnu-2422.110.17/libsyscall/mach/mach_msg.c | 258 MACH_PORT_NULL, |
266 // mach_msg(). | 259 true); |
267 mach_msg_timeout_t remaining_ms; | |
268 running = TimerRunning(deadline, &remaining_ms); | |
269 | 260 |
270 // Don’t return just yet even if |running| is false. If the timer ran | 261 if (kr != MACH_MSG_SUCCESS) { |
271 // out in between the time the request was received and now, at least | 262 if (kr == MACH_SEND_INVALID_DEST || |
272 // try to send the response. | 263 kr == MACH_SEND_TIMED_OUT || |
273 | 264 kr == MACH_SEND_INTERRUPTED) { |
274 kr = mach_msg(reply_header, | |
275 send_options, | |
276 reply_header->msgh_size, | |
277 0, | |
278 MACH_PORT_NULL, | |
279 remaining_ms, | |
280 MACH_PORT_NULL); | |
281 } while (kr == MACH_SEND_INTERRUPTED); | |
282 | |
283 if (kr != KERN_SUCCESS) { | |
284 if (kr == MACH_SEND_INVALID_DEST || kr == MACH_SEND_TIMED_OUT) { | |
285 mach_msg_destroy(reply_header); | 265 mach_msg_destroy(reply_header); |
286 } | 266 } |
287 return kr; | 267 return kr; |
288 } | 268 } |
289 | |
290 if (!running) { | |
291 // The reply message was sent successfuly, so act as though the deadline | |
292 // was reached before or during the subsequent receive operation when in | |
293 // persistent mode, and just return success when not in persistent mode. | |
294 return (persistent == kPersistent) ? MACH_RCV_TIMED_OUT : kr; | |
295 } | |
296 } | 269 } |
297 } while (persistent == kPersistent); | 270 } while (persistent == kPersistent || retry); |
298 | 271 |
299 return kr; | 272 return kr; |
300 } | 273 } |
301 | 274 |
302 } // namespace crashpad | 275 } // namespace crashpad |
OLD | NEW |