1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
8 #include "base/at_exit.h"
10 #include "base/bind_helpers.h"
11 #include "base/callback_helpers.h"
12 #include "base/command_line.h"
13 #include "base/file_version_info.h"
14 #include "base/files/file_path.h"
15 #include "base/logging_win.h"
16 #include "base/macros.h"
17 #include "base/memory/ref_counted.h"
18 #include "base/process/process.h"
19 #include "base/run_loop.h"
20 #include "base/sequenced_task_runner.h"
21 #include "base/single_thread_task_runner.h"
22 #include "base/strings/string16.h"
23 #include "base/strings/string_number_conversions.h"
24 #include "base/strings/string_piece.h"
25 #include "base/strings/utf_string_conversions.h"
26 #include "base/synchronization/waitable_event.h"
27 #include "base/template_util.h"
28 #include "base/thread_task_runner_handle.h"
29 #include "base/threading/thread.h"
30 #include "base/time/time.h"
31 #include "base/win/scoped_handle.h"
32 #include "base/win/win_util.h"
34 #include "chrome/chrome_watcher/chrome_watcher_main_api.h"
35 #include "chrome/installer/util/util_constants.h"
36 #include "components/browser_watcher/endsession_watcher_window_win.h"
37 #include "components/browser_watcher/exit_code_watcher_win.h"
38 #include "components/browser_watcher/exit_funnel_win.h"
39 #include "components/browser_watcher/window_hang_monitor_win.h"
42 #include "syzygy/kasko/api/reporter.h"
47 // Use the same log facility as Chrome for convenience.
48 // {7FE69228-633E-4f06-80C1-527FEA23E3A7}
49 const GUID kChromeWatcherTraceProviderName
= {
50 0x7fe69228, 0x633e, 0x4f06,
51 { 0x80, 0xc1, 0x52, 0x7f, 0xea, 0x23, 0xe3, 0xa7 } };
53 // The amount of time we wait around for a WM_ENDSESSION or a process exit.
54 const int kDelayTimeSeconds
= 30;
56 // Takes care of monitoring a browser. This class watches for a browser's exit
57 // code, as well as listening for WM_ENDSESSION messages. Events are recorded in
58 // an exit funnel, for reporting the next time Chrome runs.
59 class BrowserMonitor
{
61 BrowserMonitor(base::RunLoop
* run_loop
, const base::char16
* registry_path
);
64 // Initiates the asynchronous monitoring process, returns true on success.
65 // |on_initialized_event| will be signaled immediately before blocking on the
67 bool StartWatching(const base::char16
* registry_path
,
68 base::Process process
,
69 base::win::ScopedHandle on_initialized_event
);
72 // Called from EndSessionWatcherWindow on a end session messages.
73 void OnEndSessionMessage(UINT message
, LPARAM lparam
);
75 // Blocking function that runs on |background_thread_|. Signals
76 // |on_initialized_event| before waiting for the browser process to exit.
77 void Watch(base::win::ScopedHandle on_initialized_event
);
79 // Posted to main thread from Watch when browser exits.
82 // The funnel used to record events for this browser.
83 browser_watcher::ExitFunnel exit_funnel_
;
85 browser_watcher::ExitCodeWatcher exit_code_watcher_
;
86 browser_watcher::EndSessionWatcherWindow end_session_watcher_window_
;
88 // The thread that runs Watch().
89 base::Thread background_thread_
;
91 // Set when the browser has exited, used to stretch the watcher's lifetime
92 // when WM_ENDSESSION occurs before browser exit.
93 base::WaitableEvent browser_exited_
;
95 // The run loop for the main thread and its task runner.
96 base::RunLoop
* run_loop_
;
97 scoped_refptr
<base::SequencedTaskRunner
> main_thread_
;
99 DISALLOW_COPY_AND_ASSIGN(BrowserMonitor
);
102 BrowserMonitor::BrowserMonitor(base::RunLoop
* run_loop
,
103 const base::char16
* registry_path
)
104 : exit_code_watcher_(registry_path
),
105 end_session_watcher_window_(
106 base::Bind(&BrowserMonitor::OnEndSessionMessage
,
107 base::Unretained(this))),
108 background_thread_("BrowserWatcherThread"),
109 browser_exited_(true, false), // manual reset, initially non-signalled.
111 main_thread_(base::ThreadTaskRunnerHandle::Get()) {
114 BrowserMonitor::~BrowserMonitor() {
117 bool BrowserMonitor::StartWatching(
118 const base::char16
* registry_path
,
119 base::Process process
,
120 base::win::ScopedHandle on_initialized_event
) {
121 if (!exit_code_watcher_
.Initialize(process
.Pass()))
124 if (!exit_funnel_
.Init(registry_path
,
125 exit_code_watcher_
.process().Handle())) {
129 if (!background_thread_
.StartWithOptions(
130 base::Thread::Options(base::MessageLoop::TYPE_IO
, 0))) {
134 if (!background_thread_
.task_runner()->PostTask(
135 FROM_HERE
, base::Bind(&BrowserMonitor::Watch
, base::Unretained(this),
136 base::Passed(on_initialized_event
.Pass())))) {
137 background_thread_
.Stop();
144 void BrowserMonitor::OnEndSessionMessage(UINT message
, LPARAM lparam
) {
145 DCHECK_EQ(main_thread_
, base::ThreadTaskRunnerHandle::Get());
147 if (message
== WM_QUERYENDSESSION
) {
148 exit_funnel_
.RecordEvent(L
"WatcherQueryEndSession");
149 } else if (message
== WM_ENDSESSION
) {
150 exit_funnel_
.RecordEvent(L
"WatcherEndSession");
152 if (lparam
& ENDSESSION_CLOSEAPP
)
153 exit_funnel_
.RecordEvent(L
"ES_CloseApp");
154 if (lparam
& ENDSESSION_CRITICAL
)
155 exit_funnel_
.RecordEvent(L
"ES_Critical");
156 if (lparam
& ENDSESSION_LOGOFF
)
157 exit_funnel_
.RecordEvent(L
"ES_Logoff");
158 const LPARAM kKnownBits
=
159 ENDSESSION_CLOSEAPP
| ENDSESSION_CRITICAL
| ENDSESSION_LOGOFF
;
160 if (lparam
& ~kKnownBits
)
161 exit_funnel_
.RecordEvent(L
"ES_Other");
163 // If the browser hasn't exited yet, dally for a bit to try and stretch this
164 // process' lifetime to give it some more time to capture the browser exit.
165 browser_exited_
.TimedWait(base::TimeDelta::FromSeconds(kDelayTimeSeconds
));
170 void BrowserMonitor::Watch(base::win::ScopedHandle on_initialized_event
) {
171 // This needs to run on an IO thread.
172 DCHECK_NE(main_thread_
, base::ThreadTaskRunnerHandle::Get());
174 // Signal our client now that the Kasko reporter is initialized and we have
175 // cleared all of the obstacles that might lead to an early exit.
176 ::SetEvent(on_initialized_event
.Get());
177 on_initialized_event
.Close();
179 exit_code_watcher_
.WaitForExit();
180 exit_funnel_
.RecordEvent(L
"BrowserExit");
182 // Note that the browser has exited.
183 browser_exited_
.Signal();
185 main_thread_
->PostTask(FROM_HERE
,
186 base::Bind(&BrowserMonitor::BrowserExited
, base::Unretained(this)));
189 void BrowserMonitor::BrowserExited() {
190 // This runs in the main thread.
191 DCHECK_EQ(main_thread_
, base::ThreadTaskRunnerHandle::Get());
193 // Our background thread has served it's purpose.
194 background_thread_
.Stop();
196 const int exit_code
= exit_code_watcher_
.exit_code();
197 if (exit_code
>= 0 && exit_code
<= 28) {
198 // The browser exited with a well-known exit code, quit this process
202 // The browser exited abnormally, wait around for a little bit to see
203 // whether this instance will get a logoff message.
204 main_thread_
->PostDelayedTask(
206 run_loop_
->QuitClosure(),
207 base::TimeDelta::FromSeconds(kDelayTimeSeconds
));
212 const base::string16
& registry_path
,
213 base::Process process
,
214 const base::Callback
<void(const base::Process
&)>& on_hung_callback
,
215 browser_watcher::WindowHangMonitor::WindowEvent window_event
) {
216 browser_watcher::ExitFunnel exit_funnel
;
217 if (exit_funnel
.Init(registry_path
.c_str(), process
.Handle())) {
218 switch (window_event
) {
219 case browser_watcher::WindowHangMonitor::WINDOW_NOT_FOUND
:
220 exit_funnel
.RecordEvent(L
"MessageWindowNotFound");
222 case browser_watcher::WindowHangMonitor::WINDOW_HUNG
:
223 exit_funnel
.RecordEvent(L
"MessageWindowHung");
224 if (!on_hung_callback
.is_null())
225 on_hung_callback
.Run(process
);
227 case browser_watcher::WindowHangMonitor::WINDOW_VANISHED
:
228 exit_funnel
.RecordEvent(L
"MessageWindowVanished");
238 void DumpHungBrowserProcess(DWORD main_thread_id
,
239 const base::string16
& channel
,
240 const base::Process
& process
) {
241 // TODO(erikwright): Rather than recreating these crash keys here, it would be
242 // ideal to read them directly from the browser process.
244 // This is looking up the version of chrome_watcher.dll, which is equivalent
245 // for our purposes to chrome.dll.
246 scoped_ptr
<FileVersionInfo
> version_info(
247 CREATE_FILE_VERSION_INFO_FOR_CURRENT_MODULE());
248 using CrashKeyStrings
= std::pair
<base::string16
, base::string16
>;
249 std::vector
<CrashKeyStrings
> crash_key_strings
;
250 if (version_info
.get()) {
251 crash_key_strings
.push_back(
252 CrashKeyStrings(L
"prod", version_info
->product_short_name()));
253 base::string16 version
= version_info
->product_version();
254 if (!version_info
->is_official_build())
255 version
.append(base::ASCIIToUTF16("-devel"));
256 crash_key_strings
.push_back(CrashKeyStrings(L
"ver", version
));
258 // No version info found. Make up the values.
259 crash_key_strings
.push_back(CrashKeyStrings(L
"prod", L
"Chrome"));
260 crash_key_strings
.push_back(CrashKeyStrings(L
"ver", L
"0.0.0.0-devel"));
262 crash_key_strings
.push_back(CrashKeyStrings(L
"channel", channel
));
263 crash_key_strings
.push_back(CrashKeyStrings(L
"plat", L
"Win32"));
264 crash_key_strings
.push_back(CrashKeyStrings(L
"ptype", L
"browser"));
265 crash_key_strings
.push_back(
266 CrashKeyStrings(L
"pid", base::IntToString16(process
.Pid())));
267 crash_key_strings
.push_back(CrashKeyStrings(L
"hung-process", L
"1"));
269 std::vector
<const base::char16
*> key_buffers
;
270 std::vector
<const base::char16
*> value_buffers
;
271 for (auto& strings
: crash_key_strings
) {
272 key_buffers
.push_back(strings
.first
.c_str());
273 value_buffers
.push_back(strings
.second
.c_str());
275 key_buffers
.push_back(nullptr);
276 value_buffers
.push_back(nullptr);
278 // Synthesize an exception for the main thread.
279 CONTEXT thread_context
= {};
280 EXCEPTION_RECORD exception_record
= {};
281 exception_record
.ExceptionCode
= EXCEPTION_ARRAY_BOUNDS_EXCEEDED
;
282 EXCEPTION_POINTERS exception_pointers
= {&exception_record
, &thread_context
};
284 // TODO(erikwright): Make the dump-type channel-dependent.
285 kasko::api::SendReportForProcess(
286 process
.Handle(), main_thread_id
, &exception_pointers
,
287 kasko::api::LARGER_DUMP_TYPE
, key_buffers
.data(), value_buffers
.data());
290 void LoggedDeregisterEventSource(HANDLE event_source_handle
) {
291 if (!::DeregisterEventSource(event_source_handle
))
292 DPLOG(ERROR
) << "DeregisterEventSource";
295 void LoggedLocalFree(PSID sid
) {
296 if (::LocalFree(sid
) != nullptr)
297 DPLOG(ERROR
) << "LocalFree";
300 void OnCrashReportUpload(void* context
,
301 const base::char16
* report_id
,
302 const base::char16
* minidump_path
,
303 const base::char16
* const* keys
,
304 const base::char16
* const* values
) {
305 // Open the event source.
306 HANDLE event_source_handle
= ::RegisterEventSource(NULL
, L
"Chrome");
307 if (!event_source_handle
) {
308 PLOG(ERROR
) << "RegisterEventSource";
311 // Ensure cleanup on scope exit.
312 base::ScopedClosureRunner
deregister_event_source(
313 base::Bind(&LoggedDeregisterEventSource
, event_source_handle
));
315 // Get the user's SID for the log record.
316 base::string16 sid_string
;
318 if (base::win::GetUserSidString(&sid_string
)) {
319 if (!sid_string
.empty()) {
320 if (!::ConvertStringSidToSid(sid_string
.c_str(), &sid
))
321 DPLOG(ERROR
) << "ConvertStringSidToSid";
325 // Ensure cleanup on scope exit.
326 base::ScopedClosureRunner
free_sid(
327 base::Bind(&LoggedLocalFree
, base::Unretained(sid
)));
329 // Generate the message.
330 // Note that the format of this message must match the consumer in
331 // chrome/browser/crash_upload_list_win.cc.
332 base::string16 message
=
333 L
"Crash uploaded. Id=" + base::string16(report_id
) + L
".";
336 const int kCrashUploadEventId
= 2;
339 const base::char16
* strings
[] = {message
.c_str()};
340 if (!::ReportEvent(event_source_handle
, EVENTLOG_INFORMATION_TYPE
,
342 kCrashUploadEventId
, sid
,
344 0, strings
, nullptr)) {
348 // TODO(erikwright): Copy minidump to some "last dump" location?
355 // The main entry point to the watcher, declared as extern "C" to avoid name
357 extern "C" int WatcherMain(const base::char16
* registry_path
,
358 HANDLE process_handle
,
359 DWORD main_thread_id
,
360 HANDLE on_initialized_event_handle
,
361 const base::char16
* browser_data_directory
,
362 const base::char16
* message_window_name
,
363 const base::char16
* channel_name
) {
364 base::Process
process(process_handle
);
365 base::win::ScopedHandle
on_initialized_event(on_initialized_event_handle
);
367 // The exit manager is in charge of calling the dtors of singletons.
368 base::AtExitManager exit_manager
;
369 // Initialize the commandline singleton from the environment.
370 base::CommandLine::Init(0, nullptr);
372 logging::LogEventProvider::Initialize(kChromeWatcherTraceProviderName
);
374 // Arrange to be shut down as late as possible, as we want to outlive
375 // chrome.exe in order to report its exit status.
376 ::SetProcessShutdownParameters(0x100, SHUTDOWN_NORETRY
);
378 base::Callback
<void(const base::Process
&)> on_hung_callback
;
381 bool launched_kasko
= kasko::api::InitializeReporter(
382 GetKaskoEndpoint(process
.Pid()).c_str(),
383 L
"https://clients2.google.com/cr/report",
384 base::FilePath(browser_data_directory
)
385 .Append(L
"Crash Reports")
388 base::FilePath(browser_data_directory
)
389 .Append(kPermanentlyFailedReportsSubdir
)
392 &OnCrashReportUpload
, nullptr);
393 #ifdef KASKO_HANG_REPORTS
394 if (launched_kasko
&&
395 base::StringPiece16(channel_name
) == installer::kChromeChannelCanary
) {
397 base::Bind(&DumpHungBrowserProcess
, main_thread_id
, channel_name
);
399 #endif // KASKO_HANG_REPORTS
402 // Run a UI message loop on the main thread.
403 base::MessageLoop
msg_loop(base::MessageLoop::TYPE_UI
);
404 msg_loop
.set_thread_name("WatcherMainThread");
406 base::RunLoop run_loop
;
407 BrowserMonitor
monitor(&run_loop
, registry_path
);
408 if (!monitor
.StartWatching(registry_path
, process
.Duplicate(),
409 on_initialized_event
.Pass())) {
414 // Scoped to force |hang_monitor| destruction before Kasko is shut down.
415 browser_watcher::WindowHangMonitor
hang_monitor(
416 base::TimeDelta::FromSeconds(60), base::TimeDelta::FromSeconds(20),
417 base::Bind(&OnWindowEvent
, registry_path
,
418 base::Passed(process
.Duplicate()), on_hung_callback
));
419 hang_monitor
.Initialize(process
.Duplicate(), message_window_name
);
426 kasko::api::ShutdownReporter();
429 // Wind logging down.
430 logging::LogEventProvider::Uninitialize();
436 base::is_same
<decltype(&WatcherMain
), ChromeWatcherMainFunction
>::value
,
437 "WatcherMain() has wrong type");