From bfe8087358333ac1ab8fb5fc5c83ea59711aa69b Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Mon, 5 Jun 2023 15:35:20 +0100 Subject: [PATCH 1/5] [Backtracing][Linux] Add Linux crash handler to the runtime. This also adds a function to demangle a symbol, and a way for the backtracing code to report warning messages to the same place as the main runtime. I'd like to rename the _swift_isThunkFunction() SPI also, but we can't do that until we've made the changes to the _Backtracing library, so we'll do that there instead. rdar://110261430 --- include/swift/Runtime/Backtrace.h | 19 +- include/swift/Runtime/Debug.h | 3 + stdlib/public/runtime/Backtrace.cpp | 165 ++++- stdlib/public/runtime/CMakeLists.txt | 3 +- stdlib/public/runtime/CrashHandlerLinux.cpp | 770 ++++++++++++++++++++ stdlib/public/runtime/CrashHandlerMacOS.cpp | 2 +- stdlib/public/runtime/Errors.cpp | 6 + 7 files changed, 959 insertions(+), 9 deletions(-) create mode 100644 stdlib/public/runtime/CrashHandlerLinux.cpp diff --git a/include/swift/Runtime/Backtrace.h b/include/swift/Runtime/Backtrace.h index fcd67e8f5ce73..5a82d974d206b 100644 --- a/include/swift/Runtime/Backtrace.h +++ b/include/swift/Runtime/Backtrace.h @@ -17,10 +17,17 @@ #ifndef SWIFT_RUNTIME_BACKTRACE_H #define SWIFT_RUNTIME_BACKTRACE_H +#ifdef __linux__ +#include +#include + +#include +#endif // defined(__linux__) + #include "swift/Runtime/Config.h" +#include "swift/Runtime/CrashInfo.h" #include "swift/shims/Visibility.h" -#include "swift/shims/CrashInfo.h" #include @@ -50,7 +57,11 @@ typedef int ErrorCode; SWIFT_RUNTIME_STDLIB_INTERNAL ErrorCode _swift_installCrashHandler(); +#ifdef __linux__ +SWIFT_RUNTIME_STDLIB_INTERNAL bool _swift_spawnBacktracer(const ArgChar * const *argv, int memserver_fd); +#else SWIFT_RUNTIME_STDLIB_INTERNAL bool _swift_spawnBacktracer(const ArgChar * const *argv); +#endif enum class UnwindAlgorithm { Auto = 0, @@ -125,6 +136,12 @@ SWIFT_RUNTIME_STDLIB_INTERNAL BacktraceSettings _swift_backtraceSettings; SWIFT_RUNTIME_STDLIB_SPI SWIFT_CC(swift) bool _swift_isThunkFunction(const char *mangledName); +SWIFT_RUNTIME_STDLIB_SPI +char *_swift_backtrace_demangle(const char *mangledName, + size_t mangledNameLength, + char *outputBuffer, + size_t *outputBufferSize, + int *status); #ifdef __cplusplus } // namespace backtrace } // namespace runtime diff --git a/include/swift/Runtime/Debug.h b/include/swift/Runtime/Debug.h index 1559ca666e813..5d67923b603c2 100644 --- a/include/swift/Runtime/Debug.h +++ b/include/swift/Runtime/Debug.h @@ -124,6 +124,9 @@ swift_dynamicCastFailure(const void *sourceType, const char *sourceName, SWIFT_RUNTIME_EXPORT void swift_reportError(uint32_t flags, const char *message); +SWIFT_RUNTIME_EXPORT +void swift_reportWarning(uint32_t flags, const char *message); + // Halt due to an overflow in swift_retain(). SWIFT_RUNTIME_ATTRIBUTE_NORETURN SWIFT_RUNTIME_ATTRIBUTE_NOINLINE void swift_abortRetainOverflow(); diff --git a/stdlib/public/runtime/Backtrace.cpp b/stdlib/public/runtime/Backtrace.cpp index c5eb53813dcf8..61387a2d2be8a 100644 --- a/stdlib/public/runtime/Backtrace.cpp +++ b/stdlib/public/runtime/Backtrace.cpp @@ -52,6 +52,12 @@ #include #include +#ifdef _WIN32 +// We'll probably want dbghelp.h here +#else +#include +#endif + #define DEBUG_BACKTRACING_SETTINGS 0 #ifndef lengthof @@ -70,7 +76,7 @@ SWIFT_RUNTIME_STDLIB_INTERNAL BacktraceSettings _swift_backtraceSettings = { // enabled #if TARGET_OS_OSX OnOffTty::TTY, -#elif 0 // defined(__linux__) || defined(_WIN32) +#elif defined(__linux__) // || defined(_WIN32) OnOffTty::On, #else OnOffTty::Off, @@ -80,7 +86,7 @@ SWIFT_RUNTIME_STDLIB_INTERNAL BacktraceSettings _swift_backtraceSettings = { true, // interactive -#if TARGET_OS_OSX // || defined(__linux__) || defined(_WIN32) +#if TARGET_OS_OSX || defined(__linux__) // || defined(_WIN32) OnOffTty::TTY, #else OnOffTty::Off, @@ -773,6 +779,54 @@ _swift_backtraceSetupEnvironment() *penv = 0; } +#ifdef __linux__ +struct spawn_info { + const char *path; + char * const *argv; + char * const *envp; + int memserver; +}; + +uint8_t spawn_stack[4096]; + +int +do_spawn(void *ptr) { + struct spawn_info *pinfo = (struct spawn_info *)ptr; + + /* Ensure that the memory server is always on fd 4 */ + if (pinfo->memserver != 4) { + dup2(pinfo->memserver, 4); + close(pinfo->memserver); + } + + /* Clear the signal mask */ + sigset_t mask; + sigfillset(&mask); + sigprocmask(SIG_UNBLOCK, &mask, NULL); + + return execvpe(pinfo->path, pinfo->argv, pinfo->envp); +} + +int +safe_spawn(pid_t *ppid, const char *path, int memserver, + char * const argv[], char * const envp[]) +{ + struct spawn_info info = { path, argv, envp, memserver }; + + /* The CLONE_VFORK is *required* because info is on the stack; we don't + want to return until *after* the subprocess has called execvpe(). */ + int ret = clone(do_spawn, spawn_stack + sizeof(spawn_stack), + CLONE_VFORK|CLONE_VM, &info); + if (ret < 0) + return ret; + + close(memserver); + + *ppid = ret; + return 0; +} +#endif // defined(__linux__) + #endif // SWIFT_BACKTRACE_ON_CRASH_SUPPORTED } // namespace @@ -796,13 +850,109 @@ _swift_isThunkFunction(const char *mangledName) { return ctx.isThunkSymbol(mangledName); } +/// Try to demangle a symbol. +/// +/// Unlike other entry points that do this, we try both Swift and C++ here. +/// +/// @param mangledName is the symbol name to be demangled. +/// @param mangledNameLength is the length of this name. +/// @param outputBuffer is a pointer to a buffer in which to place the result. +/// @param outputBufferSize points to a variable that will be filled in with +/// the size of the allocated buffer (NOT the length of the result). +/// @param status returns the status codes defined in the C++ ABI. +/// +/// If outputBuffer and outputBufferSize are both nullptr, this function will +/// allocate memory for the result using malloc(). +/// +/// If outputBuffer is nullptr but outputBufferSize is not, the function will +/// fill outputBufferSize with the required buffer size and return nullptr. +/// +/// Otherwise, the result will be written into the output buffer, and the +/// size of the result will be written into outputBufferSize. If the buffer +/// is too small, the result will be truncated, but outputBufferSize will +/// still be set to the number of bytes that would have been required to +/// copy out the full result (including a trailing NUL). +/// +/// @returns `true` if demangling was successful. +SWIFT_RUNTIME_STDLIB_SPI char * +_swift_backtrace_demangle(const char *mangledName, + size_t mangledNameLength, + char *outputBuffer, + size_t *outputBufferSize, + int *status) { + llvm::StringRef name = llvm::StringRef(mangledName, mangledNameLength); + + if (Demangle::isSwiftSymbol(name)) { + // This is a Swift mangling + auto options = DemangleOptions::SimplifiedUIDemangleOptions(); + auto result = Demangle::demangleSymbolAsString(name, options); + size_t bufferSize; + + if (outputBufferSize) { + bufferSize = *outputBufferSize; + *outputBufferSize = result.length() + 1; + } + + if (outputBuffer == nullptr) { + outputBuffer = (char *)::malloc(result.length() + 1); + bufferSize = result.length() + 1; + } + + size_t toCopy = std::min(bufferSize - 1, result.length()); + ::memcpy(outputBuffer, result.data(), toCopy); + outputBuffer[toCopy] = '\0'; + + *status = 0; + return outputBuffer; +#ifndef _WIN32 + } else if (name.startswith("_Z")) { + // Try C++ + size_t resultLen; + char *result = abi::__cxa_demangle(mangledName, nullptr, &resultLen, status); + + if (result) { + size_t bufferSize; + + if (outputBufferSize) { + bufferSize = *outputBufferSize; + *outputBufferSize = resultLen; + } + + if (outputBuffer == nullptr) { + return result; + } + + size_t toCopy = std::min(bufferSize - 1, resultLen - 1); + ::memcpy(outputBuffer, result, toCopy); + outputBuffer[toCopy] = '\0'; + + *status = 0; + return outputBuffer; + } +#else + // On Windows, the mangling is different. + // ###TODO: Call __unDName() +#endif + } else { + *status = -2; + } + + return nullptr; +} + // N.B. THIS FUNCTION MUST BE SAFE TO USE FROM A CRASH HANDLER. On Linux // and macOS, that means it must be async-signal-safe. On Windows, there // isn't an equivalent notion but a similar restriction applies. SWIFT_RUNTIME_STDLIB_INTERNAL bool +#ifdef __linux__ +_swift_spawnBacktracer(const ArgChar * const *argv, int memserver_fd) +#else _swift_spawnBacktracer(const ArgChar * const *argv) +#endif { -#if TARGET_OS_OSX || TARGET_OS_MACCATALYST +#if !SWIFT_BACKTRACE_ON_CRASH_SUPPORTED + return false; +#elif TARGET_OS_OSX || TARGET_OS_MACCATALYST || defined(__linux__) pid_t child; const char *env[BACKTRACE_MAX_ENV_VARS + 1]; @@ -817,10 +967,16 @@ _swift_spawnBacktracer(const ArgChar * const *argv) // SUSv3 says argv and envp are "completely constant" and that the reason // posix_spawn() et al use char * const * is for compatibility. +#ifdef __linux__ + int ret = safe_spawn(&child, swiftBacktracePath, memserver_fd, + const_cast(argv), + const_cast(env)); +#else int ret = posix_spawn(&child, swiftBacktracePath, &backtraceFileActions, &backtraceSpawnAttrs, const_cast(argv), const_cast(env)); +#endif if (ret < 0) return false; @@ -835,10 +991,7 @@ _swift_spawnBacktracer(const ArgChar * const *argv) return false; - // ###TODO: Linux // ###TODO: Windows -#else - return false; #endif } diff --git a/stdlib/public/runtime/CMakeLists.txt b/stdlib/public/runtime/CMakeLists.txt index e5adea1fa1a8a..379f72b010a72 100644 --- a/stdlib/public/runtime/CMakeLists.txt +++ b/stdlib/public/runtime/CMakeLists.txt @@ -81,7 +81,8 @@ set(swift_runtime_sources set(swift_runtime_backtracing_sources Backtrace.cpp - CrashHandlerMacOS.cpp) + CrashHandlerMacOS.cpp + CrashHandlerLinux.cpp) # Acknowledge that the following sources are known. set(LLVM_OPTIONAL_SOURCES diff --git a/stdlib/public/runtime/CrashHandlerLinux.cpp b/stdlib/public/runtime/CrashHandlerLinux.cpp new file mode 100644 index 0000000000000..e8fff2a66e08f --- /dev/null +++ b/stdlib/public/runtime/CrashHandlerLinux.cpp @@ -0,0 +1,770 @@ +//===--- CrashHandlerLinux.cpp - Swift crash handler for Linux ----------- ===// +// +// 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 Linux crash handler implementation. +// +//===----------------------------------------------------------------------===// + +#ifdef __linux__ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "swift/Runtime/Backtrace.h" + +#include + +// Run the memserver in a thread (0) or separate process (1) +#define MEMSERVER_USE_PROCESS 0 + +#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(struct thread *self); +void resume_other_threads(); +void take_thread_lock(); +void release_thread_lock(); +void notify_paused(); +void wait_paused(uint32_t expected, const struct timespec *timeout); +int memserver_start(); +int memserver_entry(void *); +bool run_backtracer(int fd); + +int safe_read(int fd, void *buf, size_t len) { + int ret; + do { + ret = read(fd, buf, len); + } while (ret < 0 && errno == EINTR); + + return ret; +} + +int safe_write(int fd, const void *buf, size_t len) { + int ret; + do { + ret = write(fd, buf, len); + } while (ret < 0 && errno == EINTR); + + return ret; +} + +CrashInfo crashInfo; + +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; note that if we end up having to do a PLT lookup + for a function we call from the signal handler, we need additional + stack space for the dynamic linker, or we'll just explode. That's + what the extra 16KB is for here. */ + ss.ss_flags = 0; + ss.ss_size = SIGSTKSZ + 16384; + 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_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 +reset_signal(int signum) +{ + struct sigaction sa; + sa.sa_handler = SIG_DFL; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + + sigaction(signum, &sa, NULL); +} + +void +handle_fatal_signal(int signum, + siginfo_t *pinfo, + void *uctx) +{ + int old_err = errno; + struct thread self = { 0, (int64_t)gettid(), (uint64_t)uctx }; + + // Prevent this from exploding if more than one thread gets here at once + suspend_other_threads(&self); + + // Remove our signal handlers; crashes should kill us here + for (unsigned n = 0; n < lengthof(signalsToHandle); ++n) + reset_signal(signalsToHandle[n]); + + // Fill in crash info + crashInfo.crashing_thread = self.tid; + crashInfo.signal = signum; + crashInfo.fault_address = (uint64_t)pinfo->si_addr; + + // Start the memory server + int fd = memserver_start(); + + /* Start the backtracer; this will suspend the process, so there's no need + to try to suspend other threads from here. */ + run_backtracer(fd); + +#if !MEMSERVER_USE_PROCESS + /* If the memserver is in-process, it may have set signal handlers, + so reset SIGSEGV and SIGBUS again */ + reset_signal(SIGSEGV); + reset_signal(SIGBUS); +#endif + + // Restart the other threads + resume_other_threads(); + + // Restore errno and exit (to crash) + errno = old_err; +} + +// .. Thread handling .......................................................... + +void +reset_threads(struct thread *first) { + __atomic_store_n(&crashInfo.thread_list, (uint64_t)first, __ATOMIC_RELEASE); +} + +void +add_thread(struct thread *thread) { + uint64_t next = __atomic_load_n(&crashInfo.thread_list, __ATOMIC_ACQUIRE); + do { + thread->next = next; + } while (!__atomic_compare_exchange_n(&crashInfo.thread_list, &next, + (uint64_t)thread, + false, + __ATOMIC_RELEASE, __ATOMIC_ACQUIRE)); +} + +bool +seen_thread(pid_t tid) { + uint64_t next = __atomic_load_n(&crashInfo.thread_list, __ATOMIC_ACQUIRE); + while (next) { + struct thread *pthread = (struct thread *)next; + if (pthread->tid == tid) + return true; + next = pthread->next; + } + return false; +} + +void +pause_thread(int signum __attribute__((unused)), + siginfo_t *pinfo __attribute__((unused)), + void *uctx) +{ + int old_err = errno; + struct thread self = { 0, (int64_t)gettid(), (uint64_t)uctx }; + + add_thread(&self); + + notify_paused(); + + take_thread_lock(); + release_thread_lock(); + + errno = old_err; +} + +struct linux_dirent64 { + ino64_t d_ino; + off64_t d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[256]; +}; + +int +getdents(int fd, void *buf, size_t bufsiz) +{ + return syscall(SYS_getdents64, fd, buf, bufsiz); +} + +/* Stop all other threads in this process; we do this by establishing a + signal handler for SIGUSR1, then iterating through the threads sending + SIGUSR1. + + Finding the other threads is a pain, because Linux has no actual API + for that; instead, you have to read /proc. Unfortunately, opendir() + and readdir() are not async signal safe, so we get to do this with + the getdents system call instead. + + The SIGUSR1 signals also serve to build the thread list. */ +void +suspend_other_threads(struct thread *self) +{ + struct sigaction sa, sa_old; + + // Take the lock + take_thread_lock(); + + // Start the thread list with this thread + reset_threads(self); + + // Swap out the SIGPROF signal handler first + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_NODEFER; + sa.sa_handler = NULL; + sa.sa_sigaction = pause_thread; + + sigaction(SIGPROF, &sa, &sa_old); + + /* Now scan /proc/self/task to get the tids of the threads in this + process. We need to ignore our own thread. */ + int fd = open("/proc/self/task", + O_RDONLY|O_NDELAY|O_DIRECTORY|O_LARGEFILE|O_CLOEXEC); + int our_pid = getpid(); + char buffer[4096]; + size_t offset = 0; + size_t count = 0; + + uint32_t thread_count = 0; + uint32_t old_thread_count; + + do { + old_thread_count = thread_count; + lseek(fd, 0, SEEK_SET); + + for (;;) { + if (offset >= count) { + ssize_t bytes = getdents(fd, buffer, sizeof(buffer)); + if (bytes <= 0) + break; + count = (size_t)bytes; + offset = 0; + } + + struct linux_dirent64 *dp = (struct linux_dirent64 *)&buffer[offset]; + offset += dp->d_reclen; + + if (strcmp(dp->d_name, ".") == 0 + || strcmp(dp->d_name, "..") == 0) + continue; + + int tid = atoi(dp->d_name); + + if ((int64_t)tid != self->tid && !seen_thread(tid)) { + tgkill(our_pid, tid, SIGPROF); + ++thread_count; + } + } + + // Wait up to 5 seconds for the threads to pause + struct timespec timeout = { 5, 0 }; + wait_paused(thread_count, &timeout); + } while (old_thread_count != thread_count); + + // Close the directory + close(fd); + + // Finally, reset the signal handler + sigaction(SIGPROF, &sa_old, NULL); +} + +void +resume_other_threads() +{ + // All we need to do here is release the lock. + release_thread_lock(); +} + +// .. Locking .................................................................. + +/* We use a futex to block the threads; we also use one to let us work out + when all the threads we've asked to pause have actually paused. */ +int +futex(uint32_t *uaddr, int futex_op, uint32_t val, + const struct timespec *timeout, uint32_t *uaddr2, uint32_t val3) +{ + return syscall(SYS_futex, uaddr, futex_op, val, timeout, uaddr2, val3); +} + +uint32_t thread_lock = 0; + +void +take_thread_lock() +{ + do { + uint32_t zero = 0; + if (__atomic_compare_exchange_n(&thread_lock, + &zero, + 1, + true, + __ATOMIC_ACQUIRE, + __ATOMIC_RELAXED)) + return; + } while (!futex(&thread_lock, FUTEX_WAIT, 1, NULL, NULL, 0) + || errno == EAGAIN); +} + +void +release_thread_lock() +{ + __atomic_store_n(&thread_lock, 0, __ATOMIC_RELEASE); + futex(&thread_lock, FUTEX_WAKE, 1, NULL, NULL, 0); +} + +uint32_t threads_paused = 0; + +void +notify_paused() +{ + __atomic_fetch_add(&threads_paused, 1, __ATOMIC_RELEASE); + futex(&threads_paused, FUTEX_WAKE, 1, NULL, NULL, 0); +} + +void +wait_paused(uint32_t expected, const struct timespec *timeout) +{ + uint32_t current; + do { + current = __atomic_load_n(&threads_paused, __ATOMIC_ACQUIRE); + if (current == expected) + return; + } while (!futex(&threads_paused, FUTEX_WAIT, current, timeout, NULL, 0) + || errno == EAGAIN); +} + +// .. Memory server ............................................................ + +/* The memory server exists so that we can gain access to the crashing + process's memory space from the backtracer without having to use ptrace() + or process_vm_readv(), both of which need CAP_SYS_PTRACE. + + We don't want to require CAP_SYS_PTRACE because we're potentially being + used inside of a Docker container, which won't have that enabled. */ + +char memserver_stack[4096]; +char memserver_buffer[4096]; +int memserver_fd; +bool memserver_has_ptrace; +sigjmp_buf memserver_fault_buf; +pid_t memserver_pid; + +int +memserver_start() +{ + int ret; + int fds[2]; + + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fds); + if (ret < 0) + return ret; + + memserver_fd = fds[0]; + ret = clone(memserver_entry, memserver_stack + sizeof(memserver_stack), +#if MEMSERVER_USE_PROCESS + 0, +#else + CLONE_THREAD | CLONE_VM | CLONE_FILES + | CLONE_FS | CLONE_IO | CLONE_SIGHAND, +#endif + NULL); + if (ret < 0) + return ret; + +#if MEMSERVER_USE_PROCESS + memserver_pid = ret; + + /* Tell the Yama LSM module, if it's running, that it's OK for + the memserver to read process memory */ + prctl(PR_SET_PTRACER, ret); + + close(fds[0]); +#else + memserver_pid = getpid(); +#endif + + return fds[1]; +} + +void +memserver_fault(int sig) { + (void)sig; + siglongjmp(memserver_fault_buf, -1); +} + +ssize_t __attribute__((noinline)) +memserver_read(void *to, const void *from, size_t len) { + if (memserver_has_ptrace) { + struct iovec local = { to, len }; + struct iovec remote = { const_cast(from), len }; + return process_vm_readv(memserver_pid, &local, 1, &remote, 1, 0); + } else { + if (!sigsetjmp(memserver_fault_buf, 1)) { + memcpy(to, from, len); + return len; + } else { + return 1; + } + } +} + +int +memserver_entry(void *dummy __attribute__((unused))) { + int fd = memserver_fd; + int result = 1; + +#if MEMSERVER_USE_PROCESS + prctl(PR_SET_NAME, "[backtrace]"); +#endif + + memserver_has_ptrace = !!prctl(PR_CAPBSET_READ, CAP_SYS_PTRACE); + + if (!memserver_has_ptrace) { + struct sigaction sa; + sigfillset(&sa.sa_mask); + sa.sa_handler = memserver_fault; + sa.sa_flags = SA_NODEFER; + sigaction(SIGSEGV, &sa, NULL); + sigaction(SIGBUS, &sa, NULL); + } + + for (;;) { + struct memserver_req req; + ssize_t ret; + + ret = safe_read(fd, &req, sizeof(req)); + if (ret != sizeof(req)) + break; + + uint64_t addr = req.addr; + uint64_t bytes = req.len; + + while (bytes) { + uint64_t todo = (bytes < sizeof(memserver_buffer) + ? bytes : sizeof(memserver_buffer)); + + ret = memserver_read(memserver_buffer, (void *)addr, (size_t)todo); + + struct memserver_resp resp; + + resp.addr = addr; + resp.len = ret; + + ret = safe_write(fd, &resp, sizeof(resp)); + if (ret != sizeof(resp)) + goto fail; + + if (resp.len < 0) + break; + + ret = safe_write(fd, memserver_buffer, resp.len); + if (ret != resp.len) + goto fail; + + addr += resp.len; + bytes -= resp.len; + } + } + + result = 0; + + fail: + close(fd); + return result; +} + +// .. Starting the backtracer .................................................. + +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 + NULL +}; + +// We can't call sprintf() here because we're in a signal handler, +// so we need to be async-signal-safe. +void +format_address(uintptr_t addr, char buffer[18]) +{ + char *ptr = buffer + 18; + *--ptr = '\0'; + while (ptr > buffer) { + char digit = '0' + (addr & 0xf); + if (digit > '9') + digit += 'a' - '0' - 10; + *--ptr = digit; + addr >>= 4; + if (!addr) + break; + } + + // Left-justify in the buffer + if (ptr > buffer) { + char *pt2 = buffer; + while (*ptr) + *pt2++ = *ptr++; + *pt2++ = '\0'; + } +} +void +format_address(const void *ptr, char buffer[18]) +{ + format_address(reinterpret_cast(ptr), buffer); +} + +// See above; we can't use sprintf() here. +void +format_unsigned(unsigned u, char buffer[22]) +{ + char *ptr = buffer + 22; + *--ptr = '\0'; + while (ptr > buffer) { + char digit = '0' + (u % 10); + *--ptr = digit; + u /= 10; + if (!u) + break; + } + + // Left-justify in the buffer + if (ptr > buffer) { + char *pt2 = buffer; + while (*ptr) + *pt2++ = *ptr++; + *pt2++ = '\0'; + } +} + +const char * +trueOrFalse(bool b) { + return b ? "true" : "false"; +} + +const char * +trueOrFalse(OnOffTty oot) { + return trueOrFalse(oot == OnOffTty::On); +} + +bool +run_backtracer(int memserver_fd) +{ + // 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; + } + + backtracer_argv[28] = trueOrFalse(_swift_backtraceSettings.cache); + + format_unsigned(_swift_backtraceSettings.timeout, timeout_buf); + + if (_swift_backtraceSettings.limit < 0) + std::strcpy(limit_buf, "none"); + else + format_unsigned(_swift_backtraceSettings.limit, limit_buf); + + format_unsigned(_swift_backtraceSettings.top, top_buf); + format_address(&crashInfo, addr_buf); + + // Actually execute it + return _swift_spawnBacktracer(backtracer_argv, memserver_fd); +} + +} // namespace + +#endif // __linux__ + diff --git a/stdlib/public/runtime/CrashHandlerMacOS.cpp b/stdlib/public/runtime/CrashHandlerMacOS.cpp index 81a6c072c8be0..505a7ba2c553b 100644 --- a/stdlib/public/runtime/CrashHandlerMacOS.cpp +++ b/stdlib/public/runtime/CrashHandlerMacOS.cpp @@ -57,7 +57,7 @@ void suspend_other_threads(); void resume_other_threads(); bool run_backtracer(void); -swift::CrashInfo crashInfo; +CrashInfo crashInfo; os_unfair_lock crashLock = OS_UNFAIR_LOCK_INIT; diff --git a/stdlib/public/runtime/Errors.cpp b/stdlib/public/runtime/Errors.cpp index b126245d14bbe..040153e406f22 100644 --- a/stdlib/public/runtime/Errors.cpp +++ b/stdlib/public/runtime/Errors.cpp @@ -404,6 +404,12 @@ swift::warning(uint32_t flags, const char *format, ...) warningv(flags, format, args); } +/// Report a warning to the system console and stderr. This is exported, +/// unlike the swift::warning() function above. +void swift::swift_reportWarning(uint32_t flags, const char *message) { + warning(flags, "%s", message); +} + // Crash when a deleted method is called by accident. SWIFT_RUNTIME_EXPORT SWIFT_NORETURN void swift_deletedMethodError() { swift::fatalError(/* flags = */ 0, From dc05590747dbaabbe6c119fb54eaf3671c9a19de Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Mon, 5 Jun 2023 16:59:14 +0100 Subject: [PATCH 2/5] [Backtracing][Linux] Add missing CrashInfo.h header. This was added to a later PR, but not to this one, though we need it here. rdar://110261430 --- include/swift/Runtime/CrashInfo.h | 70 +++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 include/swift/Runtime/CrashInfo.h diff --git a/include/swift/Runtime/CrashInfo.h b/include/swift/Runtime/CrashInfo.h new file mode 100644 index 0000000000000..085529e3f7574 --- /dev/null +++ b/include/swift/Runtime/CrashInfo.h @@ -0,0 +1,70 @@ +//===--- CrashInfo.h - Swift Backtracing Crash Information ------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// +// +// Defines the CrashInfo type that holds information about why the program +// crashed. +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_CRASHINFO_H +#define SWIFT_CRASHINFO_H + +#include + +#ifdef __cplusplus +namespace swift { +namespace runtime { +namespace backtrace { +extern "C" { +#endif + +struct CrashInfo { + uint64_t crashing_thread; + uint64_t signal; + uint64_t fault_address; + +#ifdef __APPLE__ + uint64_t mctx; +#elif defined(__linux__) + uint64_t thread_list; +#endif +}; + +#ifdef __linux__ + +struct memserver_req { + uint64_t addr; + uint64_t len; +}; + +struct memserver_resp { + uint64_t addr; + int64_t len; + /* Then len bytes of data */ +}; + +struct thread { + uint64_t next; + int64_t tid; + uint64_t uctx; +}; + +#endif + +#ifdef __cplusplus +} // extern "C" +} // namespace backtrace +} // namespace runtime +} // namespace swift +#endif + +#endif // SWIFT_CRASHINFO_H From 546c93de8766b11972e53c25255b0c77f375cd79 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Mon, 5 Jun 2023 22:28:53 +0100 Subject: [PATCH 3/5] [Backtracing][Linux] Disable backtrace-on-crash until enabling PR. This should have been disabled until #66338. rdar://110261430 --- stdlib/public/runtime/Backtrace.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/public/runtime/Backtrace.cpp b/stdlib/public/runtime/Backtrace.cpp index 61387a2d2be8a..84fb8c827f661 100644 --- a/stdlib/public/runtime/Backtrace.cpp +++ b/stdlib/public/runtime/Backtrace.cpp @@ -76,7 +76,7 @@ SWIFT_RUNTIME_STDLIB_INTERNAL BacktraceSettings _swift_backtraceSettings = { // enabled #if TARGET_OS_OSX OnOffTty::TTY, -#elif defined(__linux__) // || defined(_WIN32) +#elif 0 // defined(__linux__) || defined(_WIN32) OnOffTty::On, #else OnOffTty::Off, @@ -86,7 +86,7 @@ SWIFT_RUNTIME_STDLIB_INTERNAL BacktraceSettings _swift_backtraceSettings = { true, // interactive -#if TARGET_OS_OSX || defined(__linux__) // || defined(_WIN32) +#if TARGET_OS_OSX // || defined(__linux__) || defined(_WIN32) OnOffTty::TTY, #else OnOffTty::Off, From fe6238a95bb4e88a6003fa2a5976890466f74625 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Tue, 6 Jun 2023 15:56:57 +0100 Subject: [PATCH 4/5] [Backtracing][Linux] Tidy a few things up after review. Mike and Max made various helpful suggestions, so I've added and updated various comments and amended the code to cope with partial reads and writes. rdar://110261430 --- include/swift/Runtime/CrashInfo.h | 30 ++++++++++- stdlib/public/runtime/Backtrace.cpp | 26 +++++++--- stdlib/public/runtime/CrashHandlerLinux.cpp | 55 ++++++++++++++------- 3 files changed, 85 insertions(+), 26 deletions(-) diff --git a/include/swift/Runtime/CrashInfo.h b/include/swift/Runtime/CrashInfo.h index 085529e3f7574..737888abbd25e 100644 --- a/include/swift/Runtime/CrashInfo.h +++ b/include/swift/Runtime/CrashInfo.h @@ -27,34 +27,62 @@ namespace backtrace { extern "C" { #endif +// Note: The "pointers" below are pointers in a different process's address +// space, which might not even share our bitness. That is why they are +// `uint64_t`s, rather than pointers or `uintptr_t`s. + +// The address of this structure in memory is passed to swift-backtrace. struct CrashInfo { + // The thread ID for the crashing thread. uint64_t crashing_thread; + + // The signal number corresponding to this crash. uint64_t signal; + + // The fault address. uint64_t fault_address; #ifdef __APPLE__ + // Points to the mcontext_t structure for the crashing thread; other + // threads' contexts can be recovered using Mach APIs later. uint64_t mctx; #elif defined(__linux__) + // The head of the thread list; points at a "struct thread" (see below). uint64_t thread_list; #endif }; #ifdef __linux__ +// A memory server request packet. struct memserver_req { + // Address to read. uint64_t addr; + + // Number of bytes to read. uint64_t len; }; +// A memory server response packet. struct memserver_resp { + // Address that was read from. uint64_t addr; + + // Number of bytes, *or* negative to indicate an error. int64_t len; - /* Then len bytes of data */ + + // Followed by len bytes of data if len > 0 }; +// Holds details of a running thread. struct thread { + // Points at the next thread. uint64_t next; + + // The thread ID for this thread. int64_t tid; + + // Points to the Linux ucontext_t structure. uint64_t uctx; }; diff --git a/stdlib/public/runtime/Backtrace.cpp b/stdlib/public/runtime/Backtrace.cpp index 84fb8c827f661..be20e4fcb8705 100644 --- a/stdlib/public/runtime/Backtrace.cpp +++ b/stdlib/public/runtime/Backtrace.cpp @@ -857,15 +857,15 @@ _swift_isThunkFunction(const char *mangledName) { /// @param mangledName is the symbol name to be demangled. /// @param mangledNameLength is the length of this name. /// @param outputBuffer is a pointer to a buffer in which to place the result. -/// @param outputBufferSize points to a variable that will be filled in with -/// the size of the allocated buffer (NOT the length of the result). +/// @param outputBufferSize points to a variable that contains the size of the +/// output buffer. /// @param status returns the status codes defined in the C++ ABI. /// -/// If outputBuffer and outputBufferSize are both nullptr, this function will -/// allocate memory for the result using malloc(). -/// -/// If outputBuffer is nullptr but outputBufferSize is not, the function will -/// fill outputBufferSize with the required buffer size and return nullptr. +/// If outputBuffer is nullptr, the function will allocate memory for the +/// result using malloc(). In this case, outputBufferSize may be nullptr; +/// if it is *not* nullptr, it will be set to the size of buffer that was +/// allocated. This is not necessarily the length of the string (it may be +/// somewhat higher). /// /// Otherwise, the result will be written into the output buffer, and the /// size of the result will be written into outputBufferSize. If the buffer @@ -873,7 +873,10 @@ _swift_isThunkFunction(const char *mangledName) { /// still be set to the number of bytes that would have been required to /// copy out the full result (including a trailing NUL). /// -/// @returns `true` if demangling was successful. +/// The unusual behaviour here is a consequence of the way the C++ ABI's +/// demangling function works. +/// +/// @returns a pointer to the output if demangling was successful. SWIFT_RUNTIME_STDLIB_SPI char * _swift_backtrace_demangle(const char *mangledName, size_t mangledNameLength, @@ -882,6 +885,11 @@ _swift_backtrace_demangle(const char *mangledName, int *status) { llvm::StringRef name = llvm::StringRef(mangledName, mangledNameLength); + // You must provide buffer size if you're providing your own output buffer + if (outputBuffer && !outputBufferSize) { + return nullptr; + } + if (Demangle::isSwiftSymbol(name)) { // This is a Swift mangling auto options = DemangleOptions::SimplifiedUIDemangleOptions(); @@ -926,6 +934,8 @@ _swift_backtrace_demangle(const char *mangledName, ::memcpy(outputBuffer, result, toCopy); outputBuffer[toCopy] = '\0'; + free(result); + *status = 0; return outputBuffer; } diff --git a/stdlib/public/runtime/CrashHandlerLinux.cpp b/stdlib/public/runtime/CrashHandlerLinux.cpp index e8fff2a66e08f..2a7d6515e159b 100644 --- a/stdlib/public/runtime/CrashHandlerLinux.cpp +++ b/stdlib/public/runtime/CrashHandlerLinux.cpp @@ -67,22 +67,44 @@ int memserver_start(); int memserver_entry(void *); bool run_backtracer(int fd); -int safe_read(int fd, void *buf, size_t len) { - int ret; - do { - ret = read(fd, buf, len); - } while (ret < 0 && errno == EINTR); +ssize_t safe_read(int fd, void *buf, size_t len) { + uint8_t *ptr = (uint8_t *)buf; + uint8_t *end = ptr + len; + ssize_t total = 0; + + while (ptr < end) { + ssize_t ret; + do { + ret = read(fd, buf, len); + } while (ret < 0 && errno == EINTR); + if (ret < 0) + return ret; + total += ret; + ptr += ret; + len -= ret; + } - return ret; + return total; } -int safe_write(int fd, const void *buf, size_t len) { - int ret; - do { - ret = write(fd, buf, len); - } while (ret < 0 && errno == EINTR); +ssize_t safe_write(int fd, const void *buf, size_t len) { + const uint8_t *ptr = (uint8_t *)buf; + const uint8_t *end = ptr + len; + ssize_t total = 0; + + while (ptr < end) { + ssize_t ret; + do { + ret = write(fd, buf, len); + } while (ret < 0 && errno == EINTR); + if (ret < 0) + return ret; + total += ret; + ptr += ret; + len -= ret; + } - return ret; + return total; } CrashInfo crashInfo; @@ -195,8 +217,7 @@ handle_fatal_signal(int signum, // Start the memory server int fd = memserver_start(); - /* Start the backtracer; this will suspend the process, so there's no need - to try to suspend other threads from here. */ + // Actually start the backtracer run_backtracer(fd); #if !MEMSERVER_USE_PROCESS @@ -276,15 +297,15 @@ getdents(int fd, void *buf, size_t bufsiz) } /* Stop all other threads in this process; we do this by establishing a - signal handler for SIGUSR1, then iterating through the threads sending - SIGUSR1. + signal handler for SIGPROF, then iterating through the threads sending + SIGPROF. Finding the other threads is a pain, because Linux has no actual API for that; instead, you have to read /proc. Unfortunately, opendir() and readdir() are not async signal safe, so we get to do this with the getdents system call instead. - The SIGUSR1 signals also serve to build the thread list. */ + The SIGPROF signals also serve to build the thread list. */ void suspend_other_threads(struct thread *self) { From 8ee9b8b938d287cc133ff457e57fd9916cdab2b4 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Tue, 6 Jun 2023 16:12:47 +0100 Subject: [PATCH 5/5] [Backtracing][Linux] Move doc comment to header. Moved the comment for `_swift_backtrace_demangle` into the header file instead of it being in the implementation. rdar://110261430 --- include/swift/Runtime/Backtrace.h | 27 +++++++++++++++++++++++++++ stdlib/public/runtime/Backtrace.cpp | 28 +--------------------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/include/swift/Runtime/Backtrace.h b/include/swift/Runtime/Backtrace.h index 5a82d974d206b..f6472405b8761 100644 --- a/include/swift/Runtime/Backtrace.h +++ b/include/swift/Runtime/Backtrace.h @@ -136,6 +136,33 @@ SWIFT_RUNTIME_STDLIB_INTERNAL BacktraceSettings _swift_backtraceSettings; SWIFT_RUNTIME_STDLIB_SPI SWIFT_CC(swift) bool _swift_isThunkFunction(const char *mangledName); +/// Try to demangle a symbol. +/// +/// Unlike other entry points that do this, we try both Swift and C++ here. +/// +/// @param mangledName is the symbol name to be demangled. +/// @param mangledNameLength is the length of this name. +/// @param outputBuffer is a pointer to a buffer in which to place the result. +/// @param outputBufferSize points to a variable that contains the size of the +/// output buffer. +/// @param status returns the status codes defined in the C++ ABI. +/// +/// If outputBuffer is nullptr, the function will allocate memory for the +/// result using malloc(). In this case, outputBufferSize may be nullptr; +/// if it is *not* nullptr, it will be set to the size of buffer that was +/// allocated. This is not necessarily the length of the string (it may be +/// somewhat higher). +/// +/// Otherwise, the result will be written into the output buffer, and the +/// size of the result will be written into outputBufferSize. If the buffer +/// is too small, the result will be truncated, but outputBufferSize will +/// still be set to the number of bytes that would have been required to +/// copy out the full result (including a trailing NUL). +/// +/// The unusual behaviour here is a consequence of the way the C++ ABI's +/// demangling function works. +/// +/// @returns a pointer to the output if demangling was successful. SWIFT_RUNTIME_STDLIB_SPI char *_swift_backtrace_demangle(const char *mangledName, size_t mangledNameLength, diff --git a/stdlib/public/runtime/Backtrace.cpp b/stdlib/public/runtime/Backtrace.cpp index be20e4fcb8705..104aa3be99237 100644 --- a/stdlib/public/runtime/Backtrace.cpp +++ b/stdlib/public/runtime/Backtrace.cpp @@ -850,33 +850,7 @@ _swift_isThunkFunction(const char *mangledName) { return ctx.isThunkSymbol(mangledName); } -/// Try to demangle a symbol. -/// -/// Unlike other entry points that do this, we try both Swift and C++ here. -/// -/// @param mangledName is the symbol name to be demangled. -/// @param mangledNameLength is the length of this name. -/// @param outputBuffer is a pointer to a buffer in which to place the result. -/// @param outputBufferSize points to a variable that contains the size of the -/// output buffer. -/// @param status returns the status codes defined in the C++ ABI. -/// -/// If outputBuffer is nullptr, the function will allocate memory for the -/// result using malloc(). In this case, outputBufferSize may be nullptr; -/// if it is *not* nullptr, it will be set to the size of buffer that was -/// allocated. This is not necessarily the length of the string (it may be -/// somewhat higher). -/// -/// Otherwise, the result will be written into the output buffer, and the -/// size of the result will be written into outputBufferSize. If the buffer -/// is too small, the result will be truncated, but outputBufferSize will -/// still be set to the number of bytes that would have been required to -/// copy out the full result (including a trailing NUL). -/// -/// The unusual behaviour here is a consequence of the way the C++ ABI's -/// demangling function works. -/// -/// @returns a pointer to the output if demangling was successful. +// Try to demangle a symbol. SWIFT_RUNTIME_STDLIB_SPI char * _swift_backtrace_demangle(const char *mangledName, size_t mangledNameLength,