mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Rather than just on or off, I've changed it to allow "off", "fast", or "full". "fast" means that we'll do symbol lookup, but we won't try to find inline frames and we won't run line number programs (those are the things that are taking considerable time in some cases). rdar://122302117
454 lines
11 KiB
C++
454 lines
11 KiB
C++
//===--- CrashHandlerMacOS.cpp - Swift crash handler for macOS ----------- ===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2022 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// The macOS crash handler implementation.
|
|
//
|
|
// We use signal handling rather than trying to use Mach exceptions here,
|
|
// because the latter would entail running a separate Mach server thread, and
|
|
// creates a much greater risk of interfering with the system wide Crash
|
|
// Reporter, which is a no-no.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifdef __APPLE__
|
|
|
|
#include <TargetConditionals.h>
|
|
|
|
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
|
|
|
|
#include <mach/mach.h>
|
|
#include <mach/task.h>
|
|
#include <mach/thread_act.h>
|
|
|
|
#include <sys/mman.h>
|
|
#include <sys/ucontext.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <os/lock.h>
|
|
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <spawn.h>
|
|
#include <unistd.h>
|
|
|
|
#include "swift/Runtime/Backtrace.h"
|
|
|
|
#include <cstring>
|
|
|
|
#include "BacktracePrivate.h"
|
|
|
|
#ifndef lengthof
|
|
#define lengthof(x) (sizeof(x) / sizeof(x[0]))
|
|
#endif
|
|
|
|
using namespace swift::runtime::backtrace;
|
|
|
|
namespace {
|
|
|
|
void handle_fatal_signal(int signum, siginfo_t *pinfo, void *uctx);
|
|
void suspend_other_threads();
|
|
void resume_other_threads();
|
|
bool run_backtracer(void);
|
|
|
|
CrashInfo crashInfo;
|
|
|
|
os_unfair_lock crashLock = OS_UNFAIR_LOCK_INIT;
|
|
|
|
const int signalsToHandle[] = {
|
|
SIGQUIT,
|
|
SIGABRT,
|
|
SIGBUS,
|
|
SIGFPE,
|
|
SIGILL,
|
|
SIGSEGV,
|
|
SIGTRAP
|
|
};
|
|
|
|
} // namespace
|
|
|
|
namespace swift {
|
|
namespace runtime {
|
|
namespace backtrace {
|
|
|
|
SWIFT_RUNTIME_STDLIB_INTERNAL int
|
|
_swift_installCrashHandler()
|
|
{
|
|
stack_t ss;
|
|
|
|
// See if an alternate signal stack already exists
|
|
if (sigaltstack(NULL, &ss) < 0)
|
|
return errno;
|
|
|
|
if (ss.ss_sp == 0) {
|
|
// No, so set one up
|
|
ss.ss_flags = 0;
|
|
ss.ss_size = SIGSTKSZ;
|
|
ss.ss_sp = mmap(0, ss.ss_size, PROT_READ | PROT_WRITE,
|
|
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
if (ss.ss_sp == MAP_FAILED)
|
|
return errno;
|
|
|
|
if (sigaltstack(&ss, NULL) < 0)
|
|
return errno;
|
|
}
|
|
|
|
// Now register signal handlers
|
|
struct sigaction sa;
|
|
|
|
sigfillset(&sa.sa_mask);
|
|
for (unsigned n = 0; n < lengthof(signalsToHandle); ++n) {
|
|
sigdelset(&sa.sa_mask, signalsToHandle[n]);
|
|
}
|
|
|
|
sa.sa_handler = NULL;
|
|
sa.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_NODEFER;
|
|
sa.sa_sigaction = handle_fatal_signal;
|
|
|
|
for (unsigned n = 0; n < lengthof(signalsToHandle); ++n) {
|
|
struct sigaction osa;
|
|
|
|
// See if a signal handler for this signal is already installed
|
|
if (sigaction(signalsToHandle[n], NULL, &osa) < 0)
|
|
return errno;
|
|
|
|
if (osa.sa_handler == SIG_DFL) {
|
|
// No, so install ours
|
|
if (sigaction(signalsToHandle[n], &sa, NULL) < 0)
|
|
return errno;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
} // namespace backtrace
|
|
} // namespace runtime
|
|
} // namespace swift
|
|
|
|
namespace {
|
|
|
|
void
|
|
suspend_other_threads()
|
|
{
|
|
os_unfair_lock_lock(&crashLock);
|
|
|
|
thread_t self = mach_thread_self();
|
|
thread_act_array_t threads;
|
|
mach_msg_type_number_t count = 0;
|
|
|
|
kern_return_t kr = task_threads(mach_task_self(), &threads, &count);
|
|
|
|
if (kr != KERN_SUCCESS)
|
|
return;
|
|
|
|
for (unsigned n = 0; n < count; ++n) {
|
|
if (threads[n] == self)
|
|
continue;
|
|
|
|
// Ignore the results of these two; if they fail there's nothing we can do
|
|
(void)thread_suspend(threads[n]);
|
|
(void)mach_port_deallocate(mach_task_self(), threads[n]);
|
|
}
|
|
|
|
vm_deallocate(mach_task_self(),
|
|
(vm_address_t)threads,
|
|
count * sizeof(threads[0]));
|
|
|
|
os_unfair_lock_unlock(&crashLock);
|
|
}
|
|
|
|
void
|
|
resume_other_threads()
|
|
{
|
|
os_unfair_lock_lock(&crashLock);
|
|
|
|
thread_t self = mach_thread_self();
|
|
thread_act_array_t threads;
|
|
mach_msg_type_number_t count = 0;
|
|
|
|
kern_return_t kr = task_threads(mach_task_self(), &threads, &count);
|
|
|
|
if (kr != KERN_SUCCESS)
|
|
return;
|
|
|
|
for (unsigned n = 0; n < count; ++n) {
|
|
if (threads[n] == self)
|
|
continue;
|
|
|
|
// Ignore the results of these two; if they fail there's nothing we can do
|
|
(void)thread_resume(threads[n]);
|
|
(void)mach_port_deallocate(mach_task_self(), threads[n]);
|
|
}
|
|
|
|
vm_deallocate(mach_task_self(),
|
|
(vm_address_t)threads,
|
|
count * sizeof(threads[0]));
|
|
|
|
os_unfair_lock_unlock(&crashLock);
|
|
}
|
|
|
|
void
|
|
handle_fatal_signal(int signum,
|
|
siginfo_t *pinfo,
|
|
void *uctx)
|
|
{
|
|
int old_err = errno;
|
|
|
|
// Prevent this from exploding if more than one thread gets here at once
|
|
suspend_other_threads();
|
|
|
|
// Remove our signal handlers; crashes should kill us here
|
|
for (unsigned n = 0; n < lengthof(signalsToHandle); ++n)
|
|
signal(signalsToHandle[n], SIG_DFL);
|
|
|
|
// Get our thread identifier
|
|
thread_identifier_info_data_t ident_info;
|
|
mach_msg_type_number_t ident_size = THREAD_IDENTIFIER_INFO_COUNT;
|
|
|
|
int ret = thread_info(mach_thread_self(),
|
|
THREAD_IDENTIFIER_INFO,
|
|
(int *)&ident_info,
|
|
&ident_size);
|
|
if (ret != KERN_SUCCESS)
|
|
return;
|
|
|
|
// Fill in crash info
|
|
crashInfo.crashing_thread = ident_info.thread_id;
|
|
crashInfo.signal = signum;
|
|
crashInfo.fault_address = (uint64_t)pinfo->si_addr;
|
|
crashInfo.mctx = (uint64_t)(((ucontext_t *)uctx)->uc_mcontext);
|
|
|
|
// Display a progress message
|
|
void *pc = 0;
|
|
ucontext_t *ctx = (ucontext_t *)uctx;
|
|
|
|
#if defined(__arm64__) && __DARWIN_OPAQUE_ARM_THREAD_STATE64
|
|
#define THREAD_STATE_MEMBER(x) __opaque_##x
|
|
#elif __DARWIN_UNIX03
|
|
#define THREAD_STATE_MEMBER(x) __##x
|
|
#else
|
|
#define THREAD_STATE_MEMBER(x) x
|
|
#endif
|
|
|
|
#if __DARWIN_UNIX03
|
|
#define CTX_MEMBER(x) __##x
|
|
#else
|
|
#define CTX_MEMBER(x) x
|
|
#endif
|
|
|
|
#if defined(__x86_64__)
|
|
pc = (void *)(ctx->uc_mcontext->CTX_MEMBER(ss).THREAD_STATE_MEMBER(rip));
|
|
#elif defined(__arm64__)
|
|
pc = (void *)(ctx->uc_mcontext->CTX_MEMBER(ss).THREAD_STATE_MEMBER(pc));
|
|
#endif
|
|
|
|
_swift_displayCrashMessage(signum, pc);
|
|
|
|
/* Start the backtracer; this will suspend the process, so there's no need
|
|
to try to suspend other threads from here. */
|
|
if (!run_backtracer()) {
|
|
const char *message = _swift_backtraceSettings.color == OnOffTty::On
|
|
? " failed\n\n" : " failed ***\n\n";
|
|
if (_swift_backtraceSettings.outputTo == OutputTo::Stderr)
|
|
write(STDERR_FILENO, message, strlen(message));
|
|
else
|
|
write(STDOUT_FILENO, message, strlen(message));
|
|
}
|
|
|
|
// Restart the other threads
|
|
resume_other_threads();
|
|
|
|
// Restore errno and exit (to crash)
|
|
errno = old_err;
|
|
}
|
|
|
|
char addr_buf[18];
|
|
char timeout_buf[22];
|
|
char limit_buf[22];
|
|
char top_buf[22];
|
|
const char *backtracer_argv[] = {
|
|
"swift-backtrace", // 0
|
|
"--unwind", // 1
|
|
"precise", // 2
|
|
"--demangle", // 3
|
|
"true", // 4
|
|
"--interactive", // 5
|
|
"true", // 6
|
|
"--color", // 7
|
|
"true", // 8
|
|
"--timeout", // 9
|
|
timeout_buf, // 10
|
|
"--preset", // 11
|
|
"friendly", // 12
|
|
"--crashinfo", // 13
|
|
addr_buf, // 14
|
|
"--threads", // 15
|
|
"preset", // 16
|
|
"--registers", // 17
|
|
"preset", // 18
|
|
"--images", // 19
|
|
"preset", // 20
|
|
"--limit", // 21
|
|
limit_buf, // 22
|
|
"--top", // 23
|
|
top_buf, // 24
|
|
"--sanitize", // 25
|
|
"preset", // 26
|
|
"--cache", // 27
|
|
"true", // 28
|
|
"--output-to", // 29
|
|
"stdout", // 30
|
|
"--symbolicate", // 31
|
|
"true", // 32
|
|
NULL
|
|
};
|
|
|
|
const char *
|
|
trueOrFalse(bool b) {
|
|
return b ? "true" : "false";
|
|
}
|
|
|
|
const char *
|
|
trueOrFalse(OnOffTty oot) {
|
|
return trueOrFalse(oot == OnOffTty::On);
|
|
}
|
|
|
|
bool
|
|
run_backtracer()
|
|
{
|
|
// Set-up the backtracer's command line arguments
|
|
switch (_swift_backtraceSettings.algorithm) {
|
|
case UnwindAlgorithm::Fast:
|
|
backtracer_argv[2] = "fast";
|
|
break;
|
|
default:
|
|
backtracer_argv[2] = "precise";
|
|
break;
|
|
}
|
|
|
|
// (The TTY option has already been handled at this point, so these are
|
|
// all either "On" or "Off".)
|
|
backtracer_argv[4] = trueOrFalse(_swift_backtraceSettings.demangle);
|
|
backtracer_argv[6] = trueOrFalse(_swift_backtraceSettings.interactive);
|
|
backtracer_argv[8] = trueOrFalse(_swift_backtraceSettings.color);
|
|
|
|
switch (_swift_backtraceSettings.threads) {
|
|
case ThreadsToShow::Preset:
|
|
backtracer_argv[16] = "preset";
|
|
break;
|
|
case ThreadsToShow::All:
|
|
backtracer_argv[16] = "all";
|
|
break;
|
|
case ThreadsToShow::Crashed:
|
|
backtracer_argv[16] = "crashed";
|
|
break;
|
|
}
|
|
|
|
switch (_swift_backtraceSettings.registers) {
|
|
case RegistersToShow::Preset:
|
|
backtracer_argv[18] = "preset";
|
|
break;
|
|
case RegistersToShow::None:
|
|
backtracer_argv[18] = "none";
|
|
break;
|
|
case RegistersToShow::All:
|
|
backtracer_argv[18] = "all";
|
|
break;
|
|
case RegistersToShow::Crashed:
|
|
backtracer_argv[18] = "crashed";
|
|
break;
|
|
}
|
|
|
|
switch (_swift_backtraceSettings.images) {
|
|
case ImagesToShow::Preset:
|
|
backtracer_argv[20] = "preset";
|
|
break;
|
|
case ImagesToShow::None:
|
|
backtracer_argv[20] = "none";
|
|
break;
|
|
case ImagesToShow::All:
|
|
backtracer_argv[20] = "all";
|
|
break;
|
|
case ImagesToShow::Mentioned:
|
|
backtracer_argv[20] = "mentioned";
|
|
break;
|
|
}
|
|
|
|
switch (_swift_backtraceSettings.preset) {
|
|
case Preset::Friendly:
|
|
backtracer_argv[12] = "friendly";
|
|
break;
|
|
case Preset::Medium:
|
|
backtracer_argv[12] = "medium";
|
|
break;
|
|
default:
|
|
backtracer_argv[12] = "full";
|
|
break;
|
|
}
|
|
|
|
switch (_swift_backtraceSettings.sanitize) {
|
|
case SanitizePaths::Preset:
|
|
backtracer_argv[26] = "preset";
|
|
break;
|
|
case SanitizePaths::Off:
|
|
backtracer_argv[26] = "false";
|
|
break;
|
|
case SanitizePaths::On:
|
|
backtracer_argv[26] = "true";
|
|
break;
|
|
}
|
|
|
|
switch (_swift_backtraceSettings.outputTo) {
|
|
case OutputTo::Stdout:
|
|
backtracer_argv[30] = "stdout";
|
|
break;
|
|
case OutputTo::Auto: // Shouldn't happen, but if it does pick stderr
|
|
case OutputTo::Stderr:
|
|
backtracer_argv[30] = "stderr";
|
|
break;
|
|
}
|
|
|
|
backtracer_argv[28] = trueOrFalse(_swift_backtraceSettings.cache);
|
|
|
|
switch (_swift_backtraceSettings.symbolicate) {
|
|
case Symbolication::Off:
|
|
backtracer_argv[32] = "off";
|
|
break;
|
|
case Symbolication::Fast:
|
|
backtracer_argv[32] = "fast";
|
|
break;
|
|
case Symbolication::Full:
|
|
backtracer_argv[32] = "full";
|
|
break;
|
|
}
|
|
|
|
_swift_formatUnsigned(_swift_backtraceSettings.timeout, timeout_buf);
|
|
|
|
if (_swift_backtraceSettings.limit < 0)
|
|
std::strcpy(limit_buf, "none");
|
|
else
|
|
_swift_formatUnsigned(_swift_backtraceSettings.limit, limit_buf);
|
|
|
|
_swift_formatUnsigned(_swift_backtraceSettings.top, top_buf);
|
|
_swift_formatAddress(&crashInfo, addr_buf);
|
|
|
|
// Actually execute it
|
|
return _swift_spawnBacktracer(backtracer_argv);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
#endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
|
|
|
|
#endif // __APPLE__
|
|
|