mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
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:
parent
4c6f6e52e2
commit
f34ed66b93
@ -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 hasn’t 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, don’t 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 InstallCrashHandler() {
|
||||
struct sigaction sa = {};
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
sa.sa_sigaction = HandleCrashSignal;
|
||||
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() {
|
||||
// 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) {
|
||||
// Don’t 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 that’s close
|
||||
// enough. Note that destroying the TerminateHandler would wait for its thread
|
||||
// to exit, which isn’t 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
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
],
|
||||
},
|
||||
|
@ -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',
|
||||
],
|
||||
},
|
||||
}],
|
||||
|
@ -25,7 +25,7 @@ void ScopedFileHANDLECloseTraits::Free(HANDLE handle) {
|
||||
}
|
||||
|
||||
void ScopedKernelHANDLECloseTraits::Free(HANDLE handle) {
|
||||
PCHECK(CloseHandle(handle));
|
||||
PCHECK(CloseHandle(handle)) << "CloseHandle";
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
243
util/win/session_end_watcher.cc
Normal file
243
util/win/session_end_watcher.cc
Normal 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 hasn’t 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 doesn’t 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
|
||||
// can’t 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| won’t
|
||||
// be found during a subsequent call into this function for this window.
|
||||
// Clear self->window_ too, because it refers to an object that soon won’t
|
||||
// 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
|
79
util/win/session_end_watcher.h
Normal file
79
util/win/session_end_watcher.h
Normal 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_
|
62
util/win/session_end_watcher_test.cc
Normal file
62
util/win/session_end_watcher_test.cc
Normal 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
|
Loading…
x
Reference in New Issue
Block a user