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 <stdint.h>
18 #include <string.h>
19 #include <sys/types.h>
20
21 #include <utility>
22
23 #include "base/logging.h"
24 #include "base/numerics/safe_conversions.h"
25 #include "base/rand_util.h"
26 #include "base/stl_util.h"
27 #include "base/strings/stringprintf.h"
28 #include "base/strings/utf_string_conversions.h"
29 #include "minidump/minidump_file_writer.h"
30 #include "snapshot/crashpad_info_client_options.h"
31 #include "snapshot/win/process_snapshot_win.h"
32 #include "util/file/file_writer.h"
33 #include "util/misc/tri_state.h"
34 #include "util/misc/uuid.h"
35 #include "util/win/get_function.h"
36 #include "util/win/handle.h"
37 #include "util/win/registration_protocol_win.h"
38 #include "util/win/safe_terminate_process.h"
39 #include "util/win/xp_compat.h"
40
41 namespace crashpad {
42
43 namespace {
44
GetNamedPipeClientProcessIdFunction()45 decltype(GetNamedPipeClientProcessId)* GetNamedPipeClientProcessIdFunction() {
46 static const auto get_named_pipe_client_process_id =
47 GET_FUNCTION(L"kernel32.dll", ::GetNamedPipeClientProcessId);
48 return get_named_pipe_client_process_id;
49 }
50
DuplicateEvent(HANDLE process,HANDLE event)51 HANDLE DuplicateEvent(HANDLE process, HANDLE event) {
52 HANDLE handle;
53 if (DuplicateHandle(GetCurrentProcess(),
54 event,
55 process,
56 &handle,
57 SYNCHRONIZE | EVENT_MODIFY_STATE,
58 false,
59 0)) {
60 return handle;
61 }
62 return nullptr;
63 }
64
65 } // namespace
66
67 namespace internal {
68
69 //! \brief Context information for the named pipe handler threads.
70 class PipeServiceContext {
71 public:
PipeServiceContext(HANDLE port,HANDLE pipe,ExceptionHandlerServer::Delegate * delegate,base::Lock * clients_lock,std::set<internal::ClientData * > * clients,uint64_t shutdown_token)72 PipeServiceContext(HANDLE port,
73 HANDLE pipe,
74 ExceptionHandlerServer::Delegate* delegate,
75 base::Lock* clients_lock,
76 std::set<internal::ClientData*>* clients,
77 uint64_t shutdown_token)
78 : port_(port),
79 pipe_(pipe),
80 delegate_(delegate),
81 clients_lock_(clients_lock),
82 clients_(clients),
83 shutdown_token_(shutdown_token) {}
84
port() const85 HANDLE port() const { return port_; }
pipe() const86 HANDLE pipe() const { return pipe_.get(); }
delegate() const87 ExceptionHandlerServer::Delegate* delegate() const { return delegate_; }
clients_lock() const88 base::Lock* clients_lock() const { return clients_lock_; }
clients() const89 std::set<internal::ClientData*>* clients() const { return clients_; }
shutdown_token() const90 uint64_t shutdown_token() const { return shutdown_token_; }
91
92 private:
93 HANDLE port_; // weak
94 ScopedKernelHANDLE pipe_;
95 ExceptionHandlerServer::Delegate* delegate_; // weak
96 base::Lock* clients_lock_; // weak
97 std::set<internal::ClientData*>* clients_; // weak
98 uint64_t shutdown_token_;
99
100 DISALLOW_COPY_AND_ASSIGN(PipeServiceContext);
101 };
102
103 //! \brief The context data for registered threadpool waits.
104 //!
105 //! This object must be created and destroyed on the main thread. Access must be
106 //! guarded by use of the lock() with the exception of the threadpool wait
107 //! variables which are accessed only by the main thread.
108 class ClientData {
109 public:
ClientData(HANDLE port,ExceptionHandlerServer::Delegate * delegate,ScopedKernelHANDLE process,ScopedKernelHANDLE crash_dump_requested_event,ScopedKernelHANDLE non_crash_dump_requested_event,ScopedKernelHANDLE non_crash_dump_completed_event,WinVMAddress crash_exception_information_address,WinVMAddress non_crash_exception_information_address,WinVMAddress debug_critical_section_address,WAITORTIMERCALLBACK crash_dump_request_callback,WAITORTIMERCALLBACK non_crash_dump_request_callback,WAITORTIMERCALLBACK process_end_callback)110 ClientData(HANDLE port,
111 ExceptionHandlerServer::Delegate* delegate,
112 ScopedKernelHANDLE process,
113 ScopedKernelHANDLE crash_dump_requested_event,
114 ScopedKernelHANDLE non_crash_dump_requested_event,
115 ScopedKernelHANDLE non_crash_dump_completed_event,
116 WinVMAddress crash_exception_information_address,
117 WinVMAddress non_crash_exception_information_address,
118 WinVMAddress debug_critical_section_address,
119 WAITORTIMERCALLBACK crash_dump_request_callback,
120 WAITORTIMERCALLBACK non_crash_dump_request_callback,
121 WAITORTIMERCALLBACK process_end_callback)
122 : crash_dump_request_thread_pool_wait_(INVALID_HANDLE_VALUE),
123 non_crash_dump_request_thread_pool_wait_(INVALID_HANDLE_VALUE),
124 process_end_thread_pool_wait_(INVALID_HANDLE_VALUE),
125 lock_(),
126 port_(port),
127 delegate_(delegate),
128 crash_dump_requested_event_(std::move(crash_dump_requested_event)),
129 non_crash_dump_requested_event_(
130 std::move(non_crash_dump_requested_event)),
131 non_crash_dump_completed_event_(
132 std::move(non_crash_dump_completed_event)),
133 process_(std::move(process)),
134 crash_exception_information_address_(
135 crash_exception_information_address),
136 non_crash_exception_information_address_(
137 non_crash_exception_information_address),
138 debug_critical_section_address_(debug_critical_section_address) {
139 RegisterThreadPoolWaits(crash_dump_request_callback,
140 non_crash_dump_request_callback,
141 process_end_callback);
142 }
143
~ClientData()144 ~ClientData() {
145 // It is important that this only access the threadpool waits (it's called
146 // from the main thread) until the waits are unregistered, to ensure that
147 // any outstanding callbacks are complete.
148 UnregisterThreadPoolWaits();
149 }
150
lock()151 base::Lock* lock() { return &lock_; }
port() const152 HANDLE port() const { return port_; }
delegate() const153 ExceptionHandlerServer::Delegate* delegate() const { return delegate_; }
crash_dump_requested_event() const154 HANDLE crash_dump_requested_event() const {
155 return crash_dump_requested_event_.get();
156 }
non_crash_dump_requested_event() const157 HANDLE non_crash_dump_requested_event() const {
158 return non_crash_dump_requested_event_.get();
159 }
non_crash_dump_completed_event() const160 HANDLE non_crash_dump_completed_event() const {
161 return non_crash_dump_completed_event_.get();
162 }
crash_exception_information_address() const163 WinVMAddress crash_exception_information_address() const {
164 return crash_exception_information_address_;
165 }
non_crash_exception_information_address() const166 WinVMAddress non_crash_exception_information_address() const {
167 return non_crash_exception_information_address_;
168 }
debug_critical_section_address() const169 WinVMAddress debug_critical_section_address() const {
170 return debug_critical_section_address_;
171 }
process() const172 HANDLE process() const { return process_.get(); }
173
174 private:
RegisterThreadPoolWaits(WAITORTIMERCALLBACK crash_dump_request_callback,WAITORTIMERCALLBACK non_crash_dump_request_callback,WAITORTIMERCALLBACK process_end_callback)175 void RegisterThreadPoolWaits(
176 WAITORTIMERCALLBACK crash_dump_request_callback,
177 WAITORTIMERCALLBACK non_crash_dump_request_callback,
178 WAITORTIMERCALLBACK process_end_callback) {
179 if (!RegisterWaitForSingleObject(&crash_dump_request_thread_pool_wait_,
180 crash_dump_requested_event_.get(),
181 crash_dump_request_callback,
182 this,
183 INFINITE,
184 WT_EXECUTEDEFAULT)) {
185 LOG(ERROR) << "RegisterWaitForSingleObject crash dump requested";
186 }
187
188 if (!RegisterWaitForSingleObject(&non_crash_dump_request_thread_pool_wait_,
189 non_crash_dump_requested_event_.get(),
190 non_crash_dump_request_callback,
191 this,
192 INFINITE,
193 WT_EXECUTEDEFAULT)) {
194 LOG(ERROR) << "RegisterWaitForSingleObject non-crash dump requested";
195 }
196
197 if (!RegisterWaitForSingleObject(&process_end_thread_pool_wait_,
198 process_.get(),
199 process_end_callback,
200 this,
201 INFINITE,
202 WT_EXECUTEONLYONCE)) {
203 LOG(ERROR) << "RegisterWaitForSingleObject process end";
204 }
205 }
206
207 // This blocks until outstanding calls complete so that we know it's safe to
208 // delete this object. Because of this, it must be executed on the main
209 // thread, not a threadpool thread.
UnregisterThreadPoolWaits()210 void UnregisterThreadPoolWaits() {
211 UnregisterWaitEx(crash_dump_request_thread_pool_wait_,
212 INVALID_HANDLE_VALUE);
213 crash_dump_request_thread_pool_wait_ = INVALID_HANDLE_VALUE;
214 UnregisterWaitEx(non_crash_dump_request_thread_pool_wait_,
215 INVALID_HANDLE_VALUE);
216 non_crash_dump_request_thread_pool_wait_ = INVALID_HANDLE_VALUE;
217 UnregisterWaitEx(process_end_thread_pool_wait_, INVALID_HANDLE_VALUE);
218 process_end_thread_pool_wait_ = INVALID_HANDLE_VALUE;
219 }
220
221 // These are only accessed on the main thread.
222 HANDLE crash_dump_request_thread_pool_wait_;
223 HANDLE non_crash_dump_request_thread_pool_wait_;
224 HANDLE process_end_thread_pool_wait_;
225
226 base::Lock lock_;
227 // Access to these fields must be guarded by lock_.
228 HANDLE port_; // weak
229 ExceptionHandlerServer::Delegate* delegate_; // weak
230 ScopedKernelHANDLE crash_dump_requested_event_;
231 ScopedKernelHANDLE non_crash_dump_requested_event_;
232 ScopedKernelHANDLE non_crash_dump_completed_event_;
233 ScopedKernelHANDLE process_;
234 WinVMAddress crash_exception_information_address_;
235 WinVMAddress non_crash_exception_information_address_;
236 WinVMAddress debug_critical_section_address_;
237
238 DISALLOW_COPY_AND_ASSIGN(ClientData);
239 };
240
241 } // namespace internal
242
~Delegate()243 ExceptionHandlerServer::Delegate::~Delegate() {
244 }
245
ExceptionHandlerServer(bool persistent)246 ExceptionHandlerServer::ExceptionHandlerServer(bool persistent)
247 : pipe_name_(),
248 port_(CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1)),
249 first_pipe_instance_(),
250 clients_lock_(),
251 clients_(),
252 persistent_(persistent) {
253 }
254
~ExceptionHandlerServer()255 ExceptionHandlerServer::~ExceptionHandlerServer() {
256 }
257
SetPipeName(const std::wstring & pipe_name)258 void ExceptionHandlerServer::SetPipeName(const std::wstring& pipe_name) {
259 DCHECK(pipe_name_.empty());
260 DCHECK(!pipe_name.empty());
261
262 pipe_name_ = pipe_name;
263 }
264
InitializeWithInheritedDataForInitialClient(const InitialClientData & initial_client_data,Delegate * delegate)265 void ExceptionHandlerServer::InitializeWithInheritedDataForInitialClient(
266 const InitialClientData& initial_client_data,
267 Delegate* delegate) {
268 DCHECK(pipe_name_.empty());
269 DCHECK(!first_pipe_instance_.is_valid());
270
271 first_pipe_instance_.reset(initial_client_data.first_pipe_instance());
272
273 // TODO(scottmg): Vista+. Might need to pass through or possibly find an Nt*.
274 size_t bytes = sizeof(wchar_t) * _MAX_PATH + sizeof(FILE_NAME_INFO);
275 std::unique_ptr<uint8_t[]> data(new uint8_t[bytes]);
276 if (!GetFileInformationByHandleEx(first_pipe_instance_.get(),
277 FileNameInfo,
278 data.get(),
279 static_cast<DWORD>(bytes))) {
280 PLOG(FATAL) << "GetFileInformationByHandleEx";
281 }
282 FILE_NAME_INFO* file_name_info =
283 reinterpret_cast<FILE_NAME_INFO*>(data.get());
284 pipe_name_ =
285 L"\\\\.\\pipe" + std::wstring(file_name_info->FileName,
286 file_name_info->FileNameLength /
287 sizeof(file_name_info->FileName[0]));
288
289 {
290 base::AutoLock lock(clients_lock_);
291 internal::ClientData* client = new internal::ClientData(
292 port_.get(),
293 delegate,
294 ScopedKernelHANDLE(initial_client_data.client_process()),
295 ScopedKernelHANDLE(initial_client_data.request_crash_dump()),
296 ScopedKernelHANDLE(initial_client_data.request_non_crash_dump()),
297 ScopedKernelHANDLE(initial_client_data.non_crash_dump_completed()),
298 initial_client_data.crash_exception_information(),
299 initial_client_data.non_crash_exception_information(),
300 initial_client_data.debug_critical_section_address(),
301 &OnCrashDumpEvent,
302 &OnNonCrashDumpEvent,
303 &OnProcessEnd);
304 clients_.insert(client);
305 }
306 }
307
Run(Delegate * delegate)308 void ExceptionHandlerServer::Run(Delegate* delegate) {
309 uint64_t shutdown_token = base::RandUint64();
310 ScopedKernelHANDLE thread_handles[kPipeInstances];
311 for (size_t i = 0; i < base::size(thread_handles); ++i) {
312 HANDLE pipe;
313 if (first_pipe_instance_.is_valid()) {
314 pipe = first_pipe_instance_.release();
315 } else {
316 pipe = CreateNamedPipeInstance(pipe_name_, i == 0);
317 PCHECK(pipe != INVALID_HANDLE_VALUE) << "CreateNamedPipe";
318 }
319
320 // Ownership of this object (and the pipe instance) is given to the new
321 // thread. We close the thread handles at the end of the scope. They clean
322 // up the context object and the pipe instance on termination.
323 internal::PipeServiceContext* context =
324 new internal::PipeServiceContext(port_.get(),
325 pipe,
326 delegate,
327 &clients_lock_,
328 &clients_,
329 shutdown_token);
330 thread_handles[i].reset(
331 CreateThread(nullptr, 0, &PipeServiceProc, context, 0, nullptr));
332 PCHECK(thread_handles[i].is_valid()) << "CreateThread";
333 }
334
335 delegate->ExceptionHandlerServerStarted();
336
337 // This is the main loop of the server. Most work is done on the threadpool,
338 // other than process end handling which is posted back to this main thread,
339 // as we must unregister the threadpool waits here.
340 for (;;) {
341 OVERLAPPED* ov = nullptr;
342 ULONG_PTR key = 0;
343 DWORD bytes = 0;
344 GetQueuedCompletionStatus(port_.get(), &bytes, &key, &ov, INFINITE);
345 if (!key) {
346 // Shutting down.
347 break;
348 }
349
350 // Otherwise, this is a request to unregister and destroy the given client.
351 // delete'ing the ClientData blocks in UnregisterWaitEx to ensure all
352 // outstanding threadpool waits are complete. This is important because the
353 // process handle can be signalled *before* the dump request is signalled.
354 internal::ClientData* client = reinterpret_cast<internal::ClientData*>(key);
355 base::AutoLock lock(clients_lock_);
356 clients_.erase(client);
357 delete client;
358 if (!persistent_ && clients_.empty())
359 break;
360 }
361
362 // Signal to the named pipe instances that they should terminate.
363 for (size_t i = 0; i < base::size(thread_handles); ++i) {
364 ClientToServerMessage message;
365 memset(&message, 0, sizeof(message));
366 message.type = ClientToServerMessage::kShutdown;
367 message.shutdown.token = shutdown_token;
368 ServerToClientMessage response;
369 SendToCrashHandlerServer(pipe_name_,
370 reinterpret_cast<ClientToServerMessage&>(message),
371 &response);
372 }
373
374 for (auto& handle : thread_handles)
375 WaitForSingleObject(handle.get(), INFINITE);
376
377 // Deleting ClientData does a blocking wait until the threadpool executions
378 // have terminated when unregistering them.
379 {
380 base::AutoLock lock(clients_lock_);
381 for (auto* client : clients_)
382 delete client;
383 clients_.clear();
384 }
385 }
386
Stop()387 void ExceptionHandlerServer::Stop() {
388 // Post a null key (third argument) to trigger shutdown.
389 PostQueuedCompletionStatus(port_.get(), 0, 0, nullptr);
390 }
391
392 // This function must be called with service_context.pipe() already connected to
393 // a client pipe. It exchanges data with the client and adds a ClientData record
394 // to service_context->clients().
395 //
396 // static
ServiceClientConnection(const internal::PipeServiceContext & service_context)397 bool ExceptionHandlerServer::ServiceClientConnection(
398 const internal::PipeServiceContext& service_context) {
399 ClientToServerMessage message;
400
401 if (!LoggingReadFileExactly(
402 service_context.pipe(), &message, sizeof(message)))
403 return false;
404
405 switch (message.type) {
406 case ClientToServerMessage::kShutdown: {
407 if (message.shutdown.token != service_context.shutdown_token()) {
408 LOG(ERROR) << "forged shutdown request, got: "
409 << message.shutdown.token;
410 return false;
411 }
412 ServerToClientMessage shutdown_response = {};
413 LoggingWriteFile(service_context.pipe(),
414 &shutdown_response,
415 sizeof(shutdown_response));
416 return true;
417 }
418
419 case ClientToServerMessage::kPing: {
420 // No action required, the fact that the message was processed is
421 // sufficient.
422 ServerToClientMessage shutdown_response = {};
423 LoggingWriteFile(service_context.pipe(),
424 &shutdown_response,
425 sizeof(shutdown_response));
426 return false;
427 }
428
429 case ClientToServerMessage::kRegister:
430 // Handled below.
431 break;
432
433 default:
434 LOG(ERROR) << "unhandled message type: " << message.type;
435 return false;
436 }
437
438 if (message.registration.version != RegistrationRequest::kMessageVersion) {
439 LOG(ERROR) << "unexpected version. got: " << message.registration.version
440 << " expecting: " << RegistrationRequest::kMessageVersion;
441 return false;
442 }
443
444 decltype(GetNamedPipeClientProcessId)* get_named_pipe_client_process_id =
445 GetNamedPipeClientProcessIdFunction();
446 if (get_named_pipe_client_process_id) {
447 // GetNamedPipeClientProcessId is only available on Vista+.
448 DWORD real_pid = 0;
449 if (get_named_pipe_client_process_id(service_context.pipe(), &real_pid) &&
450 message.registration.client_process_id != real_pid) {
451 LOG(ERROR) << "forged client pid, real pid: " << real_pid
452 << ", got: " << message.registration.client_process_id;
453 return false;
454 }
455 }
456
457 // We attempt to open the process as us. This is the main case that should
458 // almost always succeed as the server will generally be more privileged. If
459 // we're running as a different user, it may be that we will fail to open
460 // the process, but the client will be able to, so we make a second attempt
461 // having impersonated the client.
462 HANDLE client_process = OpenProcess(
463 kXPProcessAllAccess, false, message.registration.client_process_id);
464 if (!client_process) {
465 if (!ImpersonateNamedPipeClient(service_context.pipe())) {
466 PLOG(ERROR) << "ImpersonateNamedPipeClient";
467 return false;
468 }
469 client_process = OpenProcess(
470 kXPProcessAllAccess, false, message.registration.client_process_id);
471 PCHECK(RevertToSelf());
472 if (!client_process) {
473 LOG(ERROR) << "failed to open " << message.registration.client_process_id;
474 return false;
475 }
476 }
477
478 internal::ClientData* client;
479 {
480 base::AutoLock lock(*service_context.clients_lock());
481 client = new internal::ClientData(
482 service_context.port(),
483 service_context.delegate(),
484 ScopedKernelHANDLE(client_process),
485 ScopedKernelHANDLE(
486 CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
487 ScopedKernelHANDLE(
488 CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
489 ScopedKernelHANDLE(
490 CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
491 message.registration.crash_exception_information,
492 message.registration.non_crash_exception_information,
493 message.registration.critical_section_address,
494 &OnCrashDumpEvent,
495 &OnNonCrashDumpEvent,
496 &OnProcessEnd);
497 service_context.clients()->insert(client);
498 }
499
500 // Duplicate the events back to the client so they can request a dump.
501 ServerToClientMessage response;
502 response.registration.request_crash_dump_event =
503 HandleToInt(DuplicateEvent(
504 client->process(), client->crash_dump_requested_event()));
505 response.registration.request_non_crash_dump_event =
506 HandleToInt(DuplicateEvent(
507 client->process(), client->non_crash_dump_requested_event()));
508 response.registration.non_crash_dump_completed_event =
509 HandleToInt(DuplicateEvent(
510 client->process(), client->non_crash_dump_completed_event()));
511
512 if (!LoggingWriteFile(service_context.pipe(), &response, sizeof(response)))
513 return false;
514
515 return false;
516 }
517
518 // static
PipeServiceProc(void * ctx)519 DWORD __stdcall ExceptionHandlerServer::PipeServiceProc(void* ctx) {
520 internal::PipeServiceContext* service_context =
521 reinterpret_cast<internal::PipeServiceContext*>(ctx);
522 DCHECK(service_context);
523
524 for (;;) {
525 bool ret = !!ConnectNamedPipe(service_context->pipe(), nullptr);
526 if (!ret && GetLastError() != ERROR_PIPE_CONNECTED) {
527 PLOG(ERROR) << "ConnectNamedPipe";
528 } else if (ServiceClientConnection(*service_context)) {
529 break;
530 }
531 DisconnectNamedPipe(service_context->pipe());
532 }
533
534 delete service_context;
535
536 return 0;
537 }
538
539 // static
OnCrashDumpEvent(void * ctx,BOOLEAN)540 void __stdcall ExceptionHandlerServer::OnCrashDumpEvent(void* ctx, BOOLEAN) {
541 // This function is executed on the thread pool.
542 internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx);
543 base::AutoLock lock(*client->lock());
544
545 // Capture the exception.
546 unsigned int exit_code = client->delegate()->ExceptionHandlerServerException(
547 client->process(),
548 client->crash_exception_information_address(),
549 client->debug_critical_section_address());
550
551 SafeTerminateProcess(client->process(), exit_code);
552 }
553
554 // static
OnNonCrashDumpEvent(void * ctx,BOOLEAN)555 void __stdcall ExceptionHandlerServer::OnNonCrashDumpEvent(void* ctx, BOOLEAN) {
556 // This function is executed on the thread pool.
557 internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx);
558 base::AutoLock lock(*client->lock());
559
560 // Capture the exception.
561 client->delegate()->ExceptionHandlerServerException(
562 client->process(),
563 client->non_crash_exception_information_address(),
564 client->debug_critical_section_address());
565
566 bool result = !!SetEvent(client->non_crash_dump_completed_event());
567 PLOG_IF(ERROR, !result) << "SetEvent";
568 }
569
570 // static
OnProcessEnd(void * ctx,BOOLEAN)571 void __stdcall ExceptionHandlerServer::OnProcessEnd(void* ctx, BOOLEAN) {
572 // This function is executed on the thread pool.
573 internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx);
574 base::AutoLock lock(*client->lock());
575
576 // Post back to the main thread to have it delete this client record.
577 PostQueuedCompletionStatus(client->port(), 0, ULONG_PTR(client), nullptr);
578 }
579
580 } // namespace crashpad
581