mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
Add Signals, utilities for signal handling
Use these utilities for signal handling in crashpad_handler BUG=crashpad:30 TEST=crashpad_util_test Signals.* Change-Id: I6c9a1de35c4a81b58d77768c4753bdba5ebea4df Reviewed-on: https://chromium-review.googlesource.com/446917 Commit-Queue: Mark Mentovai <mark@chromium.org> Reviewed-by: Robert Sesek <rsesek@chromium.org>
This commit is contained in:
parent
58aac1bd87
commit
bf2c5155d2
@ -59,6 +59,7 @@
|
||||
#include "util/mach/child_port_handshake.h"
|
||||
#include "util/mach/mach_extensions.h"
|
||||
#include "util/posix/close_stdio.h"
|
||||
#include "util/posix/signals.h"
|
||||
#elif defined(OS_WIN)
|
||||
#include <windows.h>
|
||||
|
||||
@ -145,29 +146,6 @@ class CallMetricsRecordNormalExit {
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
|
||||
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;
|
||||
ALLOW_UNUSED_LOCAL(rv);
|
||||
}
|
||||
|
||||
void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) {
|
||||
MetricsRecordExit(Metrics::LifetimeMilestone::kCrashed);
|
||||
|
||||
@ -202,99 +180,19 @@ void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) {
|
||||
}
|
||||
Metrics::HandlerCrashed(metrics_code);
|
||||
|
||||
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 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) {
|
||||
// Signals received other than via hardware faults, such as those raised
|
||||
// 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.
|
||||
//
|
||||
// Unfortunately, when SIGBUS is received asynchronously via kill(),
|
||||
// siginfo->si_code makes it appear as though it was actually received via a
|
||||
// hardware fault. See 10.12.3 xnu-3789.41.3/bsd/dev/i386/unix_signal.c
|
||||
// sendsig(). An asynchronous SIGBUS will thus cause the handler-crashed
|
||||
// metric to be logged but will not cause the process to terminate. This
|
||||
// isn’t ideal, but asynchronous SIGBUS is an unexpected condition. The
|
||||
// alternative, to re-raise here on any SIGBUS, is a bad idea because it
|
||||
// would lose properties associated with the the original signal, which are
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
// re-raised signal will appear with the same context as where it was
|
||||
// initially triggered.
|
||||
int rv = raise(sig);
|
||||
DPLOG_IF(ERROR, rv != 0) << "raise";
|
||||
ALLOW_UNUSED_LOCAL(rv);
|
||||
}
|
||||
Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr);
|
||||
}
|
||||
|
||||
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";
|
||||
ALLOW_UNUSED_LOCAL(rv);
|
||||
Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr);
|
||||
}
|
||||
|
||||
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 kCrashSignals[] = {SIGQUIT,
|
||||
SIGILL,
|
||||
SIGTRAP,
|
||||
SIGABRT,
|
||||
SIGEMT,
|
||||
SIGFPE,
|
||||
SIGBUS,
|
||||
SIGSEGV,
|
||||
SIGSYS};
|
||||
InstallSignalHandler(
|
||||
std::vector<int>(&kCrashSignals[0],
|
||||
&kCrashSignals[arraysize(kCrashSignals)]),
|
||||
HandleCrashSignal);
|
||||
Signals::InstallCrashHandlers(HandleCrashSignal, 0, nullptr);
|
||||
|
||||
// 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);
|
||||
// Not a crash handler, but close enough.
|
||||
Signals::InstallTerminateHandlers(HandleTerminateSignal, 0, nullptr);
|
||||
}
|
||||
|
||||
struct ResetSIGTERMTraits {
|
||||
@ -617,7 +515,7 @@ int HandlerMain(int argc, char* argv[]) {
|
||||
base::AutoReset<ExceptionHandlerServer*> reset_g_exception_handler_server(
|
||||
&g_exception_handler_server, &exception_handler_server);
|
||||
|
||||
struct sigaction old_sa;
|
||||
struct sigaction old_sigterm_action;
|
||||
ScopedResetSIGTERM reset_sigterm;
|
||||
if (!options.mach_service.empty()) {
|
||||
// When running from launchd, no no-senders notification could ever be
|
||||
@ -627,13 +525,10 @@ int HandlerMain(int argc, char* argv[]) {
|
||||
//
|
||||
// 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;
|
||||
sa.sa_sigaction = HandleSIGTERM;
|
||||
int rv = sigaction(SIGTERM, &sa, &old_sa);
|
||||
PCHECK(rv == 0) << "sigaction";
|
||||
reset_sigterm.reset(&old_sa);
|
||||
if (Signals::InstallHandler(
|
||||
SIGTERM, HandleSIGTERM, 0, &old_sigterm_action)) {
|
||||
reset_sigterm.reset(&old_sigterm_action);
|
||||
}
|
||||
}
|
||||
#elif defined(OS_WIN)
|
||||
// Shut down as late as possible relative to programs we're watching.
|
||||
|
281
util/posix/signals.cc
Normal file
281
util/posix/signals.cc
Normal file
@ -0,0 +1,281 @@
|
||||
// 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/posix/signals.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
namespace {
|
||||
|
||||
// These are the core-generating signals.
|
||||
//
|
||||
// On macOS, these come from 10.12.3 xnu-3789.41.3/bsd/sys/signalvar.h sigprop:
|
||||
// entries with SA_CORE are in the set.
|
||||
//
|
||||
// For Linux, see linux-4.4.52/kernel/signal.c get_signal() and
|
||||
// linux-4.4.52/include/linux/signal.h sig_kernel_coredump(): signals in
|
||||
// SIG_KERNEL_COREDUMP_MASK are in the set.
|
||||
constexpr int kCrashSignals[] = {
|
||||
SIGABRT,
|
||||
SIGBUS,
|
||||
SIGFPE,
|
||||
SIGILL,
|
||||
SIGQUIT,
|
||||
SIGSEGV,
|
||||
SIGSYS,
|
||||
SIGTRAP,
|
||||
#if defined(SIGEMT)
|
||||
SIGEMT,
|
||||
#endif // defined(SIGEMT)
|
||||
#if defined(OS_LINUX)
|
||||
SIGXCPU,
|
||||
SIGXFSZ,
|
||||
#endif // defined(OS_LINUX)
|
||||
};
|
||||
|
||||
// These are the non-core-generating but terminating signals.
|
||||
//
|
||||
// On macOS, these come 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.
|
||||
//
|
||||
// For Linux, see linux-4.4.52/kernel/signal.c get_signal() and
|
||||
// linux-4.4.52/include/linux/signal.h sig_kernel_coredump(),
|
||||
// sig_kernel_ignore(), and sig_kernel_stop(): signals not in
|
||||
// SIG_KERNEL_COREDUMP_MASK, SIG_KERNEL_IGNORE_MASK, or SIG_KERNEL_STOP_MASK are
|
||||
// in the set. SIGKILL is excluded because it is uncatchable (it’s in
|
||||
// SIG_KERNEL_ONLY_MASK and qualifies for sig_kernel_only()). Real-time signals
|
||||
// in the range [SIGRTMIN, SIGRTMAX) also have termination as the default
|
||||
// action, although they are not listed here.
|
||||
constexpr int kTerminateSignals[] = {
|
||||
SIGALRM,
|
||||
SIGHUP,
|
||||
SIGINT,
|
||||
SIGPIPE,
|
||||
SIGPROF,
|
||||
SIGTERM,
|
||||
SIGUSR1,
|
||||
SIGUSR2,
|
||||
SIGVTALRM,
|
||||
#if defined(SIGPWR)
|
||||
SIGPWR,
|
||||
#endif // defined(SIGPWR)
|
||||
#if defined(SIGSTKFLT)
|
||||
SIGSTKFLT,
|
||||
#endif // defined(SIGSTKFLT)
|
||||
#if defined(OS_MACOSX)
|
||||
SIGXCPU,
|
||||
SIGXFSZ,
|
||||
#endif // defined(OS_MACOSX)
|
||||
#if defined(OS_LINUX)
|
||||
SIGIO,
|
||||
#endif // defined(OS_LINUX)
|
||||
};
|
||||
|
||||
bool InstallHandlers(const std::vector<int>& signals,
|
||||
Signals::Handler handler,
|
||||
int flags,
|
||||
Signals::OldActions* old_actions) {
|
||||
bool success = true;
|
||||
for (int sig : signals) {
|
||||
success &= Signals::InstallHandler(
|
||||
sig,
|
||||
handler,
|
||||
flags,
|
||||
old_actions ? old_actions->ActionForSignal(sig) : nullptr);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool IsSignalInSet(int sig, const int* set, size_t set_size) {
|
||||
for (size_t index = 0; index < set_size; ++index) {
|
||||
if (sig == set[index]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct sigaction* Signals::OldActions::ActionForSignal(int sig) {
|
||||
DCHECK_GT(sig, 0);
|
||||
const size_t slot = sig - 1;
|
||||
DCHECK_LT(slot, arraysize(actions_));
|
||||
return &actions_[slot];
|
||||
}
|
||||
|
||||
// static
|
||||
bool Signals::InstallHandler(int sig,
|
||||
Handler handler,
|
||||
int flags,
|
||||
struct sigaction* old_action) {
|
||||
struct sigaction action;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = flags | SA_SIGINFO;
|
||||
action.sa_sigaction = handler;
|
||||
if (sigaction(sig, &action, old_action) != 0) {
|
||||
PLOG(ERROR) << "sigaction " << sig;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
bool Signals::InstallCrashHandlers(Handler handler,
|
||||
int flags,
|
||||
OldActions* old_actions) {
|
||||
return InstallHandlers(
|
||||
std::vector<int>(kCrashSignals, kCrashSignals + arraysize(kCrashSignals)),
|
||||
handler,
|
||||
flags,
|
||||
old_actions);
|
||||
}
|
||||
|
||||
// static
|
||||
bool Signals::InstallTerminateHandlers(Handler handler,
|
||||
int flags,
|
||||
OldActions* old_actions) {
|
||||
return InstallHandlers(
|
||||
std::vector<int>(kTerminateSignals,
|
||||
kTerminateSignals + arraysize(kTerminateSignals)),
|
||||
handler,
|
||||
flags,
|
||||
old_actions);
|
||||
}
|
||||
|
||||
// static
|
||||
bool Signals::WillSignalReraiseAutonomously(const siginfo_t* siginfo) {
|
||||
// Signals received other than via hardware faults, such as those raised
|
||||
// asynchronously via kill() and raise(), and those arising via hardware traps
|
||||
// such as int3 on x86 (resulting in SIGTRAP but advancing the instruction
|
||||
// pointer), will not reoccur on their own when returning from the signal
|
||||
// handler.
|
||||
//
|
||||
// Unfortunately, on macOS, when SIGBUS is received asynchronously via kill(),
|
||||
// siginfo->si_code makes it appear as though it was actually received via a
|
||||
// hardware fault. See 10.12.3 xnu-3789.41.3/bsd/dev/i386/unix_signal.c
|
||||
// sendsig(). Asynchronous SIGBUS will not re-raise itself autonomously, but
|
||||
// this function (acting on information from the kernel) behaves as though it
|
||||
// will. This isn’t ideal, but asynchronous SIGBUS is an unexpected condition.
|
||||
// The alternative, to never treat SIGBUS as autonomously re-raising, is a bad
|
||||
// idea because the explicit re-raise would lose properties associated with
|
||||
// the the original signal, which are 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.
|
||||
//
|
||||
// SIGSEGV on macOS originating from a general protection fault is a more
|
||||
// difficult case: si_code is cleared, making the signal appear asynchronous.
|
||||
// See 10.12.3 xnu-3789.41.3/bsd/dev/i386/unix_signal.c sendsig().
|
||||
const int sig = siginfo->si_signo;
|
||||
const int code = siginfo->si_code;
|
||||
|
||||
// Only these signals can be generated from hardware faults and can re-raise
|
||||
// autonomously.
|
||||
return (sig == SIGBUS ||
|
||||
sig == SIGFPE ||
|
||||
sig == SIGILL ||
|
||||
sig == SIGSEGV) &&
|
||||
|
||||
// The signal was only generated from a hardware fault if the code is a
|
||||
// positive number not matching one of these SI_* constants. See
|
||||
// “Signal Actions” under XRAT “Rationale”/B.2.4 “Signal Concepts” in
|
||||
// POSIX.1-2008, 2016 Edition, regarding si_code. The historical
|
||||
// behavior does not use these SI_* constants and signals generated
|
||||
// asynchronously show up with a code of 0. On macOS, the SI_*
|
||||
// constants are defined but never used, and the historical value of 0
|
||||
// remains. See 10.12.3 xnu-3789.41.3/bsd/kern/kern_sig.c
|
||||
// psignal_internal().
|
||||
(code > 0 &&
|
||||
code != SI_ASYNCIO &&
|
||||
code != SI_MESGQ &&
|
||||
code != SI_QUEUE &&
|
||||
code != SI_TIMER &&
|
||||
code != SI_USER &&
|
||||
#if defined(SI_DETHREAD)
|
||||
code != SI_DETHREAD &&
|
||||
#endif // defiend(SI_DETHREAD)
|
||||
#if defined(SI_KERNEL)
|
||||
// In Linux, SI_KERNEL is used for signals that are raised by the
|
||||
// kernel in software, opposing SI_USER. See
|
||||
// linux-4.4.52/kernel/signal.c __send_signal(). Signals originating
|
||||
// from hardware faults do not use this SI_KERNEL, but a proper signal
|
||||
// code translated in architecture-specific code from the
|
||||
// characteristics of the hardware fault.
|
||||
code != SI_KERNEL &&
|
||||
#endif // defined(SI_KERNEL)
|
||||
#if defined(SI_SIGIO)
|
||||
code != SI_SIGIO &&
|
||||
#endif // defined(SI_SIGIO)
|
||||
#if defined(SI_TKILL)
|
||||
code != SI_TKILL &&
|
||||
#endif // defined(SI_TKILL)
|
||||
true);
|
||||
}
|
||||
|
||||
// static
|
||||
void Signals::RestoreHandlerAndReraiseSignalOnReturn(
|
||||
const siginfo_t* siginfo,
|
||||
const struct sigaction* old_action) {
|
||||
// Failures in this function should _exit(kFailureExitCode). This is a quick
|
||||
// and quiet failure. This function runs in signal handler context, and it’s
|
||||
// difficult to safely be loud from a signal handler.
|
||||
const int kFailureExitCode = 191;
|
||||
|
||||
struct sigaction default_action;
|
||||
sigemptyset(&default_action.sa_mask);
|
||||
default_action.sa_flags = 0;
|
||||
default_action.sa_handler = SIG_DFL;
|
||||
|
||||
const struct sigaction* restore_action =
|
||||
old_action ? old_action : &default_action;
|
||||
|
||||
// Try to restore restore_action. If that fails and restore_action was
|
||||
// old_action, the problem may have been that old_action was bogus, so try to
|
||||
// set the default action.
|
||||
const int sig = siginfo->si_signo;
|
||||
if (sigaction(sig, restore_action, nullptr) != 0 && old_action &&
|
||||
sigaction(sig, &default_action, nullptr) != 0) {
|
||||
_exit(kFailureExitCode);
|
||||
}
|
||||
|
||||
// Explicitly re-raise the signal if it will not re-raise itself. Because
|
||||
// signal handlers normally execute with their signal blocked, this raise()
|
||||
// cannot immediately deliver the signal. Delivery is deferred until the
|
||||
// signal handler returns and the signal becomes unblocked. The re-raised
|
||||
// signal will appear with the same context as where it was initially
|
||||
// triggered.
|
||||
if (!WillSignalReraiseAutonomously(siginfo) && raise(sig) != 0) {
|
||||
_exit(kFailureExitCode);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
bool Signals::IsCrashSignal(int sig) {
|
||||
return IsSignalInSet(sig, kCrashSignals, arraysize(kCrashSignals));
|
||||
}
|
||||
|
||||
// static
|
||||
bool Signals::IsTerminateSignal(int sig) {
|
||||
return IsSignalInSet(sig, kTerminateSignals, arraysize(kTerminateSignals));
|
||||
}
|
||||
|
||||
} // namespace crashpad
|
228
util/posix/signals.h
Normal file
228
util/posix/signals.h
Normal file
@ -0,0 +1,228 @@
|
||||
// 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_POSIX_SIGNALS_H_
|
||||
#define CRASHPAD_UTIL_POSIX_SIGNALS_H_
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include "base/macros.h"
|
||||
|
||||
namespace crashpad {
|
||||
|
||||
//! \brief Utilities for handling POSIX signals.
|
||||
class Signals {
|
||||
public:
|
||||
//! \brief The type used for `struct sigaction::sa_sigaction`.
|
||||
using Handler = void (*)(int, siginfo_t*, void*);
|
||||
|
||||
//! \brief A group of `struct sigaction` structures corresponding to a set
|
||||
//! of signals’ previous actions, addressable by signal number.
|
||||
//!
|
||||
//! This type is used to store previous signal actions when new actions are
|
||||
//! installed in batch by InstallCrashHandlers() or
|
||||
//! InstallTerminateHandlers().
|
||||
//!
|
||||
//! This object is not initialized by any constructor. Its expected initial
|
||||
//! state is to have its contents filled with zeroes. Because signal handlers
|
||||
//! are stateless (there is no “context” parameter), any state must be
|
||||
//! accessed via objects of static storage duration, and it is expected that
|
||||
//! objects of this class will only ever exist with static storage duration,
|
||||
//! which in the absence of a constructor will be zero-initialized as
|
||||
//! expected. In the event that an object of this class must exist with a
|
||||
//! different storage duration, such as automatic or dynamic storage duration,
|
||||
//! it must be explicitly initialized. For example: `OldActions old_actions =
|
||||
//! {};`.
|
||||
class OldActions {
|
||||
public:
|
||||
// DISALLOW_COPY_AND_ASSIGN removes the default constructor, so explicitly
|
||||
// opt for it. This should not result in any static initialization code even
|
||||
// when an object of this class is given static storage duration.
|
||||
OldActions() = default;
|
||||
|
||||
//! \brief Returns a `struct sigaction` structure corresponding to the
|
||||
//! given signal.
|
||||
//!
|
||||
//! \note This method is safe to call from a signal handler.
|
||||
struct sigaction* ActionForSignal(int sig);
|
||||
|
||||
private:
|
||||
// As a small storage optimization, don’t waste any space on a slot for
|
||||
// signal 0, because there is no signal 0.
|
||||
struct sigaction actions_[NSIG - 1];
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(OldActions);
|
||||
};
|
||||
|
||||
//! \brief Installs a new signal handler.
|
||||
//!
|
||||
//! \param[in] sig The signal number to handle.
|
||||
//! \param[in] handler A signal-handling function to execute, used as the
|
||||
//! `struct sigaction::sa_sigaction` field when calling `sigaction()`.
|
||||
//! \param[in] flags Flags to pass to `sigaction()` in the `struct
|
||||
//! sigaction::sa_flags` field. `SA_SIGINFO` will be specified implicitly.
|
||||
//! \param[out] old_action The previous action for the signal, replaced by the
|
||||
//! new action. May be `nullptr` if not needed.
|
||||
//!
|
||||
//! \return `true` on success. `false` on failure with a message logged.
|
||||
//!
|
||||
//! \warning This function may not be called from a signal handler because of
|
||||
//! its use of logging. See RestoreHandlerAndReraiseSignalOnReturn()
|
||||
//! instead.
|
||||
static bool InstallHandler(int sig,
|
||||
Handler handler,
|
||||
int flags,
|
||||
struct sigaction* old_action);
|
||||
|
||||
//! \brief Installs a new signal handler for all signals associated with
|
||||
//! crashes.
|
||||
//!
|
||||
//! Signals associated with crashes are those whose default dispositions
|
||||
//! involve creating a core dump. The precise set of signals involved varies
|
||||
//! between operating systems.
|
||||
//!
|
||||
//! A single signal may either be associated with a crash or with termination
|
||||
//! (see InstallTerminateHandlers()), and perhaps neither, but never both.
|
||||
//!
|
||||
//! \param[in] handler A signal-handling function to execute, used as the
|
||||
//! `struct sigaction::sa_sigaction` field when calling `sigaction()`.
|
||||
//! \param[in] flags Flags to pass to `sigaction()` in the `struct
|
||||
//! sigaction::sa_flags` field. `SA_SIGINFO` will be specified implicitly.
|
||||
//! \param[out] old_actions The previous actions for the signals, replaced by
|
||||
//! the new action. May be `nullptr` if not needed. The same \a
|
||||
//! old_actions object may be used for calls to both this function and
|
||||
//! InstallTerminateHandlers().
|
||||
//!
|
||||
//! \return `true` on success. `false` on failure with a message logged.
|
||||
//!
|
||||
//! \warning This function may not be called from a signal handler because of
|
||||
//! its use of logging. See RestoreHandlerAndReraiseSignalOnReturn()
|
||||
//! instead.
|
||||
static bool InstallCrashHandlers(Handler handler,
|
||||
int flags,
|
||||
OldActions* old_actions);
|
||||
|
||||
//! \brief Installs a new signal handler for all signals associated with
|
||||
//! termination.
|
||||
//!
|
||||
//! Signals associated with termination are those whose default dispositions
|
||||
//! involve terminating the process without creating a core dump. The precise
|
||||
//! set of signals involved varies between operating systems.
|
||||
//!
|
||||
//! A single signal may either be associated with termination or with a
|
||||
//! crash (see InstalCrashHandlers()), and perhaps neither, but never both.
|
||||
//!
|
||||
//! \param[in] handler A signal-handling function to execute, used as the
|
||||
//! `struct sigaction::sa_sigaction` field when calling `sigaction()`.
|
||||
//! \param[in] flags Flags to pass to `sigaction()` in the `struct
|
||||
//! sigaction::sa_flags` field. `SA_SIGINFO` will be specified implicitly.
|
||||
//! \param[out] old_actions The previous actions for the signals, replaced by
|
||||
//! the new action. May be `nullptr` if not needed. The same \a
|
||||
//! old_actions object may be used for calls to both this function and
|
||||
//! InstallCrashHandlers().
|
||||
//!
|
||||
//! \return `true` on success. `false` on failure with a message logged.
|
||||
//!
|
||||
//! \warning This function may not be called from a signal handler because of
|
||||
//! its use of logging. See RestoreHandlerAndReraiseSignalOnReturn()
|
||||
//! instead.
|
||||
static bool InstallTerminateHandlers(Handler handler,
|
||||
int flags,
|
||||
OldActions* old_actions);
|
||||
|
||||
//! \brief Determines whether a signal will be re-raised autonomously upon
|
||||
//! return from a signal handler.
|
||||
//!
|
||||
//! Certain signals, when generated synchronously in response to a hardware
|
||||
//! fault, are unrecoverable. Upon return from the signal handler, the same
|
||||
//! action that triggered the signal to be raised initially will be retried,
|
||||
//! and unless the signal handler took action to mitigate this error, the same
|
||||
//! signal will be re-raised. As an example, a CPU will not be able to read
|
||||
//! unmapped memory (causing `SIGSEGV`), thus the signal will be re-raised
|
||||
//! upon return from the signal handler unless the signal handler established
|
||||
//! a memory mapping where required.
|
||||
//!
|
||||
//! It is important to distinguish between these synchronous signals generated
|
||||
//! in response to a hardware fault and signals generated asynchronously or in
|
||||
//! software. As an example, `SIGSEGV` will not re-raise autonomously if sent
|
||||
//! by `kill()`.
|
||||
//!
|
||||
//! This function distinguishes between signals that can re-raise
|
||||
//! autonomously, and for those that can, between instances of the signal that
|
||||
//! were generated synchronously in response to a hardware fault and instances
|
||||
//! that were generated by other means.
|
||||
//!
|
||||
//! \param[in] siginfo A pointer to a `siginfo_t` object received by a signal
|
||||
//! handler.
|
||||
//!
|
||||
//! \return `true` if the signal being handled will re-raise itself
|
||||
//! autonomously upon return from a signal handler. `false` if it will
|
||||
//! not. When this function returns `false`, a signal can still be
|
||||
//! re-raised upon return from a signal handler by calling `raise()` from
|
||||
//! within the signal handler.
|
||||
//!
|
||||
//! \note This function is safe to call from a signal handler.
|
||||
static bool WillSignalReraiseAutonomously(const siginfo_t* siginfo);
|
||||
|
||||
//! \brief Restores a previous signal action and arranges to re-raise a signal
|
||||
//! on return from a signal handler.
|
||||
//!
|
||||
//! \param[in] siginfo A pointer to a `siginfo_t` object received by a signal
|
||||
//! handler.
|
||||
//! \param[in] old_action The previous action for the signal, which will be
|
||||
//! re-established as the signal’s action. May be `nullptr`, which directs
|
||||
//! the default action for the signal to be used.
|
||||
//!
|
||||
//! If this function fails, it will immediately call `_exit()` and set an exit
|
||||
//! status of `191`.
|
||||
//!
|
||||
//! \note This function may only be called from a signal handler.
|
||||
static void RestoreHandlerAndReraiseSignalOnReturn(
|
||||
const siginfo_t* siginfo,
|
||||
const struct sigaction* old_action);
|
||||
|
||||
//! \brief Determines whether a signal is associated with a crash.
|
||||
//!
|
||||
//! Signals associated with crashes are those whose default dispositions
|
||||
//! involve creating a core dump. The precise set of signals involved varies
|
||||
//! between operating systems.
|
||||
//!
|
||||
//! \param[in] sig The signal to test.
|
||||
//!
|
||||
//! \return `true` if \a sig is associated with a crash. `false` otherwise.
|
||||
//!
|
||||
//! \note This function is safe to call from a signal handler.
|
||||
static bool IsCrashSignal(int sig);
|
||||
|
||||
//! \brief Determines whether a signal is associated with termination.
|
||||
//!
|
||||
//! Signals associated with termination are those whose default dispositions
|
||||
//! involve terminating the process without creating a core dump. The precise
|
||||
//! set of signals involved varies between operating systems.
|
||||
//!
|
||||
//! \param[in] sig The signal to test.
|
||||
//!
|
||||
//! \return `true` if \a sig is associated with termination. `false`
|
||||
//! otherwise.
|
||||
//!
|
||||
//! \note This function is safe to call from a signal handler.
|
||||
static bool IsTerminateSignal(int sig);
|
||||
|
||||
private:
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(Signals);
|
||||
};
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_UTIL_POSIX_SIGNALS_H_
|
579
util/posix/signals_test.cc
Normal file
579
util/posix/signals_test.cc
Normal file
@ -0,0 +1,579 @@
|
||||
// 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/posix/signals.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/files/scoped_file.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "build/build_config.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/errors.h"
|
||||
#include "test/multiprocess.h"
|
||||
#include "test/scoped_temp_dir.h"
|
||||
|
||||
namespace crashpad {
|
||||
namespace test {
|
||||
namespace {
|
||||
|
||||
constexpr int kUnexpectedExitStatus = 3;
|
||||
|
||||
// Keep synchronized with CauseSignal().
|
||||
bool CanCauseSignal(int sig) {
|
||||
return sig == SIGABRT ||
|
||||
sig == SIGALRM ||
|
||||
sig == SIGBUS ||
|
||||
#if !defined(ARCH_CPU_ARM64)
|
||||
sig == SIGFPE ||
|
||||
#endif // !defined(ARCH_CPU_ARM64)
|
||||
#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARMEL)
|
||||
sig == SIGILL ||
|
||||
#endif // defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARMEL
|
||||
sig == SIGPIPE ||
|
||||
sig == SIGSEGV ||
|
||||
#if defined(OS_MACOSX)
|
||||
sig == SIGSYS ||
|
||||
#endif // OS_MACOSX
|
||||
#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM64)
|
||||
sig == SIGTRAP ||
|
||||
#endif // defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM64)
|
||||
false;
|
||||
}
|
||||
|
||||
// Keep synchronized with CanCauseSignal().
|
||||
void CauseSignal(int sig) {
|
||||
switch (sig) {
|
||||
case SIGABRT: {
|
||||
abort();
|
||||
break;
|
||||
}
|
||||
|
||||
case SIGALRM: {
|
||||
struct itimerval itimer = {};
|
||||
itimer.it_value.tv_usec = 1E3; // 1 millisecond
|
||||
if (setitimer(ITIMER_REAL, &itimer, nullptr) != 0) {
|
||||
PLOG(ERROR) << "setitimer";
|
||||
_exit(kUnexpectedExitStatus);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
sleep(std::numeric_limits<unsigned int>::max());
|
||||
}
|
||||
}
|
||||
|
||||
case SIGBUS: {
|
||||
char* mapped;
|
||||
{
|
||||
base::ScopedFD fd;
|
||||
{
|
||||
ScopedTempDir temp_dir;
|
||||
fd.reset(open(temp_dir.path().Append("empty").value().c_str(),
|
||||
O_RDWR | O_CREAT | O_EXCL | O_NOCTTY | O_CLOEXEC,
|
||||
0644));
|
||||
if (fd.get() < 0) {
|
||||
PLOG(ERROR) << "open";
|
||||
}
|
||||
}
|
||||
if (fd.get() < 0) {
|
||||
_exit(kUnexpectedExitStatus);
|
||||
}
|
||||
|
||||
mapped = reinterpret_cast<char*>(mmap(nullptr,
|
||||
getpagesize(),
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE,
|
||||
fd.get(),
|
||||
0));
|
||||
if (mapped == MAP_FAILED) {
|
||||
PLOG(ERROR) << "mmap";
|
||||
}
|
||||
}
|
||||
if (mapped == MAP_FAILED) {
|
||||
_exit(kUnexpectedExitStatus);
|
||||
}
|
||||
|
||||
*mapped = 0;
|
||||
|
||||
_exit(kUnexpectedExitStatus);
|
||||
break;
|
||||
}
|
||||
|
||||
#if !defined(ARCH_CPU_ARM64)
|
||||
// ARM64 has hardware integer division instructions that don’t generate a
|
||||
// trap for divide-by-zero, so this doesn’t produce SIGFPE.
|
||||
case SIGFPE: {
|
||||
// Optimization makes this tricky, so get zero from a system call likely
|
||||
// to succeed, and try to do something with the result.
|
||||
struct stat stat_buf;
|
||||
int zero = stat("/", &stat_buf);
|
||||
if (zero == -1) {
|
||||
// It’s important to check |== -1| and not |!= 0|. An optimizer is free
|
||||
// to discard an |== 0| branch entirely, because division by zero is
|
||||
// undefined behavior.
|
||||
PLOG(ERROR) << "stat";
|
||||
_exit(kUnexpectedExitStatus);
|
||||
}
|
||||
|
||||
int quotient = 2 / zero;
|
||||
fstat(quotient, &stat_buf);
|
||||
break;
|
||||
}
|
||||
#endif // ARCH_CPU_ARM64
|
||||
|
||||
#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARMEL)
|
||||
case SIGILL: {
|
||||
// __builtin_trap() causes SIGTRAP on arm64 on Android.
|
||||
__builtin_trap();
|
||||
break;
|
||||
}
|
||||
#endif // defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARMEL)
|
||||
|
||||
case SIGPIPE: {
|
||||
int pipe_fds[2];
|
||||
if (pipe(pipe_fds) != 0) {
|
||||
PLOG(ERROR) << "pipe";
|
||||
_exit(kUnexpectedExitStatus);
|
||||
}
|
||||
|
||||
if (close(pipe_fds[0]) != 0) {
|
||||
PLOG(ERROR) << "close";
|
||||
_exit(kUnexpectedExitStatus);
|
||||
}
|
||||
|
||||
char c = 0;
|
||||
ssize_t rv = write(pipe_fds[1], &c, sizeof(c));
|
||||
if (rv < 0) {
|
||||
PLOG(ERROR) << "write";
|
||||
_exit(kUnexpectedExitStatus);
|
||||
} else if (rv != sizeof(c)) {
|
||||
LOG(ERROR) << "write";
|
||||
_exit(kUnexpectedExitStatus);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SIGSEGV: {
|
||||
volatile int* i = nullptr;
|
||||
*i = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
case SIGSYS: {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
int rv = syscall(0);
|
||||
#pragma clang diagnostic pop
|
||||
if (rv != 0) {
|
||||
PLOG(ERROR) << "syscall";
|
||||
_exit(kUnexpectedExitStatus);
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif // OS_MACOSX
|
||||
|
||||
#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM64)
|
||||
case SIGTRAP: {
|
||||
#if defined(ARCH_CPU_X86_FAMILY)
|
||||
asm("int3");
|
||||
#elif defined(ARCH_CPU_ARM64)
|
||||
// bkpt #0 should work for 32-bit ARCH_CPU_ARMEL, but according to
|
||||
// https://crrev.com/f53167270c44, it only causes SIGTRAP on Linux under a
|
||||
// 64-bit kernel. For a pure 32-bit armv7 system, it generates SIGBUS.
|
||||
asm("brk #0");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
#endif // defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM64)
|
||||
|
||||
default: {
|
||||
LOG(ERROR) << "unexpected signal " << sig;
|
||||
_exit(kUnexpectedExitStatus);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SignalsTest : public Multiprocess {
|
||||
public:
|
||||
enum class SignalSource {
|
||||
kCause,
|
||||
kRaise,
|
||||
};
|
||||
enum class TestType {
|
||||
kDefaultHandler,
|
||||
kHandlerExits,
|
||||
kHandlerReraisesToDefault,
|
||||
kHandlerReraisesToPrevious,
|
||||
};
|
||||
static constexpr int kExitingHandlerExitStatus = 2;
|
||||
|
||||
SignalsTest(TestType test_type, SignalSource signal_source, int sig)
|
||||
: Multiprocess(),
|
||||
sig_(sig),
|
||||
test_type_(test_type),
|
||||
signal_source_(signal_source) {}
|
||||
~SignalsTest() {}
|
||||
|
||||
private:
|
||||
static void SignalHandler_Exit(int sig, siginfo_t* siginfo, void* context) {
|
||||
_exit(kExitingHandlerExitStatus);
|
||||
}
|
||||
|
||||
static void SignalHandler_ReraiseToDefault(int sig,
|
||||
siginfo_t* siginfo,
|
||||
void* context) {
|
||||
Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr);
|
||||
}
|
||||
|
||||
static void SignalHandler_ReraiseToPrevious(int sig,
|
||||
siginfo_t* siginfo,
|
||||
void* context) {
|
||||
Signals::RestoreHandlerAndReraiseSignalOnReturn(
|
||||
siginfo, old_actions_.ActionForSignal(sig));
|
||||
}
|
||||
|
||||
// Multiprocess:
|
||||
void MultiprocessParent() override {}
|
||||
|
||||
void MultiprocessChild() override {
|
||||
bool (*install_handlers)(Signals::Handler, int, Signals::OldActions*);
|
||||
if (Signals::IsCrashSignal(sig_)) {
|
||||
install_handlers = Signals::InstallCrashHandlers;
|
||||
} else if (Signals::IsTerminateSignal(sig_)) {
|
||||
install_handlers = Signals::InstallTerminateHandlers;
|
||||
} else {
|
||||
_exit(kUnexpectedExitStatus);
|
||||
}
|
||||
|
||||
switch (test_type_) {
|
||||
case TestType::kDefaultHandler: {
|
||||
// Don’t rely on the default handler being active. Something may have
|
||||
// changed it (particularly on Android).
|
||||
struct sigaction action;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = 0;
|
||||
action.sa_handler = SIG_DFL;
|
||||
ASSERT_EQ(0, sigaction(sig_, &action, nullptr))
|
||||
<< ErrnoMessage("sigaction");
|
||||
break;
|
||||
}
|
||||
|
||||
case TestType::kHandlerExits: {
|
||||
ASSERT_TRUE(install_handlers(SignalHandler_Exit, 0, nullptr));
|
||||
break;
|
||||
}
|
||||
|
||||
case TestType::kHandlerReraisesToDefault: {
|
||||
ASSERT_TRUE(
|
||||
install_handlers(SignalHandler_ReraiseToDefault, 0, nullptr));
|
||||
break;
|
||||
}
|
||||
|
||||
case TestType::kHandlerReraisesToPrevious: {
|
||||
ASSERT_TRUE(install_handlers(SignalHandler_Exit, 0, nullptr));
|
||||
ASSERT_TRUE(install_handlers(
|
||||
SignalHandler_ReraiseToPrevious, 0, &old_actions_));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (signal_source_) {
|
||||
case SignalSource::kCause:
|
||||
CauseSignal(sig_);
|
||||
break;
|
||||
case SignalSource::kRaise:
|
||||
raise(sig_);
|
||||
break;
|
||||
}
|
||||
|
||||
_exit(kUnexpectedExitStatus);
|
||||
}
|
||||
|
||||
int sig_;
|
||||
TestType test_type_;
|
||||
SignalSource signal_source_;
|
||||
static Signals::OldActions old_actions_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SignalsTest);
|
||||
};
|
||||
|
||||
Signals::OldActions SignalsTest::old_actions_;
|
||||
|
||||
bool ShouldTestSignal(int sig) {
|
||||
return Signals::IsCrashSignal(sig) || Signals::IsTerminateSignal(sig);
|
||||
}
|
||||
|
||||
TEST(Signals, WillSignalReraiseAutonomously) {
|
||||
const struct {
|
||||
int sig;
|
||||
int code;
|
||||
bool result;
|
||||
} kTestData[] = {
|
||||
{SIGBUS, BUS_ADRALN, true},
|
||||
{SIGFPE, FPE_FLTDIV, true},
|
||||
{SIGILL, ILL_ILLOPC, true},
|
||||
{SIGSEGV, SEGV_MAPERR, true},
|
||||
{SIGBUS, 0, false},
|
||||
{SIGFPE, -1, false},
|
||||
{SIGILL, SI_USER, false},
|
||||
{SIGSEGV, SI_QUEUE, false},
|
||||
{SIGTRAP, TRAP_BRKPT, false},
|
||||
{SIGHUP, SEGV_MAPERR, false},
|
||||
{SIGINT, SI_USER, false},
|
||||
};
|
||||
for (size_t index = 0; index < arraysize(kTestData); ++index) {
|
||||
const auto test_data = kTestData[index];
|
||||
SCOPED_TRACE(base::StringPrintf(
|
||||
"index %zu, sig %d, code %d", index, test_data.sig, test_data.code));
|
||||
siginfo_t siginfo = {};
|
||||
siginfo.si_signo = test_data.sig;
|
||||
siginfo.si_code = test_data.code;
|
||||
EXPECT_EQ(test_data.result,
|
||||
Signals::WillSignalReraiseAutonomously(&siginfo));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Signals, Cause_DefaultHandler) {
|
||||
for (int sig = 1; sig < NSIG; ++sig) {
|
||||
SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig)));
|
||||
|
||||
if (!CanCauseSignal(sig)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SignalsTest test(SignalsTest::TestType::kDefaultHandler,
|
||||
SignalsTest::SignalSource::kCause,
|
||||
sig);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, sig);
|
||||
test.Run();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Signals, Cause_HandlerExits) {
|
||||
for (int sig = 1; sig < NSIG; ++sig) {
|
||||
SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig)));
|
||||
|
||||
if (!CanCauseSignal(sig)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SignalsTest test(SignalsTest::TestType::kHandlerExits,
|
||||
SignalsTest::SignalSource::kCause,
|
||||
sig);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationNormal,
|
||||
SignalsTest::kExitingHandlerExitStatus);
|
||||
test.Run();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Signals, Cause_HandlerReraisesToDefault) {
|
||||
for (int sig = 1; sig < NSIG; ++sig) {
|
||||
SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig)));
|
||||
|
||||
if (!CanCauseSignal(sig)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SignalsTest test(SignalsTest::TestType::kHandlerReraisesToDefault,
|
||||
SignalsTest::SignalSource::kCause,
|
||||
sig);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, sig);
|
||||
test.Run();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Signals, Cause_HandlerReraisesToPrevious) {
|
||||
for (int sig = 1; sig < NSIG; ++sig) {
|
||||
SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig)));
|
||||
|
||||
if (!CanCauseSignal(sig)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SignalsTest test(SignalsTest::TestType::kHandlerReraisesToPrevious,
|
||||
SignalsTest::SignalSource::kCause,
|
||||
sig);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationNormal,
|
||||
SignalsTest::kExitingHandlerExitStatus);
|
||||
test.Run();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Signals, Raise_DefaultHandler) {
|
||||
for (int sig = 1; sig < NSIG; ++sig) {
|
||||
SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig)));
|
||||
|
||||
if (!ShouldTestSignal(sig)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SignalsTest test(SignalsTest::TestType::kDefaultHandler,
|
||||
SignalsTest::SignalSource::kRaise,
|
||||
sig);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, sig);
|
||||
test.Run();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Signals, Raise_HandlerExits) {
|
||||
for (int sig = 1; sig < NSIG; ++sig) {
|
||||
SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig)));
|
||||
|
||||
if (!ShouldTestSignal(sig)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SignalsTest test(SignalsTest::TestType::kHandlerExits,
|
||||
SignalsTest::SignalSource::kRaise,
|
||||
sig);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationNormal,
|
||||
SignalsTest::kExitingHandlerExitStatus);
|
||||
test.Run();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Signals, Raise_HandlerReraisesToDefault) {
|
||||
for (int sig = 1; sig < NSIG; ++sig) {
|
||||
SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig)));
|
||||
|
||||
if (!ShouldTestSignal(sig)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
if (sig == SIGBUS) {
|
||||
// Signal handlers can’t distinguish between SIGBUS arising out of a
|
||||
// hardware fault and SIGBUS raised asynchronously.
|
||||
// Signals::RestoreHandlerAndReraiseSignalOnReturn() assumes that SIGBUS
|
||||
// comes from a hardware fault, but this test uses raise(), so the
|
||||
// re-raise test must be skipped.
|
||||
continue;
|
||||
}
|
||||
#endif // defined(OS_MACOSX)
|
||||
|
||||
SignalsTest test(SignalsTest::TestType::kHandlerReraisesToDefault,
|
||||
SignalsTest::SignalSource::kRaise,
|
||||
sig);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationSignal, sig);
|
||||
test.Run();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Signals, Raise_HandlerReraisesToPrevious) {
|
||||
for (int sig = 1; sig < NSIG; ++sig) {
|
||||
SCOPED_TRACE(base::StringPrintf("sig %d (%s)", sig, strsignal(sig)));
|
||||
|
||||
if (!ShouldTestSignal(sig)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
if (sig == SIGBUS) {
|
||||
// Signal handlers can’t distinguish between SIGBUS arising out of a
|
||||
// hardware fault and SIGBUS raised asynchronously.
|
||||
// Signals::RestoreHandlerAndReraiseSignalOnReturn() assumes that SIGBUS
|
||||
// comes from a hardware fault, but this test uses raise(), so the
|
||||
// re-raise test must be skipped.
|
||||
continue;
|
||||
}
|
||||
#endif // defined(OS_MACOSX)
|
||||
|
||||
SignalsTest test(SignalsTest::TestType::kHandlerReraisesToPrevious,
|
||||
SignalsTest::SignalSource::kRaise,
|
||||
sig);
|
||||
test.SetExpectedChildTermination(Multiprocess::kTerminationNormal,
|
||||
SignalsTest::kExitingHandlerExitStatus);
|
||||
test.Run();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Signals, IsCrashSignal) {
|
||||
// Always crash signals.
|
||||
EXPECT_TRUE(Signals::IsCrashSignal(SIGABRT));
|
||||
EXPECT_TRUE(Signals::IsCrashSignal(SIGBUS));
|
||||
EXPECT_TRUE(Signals::IsCrashSignal(SIGFPE));
|
||||
EXPECT_TRUE(Signals::IsCrashSignal(SIGILL));
|
||||
EXPECT_TRUE(Signals::IsCrashSignal(SIGQUIT));
|
||||
EXPECT_TRUE(Signals::IsCrashSignal(SIGSEGV));
|
||||
EXPECT_TRUE(Signals::IsCrashSignal(SIGSYS));
|
||||
EXPECT_TRUE(Signals::IsCrashSignal(SIGTRAP));
|
||||
|
||||
// Always terminate signals.
|
||||
EXPECT_FALSE(Signals::IsCrashSignal(SIGALRM));
|
||||
EXPECT_FALSE(Signals::IsCrashSignal(SIGHUP));
|
||||
EXPECT_FALSE(Signals::IsCrashSignal(SIGINT));
|
||||
EXPECT_FALSE(Signals::IsCrashSignal(SIGPIPE));
|
||||
EXPECT_FALSE(Signals::IsCrashSignal(SIGPROF));
|
||||
EXPECT_FALSE(Signals::IsCrashSignal(SIGTERM));
|
||||
EXPECT_FALSE(Signals::IsCrashSignal(SIGUSR1));
|
||||
EXPECT_FALSE(Signals::IsCrashSignal(SIGUSR2));
|
||||
EXPECT_FALSE(Signals::IsCrashSignal(SIGVTALRM));
|
||||
|
||||
// Never crash or terminate signals.
|
||||
EXPECT_FALSE(Signals::IsCrashSignal(SIGCHLD));
|
||||
EXPECT_FALSE(Signals::IsCrashSignal(SIGCONT));
|
||||
EXPECT_FALSE(Signals::IsCrashSignal(SIGTSTP));
|
||||
EXPECT_FALSE(Signals::IsCrashSignal(SIGTTIN));
|
||||
EXPECT_FALSE(Signals::IsCrashSignal(SIGTTOU));
|
||||
EXPECT_FALSE(Signals::IsCrashSignal(SIGURG));
|
||||
EXPECT_FALSE(Signals::IsCrashSignal(SIGWINCH));
|
||||
}
|
||||
|
||||
TEST(Signals, IsTerminateSignal) {
|
||||
// Always terminate signals.
|
||||
EXPECT_TRUE(Signals::IsTerminateSignal(SIGALRM));
|
||||
EXPECT_TRUE(Signals::IsTerminateSignal(SIGHUP));
|
||||
EXPECT_TRUE(Signals::IsTerminateSignal(SIGINT));
|
||||
EXPECT_TRUE(Signals::IsTerminateSignal(SIGPIPE));
|
||||
EXPECT_TRUE(Signals::IsTerminateSignal(SIGPROF));
|
||||
EXPECT_TRUE(Signals::IsTerminateSignal(SIGTERM));
|
||||
EXPECT_TRUE(Signals::IsTerminateSignal(SIGUSR1));
|
||||
EXPECT_TRUE(Signals::IsTerminateSignal(SIGUSR2));
|
||||
EXPECT_TRUE(Signals::IsTerminateSignal(SIGVTALRM));
|
||||
|
||||
// Always crash signals.
|
||||
EXPECT_FALSE(Signals::IsTerminateSignal(SIGABRT));
|
||||
EXPECT_FALSE(Signals::IsTerminateSignal(SIGBUS));
|
||||
EXPECT_FALSE(Signals::IsTerminateSignal(SIGFPE));
|
||||
EXPECT_FALSE(Signals::IsTerminateSignal(SIGILL));
|
||||
EXPECT_FALSE(Signals::IsTerminateSignal(SIGQUIT));
|
||||
EXPECT_FALSE(Signals::IsTerminateSignal(SIGSEGV));
|
||||
EXPECT_FALSE(Signals::IsTerminateSignal(SIGSYS));
|
||||
EXPECT_FALSE(Signals::IsTerminateSignal(SIGTRAP));
|
||||
|
||||
// Never crash or terminate signals.
|
||||
EXPECT_FALSE(Signals::IsTerminateSignal(SIGCHLD));
|
||||
EXPECT_FALSE(Signals::IsTerminateSignal(SIGCONT));
|
||||
EXPECT_FALSE(Signals::IsTerminateSignal(SIGTSTP));
|
||||
EXPECT_FALSE(Signals::IsTerminateSignal(SIGTTIN));
|
||||
EXPECT_FALSE(Signals::IsTerminateSignal(SIGTTOU));
|
||||
EXPECT_FALSE(Signals::IsTerminateSignal(SIGURG));
|
||||
EXPECT_FALSE(Signals::IsTerminateSignal(SIGWINCH));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace test
|
||||
} // namespace crashpad
|
@ -135,6 +135,8 @@
|
||||
'posix/drop_privileges.h',
|
||||
'posix/process_info.h',
|
||||
'posix/process_info_mac.cc',
|
||||
'posix/signals.cc',
|
||||
'posix/signals.h',
|
||||
'posix/symbolic_constants_posix.cc',
|
||||
'posix/symbolic_constants_posix.h',
|
||||
'stdlib/aligned_allocator.cc',
|
||||
|
@ -74,6 +74,7 @@
|
||||
'numeric/in_range_cast_test.cc',
|
||||
'numeric/int128_test.cc',
|
||||
'posix/process_info_test.cc',
|
||||
'posix/signals_test.cc',
|
||||
'posix/symbolic_constants_posix_test.cc',
|
||||
'stdlib/aligned_allocator_test.cc',
|
||||
'stdlib/map_insert_test.cc',
|
||||
|
Loading…
x
Reference in New Issue
Block a user