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

Unified Diff: handler/win/registrar.cc

Issue 1213723004: Implement a Windows crash client registrar. (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@child_process
Patch Set: More self-review. Created 5 years, 6 months 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 side-by-side diff with in-line comments
Download patch
Index: handler/win/registrar.cc
diff --git a/handler/win/registrar.cc b/handler/win/registrar.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1ade5649df8927c7166751d57f9a1bd082a699dc
--- /dev/null
+++ b/handler/win/registrar.cc
@@ -0,0 +1,403 @@
+// Copyright 2015 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "handler/win/registrar.h"
+
+#include "base/logging.h"
+
+namespace crashpad {
+
+namespace {
+
+bool DuplicateHandleForProcess(HANDLE handle,
+ HANDLE process,
+ DWORD access,
+ HANDLE* duplicate) {
+ if (DuplicateHandle(
+ GetCurrentProcess(), handle, process, duplicate, access, FALSE, 0) !=
+ TRUE) {
+ PLOG(ERROR) << "DuplicateHandle";
+ return false;
+ }
+ return true;
+}
+
+// Implements scoped acquisition and release of a mutex.
+class AutoMutex {
+ public:
+ // Blocks indefinitely until |mutex| is acquired. |mutex| will be released in
+ // the destructor.
+ explicit AutoMutex(HANDLE mutex) : mutex_(mutex) {
+ DWORD result = WaitForSingleObject(mutex_, INFINITE);
+ if (result == WAIT_FAILED)
+ PLOG(FATAL) << "WaitForSingleObject";
+ CHECK_EQ(WAIT_OBJECT_0, result) << "WaitForSingleObject";
+ }
+
+ ~AutoMutex() { PCHECK(ReleaseMutex(mutex_)) << "ReleaseMutex"; }
+
+ private:
+ HANDLE mutex_;
+
+ DISALLOW_COPY_AND_ASSIGN(AutoMutex);
+};
+
+// Helps clean up a `HANDLE` from another process, in case of failure. Use
+// Receive() to store a `HANDLE`. If ownership is not reclaimed prior to
+// destruction of this object it will close the `HANDLE`.
+class ScopedOtherProcessHandle {
+ public:
+ explicit ScopedOtherProcessHandle(HANDLE other_process)
+ : other_process_(other_process), handle_(nullptr) {}
+
+ ~ScopedOtherProcessHandle() {
+ if (!handle_)
+ return;
+
+ if (!DuplicateHandle(other_process_,
+ handle_,
+ nullptr,
+ nullptr,
+ 0,
+ false,
+ DUPLICATE_CLOSE_SOURCE))
+ DPLOG(ERROR) << "DuplicateHandle";
+ }
+
+ // Returns a pointer to the internal `HANDLE` value. The caller may store a
+ // `HANDLE` at the returned address to pass ownership of the `HANDLE` to this
+ // instance.
+ HANDLE* Receive() {
+ CHECK(!handle_);
+ return &handle_;
+ }
+
+ // Transfers ownership of the stored `HANDLE` value to the caller.
+ HANDLE Release() {
+ HANDLE temp = handle_;
+ handle_ = nullptr;
+ return temp;
+ }
+
+ private:
+ HANDLE other_process_;
+ HANDLE handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedOtherProcessHandle);
+};
+
+// Helps clean up a registered wait handle in case of failure. Use Receive() to store a
scottmg 2015/06/29 20:29:05 80 col
+// registered wait `HANDLE`. If ownership is not reclaimed prior to destruction
+// of this object it will perform a blocking UnregisterWaitEx operation to
+// cancel the wait.
+class BlockingUnregisterWait {
+ public:
+ BlockingUnregisterWait() : handle_(nullptr) {}
+
+ ~BlockingUnregisterWait() {
+ if (!handle_)
+ return;
+ // UnregisterWaitEx with INVALID_HANDLE_VALUE blocks until any running
+ // callback has completed.
+ UnregisterWaitEx(handle_, INVALID_HANDLE_VALUE);
+ }
+
+ // Returns a pointer to the internal `HANDLE` value. The caller may store a
+ // registered wait `HANDLE` at the returned address to pass ownership of the
+ // `HANDLE` to this instance.
+ HANDLE* Receive() {
+ CHECK(!handle_);
+ return &handle_;
+ }
+
+ // Transfers ownership of the stored `HANDLE` value to the caller.
+ HANDLE Release() {
+ HANDLE temp = handle_;
+ handle_ = nullptr;
+ return temp;
+ }
+
+ private:
+ HANDLE handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlockingUnregisterWait);
+};
+
+// Tests for SYNCHRONIZE access to a `HANDLE`.
+bool HasSynchronizePrivilege(HANDLE handle) {
+ switch (WaitForSingleObject(handle, 0)) {
+ case WAIT_TIMEOUT:
+ case WAIT_OBJECT_0:
+ case WAIT_ABANDONED_0:
+ // We have the right privilege.
+ return true;
+ case WAIT_FAILED:
+ // We are probably missing privileges.
+ DCHECK(false) << "WaitForSingleObject";
+ return false;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+// Creates an auto-reset, default non-signaled event. Returns true if
+// successful. Logs an error and returns false otherwise.
+bool LoggedCreateEvent(ScopedKernelHANDLE* destination) {
scottmg 2015/06/29 20:29:05 Logged -> Logging
+ destination->reset(CreateEvent(nullptr, false, false, nullptr));
+ if (destination->is_valid())
+ return true;
+ PLOG(ERROR) << "CreateEvent";
+ return false;
+}
+
+// Creates a pair of events that may be used to communicate with a client
+// process. Creates duplicates, valid in the client process, with suitable
+// permissions.
+bool CreateEventPair(HANDLE client_process,
+ ScopedKernelHANDLE* request_report_event,
+ ScopedKernelHANDLE* report_complete_event,
+ ScopedOtherProcessHandle* client_request_report_event,
+ ScopedOtherProcessHandle* client_report_complete_event) {
+ if (!LoggedCreateEvent(request_report_event))
+ return false;
+
+ if (!LoggedCreateEvent(report_complete_event))
+ return false;
+
+ if (!DuplicateHandleForProcess(request_report_event->get(),
+ client_process,
+ EVENT_MODIFY_STATE,
+ client_request_report_event->Receive())) {
+ return false;
+ }
+
+ if (!DuplicateHandleForProcess(report_complete_event->get(),
+ client_process,
+ SYNCHRONIZE,
+ client_report_complete_event->Receive())) {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+struct Registrar::Entry {
+ ScopedKernelHANDLE process;
+ ScopedKernelHANDLE request_report_event;
+ ScopedKernelHANDLE report_complete_event;
+ HANDLE request_report_event_wait_handle;
+ HANDLE process_exit_wait_handle;
+ Registrar* registrar;
+};
+
+Registrar::Registrar(scoped_ptr<Delegate> delegate)
+ : delegate_(delegate.Pass()), mutex_() {
+ mutex_.reset(CreateMutex(nullptr, false, nullptr));
+ PCHECK(mutex_.is_valid()) << "CreateMutex";
+}
+
+Registrar::~Registrar() {
+ // Unregister and destroy all of the client entries.
+ while (true) {
+ scoped_ptr<Entry> entry;
+ {
+ AutoMutex lock(mutex_.get());
+ if (entries_.empty())
+ break;
+ entry.reset(entries_.back());
+ entries_.pop_back();
+ }
+
+ // These calls will block until running callbacks (if any) complete.
+ // ProcessExitCallback will acquire |mutex_| so it is essential that we do
+ // not hold it now.
+
+ // Since we have already removed |entry| from |entries_| a concurrent
+ // OnProcessExit would return immediately.
+ UnregisterWaitEx(entry->process_exit_wait_handle, INVALID_HANDLE_VALUE);
+
+ // The ReportRequestCallback, on the other hand, may still trigger a report now
scottmg 2015/06/29 20:29:05 80 col
+ // using |delegate_|.
+ UnregisterWaitEx(entry->request_report_event_wait_handle,
+ INVALID_HANDLE_VALUE);
+ }
+}
+
+bool Registrar::RegisterProcess(ScopedKernelHANDLE process,
+ HANDLE* request_report_event,
+ HANDLE* report_complete_event) {
+ // Verify that we have SYNCHRONIZE privilege. Otherwise
+ // RegisterWaitForSingleObject crashes!
+ DCHECK(HasSynchronizePrivilege(process.get()));
+
+ scoped_ptr<Entry> entry(new Entry);
+ entry->registrar = this;
+ entry->process = process.Pass();
+
+ ScopedOtherProcessHandle client_request_report_event(entry->process.get());
+ ScopedOtherProcessHandle client_report_complete_event(entry->process.get());
+ if (!CreateEventPair(entry->process.get(),
+ &entry->request_report_event,
+ &entry->report_complete_event,
+ &client_request_report_event,
+ &client_report_complete_event)) {
+ return false;
+ }
+
+
+ BlockingUnregisterWait request_report_event_wait_handle;
+ BlockingUnregisterWait process_exit_wait_handle;
+ if (!RegisterWaits(entry.get(),
+ request_report_event_wait_handle.Receive(),
+ process_exit_wait_handle.Receive())) {
+ return false;
+ }
+
+ // If the process has already exited the "process exit" callback may very well
scottmg 2015/06/29 20:29:05 The number of cases here worries me. Is it possibl
+ // have already executed, but it wouldn't have been able to clean |entry|
+ // since we haven't inserted it yet. If that's the case we will abort. All of
+ // our resources will be automatically cleaned up.
+
+ // This lock must have a shorter scope than |process_exit_wait_handle| to
+ // avoid a deadlock with OnProcessExit().
+ AutoMutex lock(mutex_.get());
+
+ switch(WaitForSingleObject(entry->process.get(), 0)) {
+ case WAIT_FAILED:
+ PLOG(ERROR) << "WaitForSingleObject";
+ return false;
+ case WAIT_OBJECT_0:
+ // The process already exited.
+ return false;
+ case WAIT_TIMEOUT:
+ // The client process has not exited yet, meaning the process exit
+ // callback can be relied upon to clean up |entry|.
+ break;
+ default:
+ NOTREACHED();
+ return false;
+ }
+
+ // Even if the exit happens between here and the insertion, we know the exit
+ // callback is not running because we are holding |mutex_|.
+ entry->process_exit_wait_handle = process_exit_wait_handle.Release();
+ entry->request_report_event_wait_handle =
+ request_report_event_wait_handle.Release();
+ entries_.push_back(entry.release());
+
+ // Return the client's copies of the event handles.
+ *request_report_event = client_request_report_event.Release();
+ *report_complete_event = client_report_complete_event.Release();
+
+ return true;
+}
+
+void Registrar::OnRequestReport(Entry* entry) {
+ // This method must not acquire |mutex_|. See OnProcessExit.
+ delegate_->GenerateReportForClient(entry->process.get());
+ if (!SetEvent(entry->report_complete_event.get()))
+ PLOG(ERROR) << "SetEvent";
+}
+
+void Registrar::OnProcessExit(Entry* entry) {
+ AutoMutex lock(mutex_.get());
+
+ PointerVector<Entry>::iterator it =
+ std::find(entries_.begin(), entries_.end(), entry);
+
+ // If ~Registrar is running concurrently with ProcessExitCallback it's
+ // possible this entry is already being cleaned up in the destructor. We
+ // return now to allow the destructor (which will block until our return using
+ // UnregisterWaitEx) to finish the job.
+ if (it == entries_.end())
+ return;
+
+ // Blocking call. We are holding |mutex_| as, until this call completes, it's
+ // possible to process a callback via ReportRequestCallback. Therefore,
+ // ReportRequestCallback must never acquire |mutex_|.
+ if (!UnregisterWaitEx((*it)->request_report_event_wait_handle,
+ INVALID_HANDLE_VALUE)) {
+ PLOG(FATAL) << "UnregisterWaitEx";
+ }
+
+ // Non-blocking call, since we are calling from within the callback. We expect
+ // ERROR_IO_PENDING (indicating that there is a running callback - us). Since
+ // the wait was registered with WT_EXECUTEONLYONCE, we know that there will
+ // not be another invocation.
+ if (!UnregisterWaitEx((*it)->process_exit_wait_handle, nullptr)) {
+ if (GetLastError() != ERROR_IO_PENDING)
+ PLOG(FATAL) << "UnregisterWaitEx";
+ }
+
+ delete *it;
+ entries_.erase(it);
+}
+
+// static
+bool Registrar::RegisterWaits(Entry* entry,
+ HANDLE* request_report_event_wait_handle,
+ HANDLE* process_exit_wait_handle) {
+ if (!RegisterWaitForSingleObject(request_report_event_wait_handle,
+ entry->request_report_event.get(),
+ &Registrar::ReportRequestCallback,
+ entry,
+ INFINITE,
+ WT_EXECUTELONGFUNCTION)) {
+ PLOG(ERROR) << "RegisterWaitForSingleObject";
+ return false;
+ }
+
+ // Because a process remains signaled, it is essential to pass
+ // WT_EXECUTEONLYONCE. Otherwise the callback could execute multiple times
+ // before we unregister the wait.
+ if (!RegisterWaitForSingleObject(process_exit_wait_handle,
+ entry->process.get(),
+ &Registrar::ProcessExitCallback,
+ entry,
+ INFINITE,
+ WT_EXECUTEONLYONCE)) {
+ PLOG(ERROR) << "RegisterWaitForSingleObject";
+ return false;
+ }
+
+ return true;
+}
+
+// static
+VOID CALLBACK Registrar::ReportRequestCallback(PVOID lpParameter,
+ BOOLEAN /* TimerOrWaitFired */) {
+ // This method must not acquire |mutex_|. See OnProcessExit.
+
+ // |entry| is valid because, before deleting it (in OnProcessExit or
+ // ~Registrar), we do a blocking UnregisterWaitEx.
+ Entry* entry = reinterpret_cast<Entry*>(lpParameter);
+ entry->registrar->OnRequestReport(entry);
+}
+
+// static
+VOID CALLBACK Registrar::ProcessExitCallback(PVOID lpParameter,
+ BOOLEAN /* TimerOrWaitFired */) {
+ // |entry| is valid because, before deleting it (in ~Registrar), we do a
+ // blocking UnregisterWaitEx. In OnProcessExit we do a non-blocking
+ // UnregisterWaitEx, but since this callback can only be executed once
+ // (WT_EXECUTEONLYONCE) and we are executing it now, we know we're not racing
+ // against it.
+ Entry* entry = reinterpret_cast<Entry*>(lpParameter);
+ entry->registrar->OnProcessExit(entry);
+}
+
+} // namespace crashpad

Powered by Google App Engine
This is Rietveld 408576698