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:
Mark Mentovai 2017-03-01 10:00:59 -05:00
Родитель 58aac1bd87
Коммит bf2c5155d2
6 изменённых файлов: 1102 добавлений и 116 удалений

Просмотреть файл

@ -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 hasnt 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
// isnt 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, dont 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
Просмотреть файл

@ -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 (its 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 isnt 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, dont 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 its
// 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
Просмотреть файл

@ -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, dont 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 signals 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
Просмотреть файл

@ -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 dont generate a
// trap for divide-by-zero, so this doesnt 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) {
// Its 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: {
// Dont 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 cant 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 cant 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',