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 |