1 // Copyright 2017 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/session_end_watcher.h"
16
17 #include "base/logging.h"
18 #include "base/scoped_generic.h"
19 #include "util/win/scoped_set_event.h"
20
21 extern "C" {
22 extern IMAGE_DOS_HEADER __ImageBase;
23 } // extern "C"
24
25 namespace crashpad {
26
27 namespace {
28
29 // ScopedWindowClass and ScopedWindow operate on ATOM* and HWND*, respectively,
30 // instead of ATOM and HWND, so that the actual storage can exist as a local
31 // variable or a member variable, and the scoper can be responsible for
32 // releasing things only if the actual storage hasn’t been released and zeroed
33 // already by something else.
34 struct ScopedWindowClassTraits {
InvalidValuecrashpad::__anonba756dcd0111::ScopedWindowClassTraits35 static ATOM* InvalidValue() { return nullptr; }
Freecrashpad::__anonba756dcd0111::ScopedWindowClassTraits36 static void Free(ATOM* window_class) {
37 if (*window_class) {
38 if (!UnregisterClass(MAKEINTATOM(*window_class), 0)) {
39 PLOG(ERROR) << "UnregisterClass";
40 } else {
41 *window_class = 0;
42 }
43 }
44 }
45 };
46 using ScopedWindowClass = base::ScopedGeneric<ATOM*, ScopedWindowClassTraits>;
47
48 struct ScopedWindowTraits {
InvalidValuecrashpad::__anonba756dcd0111::ScopedWindowTraits49 static HWND* InvalidValue() { return nullptr; }
Freecrashpad::__anonba756dcd0111::ScopedWindowTraits50 static void Free(HWND* window) {
51 if (*window) {
52 if (!DestroyWindow(*window)) {
53 PLOG(ERROR) << "DestroyWindow";
54 } else {
55 *window = nullptr;
56 }
57 }
58 }
59 };
60 using ScopedWindow = base::ScopedGeneric<HWND*, ScopedWindowTraits>;
61
62 // GetWindowLongPtr()’s return value doesn’t unambiguously indicate whether it
63 // was successful, because 0 could either represent successful retrieval of the
64 // value 0, or failure. This wrapper is more convenient to use.
GetWindowLongPtrAndSuccess(HWND window,int index,LONG_PTR * value)65 bool GetWindowLongPtrAndSuccess(HWND window, int index, LONG_PTR* value) {
66 SetLastError(ERROR_SUCCESS);
67 *value = GetWindowLongPtr(window, index);
68 return *value || GetLastError() == ERROR_SUCCESS;
69 }
70
71 // SetWindowLongPtr() has the same problem as GetWindowLongPtr(). Use this
72 // wrapper instead.
SetWindowLongPtrAndGetSuccess(HWND window,int index,LONG_PTR value)73 bool SetWindowLongPtrAndGetSuccess(HWND window, int index, LONG_PTR value) {
74 SetLastError(ERROR_SUCCESS);
75 LONG_PTR previous = SetWindowLongPtr(window, index, value);
76 return previous || GetLastError() == ERROR_SUCCESS;
77 }
78
79 } // namespace
80
SessionEndWatcher()81 SessionEndWatcher::SessionEndWatcher()
82 : Thread(),
83 window_(nullptr),
84 started_(nullptr),
85 stopped_(nullptr) {
86 // Set bManualReset for these events so that WaitForStart() and WaitForStop()
87 // can be called multiple times.
88
89 started_.reset(CreateEvent(nullptr, true, false, nullptr));
90 PLOG_IF(ERROR, !started_.get()) << "CreateEvent";
91
92 stopped_.reset(CreateEvent(nullptr, true, false, nullptr));
93 PLOG_IF(ERROR, !stopped_.get()) << "CreateEvent";
94
95 Start();
96 }
97
~SessionEndWatcher()98 SessionEndWatcher::~SessionEndWatcher() {
99 // Tear everything down by posting a WM_CLOSE to the window. This obviously
100 // can’t work until the window has been created, and that happens on a
101 // different thread, so wait for the start event to be signaled first.
102 WaitForStart();
103 if (window_) {
104 if (!PostMessage(window_, WM_CLOSE, 0, 0)) {
105 PLOG(ERROR) << "PostMessage";
106 }
107 }
108
109 Join();
110 DCHECK(!window_);
111 }
112
WaitForStart()113 void SessionEndWatcher::WaitForStart() {
114 if (WaitForSingleObject(started_.get(), INFINITE) != WAIT_OBJECT_0) {
115 PLOG(ERROR) << "WaitForSingleObject";
116 }
117 }
118
WaitForStop()119 void SessionEndWatcher::WaitForStop() {
120 if (WaitForSingleObject(stopped_.get(), INFINITE) != WAIT_OBJECT_0) {
121 PLOG(ERROR) << "WaitForSingleObject";
122 }
123 }
124
ThreadMain()125 void SessionEndWatcher::ThreadMain() {
126 ATOM atom = 0;
127 ScopedWindowClass window_class(&atom);
128 ScopedWindow window(&window_);
129
130 ScopedSetEvent call_set_stop(stopped_.get());
131
132 {
133 ScopedSetEvent call_set_start(started_.get());
134
135 WNDCLASS wndclass = {};
136 wndclass.lpfnWndProc = WindowProc;
137 wndclass.hInstance = reinterpret_cast<HMODULE>(&__ImageBase);
138 wndclass.lpszClassName = L"crashpad_SessionEndWatcher";
139 atom = RegisterClass(&wndclass);
140 if (!atom) {
141 PLOG(ERROR) << "RegisterClass";
142 return;
143 }
144
145 window_ = CreateWindow(MAKEINTATOM(atom), // lpClassName
146 nullptr, // lpWindowName
147 0, // dwStyle
148 0, // x
149 0, // y
150 0, // nWidth
151 0, // nHeight
152 nullptr, // hWndParent
153 nullptr, // hMenu
154 nullptr, // hInstance
155 this); // lpParam
156 if (!window_) {
157 PLOG(ERROR) << "CreateWindow";
158 return;
159 }
160 }
161
162 MSG message;
163 BOOL rv = 0;
164 while (window_ && (rv = GetMessage(&message, window_, 0, 0)) > 0) {
165 TranslateMessage(&message);
166 DispatchMessage(&message);
167 }
168 if (window_ && rv == -1) {
169 PLOG(ERROR) << "GetMessage";
170 return;
171 }
172 }
173
174 // static
WindowProc(HWND window,UINT message,WPARAM w_param,LPARAM l_param)175 LRESULT CALLBACK SessionEndWatcher::WindowProc(HWND window,
176 UINT message,
177 WPARAM w_param,
178 LPARAM l_param) {
179 // Figure out which object this is. A pointer to it is stuffed into the last
180 // parameter of CreateWindow(), which shows up as CREATESTRUCT::lpCreateParams
181 // in a WM_CREATE message. That should be processed before any of the other
182 // messages of interest to this function. Once the object is known, save a
183 // pointer to it in the GWLP_USERDATA slot for later retrieval when processing
184 // other messages.
185 SessionEndWatcher* self;
186 if (!GetWindowLongPtrAndSuccess(
187 window, GWLP_USERDATA, reinterpret_cast<LONG_PTR*>(&self))) {
188 PLOG(ERROR) << "GetWindowLongPtr";
189 }
190 if (!self && message == WM_CREATE) {
191 CREATESTRUCT* create = reinterpret_cast<CREATESTRUCT*>(l_param);
192 self = reinterpret_cast<SessionEndWatcher*>(create->lpCreateParams);
193 if (!SetWindowLongPtrAndGetSuccess(
194 window, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(self))) {
195 PLOG(ERROR) << "SetWindowLongPtr";
196 }
197 }
198
199 if (self) {
200 if (message == WM_ENDSESSION) {
201 // If w_param is false, this WM_ENDSESSION message cancels a previous
202 // WM_QUERYENDSESSION.
203 if (w_param) {
204 self->SessionEnding();
205
206 // If the session is ending, post a close message which will kick off
207 // window destruction and cause the message loop thread to terminate.
208 if (!PostMessage(self->window_, WM_CLOSE, 0, 0)) {
209 PLOG(ERROR) << "PostMessage";
210 }
211 }
212 } else if (message == WM_DESTROY) {
213 // The window is being destroyed. Clear GWLP_USERDATA so that |self| won’t
214 // be found during a subsequent call into this function for this window.
215 // Clear self->window_ too, because it refers to an object that soon won’t
216 // exist. That signals the message loop to stop processing messages.
217 if (!SetWindowLongPtrAndGetSuccess(window, GWLP_USERDATA, 0)) {
218 PLOG(ERROR) << "SetWindowLongPtr";
219 }
220 self->window_ = nullptr;
221 }
222 }
223
224 // If the message is WM_CLOSE, DefWindowProc() will call DestroyWindow(), and
225 // this function will be called again with a WM_DESTROY message.
226 return DefWindowProc(window, message, w_param, l_param);
227 }
228
229 } // namespace crashpad
230