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