// Copyright (c) 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include "base/at_exit.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback_helpers.h" #include "base/command_line.h" #include "base/environment.h" #include "base/file_version_info.h" #include "base/files/file_path.h" #include "base/logging_win.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/process/process.h" #include "base/run_loop.h" #include "base/sequenced_task_runner.h" #include "base/single_thread_task_runner.h" #include "base/strings/string16.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_piece.h" #include "base/strings/utf_string_conversions.h" #include "base/synchronization/waitable_event.h" #include "base/thread_task_runner_handle.h" #include "base/threading/thread.h" #include "base/time/time.h" #include "base/win/scoped_handle.h" #include "base/win/win_util.h" #include "chrome/chrome_watcher/chrome_watcher_main_api.h" #include "chrome/installer/util/util_constants.h" #include "components/browser_watcher/endsession_watcher_window_win.h" #include "components/browser_watcher/exit_code_watcher_win.h" #include "components/browser_watcher/window_hang_monitor_win.h" #include "third_party/kasko/kasko_features.h" #if BUILDFLAG(ENABLE_KASKO) #include "components/crash/content/app/crashpad.h" #include "syzygy/kasko/api/reporter.h" #endif namespace { // Use the same log facility as Chrome for convenience. // {7FE69228-633E-4f06-80C1-527FEA23E3A7} const GUID kChromeWatcherTraceProviderName = { 0x7fe69228, 0x633e, 0x4f06, { 0x80, 0xc1, 0x52, 0x7f, 0xea, 0x23, 0xe3, 0xa7 } }; // The amount of time we wait around for a WM_ENDSESSION or a process exit. const int kDelayTimeSeconds = 30; // Takes care of monitoring a browser. This class watches for a browser's exit // code, as well as listening for WM_ENDSESSION messages. Events are recorded in // an exit funnel, for reporting the next time Chrome runs. class BrowserMonitor { public: BrowserMonitor(base::RunLoop* run_loop, const base::char16* registry_path); ~BrowserMonitor(); // Initiates the asynchronous monitoring process, returns true on success. // |on_initialized_event| will be signaled immediately before blocking on the // exit of |process|. bool StartWatching(const base::char16* registry_path, base::Process process, base::win::ScopedHandle on_initialized_event); private: // Called from EndSessionWatcherWindow on a end session messages. void OnEndSessionMessage(UINT message, LPARAM lparam); // Blocking function that runs on |background_thread_|. Signals // |on_initialized_event| before waiting for the browser process to exit. void Watch(base::win::ScopedHandle on_initialized_event); // Posted to main thread from Watch when browser exits. void BrowserExited(); browser_watcher::ExitCodeWatcher exit_code_watcher_; browser_watcher::EndSessionWatcherWindow end_session_watcher_window_; // The thread that runs Watch(). base::Thread background_thread_; // Set when the browser has exited, used to stretch the watcher's lifetime // when WM_ENDSESSION occurs before browser exit. base::WaitableEvent browser_exited_; // The run loop for the main thread and its task runner. base::RunLoop* run_loop_; scoped_refptr main_thread_; DISALLOW_COPY_AND_ASSIGN(BrowserMonitor); }; BrowserMonitor::BrowserMonitor(base::RunLoop* run_loop, const base::char16* registry_path) : exit_code_watcher_(registry_path), end_session_watcher_window_( base::Bind(&BrowserMonitor::OnEndSessionMessage, base::Unretained(this))), background_thread_("BrowserWatcherThread"), browser_exited_(true, false), // manual reset, initially non-signalled. run_loop_(run_loop), main_thread_(base::ThreadTaskRunnerHandle::Get()) { } BrowserMonitor::~BrowserMonitor() { } bool BrowserMonitor::StartWatching( const base::char16* registry_path, base::Process process, base::win::ScopedHandle on_initialized_event) { if (!exit_code_watcher_.Initialize(std::move(process))) return false; if (!background_thread_.StartWithOptions( base::Thread::Options(base::MessageLoop::TYPE_IO, 0))) { return false; } if (!background_thread_.task_runner()->PostTask( FROM_HERE, base::Bind(&BrowserMonitor::Watch, base::Unretained(this), base::Passed(&on_initialized_event)))) { background_thread_.Stop(); return false; } return true; } void BrowserMonitor::OnEndSessionMessage(UINT message, LPARAM lparam) { DCHECK_EQ(main_thread_, base::ThreadTaskRunnerHandle::Get()); // If the browser hasn't exited yet, dally for a bit to try and stretch this // process' lifetime to give it some more time to capture the browser exit. browser_exited_.TimedWait(base::TimeDelta::FromSeconds(kDelayTimeSeconds)); run_loop_->Quit(); } void BrowserMonitor::Watch(base::win::ScopedHandle on_initialized_event) { // This needs to run on an IO thread. DCHECK_NE(main_thread_, base::ThreadTaskRunnerHandle::Get()); // Signal our client now that the Kasko reporter is initialized and we have // cleared all of the obstacles that might lead to an early exit. ::SetEvent(on_initialized_event.Get()); on_initialized_event.Close(); exit_code_watcher_.WaitForExit(); // Note that the browser has exited. browser_exited_.Signal(); main_thread_->PostTask(FROM_HERE, base::Bind(&BrowserMonitor::BrowserExited, base::Unretained(this))); } void BrowserMonitor::BrowserExited() { // This runs in the main thread. DCHECK_EQ(main_thread_, base::ThreadTaskRunnerHandle::Get()); // Our background thread has served it's purpose. background_thread_.Stop(); const int exit_code = exit_code_watcher_.exit_code(); if (exit_code >= 0 && exit_code <= 28) { // The browser exited with a well-known exit code, quit this process // immediately. run_loop_->Quit(); } else { // The browser exited abnormally, wait around for a little bit to see // whether this instance will get a logoff message. main_thread_->PostDelayedTask( FROM_HERE, run_loop_->QuitClosure(), base::TimeDelta::FromSeconds(kDelayTimeSeconds)); } } void OnWindowEvent( const base::string16& registry_path, base::Process process, const base::Callback& on_hung_callback, browser_watcher::WindowHangMonitor::WindowEvent window_event) { if (window_event == browser_watcher::WindowHangMonitor::WINDOW_HUNG && !on_hung_callback.is_null()) { on_hung_callback.Run(process); } } #if BUILDFLAG(ENABLE_KASKO) // Helper function for determining the crash server to use. Defaults to the // standard crash server, but can be overridden via an environment variable. // Enables easy integration testing. void GetKaskoCrashServerUrl(base::string16* crash_server) { const char kKaskoCrashServerUrl[] = "KASKO_CRASH_SERVER_URL"; static const wchar_t kDefaultKaskoCrashServerUrl[] = L"https://clients2.google.com/cr/report"; auto env = base::Environment::Create(); std::string env_var; if (env->GetVar(kKaskoCrashServerUrl, &env_var)) { base::UTF8ToWide(env_var.c_str(), env_var.size(), crash_server); } else { *crash_server = kDefaultKaskoCrashServerUrl; } } // Helper function for determining the crash reports directory to use. Defaults // to the browser data directory, but can be overridden via an environment // variable. Enables easy integration testing. void GetKaskoCrashReportsBaseDir(const base::char16* browser_data_directory, base::FilePath* base_dir) { const char kKaskoCrashReportBaseDir[] = "KASKO_CRASH_REPORTS_BASE_DIR"; auto env = base::Environment::Create(); std::string env_var; if (env->GetVar(kKaskoCrashReportBaseDir, &env_var)) { base::string16 wide_env_var; base::UTF8ToWide(env_var.c_str(), env_var.size(), &wide_env_var); *base_dir = base::FilePath(wide_env_var); } else { *base_dir = base::FilePath(browser_data_directory); } } void DumpHungBrowserProcess(DWORD main_thread_id, const base::string16& channel, const base::Process& process) { // Read the Crashpad module annotations for the process. std::vector annotations; crash_reporter::ReadMainModuleAnnotationsForKasko(process, &annotations); // Add a special crash key to distinguish reports generated for a hung // process. annotations.push_back(kasko::api::CrashKey{L"hung-process", L"1"}); std::vector key_buffers; std::vector value_buffers; for (const auto& crash_key : annotations) { key_buffers.push_back(crash_key.name); value_buffers.push_back(crash_key.value); } key_buffers.push_back(nullptr); value_buffers.push_back(nullptr); // Synthesize an exception for the main thread. Populate the record with the // current context of the thread to get the stack trace bucketed on the crash // backend. CONTEXT thread_context = {}; EXCEPTION_RECORD exception_record = {}; exception_record.ExceptionCode = EXCEPTION_ARRAY_BOUNDS_EXCEEDED; EXCEPTION_POINTERS exception_pointers = {&exception_record, &thread_context}; base::win::ScopedHandle main_thread(::OpenThread( THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, FALSE, main_thread_id)); bool have_context = false; if (main_thread.IsValid()) { DWORD suspend_count = ::SuspendThread(main_thread.Get()); const DWORD kSuspendFailed = static_cast(-1); if (suspend_count != kSuspendFailed) { // Best effort capture of the context. thread_context.ContextFlags = CONTEXT_FLOATING_POINT | CONTEXT_SEGMENTS | CONTEXT_INTEGER | CONTEXT_CONTROL; if (::GetThreadContext(main_thread.Get(), &thread_context) == TRUE) have_context = true; ::ResumeThread(main_thread.Get()); } } // TODO(erikwright): Make the dump-type channel-dependent. if (have_context) { kasko::api::SendReportForProcess( process.Handle(), main_thread_id, &exception_pointers, kasko::api::LARGER_DUMP_TYPE, key_buffers.data(), value_buffers.data()); } else { kasko::api::SendReportForProcess(process.Handle(), 0, nullptr, kasko::api::LARGER_DUMP_TYPE, key_buffers.data(), value_buffers.data()); } } void LoggedDeregisterEventSource(HANDLE event_source_handle) { if (!::DeregisterEventSource(event_source_handle)) DPLOG(ERROR) << "DeregisterEventSource"; } void LoggedLocalFree(PSID sid) { if (::LocalFree(sid) != nullptr) DPLOG(ERROR) << "LocalFree"; } void OnCrashReportUpload(void* context, const base::char16* report_id, const base::char16* minidump_path, const base::char16* const* keys, const base::char16* const* values) { // Open the event source. HANDLE event_source_handle = ::RegisterEventSource(NULL, L"Chrome"); if (!event_source_handle) { PLOG(ERROR) << "RegisterEventSource"; return; } // Ensure cleanup on scope exit. base::ScopedClosureRunner deregister_event_source( base::Bind(&LoggedDeregisterEventSource, event_source_handle)); // Get the user's SID for the log record. base::string16 sid_string; PSID sid = nullptr; if (base::win::GetUserSidString(&sid_string)) { if (!sid_string.empty()) { if (!::ConvertStringSidToSid(sid_string.c_str(), &sid)) DPLOG(ERROR) << "ConvertStringSidToSid"; DCHECK(sid); } } // Ensure cleanup on scope exit. base::ScopedClosureRunner free_sid( base::Bind(&LoggedLocalFree, base::Unretained(sid))); // Generate the message. // Note that the format of this message must match the consumer in // chrome/browser/crash_upload_list_win.cc. base::string16 message = L"Crash uploaded. Id=" + base::string16(report_id) + L"."; // Matches Omaha. const int kCrashUploadEventId = 2; // Report the event. const base::char16* strings[] = {message.c_str()}; if (!::ReportEvent(event_source_handle, EVENTLOG_INFORMATION_TYPE, 0, // category kCrashUploadEventId, sid, 1, // count 0, strings, nullptr)) { DPLOG(ERROR); } } #endif // BUILDFLAG(ENABLE_KASKO) } // namespace // The main entry point to the watcher, declared as extern "C" to avoid name // mangling. extern "C" int WatcherMain(const base::char16* registry_path, HANDLE process_handle, DWORD main_thread_id, HANDLE on_initialized_event_handle, const base::char16* browser_data_directory, const base::char16* channel_name) { base::Process process(process_handle); base::win::ScopedHandle on_initialized_event(on_initialized_event_handle); // The exit manager is in charge of calling the dtors of singletons. base::AtExitManager exit_manager; // Initialize the commandline singleton from the environment. base::CommandLine::Init(0, nullptr); logging::LogEventProvider::Initialize(kChromeWatcherTraceProviderName); // Arrange to be shut down as late as possible, as we want to outlive // chrome.exe in order to report its exit status. ::SetProcessShutdownParameters(0x100, SHUTDOWN_NORETRY); base::Callback on_hung_callback; #if BUILDFLAG(ENABLE_KASKO) base::string16 crash_server; GetKaskoCrashServerUrl(&crash_server); base::FilePath crash_reports_base_dir; GetKaskoCrashReportsBaseDir(browser_data_directory, &crash_reports_base_dir); bool launched_kasko = kasko::api::InitializeReporter( GetKaskoEndpoint(process.Pid()).c_str(), crash_server.c_str(), crash_reports_base_dir .Append(L"Crash Reports") .value() .c_str(), crash_reports_base_dir .Append(kPermanentlyFailedReportsSubdir) .value() .c_str(), &OnCrashReportUpload, nullptr); #if BUILDFLAG(ENABLE_KASKO_HANG_REPORTS) // Only activate hang reports for the canary channel. For testing purposes, // Chrome instances with no channels will also report hangs. if (launched_kasko && (base::StringPiece16(channel_name) == L"" || base::StringPiece16(channel_name) == installer::kChromeChannelCanary)) { on_hung_callback = base::Bind(&DumpHungBrowserProcess, main_thread_id, channel_name); } #endif // BUILDFLAG(ENABLE_KASKO_HANG_REPORTS) #endif // BUILDFLAG(ENABLE_KASKO) // Run a UI message loop on the main thread. base::MessageLoop msg_loop(base::MessageLoop::TYPE_UI); msg_loop.set_thread_name("WatcherMainThread"); base::RunLoop run_loop; BrowserMonitor monitor(&run_loop, registry_path); if (!monitor.StartWatching(registry_path, process.Duplicate(), std::move(on_initialized_event))) { return 1; } { // Scoped to force |hang_monitor| destruction before Kasko is shut down. browser_watcher::WindowHangMonitor hang_monitor( base::TimeDelta::FromSeconds(60), base::TimeDelta::FromSeconds(20), base::Bind(&OnWindowEvent, registry_path, base::Passed(process.Duplicate()), on_hung_callback)); hang_monitor.Initialize(process.Duplicate()); run_loop.Run(); } #if BUILDFLAG(ENABLE_KASKO) if (launched_kasko) kasko::api::ShutdownReporter(); #endif // BUILDFLAG(ENABLE_KASKO) // Wind logging down. logging::LogEventProvider::Uninitialize(); return 0; } static_assert( std::is_same::value, "WatcherMain() has wrong type");