Chromium Code Reviews| 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 |