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

Side by Side 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, 5 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 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 "handler/win/registrar.h"
16
17 #include "base/logging.h"
18
19 namespace crashpad {
20
21 namespace {
22
23 bool DuplicateHandleForProcess(HANDLE handle,
24 HANDLE process,
25 DWORD access,
26 HANDLE* duplicate) {
27 if (DuplicateHandle(
28 GetCurrentProcess(), handle, process, duplicate, access, FALSE, 0) !=
29 TRUE) {
30 PLOG(ERROR) << "DuplicateHandle";
31 return false;
32 }
33 return true;
34 }
35
36 // Implements scoped acquisition and release of a mutex.
37 class AutoMutex {
38 public:
39 // Blocks indefinitely until |mutex| is acquired. |mutex| will be released in
40 // the destructor.
41 explicit AutoMutex(HANDLE mutex) : mutex_(mutex) {
42 DWORD result = WaitForSingleObject(mutex_, INFINITE);
43 if (result == WAIT_FAILED)
44 PLOG(FATAL) << "WaitForSingleObject";
45 CHECK_EQ(WAIT_OBJECT_0, result) << "WaitForSingleObject";
46 }
47
48 ~AutoMutex() { PCHECK(ReleaseMutex(mutex_)) << "ReleaseMutex"; }
49
50 private:
51 HANDLE mutex_;
52
53 DISALLOW_COPY_AND_ASSIGN(AutoMutex);
54 };
55
56 // Helps clean up a `HANDLE` from another process, in case of failure. Use
57 // Receive() to store a `HANDLE`. If ownership is not reclaimed prior to
58 // destruction of this object it will close the `HANDLE`.
59 class ScopedOtherProcessHandle {
60 public:
61 explicit ScopedOtherProcessHandle(HANDLE other_process)
62 : other_process_(other_process), handle_(nullptr) {}
63
64 ~ScopedOtherProcessHandle() {
65 if (!handle_)
66 return;
67
68 if (!DuplicateHandle(other_process_,
69 handle_,
70 nullptr,
71 nullptr,
72 0,
73 false,
74 DUPLICATE_CLOSE_SOURCE))
75 DPLOG(ERROR) << "DuplicateHandle";
76 }
77
78 // Returns a pointer to the internal `HANDLE` value. The caller may store a
79 // `HANDLE` at the returned address to pass ownership of the `HANDLE` to this
80 // instance.
81 HANDLE* Receive() {
82 CHECK(!handle_);
83 return &handle_;
84 }
85
86 // Transfers ownership of the stored `HANDLE` value to the caller.
87 HANDLE Release() {
88 HANDLE temp = handle_;
89 handle_ = nullptr;
90 return temp;
91 }
92
93 private:
94 HANDLE other_process_;
95 HANDLE handle_;
96
97 DISALLOW_COPY_AND_ASSIGN(ScopedOtherProcessHandle);
98 };
99
100 // 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
101 // registered wait `HANDLE`. If ownership is not reclaimed prior to destruction
102 // of this object it will perform a blocking UnregisterWaitEx operation to
103 // cancel the wait.
104 class BlockingUnregisterWait {
105 public:
106 BlockingUnregisterWait() : handle_(nullptr) {}
107
108 ~BlockingUnregisterWait() {
109 if (!handle_)
110 return;
111 // UnregisterWaitEx with INVALID_HANDLE_VALUE blocks until any running
112 // callback has completed.
113 UnregisterWaitEx(handle_, INVALID_HANDLE_VALUE);
114 }
115
116 // Returns a pointer to the internal `HANDLE` value. The caller may store a
117 // registered wait `HANDLE` at the returned address to pass ownership of the
118 // `HANDLE` to this instance.
119 HANDLE* Receive() {
120 CHECK(!handle_);
121 return &handle_;
122 }
123
124 // Transfers ownership of the stored `HANDLE` value to the caller.
125 HANDLE Release() {
126 HANDLE temp = handle_;
127 handle_ = nullptr;
128 return temp;
129 }
130
131 private:
132 HANDLE handle_;
133
134 DISALLOW_COPY_AND_ASSIGN(BlockingUnregisterWait);
135 };
136
137 // Tests for SYNCHRONIZE access to a `HANDLE`.
138 bool HasSynchronizePrivilege(HANDLE handle) {
139 switch (WaitForSingleObject(handle, 0)) {
140 case WAIT_TIMEOUT:
141 case WAIT_OBJECT_0:
142 case WAIT_ABANDONED_0:
143 // We have the right privilege.
144 return true;
145 case WAIT_FAILED:
146 // We are probably missing privileges.
147 DCHECK(false) << "WaitForSingleObject";
148 return false;
149 default:
150 NOTREACHED();
151 return false;
152 }
153 }
154
155 // Creates an auto-reset, default non-signaled event. Returns true if
156 // successful. Logs an error and returns false otherwise.
157 bool LoggedCreateEvent(ScopedKernelHANDLE* destination) {
scottmg 2015/06/29 20:29:05 Logged -> Logging
158 destination->reset(CreateEvent(nullptr, false, false, nullptr));
159 if (destination->is_valid())
160 return true;
161 PLOG(ERROR) << "CreateEvent";
162 return false;
163 }
164
165 // Creates a pair of events that may be used to communicate with a client
166 // process. Creates duplicates, valid in the client process, with suitable
167 // permissions.
168 bool CreateEventPair(HANDLE client_process,
169 ScopedKernelHANDLE* request_report_event,
170 ScopedKernelHANDLE* report_complete_event,
171 ScopedOtherProcessHandle* client_request_report_event,
172 ScopedOtherProcessHandle* client_report_complete_event) {
173 if (!LoggedCreateEvent(request_report_event))
174 return false;
175
176 if (!LoggedCreateEvent(report_complete_event))
177 return false;
178
179 if (!DuplicateHandleForProcess(request_report_event->get(),
180 client_process,
181 EVENT_MODIFY_STATE,
182 client_request_report_event->Receive())) {
183 return false;
184 }
185
186 if (!DuplicateHandleForProcess(report_complete_event->get(),
187 client_process,
188 SYNCHRONIZE,
189 client_report_complete_event->Receive())) {
190 return false;
191 }
192
193 return true;
194 }
195
196 } // namespace
197
198 struct Registrar::Entry {
199 ScopedKernelHANDLE process;
200 ScopedKernelHANDLE request_report_event;
201 ScopedKernelHANDLE report_complete_event;
202 HANDLE request_report_event_wait_handle;
203 HANDLE process_exit_wait_handle;
204 Registrar* registrar;
205 };
206
207 Registrar::Registrar(scoped_ptr<Delegate> delegate)
208 : delegate_(delegate.Pass()), mutex_() {
209 mutex_.reset(CreateMutex(nullptr, false, nullptr));
210 PCHECK(mutex_.is_valid()) << "CreateMutex";
211 }
212
213 Registrar::~Registrar() {
214 // Unregister and destroy all of the client entries.
215 while (true) {
216 scoped_ptr<Entry> entry;
217 {
218 AutoMutex lock(mutex_.get());
219 if (entries_.empty())
220 break;
221 entry.reset(entries_.back());
222 entries_.pop_back();
223 }
224
225 // These calls will block until running callbacks (if any) complete.
226 // ProcessExitCallback will acquire |mutex_| so it is essential that we do
227 // not hold it now.
228
229 // Since we have already removed |entry| from |entries_| a concurrent
230 // OnProcessExit would return immediately.
231 UnregisterWaitEx(entry->process_exit_wait_handle, INVALID_HANDLE_VALUE);
232
233 // The ReportRequestCallback, on the other hand, may still trigger a report now
scottmg 2015/06/29 20:29:05 80 col
234 // using |delegate_|.
235 UnregisterWaitEx(entry->request_report_event_wait_handle,
236 INVALID_HANDLE_VALUE);
237 }
238 }
239
240 bool Registrar::RegisterProcess(ScopedKernelHANDLE process,
241 HANDLE* request_report_event,
242 HANDLE* report_complete_event) {
243 // Verify that we have SYNCHRONIZE privilege. Otherwise
244 // RegisterWaitForSingleObject crashes!
245 DCHECK(HasSynchronizePrivilege(process.get()));
246
247 scoped_ptr<Entry> entry(new Entry);
248 entry->registrar = this;
249 entry->process = process.Pass();
250
251 ScopedOtherProcessHandle client_request_report_event(entry->process.get());
252 ScopedOtherProcessHandle client_report_complete_event(entry->process.get());
253 if (!CreateEventPair(entry->process.get(),
254 &entry->request_report_event,
255 &entry->report_complete_event,
256 &client_request_report_event,
257 &client_report_complete_event)) {
258 return false;
259 }
260
261
262 BlockingUnregisterWait request_report_event_wait_handle;
263 BlockingUnregisterWait process_exit_wait_handle;
264 if (!RegisterWaits(entry.get(),
265 request_report_event_wait_handle.Receive(),
266 process_exit_wait_handle.Receive())) {
267 return false;
268 }
269
270 // 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
271 // have already executed, but it wouldn't have been able to clean |entry|
272 // since we haven't inserted it yet. If that's the case we will abort. All of
273 // our resources will be automatically cleaned up.
274
275 // This lock must have a shorter scope than |process_exit_wait_handle| to
276 // avoid a deadlock with OnProcessExit().
277 AutoMutex lock(mutex_.get());
278
279 switch(WaitForSingleObject(entry->process.get(), 0)) {
280 case WAIT_FAILED:
281 PLOG(ERROR) << "WaitForSingleObject";
282 return false;
283 case WAIT_OBJECT_0:
284 // The process already exited.
285 return false;
286 case WAIT_TIMEOUT:
287 // The client process has not exited yet, meaning the process exit
288 // callback can be relied upon to clean up |entry|.
289 break;
290 default:
291 NOTREACHED();
292 return false;
293 }
294
295 // Even if the exit happens between here and the insertion, we know the exit
296 // callback is not running because we are holding |mutex_|.
297 entry->process_exit_wait_handle = process_exit_wait_handle.Release();
298 entry->request_report_event_wait_handle =
299 request_report_event_wait_handle.Release();
300 entries_.push_back(entry.release());
301
302 // Return the client's copies of the event handles.
303 *request_report_event = client_request_report_event.Release();
304 *report_complete_event = client_report_complete_event.Release();
305
306 return true;
307 }
308
309 void Registrar::OnRequestReport(Entry* entry) {
310 // This method must not acquire |mutex_|. See OnProcessExit.
311 delegate_->GenerateReportForClient(entry->process.get());
312 if (!SetEvent(entry->report_complete_event.get()))
313 PLOG(ERROR) << "SetEvent";
314 }
315
316 void Registrar::OnProcessExit(Entry* entry) {
317 AutoMutex lock(mutex_.get());
318
319 PointerVector<Entry>::iterator it =
320 std::find(entries_.begin(), entries_.end(), entry);
321
322 // If ~Registrar is running concurrently with ProcessExitCallback it's
323 // possible this entry is already being cleaned up in the destructor. We
324 // return now to allow the destructor (which will block until our return using
325 // UnregisterWaitEx) to finish the job.
326 if (it == entries_.end())
327 return;
328
329 // Blocking call. We are holding |mutex_| as, until this call completes, it's
330 // possible to process a callback via ReportRequestCallback. Therefore,
331 // ReportRequestCallback must never acquire |mutex_|.
332 if (!UnregisterWaitEx((*it)->request_report_event_wait_handle,
333 INVALID_HANDLE_VALUE)) {
334 PLOG(FATAL) << "UnregisterWaitEx";
335 }
336
337 // Non-blocking call, since we are calling from within the callback. We expect
338 // ERROR_IO_PENDING (indicating that there is a running callback - us). Since
339 // the wait was registered with WT_EXECUTEONLYONCE, we know that there will
340 // not be another invocation.
341 if (!UnregisterWaitEx((*it)->process_exit_wait_handle, nullptr)) {
342 if (GetLastError() != ERROR_IO_PENDING)
343 PLOG(FATAL) << "UnregisterWaitEx";
344 }
345
346 delete *it;
347 entries_.erase(it);
348 }
349
350 // static
351 bool Registrar::RegisterWaits(Entry* entry,
352 HANDLE* request_report_event_wait_handle,
353 HANDLE* process_exit_wait_handle) {
354 if (!RegisterWaitForSingleObject(request_report_event_wait_handle,
355 entry->request_report_event.get(),
356 &Registrar::ReportRequestCallback,
357 entry,
358 INFINITE,
359 WT_EXECUTELONGFUNCTION)) {
360 PLOG(ERROR) << "RegisterWaitForSingleObject";
361 return false;
362 }
363
364 // Because a process remains signaled, it is essential to pass
365 // WT_EXECUTEONLYONCE. Otherwise the callback could execute multiple times
366 // before we unregister the wait.
367 if (!RegisterWaitForSingleObject(process_exit_wait_handle,
368 entry->process.get(),
369 &Registrar::ProcessExitCallback,
370 entry,
371 INFINITE,
372 WT_EXECUTEONLYONCE)) {
373 PLOG(ERROR) << "RegisterWaitForSingleObject";
374 return false;
375 }
376
377 return true;
378 }
379
380 // static
381 VOID CALLBACK Registrar::ReportRequestCallback(PVOID lpParameter,
382 BOOLEAN /* TimerOrWaitFired */) {
383 // This method must not acquire |mutex_|. See OnProcessExit.
384
385 // |entry| is valid because, before deleting it (in OnProcessExit or
386 // ~Registrar), we do a blocking UnregisterWaitEx.
387 Entry* entry = reinterpret_cast<Entry*>(lpParameter);
388 entry->registrar->OnRequestReport(entry);
389 }
390
391 // static
392 VOID CALLBACK Registrar::ProcessExitCallback(PVOID lpParameter,
393 BOOLEAN /* TimerOrWaitFired */) {
394 // |entry| is valid because, before deleting it (in ~Registrar), we do a
395 // blocking UnregisterWaitEx. In OnProcessExit we do a non-blocking
396 // UnregisterWaitEx, but since this callback can only be executed once
397 // (WT_EXECUTEONLYONCE) and we are executing it now, we know we're not racing
398 // against it.
399 Entry* entry = reinterpret_cast<Entry*>(lpParameter);
400 entry->registrar->OnProcessExit(entry);
401 }
402
403 } // namespace crashpad
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698