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

Side by Side Diff: third_party/crashpad/crashpad/util/win/exception_handler_server.cc

Issue 1505213004: Copy Crashpad into the Chrome tree instead of importing it via DEPS (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Address review comments, update README.chromium Created 5 years 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 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/win/exception_handler_server.h"
16
17 #include <sddl.h>
18 #include <string.h>
19
20 #include "base/logging.h"
21 #include "base/numerics/safe_conversions.h"
22 #include "base/rand_util.h"
23 #include "base/strings/stringprintf.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "minidump/minidump_file_writer.h"
26 #include "snapshot/crashpad_info_client_options.h"
27 #include "snapshot/win/process_snapshot_win.h"
28 #include "util/file/file_writer.h"
29 #include "util/stdlib/move.h"
30 #include "util/misc/random_string.h"
31 #include "util/misc/tri_state.h"
32 #include "util/misc/uuid.h"
33 #include "util/win/get_function.h"
34 #include "util/win/handle.h"
35 #include "util/win/registration_protocol_win.h"
36 #include "util/win/scoped_local_alloc.h"
37 #include "util/win/xp_compat.h"
38
39 namespace crashpad {
40
41 namespace {
42
43 // We create two pipe instances, so that there's one listening while the
44 // PipeServiceProc is processing a registration.
45 const size_t kPipeInstances = 2;
46
47 // Wraps CreateNamedPipe() to create a single named pipe instance.
48 //
49 // If first_instance is true, the named pipe instance will be created with
50 // FILE_FLAG_FIRST_PIPE_INSTANCE. This ensures that the the pipe name is not
51 // already in use when created. The first instance will be created with an
52 // untrusted integrity SACL so instances of this pipe can be connected to by
53 // processes of any integrity level.
54 HANDLE CreateNamedPipeInstance(const std::wstring& pipe_name,
55 bool first_instance) {
56 SECURITY_ATTRIBUTES security_attributes;
57 SECURITY_ATTRIBUTES* security_attributes_pointer = nullptr;
58 ScopedLocalAlloc scoped_sec_desc;
59
60 if (first_instance) {
61 // Pre-Vista does not have integrity levels.
62 const DWORD version = GetVersion();
63 const DWORD major_version = LOBYTE(LOWORD(version));
64 const bool is_vista_or_later = major_version >= 6;
65 if (is_vista_or_later) {
66 // Mandatory Label, no ACE flags, no ObjectType, integrity level
67 // untrusted.
68 const wchar_t kSddl[] = L"S:(ML;;;;;S-1-16-0)";
69
70 PSECURITY_DESCRIPTOR sec_desc;
71 PCHECK(ConvertStringSecurityDescriptorToSecurityDescriptor(
72 kSddl, SDDL_REVISION_1, &sec_desc, nullptr))
73 << "ConvertStringSecurityDescriptorToSecurityDescriptor";
74
75 // Take ownership of the allocated SECURITY_DESCRIPTOR.
76 scoped_sec_desc.reset(sec_desc);
77
78 memset(&security_attributes, 0, sizeof(security_attributes));
79 security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
80 security_attributes.lpSecurityDescriptor = sec_desc;
81 security_attributes.bInheritHandle = FALSE;
82 security_attributes_pointer = &security_attributes;
83 }
84 }
85
86 return CreateNamedPipe(
87 pipe_name.c_str(),
88 PIPE_ACCESS_DUPLEX | (first_instance ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0),
89 PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
90 kPipeInstances,
91 512,
92 512,
93 0,
94 security_attributes_pointer);
95 }
96
97 decltype(GetNamedPipeClientProcessId)* GetNamedPipeClientProcessIdFunction() {
98 static const auto get_named_pipe_client_process_id =
99 GET_FUNCTION(L"kernel32.dll", ::GetNamedPipeClientProcessId);
100 return get_named_pipe_client_process_id;
101 }
102
103 HANDLE DuplicateEvent(HANDLE process, HANDLE event) {
104 HANDLE handle;
105 if (DuplicateHandle(GetCurrentProcess(),
106 event,
107 process,
108 &handle,
109 SYNCHRONIZE | EVENT_MODIFY_STATE,
110 false,
111 0)) {
112 return handle;
113 }
114 return nullptr;
115 }
116
117 } // namespace
118
119 namespace internal {
120
121 //! \brief Context information for the named pipe handler threads.
122 class PipeServiceContext {
123 public:
124 PipeServiceContext(HANDLE port,
125 HANDLE pipe,
126 ExceptionHandlerServer::Delegate* delegate,
127 base::Lock* clients_lock,
128 std::set<internal::ClientData*>* clients,
129 uint64_t shutdown_token)
130 : port_(port),
131 pipe_(pipe),
132 delegate_(delegate),
133 clients_lock_(clients_lock),
134 clients_(clients),
135 shutdown_token_(shutdown_token) {}
136
137 HANDLE port() const { return port_; }
138 HANDLE pipe() const { return pipe_.get(); }
139 ExceptionHandlerServer::Delegate* delegate() const { return delegate_; }
140 base::Lock* clients_lock() const { return clients_lock_; }
141 std::set<internal::ClientData*>* clients() const { return clients_; }
142 uint64_t shutdown_token() const { return shutdown_token_; }
143
144 private:
145 HANDLE port_; // weak
146 ScopedKernelHANDLE pipe_;
147 ExceptionHandlerServer::Delegate* delegate_; // weak
148 base::Lock* clients_lock_; // weak
149 std::set<internal::ClientData*>* clients_; // weak
150 uint64_t shutdown_token_;
151
152 DISALLOW_COPY_AND_ASSIGN(PipeServiceContext);
153 };
154
155 //! \brief The context data for registered threadpool waits.
156 //!
157 //! This object must be created and destroyed on the main thread. Access must be
158 //! guarded by use of the lock() with the exception of the threadpool wait
159 //! variables which are accessed only by the main thread.
160 class ClientData {
161 public:
162 ClientData(HANDLE port,
163 ExceptionHandlerServer::Delegate* delegate,
164 ScopedKernelHANDLE process,
165 WinVMAddress crash_exception_information_address,
166 WinVMAddress non_crash_exception_information_address,
167 WinVMAddress debug_critical_section_address,
168 WAITORTIMERCALLBACK crash_dump_request_callback,
169 WAITORTIMERCALLBACK non_crash_dump_request_callback,
170 WAITORTIMERCALLBACK process_end_callback)
171 : crash_dump_request_thread_pool_wait_(INVALID_HANDLE_VALUE),
172 non_crash_dump_request_thread_pool_wait_(INVALID_HANDLE_VALUE),
173 process_end_thread_pool_wait_(INVALID_HANDLE_VALUE),
174 lock_(),
175 port_(port),
176 delegate_(delegate),
177 crash_dump_requested_event_(
178 CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
179 non_crash_dump_requested_event_(
180 CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
181 non_crash_dump_completed_event_(
182 CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
183 process_(crashpad::move(process)),
184 crash_exception_information_address_(
185 crash_exception_information_address),
186 non_crash_exception_information_address_(
187 non_crash_exception_information_address),
188 debug_critical_section_address_(debug_critical_section_address) {
189 RegisterThreadPoolWaits(crash_dump_request_callback,
190 non_crash_dump_request_callback,
191 process_end_callback);
192 }
193
194 ~ClientData() {
195 // It is important that this only access the threadpool waits (it's called
196 // from the main thread) until the waits are unregistered, to ensure that
197 // any outstanding callbacks are complete.
198 UnregisterThreadPoolWaits();
199 }
200
201 base::Lock* lock() { return &lock_; }
202 HANDLE port() const { return port_; }
203 ExceptionHandlerServer::Delegate* delegate() const { return delegate_; }
204 HANDLE crash_dump_requested_event() const {
205 return crash_dump_requested_event_.get();
206 }
207 HANDLE non_crash_dump_requested_event() const {
208 return non_crash_dump_requested_event_.get();
209 }
210 HANDLE non_crash_dump_completed_event() const {
211 return non_crash_dump_completed_event_.get();
212 }
213 WinVMAddress crash_exception_information_address() const {
214 return crash_exception_information_address_;
215 }
216 WinVMAddress non_crash_exception_information_address() const {
217 return non_crash_exception_information_address_;
218 }
219 WinVMAddress debug_critical_section_address() const {
220 return debug_critical_section_address_;
221 }
222 HANDLE process() const { return process_.get(); }
223
224 private:
225 void RegisterThreadPoolWaits(
226 WAITORTIMERCALLBACK crash_dump_request_callback,
227 WAITORTIMERCALLBACK non_crash_dump_request_callback,
228 WAITORTIMERCALLBACK process_end_callback) {
229 if (!RegisterWaitForSingleObject(&crash_dump_request_thread_pool_wait_,
230 crash_dump_requested_event_.get(),
231 crash_dump_request_callback,
232 this,
233 INFINITE,
234 WT_EXECUTEDEFAULT)) {
235 LOG(ERROR) << "RegisterWaitForSingleObject crash dump requested";
236 }
237
238 if (!RegisterWaitForSingleObject(&non_crash_dump_request_thread_pool_wait_,
239 non_crash_dump_requested_event_.get(),
240 non_crash_dump_request_callback,
241 this,
242 INFINITE,
243 WT_EXECUTEDEFAULT)) {
244 LOG(ERROR) << "RegisterWaitForSingleObject non-crash dump requested";
245 }
246
247 if (!RegisterWaitForSingleObject(&process_end_thread_pool_wait_,
248 process_.get(),
249 process_end_callback,
250 this,
251 INFINITE,
252 WT_EXECUTEONLYONCE)) {
253 LOG(ERROR) << "RegisterWaitForSingleObject process end";
254 }
255 }
256
257 // This blocks until outstanding calls complete so that we know it's safe to
258 // delete this object. Because of this, it must be executed on the main
259 // thread, not a threadpool thread.
260 void UnregisterThreadPoolWaits() {
261 UnregisterWaitEx(crash_dump_request_thread_pool_wait_,
262 INVALID_HANDLE_VALUE);
263 crash_dump_request_thread_pool_wait_ = INVALID_HANDLE_VALUE;
264 UnregisterWaitEx(non_crash_dump_request_thread_pool_wait_,
265 INVALID_HANDLE_VALUE);
266 non_crash_dump_request_thread_pool_wait_ = INVALID_HANDLE_VALUE;
267 UnregisterWaitEx(process_end_thread_pool_wait_, INVALID_HANDLE_VALUE);
268 process_end_thread_pool_wait_ = INVALID_HANDLE_VALUE;
269 }
270
271 // These are only accessed on the main thread.
272 HANDLE crash_dump_request_thread_pool_wait_;
273 HANDLE non_crash_dump_request_thread_pool_wait_;
274 HANDLE process_end_thread_pool_wait_;
275
276 base::Lock lock_;
277 // Access to these fields must be guarded by lock_.
278 HANDLE port_; // weak
279 ExceptionHandlerServer::Delegate* delegate_; // weak
280 ScopedKernelHANDLE crash_dump_requested_event_;
281 ScopedKernelHANDLE non_crash_dump_requested_event_;
282 ScopedKernelHANDLE non_crash_dump_completed_event_;
283 ScopedKernelHANDLE process_;
284 WinVMAddress crash_exception_information_address_;
285 WinVMAddress non_crash_exception_information_address_;
286 WinVMAddress debug_critical_section_address_;
287
288 DISALLOW_COPY_AND_ASSIGN(ClientData);
289 };
290
291 } // namespace internal
292
293 ExceptionHandlerServer::Delegate::~Delegate() {
294 }
295
296 ExceptionHandlerServer::ExceptionHandlerServer(bool persistent)
297 : pipe_name_(),
298 port_(CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1)),
299 first_pipe_instance_(),
300 clients_lock_(),
301 clients_(),
302 persistent_(persistent) {
303 }
304
305 ExceptionHandlerServer::~ExceptionHandlerServer() {
306 }
307
308 void ExceptionHandlerServer::SetPipeName(const std::wstring& pipe_name) {
309 DCHECK(pipe_name_.empty());
310 DCHECK(!pipe_name.empty());
311
312 pipe_name_ = pipe_name;
313 }
314
315 std::wstring ExceptionHandlerServer::CreatePipe() {
316 DCHECK(!first_pipe_instance_.is_valid());
317
318 int tries = 5;
319 std::string pipe_name_base =
320 base::StringPrintf("\\\\.\\pipe\\crashpad_%d_", GetCurrentProcessId());
321 std::wstring pipe_name;
322 do {
323 pipe_name = base::UTF8ToUTF16(pipe_name_base + RandomString());
324
325 first_pipe_instance_.reset(CreateNamedPipeInstance(pipe_name, true));
326
327 // CreateNamedPipe() is documented as setting the error to
328 // ERROR_ACCESS_DENIED if FILE_FLAG_FIRST_PIPE_INSTANCE is specified and the
329 // pipe name is already in use. However it may set the error to other codes
330 // such as ERROR_PIPE_BUSY (if the pipe already exists and has reached its
331 // maximum instance count) or ERROR_INVALID_PARAMETER (if the pipe already
332 // exists and its attributes differ from those specified to
333 // CreateNamedPipe()). Some of these errors may be ambiguous: for example,
334 // ERROR_INVALID_PARAMETER may also occur if CreateNamedPipe() is called
335 // incorrectly even in the absence of an existing pipe by the same name.
336 //
337 // Rather than chasing down all of the possible errors that might indicate
338 // that a pipe name is already in use, retry up to a few times on any error.
339 } while (!first_pipe_instance_.is_valid() && --tries);
340
341 PCHECK(first_pipe_instance_.is_valid()) << "CreateNamedPipe";
342
343 SetPipeName(pipe_name);
344 return pipe_name;
345 }
346
347 void ExceptionHandlerServer::Run(Delegate* delegate) {
348 uint64_t shutdown_token = base::RandUint64();
349 ScopedKernelHANDLE thread_handles[kPipeInstances];
350 for (int i = 0; i < arraysize(thread_handles); ++i) {
351 HANDLE pipe;
352 if (first_pipe_instance_.is_valid()) {
353 pipe = first_pipe_instance_.release();
354 } else {
355 pipe = CreateNamedPipeInstance(pipe_name_, i == 0);
356 PCHECK(pipe != INVALID_HANDLE_VALUE) << "CreateNamedPipe";
357 }
358
359 // Ownership of this object (and the pipe instance) is given to the new
360 // thread. We close the thread handles at the end of the scope. They clean
361 // up the context object and the pipe instance on termination.
362 internal::PipeServiceContext* context =
363 new internal::PipeServiceContext(port_.get(),
364 pipe,
365 delegate,
366 &clients_lock_,
367 &clients_,
368 shutdown_token);
369 thread_handles[i].reset(
370 CreateThread(nullptr, 0, &PipeServiceProc, context, 0, nullptr));
371 PCHECK(thread_handles[i].is_valid()) << "CreateThread";
372 }
373
374 delegate->ExceptionHandlerServerStarted();
375
376 // This is the main loop of the server. Most work is done on the threadpool,
377 // other than process end handling which is posted back to this main thread,
378 // as we must unregister the threadpool waits here.
379 for (;;) {
380 OVERLAPPED* ov = nullptr;
381 ULONG_PTR key = 0;
382 DWORD bytes = 0;
383 GetQueuedCompletionStatus(port_.get(), &bytes, &key, &ov, INFINITE);
384 if (!key) {
385 // Shutting down.
386 break;
387 }
388
389 // Otherwise, this is a request to unregister and destroy the given client.
390 // delete'ing the ClientData blocks in UnregisterWaitEx to ensure all
391 // outstanding threadpool waits are complete. This is important because the
392 // process handle can be signalled *before* the dump request is signalled.
393 internal::ClientData* client = reinterpret_cast<internal::ClientData*>(key);
394 base::AutoLock lock(clients_lock_);
395 clients_.erase(client);
396 delete client;
397 if (!persistent_ && clients_.empty())
398 break;
399 }
400
401 // Signal to the named pipe instances that they should terminate.
402 for (int i = 0; i < arraysize(thread_handles); ++i) {
403 ClientToServerMessage message;
404 memset(&message, 0, sizeof(message));
405 message.type = ClientToServerMessage::kShutdown;
406 message.shutdown.token = shutdown_token;
407 ServerToClientMessage response;
408 SendToCrashHandlerServer(pipe_name_,
409 reinterpret_cast<ClientToServerMessage&>(message),
410 &response);
411 }
412
413 for (auto& handle : thread_handles)
414 WaitForSingleObject(handle.get(), INFINITE);
415
416 // Deleting ClientData does a blocking wait until the threadpool executions
417 // have terminated when unregistering them.
418 {
419 base::AutoLock lock(clients_lock_);
420 for (auto* client : clients_)
421 delete client;
422 clients_.clear();
423 }
424 }
425
426 void ExceptionHandlerServer::Stop() {
427 // Post a null key (third argument) to trigger shutdown.
428 PostQueuedCompletionStatus(port_.get(), 0, 0, nullptr);
429 }
430
431 // This function must be called with service_context.pipe() already connected to
432 // a client pipe. It exchanges data with the client and adds a ClientData record
433 // to service_context->clients().
434 //
435 // static
436 bool ExceptionHandlerServer::ServiceClientConnection(
437 const internal::PipeServiceContext& service_context) {
438 ClientToServerMessage message;
439
440 if (!LoggingReadFile(service_context.pipe(), &message, sizeof(message)))
441 return false;
442
443 switch (message.type) {
444 case ClientToServerMessage::kShutdown: {
445 if (message.shutdown.token != service_context.shutdown_token()) {
446 LOG(ERROR) << "forged shutdown request, got: "
447 << message.shutdown.token;
448 return false;
449 }
450 ServerToClientMessage shutdown_response = {};
451 LoggingWriteFile(service_context.pipe(),
452 &shutdown_response,
453 sizeof(shutdown_response));
454 return true;
455 }
456
457 case ClientToServerMessage::kRegister:
458 // Handled below.
459 break;
460
461 default:
462 LOG(ERROR) << "unhandled message type: " << message.type;
463 return false;
464 }
465
466 if (message.registration.version != RegistrationRequest::kMessageVersion) {
467 LOG(ERROR) << "unexpected version. got: " << message.registration.version
468 << " expecting: " << RegistrationRequest::kMessageVersion;
469 return false;
470 }
471
472 decltype(GetNamedPipeClientProcessId)* get_named_pipe_client_process_id =
473 GetNamedPipeClientProcessIdFunction();
474 if (get_named_pipe_client_process_id) {
475 // GetNamedPipeClientProcessId is only available on Vista+.
476 DWORD real_pid = 0;
477 if (get_named_pipe_client_process_id(service_context.pipe(), &real_pid) &&
478 message.registration.client_process_id != real_pid) {
479 LOG(ERROR) << "forged client pid, real pid: " << real_pid
480 << ", got: " << message.registration.client_process_id;
481 return false;
482 }
483 }
484
485 // We attempt to open the process as us. This is the main case that should
486 // almost always succeed as the server will generally be more privileged. If
487 // we're running as a different user, it may be that we will fail to open
488 // the process, but the client will be able to, so we make a second attempt
489 // having impersonated the client.
490 HANDLE client_process = OpenProcess(
491 kXPProcessAllAccess, false, message.registration.client_process_id);
492 if (!client_process) {
493 if (!ImpersonateNamedPipeClient(service_context.pipe())) {
494 PLOG(ERROR) << "ImpersonateNamedPipeClient";
495 return false;
496 }
497 client_process = OpenProcess(
498 kXPProcessAllAccess, false, message.registration.client_process_id);
499 PCHECK(RevertToSelf());
500 if (!client_process) {
501 LOG(ERROR) << "failed to open " << message.registration.client_process_id;
502 return false;
503 }
504 }
505
506 internal::ClientData* client;
507 {
508 base::AutoLock lock(*service_context.clients_lock());
509 client = new internal::ClientData(
510 service_context.port(),
511 service_context.delegate(),
512 ScopedKernelHANDLE(client_process),
513 message.registration.crash_exception_information,
514 message.registration.non_crash_exception_information,
515 message.registration.critical_section_address,
516 &OnCrashDumpEvent,
517 &OnNonCrashDumpEvent,
518 &OnProcessEnd);
519 service_context.clients()->insert(client);
520 }
521
522 // Duplicate the events back to the client so they can request a dump.
523 ServerToClientMessage response;
524 response.registration.request_crash_dump_event =
525 HandleToInt(DuplicateEvent(
526 client->process(), client->crash_dump_requested_event()));
527 response.registration.request_non_crash_dump_event =
528 HandleToInt(DuplicateEvent(
529 client->process(), client->non_crash_dump_requested_event()));
530 response.registration.non_crash_dump_completed_event =
531 HandleToInt(DuplicateEvent(
532 client->process(), client->non_crash_dump_completed_event()));
533
534 if (!LoggingWriteFile(service_context.pipe(), &response, sizeof(response)))
535 return false;
536
537 return false;
538 }
539
540 // static
541 DWORD __stdcall ExceptionHandlerServer::PipeServiceProc(void* ctx) {
542 internal::PipeServiceContext* service_context =
543 reinterpret_cast<internal::PipeServiceContext*>(ctx);
544 DCHECK(service_context);
545
546 for (;;) {
547 bool ret = !!ConnectNamedPipe(service_context->pipe(), nullptr);
548 if (!ret && GetLastError() != ERROR_PIPE_CONNECTED) {
549 PLOG(ERROR) << "ConnectNamedPipe";
550 } else if (ServiceClientConnection(*service_context)) {
551 break;
552 }
553 DisconnectNamedPipe(service_context->pipe());
554 }
555
556 delete service_context;
557
558 return 0;
559 }
560
561 // static
562 void __stdcall ExceptionHandlerServer::OnCrashDumpEvent(void* ctx, BOOLEAN) {
563 // This function is executed on the thread pool.
564 internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx);
565 base::AutoLock lock(*client->lock());
566
567 // Capture the exception.
568 unsigned int exit_code = client->delegate()->ExceptionHandlerServerException(
569 client->process(),
570 client->crash_exception_information_address(),
571 client->debug_critical_section_address());
572
573 TerminateProcess(client->process(), exit_code);
574 }
575
576 // static
577 void __stdcall ExceptionHandlerServer::OnNonCrashDumpEvent(void* ctx, BOOLEAN) {
578 // This function is executed on the thread pool.
579 internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx);
580 base::AutoLock lock(*client->lock());
581
582 // Capture the exception.
583 client->delegate()->ExceptionHandlerServerException(
584 client->process(),
585 client->non_crash_exception_information_address(),
586 client->debug_critical_section_address());
587
588 bool result = !!SetEvent(client->non_crash_dump_completed_event());
589 PLOG_IF(ERROR, !result) << "SetEvent";
590 }
591
592 // static
593 void __stdcall ExceptionHandlerServer::OnProcessEnd(void* ctx, BOOLEAN) {
594 // This function is executed on the thread pool.
595 internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx);
596 base::AutoLock lock(*client->lock());
597
598 // Post back to the main thread to have it delete this client record.
599 PostQueuedCompletionStatus(client->port(), 0, ULONG_PTR(client), nullptr);
600 }
601
602 } // namespace crashpad
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698