diff --git a/handler/handler_main.cc b/handler/handler_main.cc index 3ada8c3e..9433a6af 100644 --- a/handler/handler_main.cc +++ b/handler/handler_main.cc @@ -14,9 +14,11 @@ #include "handler/handler_main.h" +#include #include #include #include +#include #include #include @@ -38,6 +40,8 @@ #include "handler/prune_crash_reports_thread.h" #include "tools/tool_support.h" #include "util/file/file_io.h" +#include "util/misc/metrics.h" +#include "util/numeric/in_range_cast.h" #include "util/stdlib/map_insert.h" #include "util/stdlib/string_number_conversion.h" #include "util/string/split_string.h" @@ -105,6 +109,122 @@ void Usage(const base::FilePath& me) { #if defined(OS_MACOSX) +struct sigaction g_original_crash_sigaction[NSIG]; + +void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) { + // 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 + // standard specifies these other constants, and while xnu never uses them, + // they are intended to denote signals generated asynchronously and are + // included here. Additionally, existing practice on other systems + // (acknowledged by the standard) is for negative numbers to indicate that a + // signal was generated asynchronously. Although xnu does not do this, allow + // for the possibility for completeness. + bool si_code_valid = !(siginfo->si_code <= 0 || + siginfo->si_code == SI_USER || + siginfo->si_code == SI_QUEUE || + siginfo->si_code == SI_TIMER || + siginfo->si_code == SI_ASYNCIO || + siginfo->si_code == SI_MESGQ); + + // 0x5343 = 'SC', signifying “signal and code”, disambiguates from the schema + // used by ExceptionCodeForMetrics(). That system primarily uses Mach + // exception types and codes, which are not available to a POSIX signal + // handler. It does provide a way to encode only signal numbers, but does so + // with the understanding that certain “raw” signals would not be encountered + // without a Mach exception. Furthermore, it does not allow siginfo->si_code + // to be encoded, because that’s not available to Mach exception handlers. It + // would be a shame to lose that information available to a POSIX signal + // handler. + int metrics_code = 0x53430000 | (InRangeCast(sig, 0xff) << 8); + if (si_code_valid) { + metrics_code |= InRangeCast(siginfo->si_code, 0xff); + } + Metrics::HandlerCrashed(metrics_code); + + // Restore the previous signal handler. + DCHECK_GT(sig, 0); + DCHECK_LT(static_cast(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; + + // 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. + + 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 or call to the previous signal handler as + // appropriate. + // + // 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. + 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 + // re-raised signal will appear with the same context as where it was + // initially triggered. + 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; + + // 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, + SIGILL, + SIGTRAP, + SIGABRT, + SIGEMT, + SIGFPE, + SIGBUS, + SIGSEGV, + SIGSYS}; + + for (int sig : kSignals) { + DCHECK_GT(sig, 0); + DCHECK_LT(static_cast(sig), arraysize(g_original_crash_sigaction)); + int rv = sigaction(sig, &sa, &g_original_crash_sigaction[sig]); + PCHECK(rv == 0) << "sigaction " << sig; + } +} + struct ResetSIGTERMTraits { static struct sigaction* InvalidValue() { return nullptr; @@ -126,9 +246,8 @@ void HandleSIGTERM(int sig, siginfo_t* siginfo, void* context) { g_exception_handler_server->Stop(); } -#endif // OS_MACOSX +#elif defined(OS_WIN) -#if defined(OS_WIN) LONG(WINAPI* g_original_exception_filter)(EXCEPTION_POINTERS*) = nullptr; LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) { @@ -139,15 +258,18 @@ LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) { else return EXCEPTION_CONTINUE_SEARCH; } -#endif // OS_WIN + +void InstallCrashHandler() { + g_original_exception_filter = + SetUnhandledExceptionFilter(&UnhandledExceptionHandler); +} + +#endif // OS_MACOSX } // namespace int HandlerMain(int argc, char* argv[]) { -#if defined(OS_WIN) - g_original_exception_filter = - SetUnhandledExceptionFilter(&UnhandledExceptionHandler); -#endif + InstallCrashHandler(); const base::FilePath argv0( ToolSupport::CommandLineArgumentToFilePathStringType(argv[0]));