metrics: Record handler lifetime milestone events

It could be useful to put our existing Crashpad.HandlerCrashed metrics
into context by getting a sense of handler starts, clean exits, and
other types of exits.

BUG=crashpad:100

Change-Id: I8982075158ea6d210eb2ddad678302e339a42192
Reviewed-on: https://chromium-review.googlesource.com/444124
Reviewed-by: Scott Graham <scottmg@chromium.org>
This commit is contained in:
Mark Mentovai 2017-02-22 13:39:46 -05:00
parent 4c6f6e52e2
commit f34ed66b93
11 changed files with 601 additions and 69 deletions

View File

@ -24,8 +24,10 @@
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/auto_reset.h"
#include "base/compiler_specific.h"
#include "base/files/file_path.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
@ -64,6 +66,7 @@
#include "util/win/exception_handler_server.h"
#include "util/win/handle.h"
#include "util/win/initial_client_data.h"
#include "util/win/session_end_watcher.h"
#endif // OS_MACOSX
namespace crashpad {
@ -108,11 +111,65 @@ void Usage(const base::FilePath& me) {
ToolSupport::UsageTail(me);
}
// Calls Metrics::HandlerLifetimeMilestone, but only on the first call. This is
// to prevent multiple exit events from inadvertently being recorded, which
// might happen if a crash occurs during destruction in what would otherwise be
// a normal exit, or if a CallMetricsRecordNormalExit object is destroyed after
// something else logs an exit event.
void MetricsRecordExit(Metrics::LifetimeMilestone milestone) {
static bool once = [](Metrics::LifetimeMilestone milestone) {
Metrics::HandlerLifetimeMilestone(milestone);
return true;
}(milestone);
ALLOW_UNUSED_LOCAL(once);
}
// Calls MetricsRecordExit() to record a failure, and returns EXIT_FAILURE for
// the convenience of callers in main() which can simply write “return
// ExitFailure();”.
int ExitFailure() {
MetricsRecordExit(Metrics::LifetimeMilestone::kFailed);
return EXIT_FAILURE;
}
class CallMetricsRecordNormalExit {
public:
CallMetricsRecordNormalExit() {}
~CallMetricsRecordNormalExit() {
MetricsRecordExit(Metrics::LifetimeMilestone::kExitedNormally);
}
private:
DISALLOW_COPY_AND_ASSIGN(CallMetricsRecordNormalExit);
};
#if defined(OS_MACOSX)
struct sigaction g_original_crash_sigaction[NSIG];
void InstallSignalHandler(const std::vector<int>& signals,
void (*handler)(int, siginfo_t*, void*)) {
struct sigaction sa = {};
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handler;
for (int sig : signals) {
int rv = sigaction(sig, &sa, nullptr);
PCHECK(rv == 0) << "sigaction " << sig;
}
}
void RestoreDefaultSignalHandler(int sig) {
struct sigaction sa = {};
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = SIG_DFL;
int rv = sigaction(sig, &sa, nullptr);
DPLOG_IF(ERROR, rv != 0) << "sigaction " << sig;
}
void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) {
MetricsRecordExit(Metrics::LifetimeMilestone::kCrashed);
// Is siginfo->si_code useful? The only interesting values on macOS are 0 (not
// useful, signals generated asynchronously such as by kill() or raise()) and
// small positive numbers (useful, signal generated via a hardware fault). The
@ -144,22 +201,17 @@ void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) {
}
Metrics::HandlerCrashed(metrics_code);
// Restore the previous signal handler.
DCHECK_GT(sig, 0);
DCHECK_LT(static_cast<size_t>(sig), arraysize(g_original_crash_sigaction));
struct sigaction* osa = &g_original_crash_sigaction[sig];
int rv = sigaction(sig, osa, nullptr);
DPLOG_IF(ERROR, rv != 0) << "sigaction " << sig;
RestoreDefaultSignalHandler(sig);
// If the signal was received synchronously resulting from a hardware fault,
// returning from the signal handler will cause the kernel to re-raise it,
// because this handler hasnt done anything to alleviate the condition that
// caused the signal to be raised in the first place. With the old signal
// handler in place (expected to be SIG_DFL), it will cause the same behavior
// to be taken as though this signal handler had never been installed at all
// (expected to be a crash). This is ideal, because the signal is re-raised
// with the same properties and from the same context that initially triggered
// it, providing the best debugging experience.
// caused the signal to be raised in the first place. With the default signal
// handler in place, it will cause the same behavior to be taken as though
// this signal handler had never been installed at all (expected to be a
// crash). This is ideal, because the signal is re-raised with the same
// properties and from the same context that initially triggered it, providing
// the best debugging experience.
if ((sig != SIGILL && sig != SIGFPE && sig != SIGBUS && sig != SIGSEGV) ||
!si_code_valid) {
@ -167,8 +219,7 @@ void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) {
// asynchronously via kill() and raise(), and those arising via hardware
// traps such as int3 (resulting in SIGTRAP but advancing the instruction
// pointer), will not reoccur on their own when returning from the signal
// handler. Re-raise them or call to the previous signal handler as
// appropriate.
// handler. Re-raise them.
//
// Unfortunately, when SIGBUS is received asynchronously via kill(),
// siginfo->si_code makes it appear as though it was actually received via a
@ -181,34 +232,34 @@ void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) {
// very valuable for debugging and are visible to a Mach exception handler.
// Since SIGBUS is normally received synchronously in response to a hardware
// fault, dont sweat the unexpected asynchronous case.
if (osa->sa_handler == SIG_DFL) {
//
// Because this signal handler executes with the signal blocked, this
// raise() cannot immediately deliver the signal. Delivery is deferred
// until this signal handler returns and the signal becomes unblocked. The
// raise() cannot immediately deliver the signal. Delivery is deferred until
// this signal handler returns and the signal becomes unblocked. The
// re-raised signal will appear with the same context as where it was
// initially triggered.
rv = raise(sig);
int rv = raise(sig);
DPLOG_IF(ERROR, rv != 0) << "raise";
} else if (osa->sa_handler != SIG_IGN) {
if (osa->sa_flags & SA_SIGINFO) {
osa->sa_sigaction(sig, siginfo, context);
} else {
osa->sa_handler(sig);
}
}
}
void HandleTerminateSignal(int sig, siginfo_t* siginfo, void* context) {
MetricsRecordExit(Metrics::LifetimeMilestone::kTerminated);
RestoreDefaultSignalHandler(sig);
// Re-raise the signal. See the explanation in HandleCrashSignal(). Note that
// no checks for signals arising from synchronous hardware faults are made
// because termination signals never originate in that way.
int rv = raise(sig);
DPLOG_IF(ERROR, rv != 0) << "raise";
}
void InstallCrashHandler() {
struct sigaction sa = {};
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = HandleCrashSignal;
// These are the core-generating signals from 10.12.3
// xnu-3789.41.3/bsd/sys/signalvar.h sigprop: entries with SA_CORE are in the
// set.
const int kSignals[] = {SIGQUIT,
const int kCrashSignals[] = {SIGQUIT,
SIGILL,
SIGTRAP,
SIGABRT,
@ -217,13 +268,30 @@ void InstallCrashHandler() {
SIGBUS,
SIGSEGV,
SIGSYS};
InstallSignalHandler(
std::vector<int>(&kCrashSignals[0],
&kCrashSignals[arraysize(kCrashSignals)]),
HandleCrashSignal);
for (int sig : kSignals) {
DCHECK_GT(sig, 0);
DCHECK_LT(static_cast<size_t>(sig), arraysize(g_original_crash_sigaction));
int rv = sigaction(sig, &sa, &g_original_crash_sigaction[sig]);
PCHECK(rv == 0) << "sigaction " << sig;
}
// Not a crash handler, but close enough. These are non-core-generating but
// terminating signals from 10.12.3 xnu-3789.41.3/bsd/sys/signalvar.h sigprop:
// entries with SA_KILL but not SA_CORE are in the set. SIGKILL is excluded
// because it is uncatchable.
const int kTerminateSignals[] = {SIGHUP,
SIGINT,
SIGPIPE,
SIGALRM,
SIGTERM,
SIGXCPU,
SIGXFSZ,
SIGVTALRM,
SIGPROF,
SIGUSR1,
SIGUSR2};
InstallSignalHandler(
std::vector<int>(&kTerminateSignals[0],
&kTerminateSignals[arraysize(kTerminateSignals)]),
HandleTerminateSignal);
}
struct ResetSIGTERMTraits {
@ -243,6 +311,9 @@ ExceptionHandlerServer* g_exception_handler_server;
// This signal handler is only operative when being run from launchd.
void HandleSIGTERM(int sig, siginfo_t* siginfo, void* context) {
// Dont call MetricsRecordExit(). This is part of the normal exit path when
// running from launchd.
DCHECK(g_exception_handler_server);
g_exception_handler_server->Stop();
}
@ -252,6 +323,7 @@ void HandleSIGTERM(int sig, siginfo_t* siginfo, void* context) {
LONG(WINAPI* g_original_exception_filter)(EXCEPTION_POINTERS*) = nullptr;
LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) {
MetricsRecordExit(Metrics::LifetimeMilestone::kCrashed);
Metrics::HandlerCrashed(exception_pointers->ExceptionRecord->ExceptionCode);
if (g_original_exception_filter)
@ -260,9 +332,36 @@ LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) {
return EXCEPTION_CONTINUE_SEARCH;
}
// Handles events like Control-C and Control-Break on a console.
BOOL WINAPI ConsoleHandler(DWORD console_event) {
MetricsRecordExit(Metrics::LifetimeMilestone::kTerminated);
return false;
}
// Handles a WM_ENDSESSION message sent when the user session is ending.
class TerminateHandler final : public SessionEndWatcher {
public:
TerminateHandler() : SessionEndWatcher() {}
~TerminateHandler() override {}
private:
// SessionEndWatcher:
void SessionEnding() override {
MetricsRecordExit(Metrics::LifetimeMilestone::kTerminated);
}
DISALLOW_COPY_AND_ASSIGN(TerminateHandler);
};
void InstallCrashHandler() {
g_original_exception_filter =
SetUnhandledExceptionFilter(&UnhandledExceptionHandler);
// These are termination handlers, not crash handlers, but thats close
// enough. Note that destroying the TerminateHandler would wait for its thread
// to exit, which isnt necessary or desirable.
SetConsoleCtrlHandler(ConsoleHandler, true);
static TerminateHandler* terminate_handler = new TerminateHandler();
}
#endif // OS_MACOSX
@ -271,6 +370,7 @@ void InstallCrashHandler() {
int HandlerMain(int argc, char* argv[]) {
InstallCrashHandler();
CallMetricsRecordNormalExit metrics_record_normal_exit;
const base::FilePath argv0(
ToolSupport::CommandLineArgumentToFilePathStringType(argv[0]));
@ -367,7 +467,7 @@ int HandlerMain(int argc, char* argv[]) {
std::string value;
if (!SplitStringFirst(optarg, '=', &key, &value)) {
ToolSupport::UsageHint(me, "--annotation requires KEY=VALUE");
return EXIT_FAILURE;
return ExitFailure();
}
std::string old_value;
if (!MapInsertOrReplace(&options.annotations, key, value, &old_value)) {
@ -386,7 +486,7 @@ int HandlerMain(int argc, char* argv[]) {
options.handshake_fd < 0) {
ToolSupport::UsageHint(me,
"--handshake-fd requires a file descriptor");
return EXIT_FAILURE;
return ExitFailure();
}
break;
}
@ -399,7 +499,7 @@ int HandlerMain(int argc, char* argv[]) {
if (!options.initial_client_data.InitializeFromString(optarg)) {
ToolSupport::UsageHint(
me, "failed to parse --initial-client-data");
return EXIT_FAILURE;
return ExitFailure();
}
break;
}
@ -433,15 +533,17 @@ int HandlerMain(int argc, char* argv[]) {
}
case kOptionHelp: {
Usage(me);
MetricsRecordExit(Metrics::LifetimeMilestone::kExitedEarly);
return EXIT_SUCCESS;
}
case kOptionVersion: {
ToolSupport::Version(me);
MetricsRecordExit(Metrics::LifetimeMilestone::kExitedEarly);
return EXIT_SUCCESS;
}
default: {
ToolSupport::UsageHint(me, nullptr);
return EXIT_FAILURE;
return ExitFailure();
}
}
}
@ -451,34 +553,34 @@ int HandlerMain(int argc, char* argv[]) {
#if defined(OS_MACOSX)
if (options.handshake_fd < 0 && options.mach_service.empty()) {
ToolSupport::UsageHint(me, "--handshake-fd or --mach-service is required");
return EXIT_FAILURE;
return ExitFailure();
}
if (options.handshake_fd >= 0 && !options.mach_service.empty()) {
ToolSupport::UsageHint(
me, "--handshake-fd and --mach-service are incompatible");
return EXIT_FAILURE;
return ExitFailure();
}
#elif defined(OS_WIN)
if (!options.initial_client_data.IsValid() && options.pipe_name.empty()) {
ToolSupport::UsageHint(me,
"--initial-client-data or --pipe-name is required");
return EXIT_FAILURE;
return ExitFailure();
}
if (options.initial_client_data.IsValid() && !options.pipe_name.empty()) {
ToolSupport::UsageHint(
me, "--initial-client-data and --pipe-name are incompatible");
return EXIT_FAILURE;
return ExitFailure();
}
#endif // OS_MACOSX
if (!options.database) {
ToolSupport::UsageHint(me, "--database is required");
return EXIT_FAILURE;
return ExitFailure();
}
if (argc) {
ToolSupport::UsageHint(me, nullptr);
return EXIT_FAILURE;
return ExitFailure();
}
#if defined(OS_MACOSX)
@ -503,7 +605,7 @@ int HandlerMain(int argc, char* argv[]) {
}
if (!receive_right.is_valid()) {
return EXIT_FAILURE;
return ExitFailure();
}
ExceptionHandlerServer exception_handler_server(
@ -520,6 +622,7 @@ int HandlerMain(int argc, char* argv[]) {
// launchd.plist(5).
//
// Set up a SIGTERM handler that will call exception_handler_server.Stop().
// This replaces the HandleTerminateSignal handler for SIGTERM.
struct sigaction sa = {};
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
@ -553,11 +656,13 @@ int HandlerMain(int argc, char* argv[]) {
}
}
Metrics::HandlerLifetimeMilestone(Metrics::LifetimeMilestone::kStarted);
std::unique_ptr<CrashReportDatabase> database(CrashReportDatabase::Initialize(
base::FilePath(ToolSupport::CommandLineArgumentToFilePathStringType(
options.database))));
if (!database) {
return EXIT_FAILURE;
return ExitFailure();
}
// TODO(scottmg): options.rate_limit should be removed when we have a

View File

@ -98,6 +98,14 @@ void Metrics::ExceptionEncountered() {
ExceptionProcessing(ExceptionProcessingState::kStarted);
}
// static
void Metrics::HandlerLifetimeMilestone(LifetimeMilestone milestone) {
UMA_HISTOGRAM_ENUMERATION("Crashpad.HandlerLifetimeMilestone",
static_cast<int32_t>(milestone),
static_cast<int32_t>(LifetimeMilestone::kMaxValue));
}
// static
void Metrics::HandlerCrashed(uint32_t exception_code) {
UMA_HISTOGRAM_SPARSE_SLOWLY(
"Crashpad.HandlerCrash.ExceptionCode." METRICS_OS_NAME,

View File

@ -130,6 +130,34 @@ class Metrics {
//! \brief The exception handler server started capturing an exception.
static void ExceptionEncountered();
//! \brief An important event in a handler process lifetime.
enum class LifetimeMilestone : int32_t {
//! \brief The handler process started.
kStarted = 0,
//! \brief The handler process exited normally and cleanly.
kExitedNormally,
//! \brief The handler process exited early, but was successful in
//! performing some non-default action on user request.
kExitedEarly,
//! \brief The handler process exited with a failure code.
kFailed,
//! \brief The handler process was forcibly terminated.
kTerminated,
//! \brief The handler process crashed.
kCrashed,
//! \brief The number of values in this enumeration; not a valid value.
kMaxValue
};
//! \brief Records a handler start/exit/crash event.
static void HandlerLifetimeMilestone(LifetimeMilestone milestone);
//! \brief The handler process crashed with the given exception code.
//!
//! This is currently only reported on Windows.

View File

@ -14,20 +14,22 @@
#include "util/thread/thread.h"
#include <errno.h>
#include "base/logging.h"
namespace crashpad {
void Thread::Start() {
DCHECK(!platform_thread_);
int rv = pthread_create(&platform_thread_, nullptr, ThreadEntryThunk, this);
PCHECK(0 == rv);
errno = pthread_create(&platform_thread_, nullptr, ThreadEntryThunk, this);
PCHECK(errno == 0) << "pthread_create";
}
void Thread::Join() {
DCHECK(platform_thread_);
int rv = pthread_join(platform_thread_, nullptr);
PCHECK(0 == rv);
errno = pthread_join(platform_thread_, nullptr);
PCHECK(errno == 0) << "pthread_join";
platform_thread_ = 0;
}

View File

@ -22,13 +22,13 @@ void Thread::Start() {
DCHECK(!platform_thread_);
platform_thread_ =
CreateThread(nullptr, 0, ThreadEntryThunk, this, 0, nullptr);
PCHECK(platform_thread_);
PCHECK(platform_thread_) << "CreateThread";
}
void Thread::Join() {
DCHECK(platform_thread_);
DWORD result = WaitForSingleObject(platform_thread_, INFINITE);
PCHECK(WAIT_OBJECT_0 == result);
PCHECK(result == WAIT_OBJECT_0) << "WaitForSingleObject";
platform_thread_ = 0;
}

View File

@ -198,6 +198,8 @@
'win/scoped_local_alloc.h',
'win/scoped_process_suspend.cc',
'win/scoped_process_suspend.h',
'win/session_end_watcher.cc',
'win/session_end_watcher.h',
'win/termination_codes.h',
'win/time.cc',
'win/time.h',
@ -268,6 +270,7 @@
['OS=="win"', {
'link_settings': {
'libraries': [
'-luser32.lib',
'-lwinhttp.lib',
],
},

View File

@ -95,6 +95,7 @@
'win/process_info_test.cc',
'win/registration_protocol_win_test.cc',
'win/scoped_process_suspend_test.cc',
'win/session_end_watcher_test.cc',
'win/time_test.cc',
],
'conditions': [
@ -114,6 +115,7 @@
'-ladvapi32.lib',
'-limagehlp.lib',
'-lrpcrt4.lib',
'-luser32.lib',
],
},
}],

View File

@ -25,7 +25,7 @@ void ScopedFileHANDLECloseTraits::Free(HANDLE handle) {
}
void ScopedKernelHANDLECloseTraits::Free(HANDLE handle) {
PCHECK(CloseHandle(handle));
PCHECK(CloseHandle(handle)) << "CloseHandle";
}
} // namespace internal

View File

@ -0,0 +1,243 @@
// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "util/win/session_end_watcher.h"
#include "base/logging.h"
#include "base/scoped_generic.h"
extern "C" {
extern IMAGE_DOS_HEADER __ImageBase;
} // extern "C"
namespace crashpad {
namespace {
class ScopedSetEvent {
public:
explicit ScopedSetEvent(HANDLE event) : event_(event) {}
~ScopedSetEvent() {
if (!SetEvent(event_)) {
PLOG(ERROR) << "SetEvent";
}
}
private:
HANDLE event_;
DISALLOW_COPY_AND_ASSIGN(ScopedSetEvent);
};
// ScopedWindowClass and ScopedWindow operate on ATOM* and HWND*, respectively,
// instead of ATOM and HWND, so that the actual storage can exist as a local
// variable or a member variable, and the scoper can be responsible for
// releasing things only if the actual storage hasnt been released and zeroed
// already by something else.
struct ScopedWindowClassTraits {
static ATOM* InvalidValue() { return nullptr; }
static void Free(ATOM* window_class) {
if (*window_class) {
if (!UnregisterClass(MAKEINTATOM(*window_class), 0)) {
PLOG(ERROR) << "UnregisterClass";
} else {
*window_class = 0;
}
}
}
};
using ScopedWindowClass = base::ScopedGeneric<ATOM*, ScopedWindowClassTraits>;
struct ScopedWindowTraits {
static HWND* InvalidValue() { return nullptr; }
static void Free(HWND* window) {
if (*window) {
if (!DestroyWindow(*window)) {
PLOG(ERROR) << "DestroyWindow";
} else {
*window = nullptr;
}
}
}
};
using ScopedWindow = base::ScopedGeneric<HWND*, ScopedWindowTraits>;
// GetWindowLongPtr()s return value doesnt unambiguously indicate whether it
// was successful, because 0 could either represent successful retrieval of the
// value 0, or failure. This wrapper is more convenient to use.
bool GetWindowLongPtrAndSuccess(HWND window, int index, LONG_PTR* value) {
SetLastError(ERROR_SUCCESS);
*value = GetWindowLongPtr(window, index);
return *value || GetLastError() == ERROR_SUCCESS;
}
// SetWindowLongPtr() has the same problem as GetWindowLongPtr(). Use this
// wrapper instead.
bool SetWindowLongPtrAndGetSuccess(HWND window, int index, LONG_PTR value) {
SetLastError(ERROR_SUCCESS);
LONG_PTR previous = SetWindowLongPtr(window, index, value);
return previous || GetLastError() == ERROR_SUCCESS;
}
} // namespace
SessionEndWatcher::SessionEndWatcher()
: Thread(),
window_(nullptr),
started_(nullptr),
stopped_(nullptr) {
// Set bManualReset for these events so that WaitForStart() and WaitForStop()
// can be called multiple times.
started_.reset(CreateEvent(nullptr, true, false, nullptr));
PLOG_IF(ERROR, !started_.get()) << "CreateEvent";
stopped_.reset(CreateEvent(nullptr, true, false, nullptr));
PLOG_IF(ERROR, !stopped_.get()) << "CreateEvent";
Start();
}
SessionEndWatcher::~SessionEndWatcher() {
// Tear everything down by posting a WM_CLOSE to the window. This obviously
// cant work until the window has been created, and that happens on a
// different thread, so wait for the start event to be signaled first.
WaitForStart();
if (window_) {
if (!PostMessage(window_, WM_CLOSE, 0, 0)) {
PLOG(ERROR) << "PostMessage";
}
}
Join();
DCHECK(!window_);
}
void SessionEndWatcher::WaitForStart() {
if (WaitForSingleObject(started_.get(), INFINITE) != WAIT_OBJECT_0) {
PLOG(ERROR) << "WaitForSingleObject";
}
}
void SessionEndWatcher::WaitForStop() {
if (WaitForSingleObject(stopped_.get(), INFINITE) != WAIT_OBJECT_0) {
PLOG(ERROR) << "WaitForSingleObject";
}
}
void SessionEndWatcher::ThreadMain() {
ATOM atom = 0;
ScopedWindowClass window_class(&atom);
ScopedWindow window(&window_);
ScopedSetEvent call_set_stop(stopped_.get());
{
ScopedSetEvent call_set_start(started_.get());
WNDCLASS wndclass = {};
wndclass.lpfnWndProc = WindowProc;
wndclass.hInstance = reinterpret_cast<HMODULE>(&__ImageBase);
wndclass.lpszClassName = L"crashpad_SessionEndWatcher";
atom = RegisterClass(&wndclass);
if (!atom) {
PLOG(ERROR) << "RegisterClass";
return;
}
window_ = CreateWindow(MAKEINTATOM(atom), // lpClassName
nullptr, // lpWindowName
0, // dwStyle
0, // x
0, // y
0, // nWidth
0, // nHeight
nullptr, // hWndParent
nullptr, // hMenu
nullptr, // hInstance
this); // lpParam
if (!window_) {
PLOG(ERROR) << "CreateWindow";
return;
}
}
MSG message;
BOOL rv = 0;
while (window_ && (rv = GetMessage(&message, window_, 0, 0)) > 0) {
TranslateMessage(&message);
DispatchMessage(&message);
}
if (window_ && rv == -1) {
PLOG(ERROR) << "GetMessage";
return;
}
}
// static
LRESULT CALLBACK SessionEndWatcher::WindowProc(HWND window,
UINT message,
WPARAM w_param,
LPARAM l_param) {
// Figure out which object this is. A pointer to it is stuffed into the last
// parameter of CreateWindow(), which shows up as CREATESTRUCT::lpCreateParams
// in a WM_CREATE message. That should be processed before any of the other
// messages of interest to this function. Once the object is known, save a
// pointer to it in the GWLP_USERDATA slot for later retrieval when processing
// other messages.
SessionEndWatcher* self;
if (!GetWindowLongPtrAndSuccess(
window, GWLP_USERDATA, reinterpret_cast<LONG_PTR*>(&self))) {
PLOG(ERROR) << "GetWindowLongPtr";
}
if (!self && message == WM_CREATE) {
CREATESTRUCT* create = reinterpret_cast<CREATESTRUCT*>(l_param);
self = reinterpret_cast<SessionEndWatcher*>(create->lpCreateParams);
if (!SetWindowLongPtrAndGetSuccess(
window, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(self))) {
PLOG(ERROR) << "SetWindowLongPtr";
}
}
if (self) {
if (message == WM_ENDSESSION) {
// If w_param is false, this WM_ENDSESSION message cancels a previous
// WM_QUERYENDSESSION.
if (w_param) {
self->SessionEnding();
// If the session is ending, post a close message which will kick off
// window destruction and cause the message loop thread to terminate.
if (!PostMessage(self->window_, WM_CLOSE, 0, 0)) {
PLOG(ERROR) << "PostMessage";
}
}
} else if (message == WM_DESTROY) {
// The window is being destroyed. Clear GWLP_USERDATA so that |self| wont
// be found during a subsequent call into this function for this window.
// Clear self->window_ too, because it refers to an object that soon wont
// exist. That signals the message loop to stop processing messages.
if (!SetWindowLongPtrAndGetSuccess(window, GWLP_USERDATA, 0)) {
PLOG(ERROR) << "SetWindowLongPtr";
}
self->window_ = nullptr;
}
}
// If the message is WM_CLOSE, DefWindowProc() will call DestroyWindow(), and
// this function will be called again with a WM_DESTROY message.
return DefWindowProc(window, message, w_param, l_param);
}
} // namespace crashpad

View File

@ -0,0 +1,79 @@
// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CRASHPAD_UTIL_WIN_SESSION_END_WATCHER_H_
#define CRASHPAD_UTIL_WIN_SESSION_END_WATCHER_H_
#include <windows.h>
#include "base/macros.h"
#include "util/thread/thread.h"
#include "util/win/scoped_handle.h"
namespace crashpad {
//! \brief Creates a hidden window and waits for a `WM_ENDSESSION` message,
//! indicating that the session is ending and the application should
//! terminate.
//!
//! A dedicated thread will be created to run the `GetMessage()`-based message
//! loop required to monitor for this message.
//!
//! Users should subclass this class and receive notifications by implementing
//! the SessionEndWatcherEvent() method.
class SessionEndWatcher : public Thread {
public:
SessionEndWatcher();
//! \note The destructor waits for the thread that runs the message loop to
//! terminate.
~SessionEndWatcher() override;
protected:
// Exposed for testing.
HWND GetWindow() const { return window_; }
// Exposed for testing. Blocks until window_ has been created. May be called
// multiple times if necessary.
void WaitForStart();
// Exposed for testing. Blocks until the message loop ends. May be called
// multiple times if necessary.
void WaitForStop();
private:
// Thread:
void ThreadMain() override;
static LRESULT CALLBACK WindowProc(HWND window,
UINT message,
WPARAM w_param,
LPARAM l_param);
//! \brief A `WM_ENDSESSION` message was received and it indicates that the
//! user session will be ending imminently.
//!
//! This method is called on the thread that runs the message loop.
virtual void SessionEnding() = 0;
HWND window_; // Conceptually strong, but ownership managed in ThreadMain()
ScopedKernelHANDLE started_;
ScopedKernelHANDLE stopped_;
DISALLOW_COPY_AND_ASSIGN(SessionEndWatcher);
};
} // namespace crashpad
#endif // CRASHPAD_UTIL_WIN_SESSION_END_WATCHER_H_

View File

@ -0,0 +1,62 @@
// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "util/win/session_end_watcher.h"
#include "gtest/gtest.h"
#include "test/errors.h"
namespace crashpad {
namespace test {
namespace {
class SessionEndWatcherTest final : public SessionEndWatcher {
public:
SessionEndWatcherTest() : SessionEndWatcher(), called_(false) {}
~SessionEndWatcherTest() override {}
void Run() {
WaitForStart();
HWND window = GetWindow();
ASSERT_TRUE(window);
EXPECT_TRUE(PostMessage(window, WM_ENDSESSION, 1, 0));
WaitForStop();
EXPECT_TRUE(called_);
}
private:
// SessionEndWatcher:
void SessionEnding() override { called_ = true; }
bool called_;
DISALLOW_COPY_AND_ASSIGN(SessionEndWatcherTest);
};
TEST(SessionEndWatcher, SessionEndWatcher) {
SessionEndWatcherTest test;
test.Run();
}
TEST(SessionEndWatcher, DoNothing) {
SessionEndWatcherTest test;
}
} // namespace
} // namespace test
} // namespace crashpad