diff --git a/CMakeLists.txt b/CMakeLists.txt index 6396be8040ba2..7a3f6be48d7b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,22 @@ else() set(CMAKE_JOB_POOL_LINK local_jobs) endif() -ENABLE_LANGUAGE(C) +enable_language(C) +enable_language(CXX) + +# On Windows, use MASM or MARMASM +set(SWIFT_ASM_DIALECT ASM) +set(SWIFT_ASM_EXT S) +if(CMAKE_SYSTEM_NAME STREQUAL Windows) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64") + set(SWIFT_ASM_DIALECT ASM_MARMASM) + else() + set(SWIFT_ASM_DIALECT ASM_MASM) + endif() + set(SWIFT_ASM_EXT asm) +endif() + +enable_language(${SWIFT_ASM_DIALECT}) # Use C++14. set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard to conform to") @@ -560,6 +575,10 @@ option(SWIFT_IMPLICIT_CONCURRENCY_IMPORT "Implicitly import the Swift concurrency module" TRUE) +option(SWIFT_IMPLICIT_BACKTRACING_IMPORT + "Implicitly import the Swift backtracing module" + FALSE) + option(SWIFT_ENABLE_EXPERIMENTAL_CONCURRENCY "Enable build of the Swift concurrency module" FALSE) @@ -618,7 +637,7 @@ if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") endif() if(SWIFT_BUILT_STANDALONE) - project(Swift C CXX ASM) + project(Swift C CXX ${SWIFT_ASM_DIALECT}) endif() if(MSVC OR "${CMAKE_SIMULATE_ID}" STREQUAL MSVC) @@ -688,6 +707,7 @@ execute_process(COMMAND ${CMAKE_MAKE_PROGRAM} ${version_flag} message(STATUS "CMake Make Program (${CMAKE_MAKE_PROGRAM}) Version: ${_CMAKE_MAKE_PROGRAM_VERSION}") message(STATUS "C Compiler (${CMAKE_C_COMPILER}) Version: ${CMAKE_C_COMPILER_VERSION}") message(STATUS "C++ Compiler (${CMAKE_CXX_COMPILER}) Version: ${CMAKE_CXX_COMPILER_VERSION}") +message(STATUS "Assembler (${CMAKE_${SWIFT_ASM_DIALECT}_COMPILER}) Version: ${CMAKE_${SWIFT_ASM_DIALECT}_COMPILER_VERSION}") if (CMAKE_Swift_COMPILER) message(STATUS "Swift Compiler (${CMAKE_Swift_COMPILER}) Version: ${CMAKE_Swift_COMPILER_VERSION}") else() diff --git a/cmake/modules/SwiftImplicitImport.cmake b/cmake/modules/SwiftImplicitImport.cmake new file mode 100644 index 0000000000000..1dbc8bcecb2dc --- /dev/null +++ b/cmake/modules/SwiftImplicitImport.cmake @@ -0,0 +1,20 @@ +# Test if the Swift compiler supports -disable-implicit--module-import +function(swift_supports_implicit_module module_name out_var) + file(WRITE "${CMAKE_BINARY_DIR}/tmp/empty-check-${module_name}.swift" "") + execute_process( + COMMAND + "${CMAKE_Swift_COMPILER}" + -Xfrontend -disable-implicit-${module_name}-module-import + -c - -o /dev/null + INPUT_FILE + "${CMAKE_BINARY_DIR}/tmp/empty-check-${module_name}.swift" + OUTPUT_QUIET ERROR_QUIET + RESULT_VARIABLE + result + ) + if(NOT result) + set("${out_var}" "TRUE" PARENT_SCOPE) + else() + set("${out_var}" "FALSE" PARENT_SCOPE) + endif() +endfunction() diff --git a/docs/Backtracing.rst b/docs/Backtracing.rst new file mode 100644 index 0000000000000..b0c8cc7d13ee1 --- /dev/null +++ b/docs/Backtracing.rst @@ -0,0 +1,227 @@ +Backtracing support in Swift +============================ + +When things go wrong, it's always useful to be able to get a backtrace showing +where the problem occurred in your program. + +Broadly speaking there are three circumstances where you might want a backtrace, +namely: + + * Program crashes + * Runtime errors + * Specific user-defined program events + +Historically, Swift has tended to lean on operating system crash catching +support for the first two of these, and hasn't really provided any built-in +support for the latter. This is fine for Darwin, where the operating system +provides a comprehensive system-wide crash catching facility; it's just about OK +on Windows, which also has system-wide crash logging; but it isn't great +elsewhere, in particular on Linux where a lot of server-side Swift programs +currently rely on a separate package to provide them with some level of +backtrace support when errors happen. + +What does Swift now support? +---------------------------- + +Swift now supports: + + * Automatic crash catching and backtrace generation out of the box. + * Built-in symbolication. + * A choice of unwind algorithms, including "fast", DWARF and SEH. + * Interactive(!) crash/runtime error catching. + +Crash catching is enabled by default, and won't interfere with any system-wide +crash reporters you might be using. + +How do I configure backtracing? +------------------------------- + +There is an environment variable, ``SWIFT_BACKTRACE``, that can be used to +configure Swift's crash catching and backtracing support. The variable should +contain a ``,``-separated list of ``key=value`` pairs. Supported keys are as +follows: + ++-----------------+---------+--------------------------------------------------+ +| Key | Default | Meaning | ++=================+=========+==================================================+ +| enable | yes* | Set to ``no`` to disable crash catching, or | +| | | ``tty`` to enable only if stdin is a terminal. | ++-----------------+---------+--------------------------------------------------+ +| demangle | yes | Set to ``no`` to disable demangling. | ++-----------------+---------+--------------------------------------------------+ +| interactive | tty | Set to ``no`` to disable interaction, or ``yes`` | +| | | to enable always. | ++-----------------+---------+--------------------------------------------------+ +| color | tty | Set to ``yes`` to enable always, or ``no`` to | +| | | disable. Uses ANSI escape sequences. | ++-----------------+---------+--------------------------------------------------+ +| timeout | 30s | Time to wait for interaction when a crash | +| | | occurs. Setting this to ``none`` or ``0s`` will | +| | | disable interaction. | ++-----------------+---------+--------------------------------------------------+ +| unwind | auto | Specifies which unwind algorithm to use. | +| | | ``auto`` means to choose appropriately for the | +| | | platform. Other options are ``fast``, which | +| | | does a naïve stack walk; and ``precise``, which | +| | | uses exception handling data to perform an | +| | | unwind. | ++-----------------+---------+--------------------------------------------------+ +| preset | auto | Specifies which set of preset formatting options | +| | | to use. Options are ``friendly``, ``medium`` or | +| | | ``full``. ``auto`` means to use ``friendly`` if | +| | | interactive, and ``full`` otherwise. | ++-----------------+---------+--------------------------------------------------+ +| sanitize | preset | If ``yes``, we will try to process paths to | +| | | remove PII. Exact behaviour is platform | +| | | dependent. | ++-----------------+---------+--------------------------------------------------+ +| threads | preset | Options are ``all`` to show backtraces for every | +| | | thread, or ``crashed`` to show only the crashing | +| | | thread. | ++-----------------+---------+--------------------------------------------------+ +| registers | preset | Options are ``none``, ``all`` or ``crashed``. | ++-----------------+---------+--------------------------------------------------+ +| images | preset | Options are ``none``, ``all``, or ``mentioned``, | +| | | which only displays images mentioned in a | +| | | backtrace. | ++-----------------+---------+--------------------------------------------------+ +| limit | 64 | Limits the length of the captured backtrace. See | +| | | below for a discussion of its behaviour. Can be | +| | | set to ``none`` to mean no limit. | ++-----------------+---------+--------------------------------------------------+ +| top | 16 | Specify a minimum number of frames to capture | +| | | from the top of the stack. See below for more. | ++-----------------+---------+--------------------------------------------------+ +| cache | yes | Set to ``no`` to disable symbol caching. This | +| | | only has effect on platforms that have a symbol | +| | | cache that can be controlled by the runtime. | ++-----------------+---------+--------------------------------------------------+ +| swift-backtrace | | If specified, gives the full path to the | +| | | swift-backtrace binary to use for crashes. | +| | | Otherwise, Swift will locate the binary relative | +| | | to the runtime library, or using ``SWIFT_ROOT``. | ++-----------------+---------+--------------------------------------------------+ + +(*) On macOS, this defaults to ``tty`` rather than ``yes``. + +Backtrace limits +---------------- + +The limit settings are provided both to prevent runaway backtraces and to allow +for a sensible backtrace to be produced even when a function has blown the stack +through excessive recursion. + +Typically in the latter case you want to capture some frames at the top of the +stack so that you can see how the recursion was entered, and the frames at the +bottom of the stack where the actual fault occurred. + +1. There are ``limit`` or fewer frames. In this case we will display all + the frames in the backtrace. Note that this _includes_ the case where there + are exactly ``limit`` frames. + +2. There are more than ``limit`` frames. + + a. ``top`` is ``0``. We will display the first ``limit - 1`` frames followed + by ``...`` to indicate that more frames exist. + + b. ``top`` is less than ``limit - 1``. We will display ``limit - 1 - top`` + frames from the bottom of the stack, then a ``...``, then ``top`` frames + from the top of the stack. + + c. ``top`` is greater or equal to ``limit - 1``. We will display ``...``, + followed by ``limit - 1`` frames from the top of the stack. + +For example, let's say we have a stack containing 10 frames numbered here 1 to +10, with 10 being the innermost frame. With ``limit`` set to 5, you would see:: + + 10 + 9 + 8 + 7 + ... + +With ``limit`` set to 5 and ``top`` to 2, you would instead see:: + + 10 + 9 + ... + 2 + 1 + +And with ``limit`` set to 5 and ``top`` to 4 or above, you would see:: + + ... + 4 + 3 + 2 + 1 + +What is the swift-backtrace binary? +----------------------------------- + +``swift-backtrace`` is a program that gets invoked when your program crashes. +We do this because when a program crashes, it is potentially in an invalid state +and there is very little that is safe for us to do. By executing an external +helper program, we ensure that we do not interfere with the way the program was +going to crash (so that system-wide crash catchers will still generate the +correct information), and we are also able to use any functionality we need to +generate a decent backtrace, including symbolication (which might in general +require memory allocation, fetching and reading remote files and so on). + +You shouldn't try to run ``swift-backtrace`` yourself; it has unusual +requirements, which vary from platform to platform. Instead, it will be +triggered automatically by the runtime. + +System specifics +---------------- + +macOS +^^^^^ + +On macOS, we catch crashes and other events using a signal handler. At time of +writing, this is installed for the following signals: + ++--------------+--------------------------+-------------------------------------+ +| Signal | Description | Comment | ++====+=========+==========================+=====================================+ +| 3 | SIGQUIT | Quit program | | ++----+---------+--------------------------+-------------------------------------+ +| 4 | SIGILL | Illegal instruction | | ++----+---------+--------------------------+-------------------------------------+ +| 5 | SIGTRAP | Trace trap | | ++----+---------+--------------------------+-------------------------------------+ +| 6 | SIGABRT | Abort program | | ++----+---------+--------------------------+-------------------------------------+ +| 8 | SIGFPE | Floating point exception | On Intel, integer divide by zero | +| | | | also triggers this. | ++----+---------+--------------------------+-------------------------------------+ +| 10 | SIGBUS | Bus error | | ++----+---------+--------------------------+-------------------------------------+ +| 11 | SIGSEGV | Segmentation violation | | ++----+---------+--------------------------+-------------------------------------+ + +If crash catching is enabled, the signal handler will be installed for any +process that links the Swift runtime. If you replace the handlers for any of +these signals, your program will no longer produce backtraces for program +failures that lead to the handler you have replaced. + +Additionally, the runtime will configure an alternate signal handling stack, so +that stack overflows can be successfully trapped. + +Note that the runtime will not install its signal handlers for a signal if it +finds that there is already a handler for that signal. Similarly if something +else has already configured an alternate signal stack, it will leave that +stack alone. + +Once the backtracer has finished handling the crash, it will allow the crashing +program to continue and crash normally, which will result in the usual Crash +Reporter log file being generated. + +Crash catching *cannot* be enabled for setuid binaries. This is intentional as +doing so might create a security hole. + +Other Darwin (iOS, tvOS) +^^^^^^^^^^^^^^^^^^^^^^^^ + +Crash catching is not enabled for non-macOS Darwin. You should continue to look +at the system-provided crash logs. diff --git a/docs/README.md b/docs/README.md index d0186877afdea..5cdda2f94b11f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -136,6 +136,9 @@ documentation, please create a thread on the Swift forums under the operations on [currency](/docs/Lexicon.md#currency-type) data types and optimizes accordingly. Includes a thorough discussion of the `@_semantics` attribute. +- Runtime specifics: + - [Backtracing.rst](/docs/Backtracing.rst): + Describes Swift's backtracing and crash catching support. ### SourceKit subsystems diff --git a/include/swift/AST/DiagnosticsFrontend.def b/include/swift/AST/DiagnosticsFrontend.def index 23ed5867c8f22..744d0d3177e80 100644 --- a/include/swift/AST/DiagnosticsFrontend.def +++ b/include/swift/AST/DiagnosticsFrontend.def @@ -172,6 +172,8 @@ WARNING(warn_implicit_concurrency_import_failed,none, "unable to perform implicit import of \"_Concurrency\" module: no such module found", ()) REMARK(warn_implicit_string_processing_import_failed,none, "unable to perform implicit import of \"_StringProcessing\" module: no such module found", ()) +REMARK(warn_implicit_backtracing_import_failed,none, + "unable to perform implicit import of \"_Backtracing\" module: no such module found", ()) ERROR(error_module_name_required,none, "-module-name is required", ()) ERROR(error_bad_module_name,none, diff --git a/include/swift/AST/KnownIdentifiers.def b/include/swift/AST/KnownIdentifiers.def index 57cbab1f95226..d91b4b6a4a9a9 100644 --- a/include/swift/AST/KnownIdentifiers.def +++ b/include/swift/AST/KnownIdentifiers.def @@ -263,6 +263,9 @@ IDENTIFIER(version) IDENTIFIER_(StringProcessing) IDENTIFIER(RegexBuilder) +// Backtracing +IDENTIFIER_(Backtracing) + // Distributed actors IDENTIFIER(ActorID) IDENTIFIER(ActorSystem) diff --git a/include/swift/Basic/LangOptions.h b/include/swift/Basic/LangOptions.h index 5449838eac1b9..695533e6774e4 100644 --- a/include/swift/Basic/LangOptions.h +++ b/include/swift/Basic/LangOptions.h @@ -352,6 +352,10 @@ namespace swift { /// Disable the implicit import of the _StringProcessing module. bool DisableImplicitStringProcessingModuleImport = false; + /// Disable the implicit import of the _Backtracing module. + bool DisableImplicitBacktracingModuleImport = + !SWIFT_IMPLICIT_BACKTRACING_IMPORT; + /// Should we check the target OSs of serialized modules to see that they're /// new enough? bool EnableTargetOSChecking = true; diff --git a/include/swift/Config.h.in b/include/swift/Config.h.in index 495c34d1fefb7..3e26daaaec5f7 100644 --- a/include/swift/Config.h.in +++ b/include/swift/Config.h.in @@ -10,6 +10,8 @@ #cmakedefine01 SWIFT_IMPLICIT_CONCURRENCY_IMPORT +#cmakedefine01 SWIFT_IMPLICIT_BACKTRACING_IMPORT + #cmakedefine01 SWIFT_ENABLE_EXPERIMENTAL_DISTRIBUTED #cmakedefine01 SWIFT_ENABLE_GLOBAL_ISEL_ARM64 diff --git a/include/swift/Frontend/Frontend.h b/include/swift/Frontend/Frontend.h index 4ea8e4c963a40..30785440fa5fe 100644 --- a/include/swift/Frontend/Frontend.h +++ b/include/swift/Frontend/Frontend.h @@ -380,6 +380,10 @@ class CompilerInvocation { /// imported. bool shouldImportSwiftStringProcessing() const; + /// Whether the Swift Backtracing support library should be implicitly + /// imported. + bool shouldImportSwiftBacktracing() const; + /// Performs input setup common to these tools: /// sil-opt, sil-func-extractor, sil-llvm-gen, and sil-nm. /// Return value includes the buffer so caller can keep it alive. @@ -575,6 +579,14 @@ class CompilerInstance { /// i.e. if it can be found. bool canImportSwiftStringProcessing() const; + /// Verify that if an implicit import of the `Backtracing` module if + /// expected, it can actually be imported. Emit a warning, otherwise. + void verifyImplicitBacktracingImport(); + + /// Whether the Swift Backtracing support library can be imported + /// i.e. if it can be found. + bool canImportSwiftBacktracing() const; + /// Whether the CxxShim library can be imported /// i.e. if it can be found. bool canImportCxxShim() const; diff --git a/include/swift/Option/FrontendOptions.td b/include/swift/Option/FrontendOptions.td index 3b98cd4dd0c0d..0e942767a3867 100644 --- a/include/swift/Option/FrontendOptions.td +++ b/include/swift/Option/FrontendOptions.td @@ -441,6 +441,14 @@ def disable_implicit_string_processing_module_import : Flag<["-"], "disable-implicit-string-processing-module-import">, HelpText<"Disable the implicit import of the _StringProcessing module.">; +def enable_implicit_backtracing_module_import : Flag<["-"], + "enable-implicit-backtracing-module-import">, + HelpText<"Enable the implicit import of the _Backtracing module.">; + +def disable_implicit_backtracing_module_import : Flag<["-"], + "disable-implicit-backtracing-module-import">, + HelpText<"Disable the implicit import of the _Backtracing module.">; + def disable_arc_opts : Flag<["-"], "disable-arc-opts">, HelpText<"Don't run SIL ARC optimization passes.">; def disable_ossa_opts : Flag<["-"], "disable-ossa-opts">, diff --git a/include/swift/Runtime/Backtrace.h b/include/swift/Runtime/Backtrace.h new file mode 100644 index 0000000000000..0328c3ff150e2 --- /dev/null +++ b/include/swift/Runtime/Backtrace.h @@ -0,0 +1,128 @@ +//===--- Backtrace.cpp - Swift crash catching and backtracing support ---- ===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Definitions relating to backtracing. +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_RUNTIME_BACKTRACE_H +#define SWIFT_RUNTIME_BACKTRACE_H + +#include "swift/Runtime/Config.h" + +#include "swift/shims/Visibility.h" +#include "swift/shims/_SwiftBacktracing.h" + +#include + +#ifdef _WIN32 +// For DWORD +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include + +// For wchar_t +#include +#endif + +#ifdef __cplusplus +namespace swift { +namespace runtime { +namespace backtrace { +#endif + +#ifdef _WIN32 +typedef wchar_t ArgChar; +typedef DWORD ErrorCode; +#else +typedef char ArgChar; +typedef int ErrorCode; +#endif + +SWIFT_RUNTIME_STDLIB_INTERNAL ErrorCode _swift_installCrashHandler(); + +SWIFT_RUNTIME_STDLIB_INTERNAL bool _swift_spawnBacktracer(const ArgChar * const *argv); + +enum class UnwindAlgorithm { + Auto = 0, + Fast = 1, + Precise = 2 +}; + +enum class OnOffTty { + Off = 0, + On = 1, + TTY = 2 +}; + +enum class Preset { + Auto = -1, + Friendly = 0, + Medium = 1, + Full = 2 +}; + +enum class ThreadsToShow { + Preset = -1, + All = 0, + Crashed = 1 +}; + +enum class RegistersToShow { + Preset = -1, + None = 0, + All = 1, + Crashed = 2 +}; + +enum class ImagesToShow { + Preset = -1, + None = 0, + All = 1, + Mentioned = 2 +}; + +enum class SanitizePaths { + Preset = -1, + Off = 0, + On = 1 +}; + +struct BacktraceSettings { + UnwindAlgorithm algorithm; + OnOffTty enabled; + bool demangle; + OnOffTty interactive; + OnOffTty color; + unsigned timeout; + ThreadsToShow threads; + RegistersToShow registers; + ImagesToShow images; + unsigned limit; + unsigned top; + SanitizePaths sanitize; + Preset preset; + bool cache; + const char *swiftBacktracePath; +}; + +SWIFT_RUNTIME_STDLIB_INTERNAL BacktraceSettings _swift_backtraceSettings; + +SWIFT_RUNTIME_STDLIB_SPI SWIFT_CC(swift) bool _swift_isThunkFunction(const char *mangledName); + +#ifdef __cplusplus +} // namespace backtrace +} // namespace runtime +} // namespace swift +#endif + +#endif // SWIFT_RUNTIME_BACKTRACE_H diff --git a/include/swift/Runtime/Config.h b/include/swift/Runtime/Config.h index 28d6dd86b8fa5..ac44146508516 100644 --- a/include/swift/Runtime/Config.h +++ b/include/swift/Runtime/Config.h @@ -481,6 +481,34 @@ swift_auth_code(T value, unsigned extra) { #endif } +/// Does this platform support backtrace-on-crash? +#ifdef __APPLE__ +# include +# if TARGET_OS_OSX +# define SWIFT_BACKTRACE_ON_CRASH_SUPPORTED 1 +# define SWIFT_BACKTRACE_SECTION "__DATA,swift5_backtrace" +# else +# define SWIFT_BACKTRACE_ON_CRASH_SUPPORTED 0 +# endif +#elif defined(_WIN32) +# define SWIFT_BACKTRACE_ON_CRASH_SUPPORTED 0 +# define SWIFT_BACKTRACE_SECTION ".sw5bckt" +#elif defined(__linux__) +# define SWIFT_BACKTRACE_ON_CRASH_SUPPORTED 0 +# define SWIFT_BACKTRACE_SECTION "swift5_backtrace" +#else +# define SWIFT_BACKTRACE_ON_CRASH_SUPPORTED 0 +#endif + +/// What is the system page size? +#if defined(__APPLE__) && defined(__arm64__) + // Apple Silicon systems use a 16KB page size + #define SWIFT_PAGE_SIZE 16384 +#else + // Everything else uses 4KB pages + #define SWIFT_PAGE_SIZE 4096 +#endif + #endif #endif // SWIFT_RUNTIME_CONFIG_H diff --git a/include/swift/Strings.h b/include/swift/Strings.h index 745e2c3e2def8..ddb8d4fb15796 100644 --- a/include/swift/Strings.h +++ b/include/swift/Strings.h @@ -30,6 +30,8 @@ constexpr static const StringLiteral SWIFT_CONCURRENCY_SHIMS_NAME = "_SwiftConcu constexpr static const StringLiteral SWIFT_DISTRIBUTED_NAME = "Distributed"; /// The name of the StringProcessing module, which supports that extension. constexpr static const StringLiteral SWIFT_STRING_PROCESSING_NAME = "_StringProcessing"; +/// The name of the Backtracing module, which supports that extension. +constexpr static const StringLiteral SWIFT_BACKTRACING_NAME = "_Backtracing"; /// The name of the SwiftShims module, which contains private stdlib decls. constexpr static const StringLiteral SWIFT_SHIMS_NAME = "SwiftShims"; /// The name of the CxxShim module, which contains a cxx casting utility. diff --git a/lib/AST/NameLookup.cpp b/lib/AST/NameLookup.cpp index 768f77dcacae9..f2d95dd4a5b14 100644 --- a/lib/AST/NameLookup.cpp +++ b/lib/AST/NameLookup.cpp @@ -662,6 +662,22 @@ static void recordShadowedDeclsAfterTypeMatch( } } + // Next, prefer any other module over the _Backtracing module. + if (auto spModule = ctx.getLoadedModule(ctx.Id_Backtracing)) { + if ((firstModule == spModule) != (secondModule == spModule)) { + // If second module is _StringProcessing, then it is shadowed by + // first. + if (secondModule == spModule) { + shadowed.insert(secondDecl); + continue; + } + + // Otherwise, the first declaration is shadowed by the second. + shadowed.insert(firstDecl); + break; + } + } + // The Foundation overlay introduced Data.withUnsafeBytes, which is // treated as being ambiguous with SwiftNIO's Data.withUnsafeBytes // extension. Apply a special-case name shadowing rule to use the diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 5a12424cdf8a6..3687b86a4c3bb 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -15,22 +15,18 @@ list(APPEND LLVM_COMMON_DEPENDS intrinsics_gen clang-tablegen-targets) # Add generated Swift Syntax headers to global dependencies. list(APPEND LLVM_COMMON_DEPENDS swift-ast-generated-headers) +include(SwiftImplicitImport) + # Set up for linking against swift-syntax. if (SWIFT_SWIFT_PARSER) - # Pure-Swift host libraries need to not link the _StringPreprocessing - # module. Check whether the host compiler is new enough that we can pass this - # flag. - file(WRITE "${CMAKE_BINARY_DIR}/tmp/empty-check-string-processing.swift" "") - execute_process( - COMMAND - "${CMAKE_Swift_COMPILER}" - -Xfrontend -disable-implicit-string-processing-module-import - -c - -o /dev/null - INPUT_FILE - "${CMAKE_BINARY_DIR}/tmp/empty-check-string-processing.swift" - OUTPUT_QUIET ERROR_QUIET - RESULT_VARIABLE - SWIFT_SUPPORTS_DISABLE_IMPLICIT_STRING_PROCESSING_MODULE_IMPORT) + # Ensure that we do not link the _StringProcessing module. But we can + # only pass this flag for new-enough compilers that support it. + swift_supports_implicit_module("string-processing" + SWIFT_SUPPORTS_DISABLE_IMPLICIT_STRING_PROCESSING_MODULE_IMPORT) + + # Same for _Backtracing + swift_supports_implicit_module("backtracing" + SWIFT_SUPPORTS_DISABLE_IMPLICIT_BACKTRACING_MODULE_IMPORT) # Set up linking against the swift-syntax modules. # Link against the swift-syntax modules. @@ -194,12 +190,19 @@ function(add_pure_swift_host_library name) add_library(${name} ${libkind} ${APSHL_SOURCES}) # Avoid introducing an implicit dependency on the string-processing library. - if (NOT SWIFT_SUPPORTS_DISABLE_IMPLICIT_STRING_PROCESSING_MODULE_IMPORT) + if(SWIFT_SUPPORTS_DISABLE_IMPLICIT_STRING_PROCESSING_MODULE_IMPORT) target_compile_options(${name} PRIVATE $<$:-Xfrontend> $<$:-disable-implicit-string-processing-module-import>) endif() + # Same for backtracing + if (SWIFT_SUPPORTS_DISABLE_IMPLICIT_BACKTRACING_MODULE_IMPORT) + target_compile_options(${name} PRIVATE + $<$:-Xfrontend> + $<$:-disable-implicit-backtracing-module-import>) + endif() + # The compat56 library is not available in current toolchains. The stage-0 # compiler will build fine since the builder compiler is not aware of the 56 # compat library, but the stage-1 and subsequent stage compilers will fail as diff --git a/lib/DriverTool/autolink_extract_main.cpp b/lib/DriverTool/autolink_extract_main.cpp index 3d6b7fdb8a2e1..efcbbb1e096ab 100644 --- a/lib/DriverTool/autolink_extract_main.cpp +++ b/lib/DriverTool/autolink_extract_main.cpp @@ -250,7 +250,8 @@ int autolink_extract_main(ArrayRef Args, const char *Argv0, {"-lswiftCore", false}, {"-lswift_Concurrency", false}, {"-lswift_StringProcessing", false}, - {"-lswift_RegexParser", false} + {"-lswift_RegexParser", false}, + {"-lswift_Backtracing", false}, }; // Extract the linker flags from the objects. diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index dab058962bd92..0e29265589355 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -487,6 +487,11 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args, Opts.DisableImplicitStringProcessingModuleImport |= Args.hasArg(OPT_disable_implicit_string_processing_module_import); + Opts.DisableImplicitBacktracingModuleImport = + Args.hasFlag(OPT_disable_implicit_backtracing_module_import, + OPT_enable_implicit_backtracing_module_import, + true); + if (Args.hasArg(OPT_enable_experimental_async_top_level)) Diags.diagnose(SourceLoc(), diag::warn_flag_deprecated, "-enable-experimental-async-top-level"); diff --git a/lib/Frontend/Frontend.cpp b/lib/Frontend/Frontend.cpp index 314ed51395ba1..459546c2b4250 100644 --- a/lib/Frontend/Frontend.cpp +++ b/lib/Frontend/Frontend.cpp @@ -848,6 +848,20 @@ bool CompilerInvocation::shouldImportSwiftStringProcessing() const { FrontendOptions::ParseInputMode::SwiftModuleInterface; } +/// Enable Swift backtracing on a per-target basis +static bool shouldImportSwiftBacktracingByDefault(const llvm::Triple &target) { + if (target.isOSDarwin() || target.isOSWindows() || target.isOSLinux()) + return true; + return false; +} + +bool CompilerInvocation::shouldImportSwiftBacktracing() const { + return shouldImportSwiftBacktracingByDefault(getLangOptions().Target) && + !getLangOptions().DisableImplicitBacktracingModuleImport && + getFrontendOptions().InputMode != + FrontendOptions::ParseInputMode::SwiftModuleInterface; +} + /// Implicitly import the SwiftOnoneSupport module in non-optimized /// builds. This allows for use of popular specialized functions /// from the standard library, which makes the non-optimized builds @@ -912,6 +926,21 @@ bool CompilerInstance::canImportSwiftStringProcessing() const { return getASTContext().canImportModule(modulePath); } +void CompilerInstance::verifyImplicitBacktracingImport() { + if (Invocation.shouldImportSwiftBacktracing() && + !canImportSwiftBacktracing()) { + Diagnostics.diagnose(SourceLoc(), + diag::warn_implicit_backtracing_import_failed); + } +} + +bool CompilerInstance::canImportSwiftBacktracing() const { + ImportPath::Module::Builder builder( + getASTContext().getIdentifier(SWIFT_BACKTRACING_NAME)); + auto modulePath = builder.get(); + return getASTContext().canImportModule(modulePath); +} + bool CompilerInstance::canImportCxxShim() const { ImportPath::Module::Builder builder( getASTContext().getIdentifier(CXX_SHIM_NAME)); @@ -976,6 +1005,19 @@ ImplicitImportInfo CompilerInstance::getImplicitImportInfo() const { } } + if (Invocation.shouldImportSwiftBacktracing()) { + switch (imports.StdlibKind) { + case ImplicitStdlibKind::Builtin: + case ImplicitStdlibKind::None: + break; + + case ImplicitStdlibKind::Stdlib: + if (canImportSwiftBacktracing()) + pushImport(SWIFT_BACKTRACING_NAME); + break; + } + } + if (Invocation.getLangOptions().EnableCXXInterop && canImportCxxShim()) { pushImport(CXX_SHIM_NAME); } diff --git a/stdlib/cmake/modules/AddSwiftStdlib.cmake b/stdlib/cmake/modules/AddSwiftStdlib.cmake index 22f596afe8192..cbf82749bb95b 100644 --- a/stdlib/cmake/modules/AddSwiftStdlib.cmake +++ b/stdlib/cmake/modules/AddSwiftStdlib.cmake @@ -1824,10 +1824,9 @@ function(add_swift_target_library name) "-Xfrontend;-disable-implicit-string-processing-module-import") endif() - # Turn off implicit import of _StringProcessing when building libraries - if(SWIFT_ENABLE_EXPERIMENTAL_STRING_PROCESSING) - list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS - "-Xfrontend;-disable-implicit-string-processing-module-import") + # Turn off implicit import of _Backtracing when building libraries + if(SWIFT_IMPLICIT_BACKTRACING_IMPORT) + list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-Xfrontend;-disable-implicit-backtracing-module-import") endif() if(SWIFTLIB_IS_STDLIB AND SWIFT_STDLIB_ENABLE_PRESPECIALIZATION) @@ -2568,6 +2567,11 @@ function(_add_swift_target_executable_single name) string(MAKE_C_IDENTIFIER "${name}" module_name) + if(SWIFTEXE_SINGLE_SDK STREQUAL WINDOWS) + list(APPEND SWIFTEXE_SINGLE_COMPILE_FLAGS + -vfsoverlay;"${SWIFT_WINDOWS_VFS_OVERLAY}") + endif() + handle_swift_sources( dependency_target unused_module_dependency_target @@ -2603,7 +2607,6 @@ function(_add_swift_target_executable_single name) ${SWIFTEXE_SINGLE_ARCHITECTURE}_INCLUDE) target_include_directories(${name} SYSTEM PRIVATE ${${SWIFTEXE_SINGLE_ARCHITECTURE}_INCLUDE}) - if(NOT ${CMAKE_C_COMPILER_ID} STREQUAL MSVC) # MSVC doesn't support -Xclang. We don't need to manually specify # the dependent libraries as `cl` does so. @@ -2613,6 +2616,7 @@ function(_add_swift_target_executable_single name) "SHELL:-Xclang --dependent-lib=msvcrt$<$:d>") endif() endif() + target_compile_options(${name} PRIVATE ${c_compile_flags}) target_link_directories(${name} PRIVATE @@ -2676,6 +2680,8 @@ function(add_swift_target_executable name) SWIFT_MODULE_DEPENDS_FROM_SDK SWIFT_MODULE_DEPENDS_MACCATALYST SWIFT_MODULE_DEPENDS_MACCATALYST_UNZIPPERED + TARGET_SDKS + COMPILE_FLAGS ) # Parse the arguments we were given. @@ -2697,12 +2703,33 @@ function(add_swift_target_executable name) set(install_in_component "${SWIFTEXE_TARGET_INSTALL_IN_COMPONENT}") endif() + # Turn off implicit imports + list(APPEND SWIFTEXE_TARGET_COMPILE_FLAGS "-Xfrontend;-disable-implicit-concurrency-module-import") + + if(SWIFT_ENABLE_EXPERIMENTAL_STRING_PROCESSING) + list(APPEND SWIFTEXE_TARGET_COMPILE_FLAGS + "-Xfrontend;-disable-implicit-string-processing-module-import") + endif() + + if(SWIFT_IMPLICIT_BACKTRACING_IMPORT) + list(APPEND SWIFTEXE_TARGET_COMPILE_FLAGS "-Xfrontend;-disable-implicit-backtracing-module-import") + endif() + # All Swift executables depend on the standard library. list(APPEND SWIFTEXE_TARGET_SWIFT_MODULE_DEPENDS Core) # All Swift executables depend on the swiftSwiftOnoneSupport library. list(APPEND SWIFTEXE_TARGET_SWIFT_MODULE_DEPENDS SwiftOnoneSupport) - foreach(sdk ${SWIFT_SDKS}) + # If target SDKs are not specified, build for all known SDKs. + if("${SWIFTEXE_TARGET_TARGET_SDKS}" STREQUAL "") + set(SWIFTEXE_TARGET_TARGET_SDKS ${SWIFT_SDKS}) + endif() + list_replace(SWIFTEXE_TARGET_TARGET_SDKS ALL_APPLE_PLATFORMS "${SWIFT_DARWIN_PLATFORMS}") + + list_intersect( + "${SWIFTEXE_TARGET_TARGET_SDKS}" "${SWIFT_SDKS}" SWIFTEXE_TARGET_TARGET_SDKS) + + foreach(sdk ${SWIFTEXE_TARGET_TARGET_SDKS}) set(THIN_INPUT_TARGETS) # Collect architecture agnostic SDK module dependencies @@ -2834,6 +2861,8 @@ function(add_swift_target_executable name) ${swiftexe_module_dependency_targets} SDK "${sdk}" ARCHITECTURE "${arch}" + COMPILE_FLAGS + ${SWIFTEXE_TARGET_COMPILE_FLAGS} INSTALL_IN_COMPONENT ${install_in_component}) _list_add_string_suffix( diff --git a/stdlib/public/Backtracing/Backtrace.swift b/stdlib/public/Backtracing/Backtrace.swift new file mode 100644 index 0000000000000..1d244b1ee63a2 --- /dev/null +++ b/stdlib/public/Backtracing/Backtrace.swift @@ -0,0 +1,499 @@ +//===--- Backtrace.swift --------------------------------------*- swift -*-===// +// +// 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 `Backtrace` struct that represents a captured backtrace. +// +//===----------------------------------------------------------------------===// + +import Swift + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + +@_implementationOnly import Darwin.Mach +@_implementationOnly import _SwiftBacktracingShims + +#endif + +/// Holds a backtrace. +public struct Backtrace: CustomStringConvertible, Sendable { + /// The type of an address. + /// + /// This is intentionally _not_ a pointer, because you shouldn't be + /// dereferencing them; they may refer to some other process, for + /// example. + public typealias Address = UInt + + /// The unwind algorithm to use. + public enum UnwindAlgorithm { + /// Choose the most appropriate for the platform. + case auto + + /// Use the fastest viable method. + /// + /// Typically this means walking the frame pointers. + case fast + + /// Use the most precise available method. + /// + /// On Darwin and on ELF platforms, this will use EH unwind + /// information. On Windows, it will use Win32 API functions. + case precise + } + + /// Represents an individual frame in a backtrace. + public enum Frame: CustomStringConvertible, Sendable { + /// A program counter value. + /// + /// This might come from a signal handler, or an exception or some + /// other situation in which we have captured the actual program counter. + /// + /// These can be directly symbolicated, as-is, with no adjustment. + case programCounter(Address) + + /// A return address. + /// + /// Corresponds to a normal function call. + /// + /// Requires adjustment when symbolicating for a backtrace, because it + /// points at the address after the one that triggered the child frame. + case returnAddress(Address) + + /// An async resume point. + /// + /// Corresponds to an `await` in an async task. + /// + /// Can be directly symbolicated, as-is. + case asyncResumePoint(Address) + + /// Indicates a discontinuity in the backtrace. + /// + /// This occurs when you set a limit and a minimum number of frames at + /// the top. For example, if you set a limit of 10 frames and a minimum + /// of 4 top frames, but the backtrace generated 100 frames, you will see + /// + /// 0: frame 100 <----- bottom of call stack + /// 1: frame 99 + /// 2: frame 98 + /// 3: frame 97 + /// 4: frame 96 + /// 5: ... <----- omittedFrames(92) + /// 6: frame 3 + /// 7: frame 2 + /// 8: frame 1 + /// 9: frame 0 <----- top of call stack + /// + /// Note that the limit *includes* the discontinuity. + /// + /// This is good for handling cases involving deep recursion. + case omittedFrames(Int) + + /// Indicates a discontinuity of unknown length. + case truncated + + /// The program counter, without any adjustment. + public var originalProgramCounter: Address { + switch self { + case let .returnAddress(addr): + return addr + case let .programCounter(addr): + return addr + case let .asyncResumePoint(addr): + return addr + case .omittedFrames(_), .truncated: + return 0 + } + } + + /// The adjusted program counter to use for symbolication. + public var adjustedProgramCounter: Address { + switch self { + case let .returnAddress(addr): + return addr - 1 + case let .programCounter(addr): + return addr + case let .asyncResumePoint(addr): + return addr + case .omittedFrames(_), .truncated: + return 0 + } + } + + /// A textual description of this frame. + public var description: String { + switch self { + case let .programCounter(addr): + return "\(hex(addr))" + case let .returnAddress(addr): + return "\(hex(addr)) [ra]" + case let .asyncResumePoint(addr): + return "\(hex(addr)) [async]" + case .omittedFrames(_), .truncated: + return "..." + } + } + } + + /// Represents an image loaded in the process's address space + public struct Image: CustomStringConvertible, Sendable { + /// The name of the image (e.g. libswiftCore.dylib). + public var name: String + + /// The full path to the image (e.g. /usr/lib/swift/libswiftCore.dylib). + public var path: String + + /// The build ID of the image, as a byte array (note that the exact number + /// of bytes may vary, and that some images may not have a build ID). + public var buildID: [UInt8]? + + /// The base address of the image. + public var baseAddress: Backtrace.Address + + /// The end of the text segment in this image. + public var endOfText: Backtrace.Address + + /// Provide a textual description of an Image. + public var description: String { + if let buildID = self.buildID { + return "\(hex(baseAddress))-\(hex(endOfText)) \(hex(buildID)) \(name) \(path)" + } else { + return "\(hex(baseAddress))-\(hex(endOfText)) \(name) \(path)" + } + } + } + + /// A list of captured frame information. + public var frames: [Frame] + + /// A list of captured images. + /// + /// Some backtracing algorithms may require this information, in which case + /// it will be filled in by the `capture()` method. Other algorithms may + /// not, in which case it will be `nil` and you can capture an image list + /// separately yourself using `captureImages()`. + public var images: [Image]? + + /// Holds information about the shared cache. + public struct SharedCacheInfo: Sendable { + /// The UUID from the shared cache. + public var uuid: [UInt8] + + /// The base address of the shared cache. + public var baseAddress: Backtrace.Address + + /// Says whether there is in fact a shared cache. + public var noCache: Bool + } + + /// Information about the shared cache. + /// + /// Holds information about the shared cache. On Darwin only, this is + /// required for symbolication. On non-Darwin platforms it will always + /// be `nil`. + public var sharedCacheInfo: SharedCacheInfo? + + /// Capture a backtrace from the current program location. + /// + /// The `capture()` method itself will not be included in the backtrace; + /// i.e. the first frame will be the one in which `capture()` was called, + /// and its programCounter value will be the return address for the + /// `capture()` method call. + /// + /// @param algorithm Specifies which unwind mechanism to use. If this + /// is set to `.auto`, we will use the platform default. + /// @param limit The backtrace will include at most this number of + /// frames; you can set this to `nil` to remove the + /// limit completely if required. + /// @param offset Says how many frames to skip; this makes it easy to + /// wrap this API without having to inline things and + /// without including unnecessary frames in the backtrace. + /// @param top Sets the minimum number of frames to capture at the + /// top of the stack. + /// + /// @returns A new `Backtrace` struct. + @inline(never) + public static func capture(algorithm: UnwindAlgorithm = .auto, + limit: Int? = 64, + offset: Int = 0, + top: Int = 16) throws -> Backtrace { + // N.B. We use offset+1 here to skip this frame, rather than inlining + // this code into the client. + return try HostContext.withCurrentContext { ctx in + try capture(from: ctx, + using: UnsafeLocalMemoryReader(), + algorithm: algorithm, + limit: limit, + offset: offset + 1, + top: top) + } + } + + @_spi(Internal) + public static func capture(from context: some Context, + using memoryReader: some MemoryReader, + algorithm: UnwindAlgorithm = .auto, + limit: Int? = 64, + offset: Int = 0, + top: Int = 16) throws -> Backtrace { + switch algorithm { + // All of them, for now, use the frame pointer unwinder. In the long + // run, we should be using DWARF EH frame data for .precise. + case .auto, .fast, .precise: + let unwinder = + FramePointerUnwinder(context: context, memoryReader: memoryReader) + .dropFirst(offset) + + if let limit = limit { + if limit <= 0 { + return Backtrace(frames: [.truncated]) + } + + let realTop = top < limit ? top : limit - 1 + var iterator = unwinder.makeIterator() + var frames: [Frame] = [] + + // Capture frames normally until we hit limit + while let frame = iterator.next() { + if frames.count < limit { + frames.append(frame) + if frames.count == limit { + break + } + } + } + + if realTop == 0 { + if let _ = iterator.next() { + // More frames than we were asked for; replace the last + // one with a discontinuity + frames[limit - 1] = .truncated + } + + return Backtrace(frames: frames) + } else { + + // If we still have frames at this point, start tracking the + // last `realTop` frames in a circular buffer. + if let frame = iterator.next() { + let topSection = limit - realTop + var topFrames: [Frame] = [] + var topNdx = 0 + var omittedFrames = 0 + + topFrames.reserveCapacity(realTop) + topFrames.insert(contentsOf: frames.suffix(realTop - 1), at: 0) + topFrames.append(frame) + + while let frame = iterator.next() { + topFrames[topNdx] = frame + topNdx += 1 + omittedFrames += 1 + if topNdx >= realTop { + topNdx = 0 + } + } + + // Fix the backtrace to include a discontinuity followed by + // the contents of the circular buffer. + let firstPart = realTop - topNdx + let secondPart = topNdx + frames[topSection - 1] = .omittedFrames(omittedFrames) + + frames.replaceSubrange(topSection..<(topSection+firstPart), + with: topFrames.suffix(firstPart)) + frames.replaceSubrange((topSection+firstPart).. [Image] { + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + return captureImages(for: mach_task_self_) + #else + return [] + #endif + } + + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + private static func withDyldProcessInfo(for task: task_t, + fn: (OpaquePointer?) throws -> T) + rethrows -> T { + var kret: kern_return_t = KERN_SUCCESS + let dyldInfo = _dyld_process_info_create(task, 0, &kret) + + if kret != KERN_SUCCESS { + fatalError("error: cannot create dyld process info") + } + + defer { + _dyld_process_info_release(dyldInfo) + } + + return try fn(dyldInfo) + } + #endif + + @_spi(Internal) + public static func captureImages(for process: Any) -> [Image] { + var images: [Image] = [] + + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + let task = process as! task_t + + withDyldProcessInfo(for: task) { dyldInfo in + _dyld_process_info_for_each_image(dyldInfo) { + (machHeaderAddress, uuid, path) in + + if let path = path, let uuid = uuid { + let pathString = String(cString: path) + let theUUID = Array(UnsafeBufferPointer(start: uuid, + count: MemoryLayout.size)) + let name: String + if let slashIndex = pathString.lastIndex(of: "/") { + name = String(pathString.suffix(from: + pathString.index(after:slashIndex))) + } else { + name = pathString + } + + // Find the end of the __TEXT segment + var endOfText = machHeaderAddress + 4096 + + _dyld_process_info_for_each_segment(dyldInfo, machHeaderAddress) { + address, size, name in + + if let name = String(validatingUTF8: name!), name == "__TEXT" { + endOfText = address + size + } + } + + images.append(Image(name: name, + path: pathString, + buildID: theUUID, + baseAddress: Address(machHeaderAddress), + endOfText: Address(endOfText))) + } + } + } + #endif // os(macOS) || os(iOS) || os(watchOS) + + return images.sorted(by: { $0.baseAddress < $1.baseAddress }) + } + + /// Capture shared cache information. + /// + /// @returns A `SharedCacheInfo`. + public static func captureSharedCacheInfo() -> SharedCacheInfo? { + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + return captureSharedCacheInfo(for: mach_task_self_) + #else + return nil + #endif + } + + @_spi(Internal) + public static func captureSharedCacheInfo(for t: Any) -> SharedCacheInfo? { + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + let task = t as! task_t + return withDyldProcessInfo(for: task) { dyldInfo in + var cacheInfo = dyld_process_cache_info() + _dyld_process_info_get_cache(dyldInfo, &cacheInfo) + let theUUID = withUnsafePointer(to: cacheInfo.cacheUUID) { + Array(UnsafeRawBufferPointer(start: $0, + count: MemoryLayout.size)) + } + return SharedCacheInfo(uuid: theUUID, + baseAddress: Address(cacheInfo.cacheBaseAddress), + noCache: cacheInfo.noCache) + } + #else // !os(Darwin) + return nil + #endif + } + + /// Return a symbolicated version of the backtrace. + /// + /// @param images Specifies the set of images to use for symbolication. + /// If `nil`, the function will look to see if the `Backtrace` + /// has already captured images. If it has, those will be + /// used; otherwise we will capture images at this point. + /// + /// @param sharedCacheInfo Provides information about the location and + /// identity of the shared cache, if applicable. + /// + /// @param showInlineFrames If `true` and we know how on the platform we're + /// running on, add virtual frames to show inline + /// function calls. + /// + /// @param useSymbolCache If the system we are on has a symbol cache, + /// says whether or not to use it. + /// + /// @returns A new `SymbolicatedBacktrace`. + public func symbolicated(with images: [Image]? = nil, + sharedCacheInfo: SharedCacheInfo? = nil, + showInlineFrames: Bool = true, + useSymbolCache: Bool = true) + -> SymbolicatedBacktrace? { + return SymbolicatedBacktrace.symbolicate(backtrace: self, + images: images, + sharedCacheInfo: sharedCacheInfo, + showInlineFrames: showInlineFrames, + useSymbolCache: useSymbolCache) + } + + /// Provide a textual version of the backtrace. + public var description: String { + var lines: [String] = [] + + var n = 0 + for frame in frames { + lines.append("\(n)\t\(frame)") + switch frame { + case let .omittedFrames(count): + n += count + default: + n += 1 + } + } + + if let images = images { + lines.append("") + lines.append("Images:") + lines.append("") + for (n, image) in images.enumerated() { + lines.append("\(n)\t\(image)") + } + } + + #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) + if let sharedCacheInfo = sharedCacheInfo { + lines.append("") + lines.append("Shared Cache:") + lines.append("") + lines.append(" UUID: \(hex(sharedCacheInfo.uuid))") + lines.append(" Base: \(hex(sharedCacheInfo.baseAddress))") + } + #endif + + return lines.joined(separator: "\n") + } +} diff --git a/stdlib/public/Backtracing/BacktraceFormatter.swift b/stdlib/public/Backtracing/BacktraceFormatter.swift new file mode 100644 index 0000000000000..80672e5d7d40b --- /dev/null +++ b/stdlib/public/Backtracing/BacktraceFormatter.swift @@ -0,0 +1,985 @@ +//===--- BacktraceFormatter.swift -----------------------------*- swift -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Provides functionality to format backtraces, with various additional +// options. +// +//===----------------------------------------------------------------------===// + +import Swift + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_implementationOnly import Darwin +#elseif os(Windows) +@_implementationOnly import CRT +#elseif os(Linux) +@_implementationOnly import Glibc +#endif + +@_implementationOnly import _SwiftBacktracingShims + +/// A backtrace formatting theme. +@_spi(Formatting) +public protocol BacktraceFormattingTheme { + func frameIndex(_ s: String) -> String + func programCounter(_ s: String) -> String + func frameAttribute(_ s: String) -> String + func symbol(_ s: String) -> String + func offset(_ s: String) -> String + func sourceLocation(_ s: String) -> String + func lineNumber(_ s: String) -> String + func code(_ s: String) -> String + func crashedLineNumber(_ s: String) -> String + func crashedLine(_ s: String) -> String + func crashLocation() -> String + func imageName(_ s: String) -> String + func imageAddressRange(_ s: String) -> String + func imageBuildID(_ s: String) -> String + func imagePath(_ s: String) -> String +} + +extension BacktraceFormattingTheme { + public func frameIndex(_ s: String) -> String { return s } + public func programCounter(_ s: String) -> String { return s } + public func frameAttribute(_ s: String) -> String { return "[\(s)]" } + public func symbol(_ s: String) -> String { return s } + public func offset(_ s: String) -> String { return s } + public func sourceLocation(_ s: String) -> String { return s } + public func lineNumber(_ s: String) -> String { return " \(s)|" } + public func code(_ s: String) -> String { return s } + public func crashedLineNumber(_ s: String) -> String { return "*\(s)|" } + public func crashedLine(_ s: String) -> String { return s } + public func crashLocation() -> String { return "^" } + public func imageName(_ s: String) -> String { return s } + public func imageAddressRange(_ s: String) -> String { return s } + public func imageBuildID(_ s: String) -> String { return s } + public func imagePath(_ s: String) -> String { return s} +} + +/// Options for backtrace formatting. +/// +/// This is used by chaining modifiers, e.g. .theme(.color).showSourceCode(). +@_spi(Formatting) +public struct BacktraceFormattingOptions { + var _theme: BacktraceFormattingTheme = BacktraceFormatter.Themes.plain + var _showSourceCode: Bool = false + var _sourceContextLines: Int = 2 + var _showAddresses: Bool = true + var _showImages: ImagesToShow = .mentioned + var _showImageNames: Bool = true + var _showFrameAttributes: Bool = true + var _skipRuntimeFailures: Bool = false + var _skipThunkFunctions: Bool = true + var _skipSystemFrames: Bool = true + var _sanitizePaths: Bool = true + var _demangle: Bool = true + var _width: Int = 80 + + public var selectedTheme: BacktraceFormattingTheme { return _theme } + public var shouldShowSourceCode: Bool { return _showSourceCode } + public var sourceContextLines: Int { return _sourceContextLines } + public var shouldShowAddresses: Bool { return _showAddresses } + public var imagesToShow: ImagesToShow { return _showImages } + public var shouldShowImageNames: Bool { return _showImageNames } + public var shouldShowFrameAttributes: Bool { return _showFrameAttributes } + public var shouldSkipRuntimeFailures: Bool { return _skipRuntimeFailures } + public var shouldSkipThunkFunctions: Bool { return _skipThunkFunctions } + public var shouldSkipSystemFrames: Bool { return _skipSystemFrames } + public var shouldSanitizePaths: Bool { return _sanitizePaths } + public var shouldDemangle: Bool { return _demangle } + public var formattingWidth: Int { return _width } + + public init() {} + + /// Theme to use for formatting. + /// + /// @param theme A `BacktraceFormattingTheme` structure. + /// + /// @returns A new `BacktraceFormattingOptions` structure. + public static func theme(_ theme: BacktraceFormattingTheme) -> BacktraceFormattingOptions { + return BacktraceFormattingOptions().theme(theme) + } + public func theme(_ theme: BacktraceFormattingTheme) -> BacktraceFormattingOptions { + var newOptions = self + newOptions._theme = theme + return newOptions + } + + /// Enable or disable the display of source code in the backtrace. + /// + /// @param enabled Whether or not to enable source code. + /// + /// @param contextLines The number of lines of context either side of the + /// line associated with the backtrace frame. + /// + /// @returns A new `BacktraceFormattingOptions` structure. + public static func showSourceCode(_ enabled: Bool = true, contextLines: Int = 2) -> BacktraceFormattingOptions { + return BacktraceFormattingOptions().showSourceCode(enabled, + contextLines: contextLines) + } + public func showSourceCode(_ enabled: Bool = true, contextLines: Int = 2) -> BacktraceFormattingOptions { + var newOptions = self + newOptions._showSourceCode = enabled + newOptions._sourceContextLines = contextLines + return newOptions + } + + /// Enable or disable the display of raw addresses. + /// + /// @param enabled If false, we will only display a raw address in the + /// backtrace if we haven't been able to symbolicate. + /// + /// @returns A new `BacktraceFormattingOptions` structure. + public static func showAddresses(_ enabled: Bool = true) -> BacktraceFormattingOptions { + return BacktraceFormattingOptions().showAddresses(enabled) + } + public func showAddresses(_ enabled: Bool = true) -> BacktraceFormattingOptions { + var newOptions = self + newOptions._showAddresses = enabled + return newOptions + } + + /// Enable or disable the display of the image list. + /// + /// @param enabled Says whether or not to output the image list. + /// + /// @returns A new `BacktraceFormattingOptions` structure. + public enum ImagesToShow { + case none + case mentioned + case all + } + public static func showImages(_ toShow: ImagesToShow = .all) -> BacktraceFormattingOptions { + return BacktraceFormattingOptions().showImages(toShow) + } + public func showImages(_ toShow: ImagesToShow = .all) -> BacktraceFormattingOptions { + var newOptions = self + newOptions._showImages = toShow + return newOptions + } + + /// Enable or disable the display of image names in the frame list. + /// + /// @param enabled If true, we will display the name of the image for + /// each frame. + /// + /// @returns A new `BacktraceFormattingOptions` structure. + public static func showImageNames(_ enabled: Bool = true) -> BacktraceFormattingOptions { + return BacktraceFormattingOptions().showImageNames(enabled) + } + public func showImageNames(_ enabled: Bool = true) -> BacktraceFormattingOptions { + var newOptions = self + newOptions._showImageNames = enabled + return newOptions + } + + /// Enable or disable the display of frame attributes in the frame list. + /// + /// @param enabled If true, we will display the frame attributes. + /// + /// @returns A new `BacktraceFormattingOptions` structure. + public static func showFrameAttributes(_ enabled: Bool = true) -> BacktraceFormattingOptions { + return BacktraceFormattingOptions().showFrameAttributes(enabled) + } + public func showFrameAttributes(_ enabled: Bool = true) -> BacktraceFormattingOptions { + var newOptions = self + newOptions._showFrameAttributes = enabled + return newOptions + } + + /// Set whether or not to show Swift runtime failure frames. + /// + /// @param enabled If true, we will skip Swift runtime failure frames. + /// + /// @returns A new `BacktraceFormattingOptions` structure. + public static func skipRuntimeFailures(_ enabled: Bool = true) -> BacktraceFormattingOptions { + return BacktraceFormattingOptions().skipRuntimeFailures(enabled) + } + public func skipRuntimeFailures(_ enabled: Bool = true) -> BacktraceFormattingOptions { + var newOptions = self + newOptions._skipRuntimeFailures = enabled + return newOptions + } + + /// Set whether or not to show Swift thunk function frames. + /// + /// @param enabled If true, we will skip Swift thunk function frames. + /// + /// @returns A new `BacktraceFormattingOptions` structure. + public static func skipThunkFunctions(_ enabled: Bool = true) -> BacktraceFormattingOptions { + return BacktraceFormattingOptions().skipThunkFunctions(enabled) + } + public func skipThunkFunctions(_ enabled: Bool = true) -> BacktraceFormattingOptions { + var newOptions = self + newOptions._skipThunkFunctions = enabled + return newOptions + } + + /// Set whether or not to show system frames. + /// + /// For instance, on macOS, this will cause us to skip the "start" frame + /// at the very top of the stack. + /// + /// @param enabled If true, we will skip system frames. + /// + /// @returns A new `BacktraceFormattingOptions` structure. + public static func skipSystemFrames(_ enabled: Bool = true) -> BacktraceFormattingOptions { + return BacktraceFormattingOptions().skipSystemFrames(enabled) + } + public func skipSystemFrames(_ enabled: Bool = true) -> BacktraceFormattingOptions { + var newOptions = self + newOptions._skipSystemFrames = enabled + return newOptions + } + + /// Enable or disable path sanitization. + /// + /// This is intended to avoid leaking PII into crash logs. + /// + /// @param enabled If true, paths will be sanitized. + /// + /// @returns A new `BacktraceFormattingOptions` structure. + public static func sanitizePaths(_ enabled: Bool = true) -> BacktraceFormattingOptions { + return BacktraceFormattingOptions().sanitizePaths(enabled) + } + public func sanitizePaths(_ enabled: Bool = true) -> BacktraceFormattingOptions { + var newOptions = self + newOptions._sanitizePaths = enabled + return newOptions + } + + /// Set whether we show mangled or demangled names. + /// + /// @param enabled If true, we show demangled names if we have them. + /// + /// @returns A new `BacktraceFormattingOptions` structure. + public static func demangle(_ enabled: Bool = true) -> BacktraceFormattingOptions { + return BacktraceFormattingOptions().demangle(enabled) + } + public func demangle(_ enabled: Bool = true) -> BacktraceFormattingOptions { + var newOptions = self + newOptions._demangle = enabled + return newOptions + } + + /// Set the output width. + /// + /// @param width The output width in characters. This is only used to + /// highlight information, and defaults to 80. + /// + /// returns A new `BacktraceFormattingOptions` structure. + public static func width(_ width: Int) -> BacktraceFormattingOptions { + return BacktraceFormattingOptions().width(width) + } + public func width(_ width: Int) -> BacktraceFormattingOptions { + var newOptions = self + newOptions._width = width + return newOptions + } +} + +/// Return the width of a given Unicode.Scalar. +/// +/// It would be nice to have the Unicode width data, which would let us do +/// a better job of this. +private func measure(_ ch: Unicode.Scalar) -> Int { + if ch.isASCII { + return 1 + } + + if ch.properties.isEmoji { + return 2 + } + + if ch.properties.isIdeographic + && !(ch.value >= 0xff61 && ch.value <= 0xffdc) + && !(ch.value >= 0xffe8 && ch.value <= 0xffee) { + return 2 + } + + if ch.properties.canonicalCombiningClass.rawValue != 0 { + return 0 + } + + switch ch.properties.generalCategory { + case .control, .nonspacingMark: + return 0 + default: + return 1 + } +} + +/// Compute the width of the given string, ignoring CSI formatting codes. +private enum MeasureState { + // Normal state + case normal + + // Start of an escape + case escape + + // In a CSI escape + case csi +} + +private func measure(_ s: S) -> Int { + var totalWidth = 0 + var state: MeasureState = .normal + + for ch in s.unicodeScalars { + switch state { + case .normal: + if ch.value == 27 { + // This is an escape sequence + state = .escape + } else { + totalWidth += measure(ch) + } + case .escape: + if ch.value == 0x5b { + state = .csi + } else { + state = .normal + } + case .csi: + if ch.value >= 0x40 && ch.value <= 0x7e { + state = .normal + } + } + } + return totalWidth +} + +/// Pad the given string to the given width using spaces. +private func pad(_ s: String, to width: Int, + aligned alignment: BacktraceFormatter.Alignment = .left) + -> String { + + let currentWidth = measure(s) + let padding = max(width - currentWidth, 0) + + switch alignment { + case .left: + let spaces = String(repeating: " ", count: padding) + + return s + spaces + case .right: + let spaces = String(repeating: " ", count: padding) + + return spaces + s + case .center: + let left = padding / 2 + let right = padding - left + let leftSpaces = String(repeating: " ", count: left) + let rightSpaces = String(repeating: " ", count: right) + + return "\(leftSpaces)\(s)\(rightSpaces)" + } +} + +/// Untabify the given string, assuming tabs of the specified size. +/// +/// @param s The string to untabify. +/// @param tabWidth The tab width to assume (default 8). +/// +/// @returns A string with all the tabs replaced with appropriate numbers +/// of spaces. +private func untabify(_ s: String, tabWidth: Int = 8) -> String { + var result: String = "" + var first = true + for chunk in s.split(separator: "\t", omittingEmptySubsequences: false) { + if first { + first = false + } else { + let toTabStop = tabWidth - measure(result) % tabWidth + result += String(repeating: " ", count: toTabStop) + } + result += chunk + } + return result +} + +/// Sanitize a path to remove usernames, volume names and so on. +/// +/// The point of this function is to try to remove anything that might +/// contain PII before it ends up in a log file somewhere. +/// +/// @param path The path to sanitize. +/// +/// @returns A string containing the sanitized path. +private func sanitizePath(_ path: String) -> String { + #if os(macOS) + return CRCopySanitizedPath(path, + kCRSanitizePathGlobAllTypes + | kCRSanitizePathKeepFile) + #else + // For now, on non-macOS systems, do nothing + return path + #endif +} + +/// Trim whitespace from the right hand end of a string. +/// +/// @param s The string to trim. +/// +/// @returns A string with the whitespace trimmed. +private func rtrim(_ s: S) -> S.SubSequence { + if let lastNonWhitespace = s.lastIndex(where: { !$0.isWhitespace }) { + return s.prefix(through: lastNonWhitespace) + } + return s.dropLast(0) +} + +/// Responsible for formatting backtraces. +@_spi(Formatting) +public struct BacktraceFormatter { + + /// The formatting options to apply when formatting data. + public var options: BacktraceFormattingOptions + + public struct Themes { + /// A plain formatting theme. + public struct PlainTheme: BacktraceFormattingTheme { + } + + public static let plain = PlainTheme() + } + + public init(_ options: BacktraceFormattingOptions) { + self.options = options + } + + public enum TableRow { + case columns([String]) + case raw(String) + } + + public enum Alignment { + case left + case right + case center + } + + /// Output a table with each column nicely aligned. + /// + /// @param rows An array of table rows, each of which holds an array + /// of table columns. + /// + /// @result A `String` containing the formatted table. + public static func formatTable(_ rows: [TableRow], + alignments: [Alignment] = []) -> String { + // Work out how many columns we have + let colCount = rows.map{ + if case let .columns(columns) = $0 { + return columns.count + } else { + return 0 + } + }.reduce(0, max) + + // Now compute their widths + var widths = Array(repeating: 0, count: colCount) + for row in rows { + if case let .columns(columns) = row { + for (n, width) in columns.lazy.map(measure).enumerated() { + widths[n] = max(widths[n], width) + } + } + } + + // Generate lines for the table + var lines: [Substring] = [] + for row in rows { + switch row { + case let .columns(columns): + let line = columns.enumerated().map{ n, column in + let alignment = n < alignments.count ? alignments[n] : .left + if n == colCount - 1 && alignment == .left { + return column + } else { + return pad(column, to: widths[n], aligned: alignment) + } + }.joined(separator: " ") + + lines.append(rtrim(line)) + case let .raw(line): + lines.append(rtrim(line)) + } + } + + // Trim any empty lines from the end + guard let lastNonEmpty = lines.lastIndex(where: { !$0.isEmpty }) else { + return "" + } + + return lines.prefix(through: lastNonEmpty).joined(separator: "\n") + } + + /// Format an individual frame into a list of columns. + /// + /// @param frame The frame to format. + /// @param index The frame index, if required. + /// + /// @result An array of strings, one per column. + public func formatColumns(frame: Backtrace.Frame, + index: Int? = nil) -> [String] { + let pc: String + var attrs: [String] = [] + + switch frame { + case let .programCounter(address): + pc = "\(hex(address))" + case let .returnAddress(address): + pc = "\(hex(address))" + attrs.append("ra") + case let .asyncResumePoint(address): + pc = "\(hex(address))" + attrs.append("async") + case .omittedFrames(_), .truncated: + pc = "..." + } + + var columns: [String] = [] + if let index = index { + columns.append(options._theme.frameIndex("\(index)")) + } + columns.append(options._theme.programCounter(pc)) + if options._showFrameAttributes { + columns.append(attrs.map( + options._theme.frameAttribute + ).joined(separator: " ")) + } + + return columns + } + + /// Format a frame into a list of rows. + /// + /// @param frame The frame to format. + /// @param index The frame index, if required. + /// + /// @result An array of table rows. + public func formatRows(frame: Backtrace.Frame, + index: Int? = nil) -> [TableRow] { + return [.columns(formatColumns(frame: frame, index: index))] + } + + /// Format just one frame. + /// + /// @param frame The frame to format. + /// @param index (Optional) frame index. + /// + /// @result A `String` containing the formatted data. + public func format(frame: Backtrace.Frame, index: Int? = nil) -> String { + let rows = formatRows(frame: frame, index: index) + return BacktraceFormatter.formatTable(rows, alignments: [.right]) + } + + /// Format the frame list from a backtrace. + /// + /// @param frames The frames to format. + /// + /// @result A `String` containing the formatted data. + public func format(frames: some Sequence) -> String { + var rows: [TableRow] = [] + + var n = 0 + for frame in frames { + rows += formatRows(frame: frame, index: n) + + if case let .omittedFrames(count) = frame { + n += count + } else { + n += 1 + } + } + + return BacktraceFormatter.formatTable(rows, alignments: [.right]) + } + + /// Format a `Backtrace` + /// + /// @param backtrace The `Backtrace` object to format. + /// + /// @result A `String` containing the formatted data. + public func format(backtrace: Backtrace) -> String { + return format(frames: backtrace.frames) + } + + /// Grab source lines for a symbolicated backtrace. + /// + /// Tries to open the file corresponding to the symbol; if successful, + /// it will return a string containing the specified lines of context, + /// with the point at which the program crashed highlighted. + private func formattedSourceLines(from sourceLocation: SymbolicatedBacktrace.SourceLocation, + indent theIndent: Int = 2) -> String? { + guard let fp = fopen(sourceLocation.path, "rt") else { return nil } + defer { + fclose(fp) + } + + let indent = String(repeating: " ", count: theIndent) + var lines: [String] = [] + var line = 1 + let buffer = UnsafeMutableBufferPointer.allocate(capacity: 4096) + var currentLine = "" + + let maxLine = sourceLocation.line + options._sourceContextLines + let maxLineWidth = max("\(maxLine)".count, 4) + + let doLine = { sourceLine in + if line >= sourceLocation.line - options._sourceContextLines + && line <= sourceLocation.line + options._sourceContextLines { + let untabified = untabify(sourceLine) + let code = options._theme.code(untabified) + let theLine: String + if line == sourceLocation.line { + let lineNumber = options._theme.crashedLineNumber(pad("\(line)", + to: maxLineWidth, + aligned: .right)) + let highlightWidth = options._width - 2 * theIndent + theLine = options._theme.crashedLine(pad("\(lineNumber) \(code)", + to: highlightWidth)) + } else { + let lineNumber = options._theme.lineNumber(pad("\(line)", + to: maxLineWidth, + aligned: .right)) + theLine = "\(lineNumber) \(code)" + } + lines.append("\(indent)\(theLine)") + + if line == sourceLocation.line { + // sourceLocation.column is an index in UTF-8 code units in + // `untabified`. We should point at the grapheme cluster that + // contains that UTF-8 index. + let adjustedColumn = max(sourceLocation.column, 1) + let utf8Ndx + = untabified.utf8.index(untabified.utf8.startIndex, + offsetBy: adjustedColumn, + limitedBy: untabified.utf8.endIndex) + ?? untabified.utf8.endIndex + + // Adjust it to point at a grapheme cluster start + let strNdx = untabified.index( + untabified.index(utf8Ndx, offsetBy: 1, + limitedBy: untabified.endIndex) + ?? untabified.endIndex, + offsetBy: -1, + limitedBy: untabified.startIndex) ?? untabified.startIndex + + // Work out the terminal width up to that point + let terminalWidth = measure(untabified.prefix(upTo: strNdx)) + + let pad = String(repeating: " ", + count: max(terminalWidth - 1, 0)) + + let marker = options._theme.crashLocation() + let blankForNumber = options._theme.lineNumber( + String(repeating: " ", count: maxLineWidth)) + + lines.append("\(indent)\(blankForNumber) \(pad)\(marker)") + } + } + } + + while feof(fp) == 0 && ferror(fp) == 0 { + guard let result = fgets(buffer.baseAddress, + CInt(buffer.count), fp) else { + break + } + + let chunk = String(cString: result) + currentLine += chunk + if currentLine.hasSuffix("\n") { + currentLine.removeLast() + doLine(currentLine) + currentLine = "" + line += 1 + } + } + + doLine(currentLine) + + return lines.joined(separator: "\n") + } + + /// Format an individual frame into a list of columns. + /// + /// @params frame The frame to format. + /// + /// @result An array of strings, one per column. + public func formatColumns(frame: SymbolicatedBacktrace.Frame, + index: Int? = nil) -> [String] { + let pc: String + var attrs: [String] = [] + + switch frame.captured { + case let .programCounter(address): + pc = "\(hex(address))" + case let .returnAddress(address): + pc = "\(hex(address))" + attrs.append("ra") + case let .asyncResumePoint(address): + pc = "\(hex(address))" + attrs.append("async") + case .omittedFrames(_), .truncated: + pc = "" + } + + if frame.inlined { + attrs.append("inlined") + } + + if frame.isSwiftThunk { + attrs.append("thunk") + } + + if frame.isSystem { + attrs.append("system") + } + + var formattedSymbol: String? = nil + var hasSourceLocation = false + + if let symbol = frame.symbol { + let displayName = options._demangle ? symbol.name : symbol.rawName + let themedName = options._theme.symbol(displayName) + + let offset: String + if symbol.offset > 0 { + offset = options._theme.offset(" + \(symbol.offset)") + } else if symbol.offset < 0 { + offset = options._theme.offset(" - \(-symbol.offset)") + } else { + offset = "" + } + + let imageName: String + if options._showImageNames { + if symbol.imageIndex >= 0 { + imageName = " in " + options._theme.imageName(symbol.imageName) + } else { + imageName = "" + } + } else { + imageName = "" + } + + let location: String + if var sourceLocation = symbol.sourceLocation { + if options._sanitizePaths { + sourceLocation.path = sanitizePath(sourceLocation.path) + } + location = " at " + options._theme.sourceLocation("\(sourceLocation)") + hasSourceLocation = true + } else { + location = "" + } + + formattedSymbol = "\(themedName)\(offset)\(imageName)\(location)" + } + + let location: String + if !hasSourceLocation || options._showAddresses { + let formattedPc = options._theme.programCounter(pc) + if let formattedSymbol = formattedSymbol { + location = "\(formattedPc) \(formattedSymbol)" + } else { + location = formattedPc + } + } else if let formattedSymbol = formattedSymbol { + location = formattedSymbol + } else { + location = options._theme.programCounter(pc) + } + + var columns: [String] = [] + + if let index = index { + let frameIndex: String + switch frame.captured { + case .omittedFrames(_), .truncated: + frameIndex = options._theme.frameIndex("...") + default: + frameIndex = options._theme.frameIndex("\(index)") + } + columns.append(frameIndex) + } + + if options._showFrameAttributes { + columns.append(attrs.map( + options._theme.frameAttribute + ).joined(separator: " ")) + } + + columns.append(location) + + return columns + } + + /// Format a frame into a list of rows. + /// + /// @param frame The frame to format. + /// @param index The frame index, if required. + /// + /// @result An array of table rows. + public func formatRows(frame: SymbolicatedBacktrace.Frame, + index: Int? = nil, + showSource: Bool = true) -> [TableRow] { + let columns = formatColumns(frame: frame, index: index) + var rows: [TableRow] = [.columns(columns)] + + if showSource { + if let symbol = frame.symbol, + let sourceLocation = symbol.sourceLocation, + let lines = formattedSourceLines(from: sourceLocation) { + rows.append(.raw("")) + rows.append(.raw(lines)) + rows.append(.raw("")) + } + } + + return rows + } + + /// Format just one frame. + /// + /// @param frame The frame to format. + /// @param index (Optional) frame index. + /// + /// @result A `String` containing the formatted data. + public func format(frame: SymbolicatedBacktrace.Frame, + index: Int? = nil, + showSource: Bool = true) -> String { + let rows = formatRows(frame: frame, index: index, showSource: showSource) + return BacktraceFormatter.formatTable(rows, alignments: [.right]) + } + + /// Return `true` if we should skip the specified frame + public func shouldSkip(_ frame: SymbolicatedBacktrace.Frame) -> Bool { + return (options._skipRuntimeFailures && frame.isSwiftRuntimeFailure) + || (options._skipSystemFrames && frame.isSystem) + || (options._skipThunkFunctions && frame.isSwiftThunk) + } + + /// Format the frame list from a symbolicated backtrace. + /// + /// @param frames The frames to format. + /// + /// @result A `String` containing the formatted data. + public func format(frames: some Sequence) -> String { + var rows: [TableRow] = [] + var sourceLocationsShown = Set() + + var n = 0 + for frame in frames { + if shouldSkip(frame) { + continue + } + + var showSource = options._showSourceCode + if let symbol = frame.symbol, + let sourceLocation = symbol.sourceLocation { + if sourceLocationsShown.contains(sourceLocation) { + showSource = false + } else { + sourceLocationsShown.insert(sourceLocation) + } + } + + rows += formatRows(frame: frame, index: n, showSource: showSource) + + if case let .omittedFrames(count) = frame.captured { + n += count + } else { + n += 1 + } + } + + return BacktraceFormatter.formatTable(rows, alignments: [.right]) + } + + /// Format a `SymbolicatedBacktrace` + /// + /// @param backtrace The `SymbolicatedBacktrace` object to format. + /// + /// @result A `String` containing the formatted data. + public func format(backtrace: SymbolicatedBacktrace) -> String { + var result = format(frames: backtrace.frames) + + switch options._showImages { + case .none: + break + case .all: + result += "\n\nImages:\n" + result += format(images: backtrace.images) + case .mentioned: + var mentionedImages = Set() + for frame in backtrace.frames { + if shouldSkip(frame) { + continue + } + if let symbol = frame.symbol, symbol.imageIndex >= 0 { + mentionedImages.insert(symbol.imageIndex) + } + } + + let images = mentionedImages.sorted().map{ backtrace.images[$0] } + let omitted = backtrace.images.count - images.count + if omitted > 0 { + result += "\n\nImages (\(omitted) omitted):\n" + } else { + result += "\n\nImages (only mentioned):\n" + } + result += format(images: images) + } + + return result + } + + /// Format a `Backtrace.Image` into a list of columns. + /// + /// @param image The `Image` object to format. + /// + /// @result An array of strings, one per column. + public func formatColumns(image: Backtrace.Image) -> [String] { + let addressRange = "\(hex(image.baseAddress))–\(hex(image.endOfText))" + let buildID: String + if let bytes = image.buildID { + buildID = hex(bytes) + } else { + buildID = "" + } + let imagePath: String + if options._sanitizePaths { + imagePath = sanitizePath(image.path) + } else { + imagePath = image.path + } + return [ + options._theme.imageAddressRange(addressRange), + options._theme.imageBuildID(buildID), + options._theme.imageName(image.name), + options._theme.imagePath(imagePath) + ] + } + + /// Format an array of `Backtrace.Image`s. + /// + /// @param images The array of `Image` objects to format. + /// + /// @result A string containing the formatted data. + public func format(images: some Sequence) -> String { + let rows = images.map{ TableRow.columns(formatColumns(image: $0)) } + + return BacktraceFormatter.formatTable(rows) + } +} diff --git a/stdlib/public/Backtracing/CMakeLists.txt b/stdlib/public/Backtracing/CMakeLists.txt new file mode 100644 index 0000000000000..0e537a0cf35d1 --- /dev/null +++ b/stdlib/public/Backtracing/CMakeLists.txt @@ -0,0 +1,60 @@ +#===--- CMakeLists.txt - Backtracing support library -----------------------===# +# +# 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 +# +#===------------------------------------------------------------------------===# + +set(swift_backtracing_link_libraries + swiftCore +) + +set(BACKTRACING_SOURCES + Backtrace.swift + BacktraceFormatter.swift + Context.swift + CoreSymbolication.swift + FramePointerUnwinder.swift + MemoryReader.swift + Registers.swift + SymbolicatedBacktrace.swift + Utils.swift + + get-cpu-context.${SWIFT_ASM_EXT} +) + +set(LLVM_OPTIONAL_SOURCES + get-cpu-context.S + get-cpu-context.asm +) + +add_swift_target_library(swift_Backtracing ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_STDLIB + ${BACKTRACING_SOURCES} + + SWIFT_MODULE_DEPENDS_IOS Darwin _Concurrency + SWIFT_MODULE_DEPENDS_OSX Darwin _Concurrency + SWIFT_MODULE_DEPENDS_TVOS Darwin _Concurrency + SWIFT_MODULE_DEPENDS_WATCHOS Darwin _Concurrency + SWIFT_MODULE_DEPENDS_MACCATALYST Darwin _Concurrency + SWIFT_MODULE_DEPENDS_LINUX Glibc _Concurrency + SWIFT_MODULE_DEPENDS_WINDOWS CRT _Concurrency + + LINK_LIBRARIES ${swift_backtracing_link_libraries} + + SWIFT_COMPILE_FLAGS + ${SWIFT_STANDARD_LIBRARY_SWIFT_FLAGS} + -parse-stdlib + + LINK_FLAGS + ${SWIFT_RUNTIME_SWIFT_LINK_FLAGS} + + INSTALL_IN_COMPONENT stdlib + MACCATALYST_BUILD_FLAVOR "zippered" + + TARGET_SDKS OSX +) diff --git a/stdlib/public/Backtracing/Context.swift b/stdlib/public/Backtracing/Context.swift new file mode 100644 index 0000000000000..260ef79b762f7 --- /dev/null +++ b/stdlib/public/Backtracing/Context.swift @@ -0,0 +1,885 @@ +//===--- Context.swift - Unwind context structure -------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Defines the Context protocol and some concrete implementations for various +// different types of CPU. +// +// Context holds register values during unwinding. +// +//===----------------------------------------------------------------------===// + +import Swift + +@_implementationOnly import _SwiftBacktracingShims + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_implementationOnly import Darwin.Mach +#endif + +@_spi(Contexts) public enum ContextError: Error { + case unableToFormTLSAddress +} + +@_spi(Contexts) public protocol Context: CustomStringConvertible { + /// Represents a machine address for this type of machine + associatedtype Address: FixedWidthInteger + + /// Represents a size for this type of machine + associatedtype Size: FixedWidthInteger + + /// The type of a general purpose register on this machine + associatedtype GPRValue: FixedWidthInteger + + /// An enumerated type defining the registers for the machine (this comes + /// from the architecture specific DWARF specification). + associatedtype Register: RawRepresentable where Register.RawValue == Int + + /// The program counter; this is likely a return address + var programCounter: GPRValue { get set } + + /// The stack pointer + var stackPointer: GPRValue { get set } + + /// The frame pointer + var framePointer: GPRValue { get set } + + /// The CFA as defined by the relevant architecture specific DWARF + /// specification. For the architectures we have currently, it turns out + /// that this is the stack pointer, but it might in general be some other + /// thing. + var callFrameAddress: GPRValue { get set } + + /// The number of register slots to reserve in the unwinder (this corresponds + /// to the DWARF register numbers, which is why some of these reserve a lot + /// of slots). + static var registerCount: Int { get } + + /// Given a thread local address, form a genuine machine address + func formTLSAddress(threadLocal: Address) throws -> Address + + /// Get the value of the specified general purpose register, or nil if unknown + func getRegister(_ register: Register) -> GPRValue? + + /// Set the value of the specified general purpose register (or mark it as + /// unknown if nil is passed) + mutating func setRegister(_ register: Register, to value: GPRValue?) + + /// Set all of the registers in bulk + mutating func setRegisters(_ registers: [GPRValue?]) + + /// Strip any pointer authentication that might apply from an address. + static func stripPtrAuth(address: Address) -> Address + + /// Test if an address is appropriately aligned for the stack. + static func isAlignedForStack(framePointer: Address) -> Bool +} + +extension Context { + public func formTLSAddress(threadLocal: Address) throws -> Address { + throw ContextError.unableToFormTLSAddress + } + + public mutating func setRegisters(_ registers: [GPRValue?]) { + for (ndx, value) in registers.enumerated() { + if let reg = Register(rawValue: ndx) { + setRegister(reg, to: value) + } + } + } + + public static func stripPtrAuth(address: Address) -> Address { + return address + } +} + +// .. Extensions to the GPR structures ......................................... + +// We need these because the arrays in the _gprs structs (which are defined +// in C so that the layout is fixed) get imported as tuples. + +extension x86_64_gprs { + func getR(_ ndx: Int) -> UInt64 { + return withUnsafePointer(to: _r) { + $0.withMemoryRebound(to: UInt64.self, capacity: 16) { + $0[ndx] + } + } + } + + mutating func setR(_ ndx: Int, to value: UInt64) { + withUnsafeMutablePointer(to: &_r) { + $0.withMemoryRebound(to: UInt64.self, capacity: 16) { + $0[ndx] = value + } + } + valid |= 1 << ndx + } +} + +extension i386_gprs { + func getR(_ ndx: Int) -> UInt32 { + return withUnsafePointer(to: _r) { + $0.withMemoryRebound(to: UInt32.self, capacity: 8) { + $0[ndx] + } + } + } + + mutating func setR(_ ndx: Int, to value: UInt32) { + withUnsafeMutablePointer(to: &_r) { + $0.withMemoryRebound(to: UInt32.self, capacity: 8) { + $0[ndx] = value + } + } + valid |= 1 << ndx + } +} + +extension arm64_gprs { + func getX(_ ndx: Int) -> UInt64 { + return withUnsafePointer(to: _x) { + $0.withMemoryRebound(to: UInt64.self, capacity: 32) { + $0[ndx] + } + } + } + + mutating func setX(_ ndx: Int, to value: UInt64) { + withUnsafeMutablePointer(to: &_x) { + $0.withMemoryRebound(to: UInt64.self, capacity: 32) { + $0[ndx] = value + } + } + valid |= 1 << ndx + } +} + +extension arm_gprs { + func getR(_ ndx: Int) -> UInt32 { + return withUnsafePointer(to: _r) { + $0.withMemoryRebound(to: UInt32.self, capacity: 16) { + $0[ndx] + } + } + } + + mutating func setR(_ ndx: Int, to value: UInt32) { + withUnsafeMutablePointer(to: &_r) { + $0.withMemoryRebound(to: UInt32.self, capacity: 16) { + $0[ndx] = value + } + } + valid |= 1 << ndx + } +} + +// .. x86-64 ................................................................... + +@_spi(Contexts) public struct X86_64Context: Context { + public typealias Address = UInt64 + public typealias Size = UInt64 + public typealias GPRValue = UInt64 + public typealias Register = X86_64Register + + var gprs = x86_64_gprs() + + public var programCounter: Address { + get { return gprs.rip } + set { + gprs.rip = newValue + gprs.valid |= 1 << 20 + } + } + public var framePointer: Address { + get { return gprs.getR(X86_64Register.rbp.rawValue) } + set { + gprs.setR(X86_64Register.rbp.rawValue, to: newValue) + } + } + public var stackPointer: Address { + get { return gprs.getR(X86_64Register.rsp.rawValue) } + set { + gprs.setR(X86_64Register.rsp.rawValue, to: newValue) + } + } + + public var callFrameAddress: GPRValue { + get { return stackPointer } + set { stackPointer = newValue } + } + + public static var registerCount: Int { return 56 } + + #if os(macOS) && arch(x86_64) + init?(from thread: thread_t) { + var state = darwin_x86_64_thread_state() + let kr = mach_thread_get_state(thread, x86_THREAD_STATE64, &state) + if kr != KERN_SUCCESS { + return nil + } + + self.init(from: state) + } + + init(with mctx: darwin_x86_64_mcontext) { + self.init(from: mctx.ss) + } + + init(from state: darwin_x86_64_thread_state) { + gprs.setR(X86_64Register.rax.rawValue, to: state.rax) + gprs.setR(X86_64Register.rbx.rawValue, to: state.rbx) + gprs.setR(X86_64Register.rcx.rawValue, to: state.rcx) + gprs.setR(X86_64Register.rdx.rawValue, to: state.rdx) + gprs.setR(X86_64Register.rdi.rawValue, to: state.rdi) + gprs.setR(X86_64Register.rsi.rawValue, to: state.rsi) + gprs.setR(X86_64Register.rbp.rawValue, to: state.rbp) + gprs.setR(X86_64Register.rsp.rawValue, to: state.rsp) + gprs.setR(X86_64Register.r8.rawValue, to: state.r8) + gprs.setR(X86_64Register.r9.rawValue, to: state.r9) + gprs.setR(X86_64Register.r10.rawValue, to: state.r10) + gprs.setR(X86_64Register.r11.rawValue, to: state.r11) + gprs.setR(X86_64Register.r12.rawValue, to: state.r12) + gprs.setR(X86_64Register.r13.rawValue, to: state.r13) + gprs.setR(X86_64Register.r14.rawValue, to: state.r14) + gprs.setR(X86_64Register.r15.rawValue, to: state.r15) + gprs.rip = state.rip + gprs.rflags = state.rflags + gprs.cs = UInt16(state.cs) + gprs.fs = UInt16(state.fs) + gprs.gs = UInt16(state.gs) + gprs.valid = 0x1fffff + } + + public static func fromHostThread(_ thread: Any) -> HostContext? { + return X86_64Context(from: thread as! thread_t) + } + + public static func fromHostMContext(_ mcontext: Any) -> HostContext { + return X86_64Context(with: mcontext as! darwin_x86_64_mcontext) + } + #endif + + #if os(Windows) + struct NotYetImplemented: Error {} + public static func withCurrentContext(fn: (X86_64Context) throws -> T) throws -> T { + throw NotYetImplemented() + } + #elseif arch(x86_64) + @_silgen_name("_swift_get_cpu_context") + static func _swift_get_cpu_context() -> X86_64Context + + public static func withCurrentContext(fn: (X86_64Context) throws -> T) rethrows -> T { + return try fn(_swift_get_cpu_context()) + } + #endif + + private func validNdx(_ register: Register) -> Int? { + switch register { + case .rax ... .r15: + return register.rawValue + case .rflags: + return 16 + case .cs: + return 17 + case .fs: + return 18 + case .gs: + return 19 + default: + return nil + } + } + + private func isValid(_ register: Register) -> Bool { + guard let ndx = validNdx(register) else { + return false + } + return (gprs.valid & (UInt64(1) << ndx)) != 0 + } + + private mutating func setValid(_ register: Register) { + guard let ndx = validNdx(register) else { + return + } + gprs.valid |= UInt64(1) << ndx + } + + private mutating func clearValid(_ register: Register) { + guard let ndx = validNdx(register) else { + return + } + gprs.valid &= ~(UInt64(1) << ndx) + } + + public func getRegister(_ register: Register) -> GPRValue? { + if !isValid(register) { + return nil + } + + switch register { + case .rax ... .r15: + return gprs.getR(register.rawValue) + case .rflags: return gprs.rflags + case .cs: return UInt64(gprs.cs) + case .fs: return UInt64(gprs.fs) + case .gs: return UInt64(gprs.gs) + default: + return nil + } + } + + public mutating func setRegister(_ register: Register, to value: GPRValue?) { + if let value = value { + switch register { + case .rax ... .r15: + gprs.setR(register.rawValue, to: value) + case .rflags: + gprs.rflags = value + setValid(register) + case .cs: + gprs.cs = UInt16(value) + setValid(register) + case .fs: + gprs.fs = UInt16(value) + setValid(register) + case .gs: + gprs.gs = UInt16(value) + setValid(register) + default: + return + } + } else { + clearValid(register) + } + } + + public var description: String { + return """ + rax: \(hex(gprs.getR(0))) rbx: \(hex(gprs.getR(3))) rcx: \(hex(gprs.getR(2))) + rdx: \(hex(gprs.getR(1))) rsi: \(hex(gprs.getR(4))) rdi: \(hex(gprs.getR(5))) + rbp: \(hex(gprs.getR(6))) rsp: \(hex(gprs.getR(7))) r8: \(hex(gprs.getR(8))) + r9: \(hex(gprs.getR(9))) r10: \(hex(gprs.getR(10))) r11: \(hex(gprs.getR(11))) + r12: \(hex(gprs.getR(12))) r13: \(hex(gprs.getR(13))) r14: \(hex(gprs.getR(14))) + r15: \(hex(gprs.getR(15))) + + cs: \(hex(gprs.cs)) fs: \(hex(gprs.fs)) gs: \(hex(gprs.gs)) + + rip: \(hex(gprs.rip)) rflags: \(hex(gprs.rflags)) + """ + } + + public static func isAlignedForStack(framePointer: Address) -> Bool { + return (framePointer & 0xf) == 0 + } + + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + internal static var coreSymbolicationArchitecture: CSArchitecture { + return kCSArchitectureX86_64 + } + #endif +} + +// .. i386 ..................................................................... + +@_spi(Contexts) public struct I386Context: Context { + public typealias Address = UInt32 + public typealias Size = UInt32 + public typealias GPRValue = UInt32 + public typealias Register = I386Register + + var gprs = i386_gprs() + + public var programCounter: GPRValue { + get { return gprs.eip } + set { + gprs.eip = newValue + gprs.valid |= 1 << 15 + } + } + + public var framePointer: GPRValue { + get { return gprs.getR(I386Register.ebp.rawValue) } + set { gprs.setR(I386Register.ebp.rawValue, to: newValue) } + } + + public var stackPointer: GPRValue { + get { return gprs.getR(I386Register.esp.rawValue) } + set { gprs.setR(I386Register.esp.rawValue, to: newValue) } + } + + public var callFrameAddress: GPRValue { + get { return stackPointer } + set { stackPointer = newValue } + } + + public static var registerCount: Int { return 50 } + + #if os(Windows) + struct NotYetImplemented: Error {} + public static func withCurrentContext(fn: (I386Context) throws -> T) throws -> T { + throw NotYetImplemented() + } + #elseif arch(i386) + @_silgen_name("_swift_get_cpu_context") + static func _swift_get_cpu_context() -> I386Context + + public static func withCurrentContext(fn: (I386Context) throws -> T) rethrows -> T { + return try fn(_swift_get_cpu_context()) + } + #endif + + private func validNdx(_ register: Register) -> Int? { + switch register { + case .eax ... .edi: + return register.rawValue + case .eflags: + return 8 + case .es, .cs, .ss, .ds, .fs, .gs: + return 9 + register.rawValue - Register.es.rawValue + case .ra: + return 15 + default: + return nil + } + } + + private func isValid(_ register: Register) -> Bool { + guard let ndx = validNdx(register) else { + return false + } + return (gprs.valid & (UInt32(1) << ndx)) != 0 + } + + private mutating func setValid(_ register: Register) { + guard let ndx = validNdx(register) else { + return + } + gprs.valid |= UInt32(1) << ndx + } + + private mutating func clearValid(_ register: Register) { + guard let ndx = validNdx(register) else { + return + } + gprs.valid &= ~(UInt32(1) << ndx) + } + + public func getRegister(_ register: Register) -> GPRValue? { + if !isValid(register) { + return nil + } + switch register { + case .eax ... .edi: + return gprs.getR(register.rawValue) + case .eflags: return gprs.eflags + case .es ... .gs: + return withUnsafeBytes(of: gprs.segreg) { ptr in + return ptr.withMemoryRebound(to: GPRValue.self) { regs in + return regs[register.rawValue - Register.es.rawValue] + } + } + case .ra: return gprs.eip + default: + return nil + } + } + + public mutating func setRegister(_ register: Register, to value: GPRValue?) { + if let value = value { + switch register { + case .eax ... .edi: + gprs.setR(register.rawValue, to: value) + case .eflags: + gprs.eflags = value + setValid(register) + case .es ... .gs: + withUnsafeMutableBytes(of: &gprs.segreg) { ptr in + ptr.withMemoryRebound(to: GPRValue.self) { regs in + regs[register.rawValue - Register.es.rawValue] = value + } + } + setValid(register) + case .ra: + gprs.eip = value + setValid(register) + default: + return + } + } else { + clearValid(register) + } + } + + public var description: String { + return """ + eax: \(hex(gprs.getR(0))) ebx: \(hex(gprs.getR(3))) ecx: \(hex(gprs.getR(1))) edx: \(hex(gprs.getR(2))) + esi: \(hex(gprs.getR(6))) edi: \(hex(gprs.getR(7))) ebp: \(hex(gprs.getR(5))) esp: \(hex(gprs.getR(4))) + + es: \(hex(gprs.segreg.0)) cs: \(hex(gprs.segreg.1)) ss: \(hex(gprs.segreg.2)) ds: \(hex(gprs.segreg.3)) fs: \(hex(gprs.segreg.4)) gs: \(hex(gprs.segreg.5)) + + eip: \(hex(gprs.eip)) eflags: \(hex(gprs.eflags)) + """ + } + + public static func isAlignedForStack(framePointer: Address) -> Bool { + return (framePointer & 0xf) == 8 + } + + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + internal static var coreSymbolicationArchitecture: CSArchitecture { + return kCSArchitectureI386 + } + #endif +} + +// .. ARM64 .................................................................... + +@_spi(Contexts) public struct ARM64Context: Context { + public typealias Address = UInt64 + public typealias Size = UInt64 + public typealias GPRValue = UInt64 + public typealias Register = ARM64Register + + var gprs = arm64_gprs() + + public var programCounter: GPRValue { + get { return gprs.pc } + set { + gprs.pc = newValue + gprs.valid |= 1 << 32 + } + } + + public var stackPointer: GPRValue { + get { return gprs.getX(ARM64Register.sp.rawValue) } + set { + gprs.setX(ARM64Register.sp.rawValue, to: newValue) + } + } + + public var framePointer: GPRValue { + get { return gprs.getX(ARM64Register.x29.rawValue) } + set { + gprs.setX(ARM64Register.x29.rawValue, to: newValue) + } + } + + public var callFrameAddress: GPRValue { + get { return stackPointer } + set { stackPointer = newValue } + } + + public static var registerCount: Int { return 40 } + + #if os(macOS) && arch(arm64) + init?(from thread: thread_t) { + var state = darwin_arm64_thread_state() + let kr = mach_thread_get_state(thread, ARM_THREAD_STATE64, &state) + if kr != KERN_SUCCESS { + return nil + } + + self.init(from: state) + } + + init(with mctx: darwin_arm64_mcontext) { + self.init(from: mctx.ss) + } + + init(from state: darwin_arm64_thread_state) { + withUnsafeMutablePointer(to: &gprs._x) { + $0.withMemoryRebound(to: UInt64.self, capacity: 32){ to in + withUnsafePointer(to: state._x) { + $0.withMemoryRebound(to: UInt64.self, capacity: 29){ from in + for n in 0..<29 { + to[n] = from[n] + } + } + } + + to[29] = state.fp + to[30] = state.lr + to[31] = state.sp + } + } + gprs.pc = state.pc + gprs.valid = 0x1ffffffff + } + + public static func fromHostThread(_ thread: Any) -> HostContext? { + return ARM64Context(from: thread as! thread_t) + } + + public static func fromHostMContext(_ mcontext: Any) -> HostContext { + return ARM64Context(with: mcontext as! darwin_arm64_mcontext) + } +#endif + + #if os(Windows) + struct NotYetImplemented: Error {} + public static func withCurrentContext(fn: (ARM64Context) throws -> T) throws -> T { + throw NotYetImplemented() + } + #elseif arch(arm64) || arch(arm64_32) + @_silgen_name("_swift_get_cpu_context") + static func _swift_get_cpu_context() -> ARM64Context + + public static func withCurrentContext(fn: (ARM64Context) throws -> T) rethrows -> T { + return try fn(_swift_get_cpu_context()) + } + #endif + + private func isValid(_ register: Register) -> Bool { + if register.rawValue < 33 { + return (gprs.valid & (UInt64(1) << register.rawValue)) != 0 + } + return false + } + + private mutating func setValid(_ register: Register) { + if register.rawValue < 33 { + gprs.valid |= UInt64(1) << register.rawValue + } + } + + private mutating func clearValid(_ register: Register) { + if register.rawValue < 33 { + gprs.valid &= ~(UInt64(1) << register.rawValue) + } + } + + public func getRegister(_ reg: Register) -> GPRValue? { + if !isValid(reg) { + return nil + } + switch reg { + case .x0 ... .sp: + return gprs.getX(reg.rawValue) + case .pc: + return gprs.pc + default: + return nil + } + } + + public mutating func setRegister(_ reg: Register, to value: GPRValue?) { + if let value = value { + switch reg { + case .x0 ... .sp: + gprs.setX(reg.rawValue, to: value) + case .pc: + gprs.pc = value + setValid(reg) + default: + break + } + } else { + clearValid(reg) + } + } + + public var description: String { + return """ + x0: \(hex(gprs.getX(0))) x1: \(hex(gprs.getX(1))) + x2: \(hex(gprs.getX(2))) x3: \(hex(gprs.getX(3))) + x4: \(hex(gprs.getX(4))) x5: \(hex(gprs.getX(5))) + x6: \(hex(gprs.getX(6))) x7: \(hex(gprs.getX(7))) + x8: \(hex(gprs.getX(8))) x9: \(hex(gprs.getX(9))) + x10: \(hex(gprs.getX(10))) x11: \(hex(gprs.getX(11))) + x12: \(hex(gprs.getX(12))) x13: \(hex(gprs.getX(13))) + x14: \(hex(gprs.getX(14))) x15: \(hex(gprs.getX(15))) + x16: \(hex(gprs.getX(16))) x17: \(hex(gprs.getX(17))) + x18: \(hex(gprs.getX(18))) x19: \(hex(gprs.getX(19))) + x20: \(hex(gprs.getX(20))) x21: \(hex(gprs.getX(21))) + x22: \(hex(gprs.getX(22))) x23: \(hex(gprs.getX(23))) + x24: \(hex(gprs.getX(24))) x25: \(hex(gprs.getX(25))) + x26: \(hex(gprs.getX(26))) x27: \(hex(gprs.getX(27))) + x28: \(hex(gprs.getX(28))) + + fp: \(hex(gprs.getX(29))) (aka x29) + lr: \(hex(gprs.getX(30))) (aka x30) + sp: \(hex(gprs.getX(31))) (aka x31) + + pc: \(hex(gprs.pc)) + """ + } + + public static func isAlignedForStack(framePointer: Address) -> Bool { + return (framePointer & 1) == 0 + } + + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + public static func stripPtrAuth(address: Address) -> Address { + // Is there a better way to do this? It'd be easy if we just wanted to + // strip for the *host*, but we might conceivably want this under other + // circumstances too. + return address & 0x00007fffffffffff + } + + internal static var coreSymbolicationArchitecture: CSArchitecture { + return kCSArchitectureArm64 + } + #endif +} + +// .. 32-bit ARM ............................................................... + +@_spi(Contexts) public struct ARMContext: Context { + public typealias Address = UInt32 + public typealias Size = UInt32 + public typealias GPRValue = UInt32 + public typealias Register = ARMRegister + + var gprs = arm_gprs() + + public var programCounter: GPRValue { + get { return gprs.getR(ARMRegister.r15.rawValue) } + set { gprs.setR(ARMRegister.r15.rawValue, to: newValue) } + } + + public var stackPointer: GPRValue { + get { return gprs.getR(ARMRegister.r13.rawValue) } + set { gprs.setR(ARMRegister.r13.rawValue, to: newValue) } + } + + public var framePointer: GPRValue { + get { return gprs.getR(ARMRegister.r11.rawValue) } + set { gprs.setR(ARMRegister.r11.rawValue, to: newValue) } + } + + public var callFrameAddress: GPRValue { + get { return stackPointer } + set { stackPointer = newValue } + } + + public static var registerCount: Int { return 16 } + + #if os(Windows) + struct NotYetImplemented: Error {} + public static func withCurrentContext(fn: (ARMContext) throws -> T) throws -> T { + throw NotYetImplemented() + } + #elseif arch(arm) + @_silgen_name("_swift_get_cpu_context") + static func _swift_get_cpu_context() -> ARMContext + + public static func withCurrentContext(fn: (ARMContext) throws -> T) rethrows -> T { + return try fn(_swift_get_cpu_context()) + } + #endif + + private func isValid(_ register: Register) -> Bool { + if register.rawValue < 16 { + return (gprs.valid & (UInt32(1) << register.rawValue)) != 0 + } + return false + } + + private mutating func setValid(_ register: Register) { + if register.rawValue < 16 { + gprs.valid |= UInt32(1) << register.rawValue + } + } + + private mutating func clearValid(_ register: Register) { + if register.rawValue < 16 { + gprs.valid &= ~(UInt32(1) << register.rawValue) + } + } + + public func getRegister(_ reg: Register) -> GPRValue? { + if !isValid(reg) { + return nil + } + switch reg { + case .r0 ... .r15: + return gprs.getR(reg.rawValue) + default: + return nil + } + } + + public mutating func setRegister(_ reg: Register, to value: GPRValue?) { + if let value = value { + switch reg { + case .r0 ... .r15: + gprs.setR(reg.rawValue, to: value) + default: + break + } + } else { + clearValid(reg) + } + } + + public var description: String { + return """ + r0: \(hex(gprs.getR(0))) r1: \(hex(gprs.getR(1))) + r2: \(hex(gprs.getR(2))) r3: \(hex(gprs.getR(3))) + r4: \(hex(gprs.getR(4))) r5: \(hex(gprs.getR(5))) + r6: \(hex(gprs.getR(6))) r7: \(hex(gprs.getR(7))) + r8: \(hex(gprs.getR(8))) r9: \(hex(gprs.getR(9))) + r10: \(hex(gprs.getR(10))) + + fp: \(hex(gprs.getR(11))) (aka r11) + ip: \(hex(gprs.getR(12))) (aka r12) + sp: \(hex(gprs.getR(13))) (aka r13) + lr: \(hex(gprs.getR(14))) (aka r14) + pc: \(hex(gprs.getR(15))) (aka r15) + """ + } + + public static func isAlignedForStack(framePointer: Address) -> Bool { + return (framePointer & 1) == 0 + } + + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + internal static var coreSymbolicationArchitecture: CSArchitecture { + return kCSArchitectureArmV7K + } + #endif +} + +// .. Darwin specifics ......................................................... + +#if os(macOS) +private func mach_thread_get_state(_ thread: thread_t, + _ flavor: CInt, + _ result: inout T) -> kern_return_t { + var count: mach_msg_type_number_t + = mach_msg_type_number_t(MemoryLayout.stride + / MemoryLayout.stride) + + return withUnsafeMutablePointer(to: &result) { ptr in + ptr.withMemoryRebound(to: natural_t.self, capacity: Int(count)) { intPtr in + return thread_get_state(thread, + thread_state_flavor_t(flavor), + intPtr, + &count) + } + } +} +#endif + +// .. HostContext .............................................................. + +/// HostContext is an alias for the appropriate context for the machine on which +/// the code was compiled. +#if arch(x86_64) +@_spi(Contexts) public typealias HostContext = X86_64Context +#elseif arch(i386) +@_spi(Contexts) public typealias HostContext = I386Context +#elseif arch(arm64) || arch(arm64_32) +@_spi(Contexts) public typealias HostContext = ARM64Context +#elseif arch(arm) +@_spi(Contexts) public typealias HostContext = ARMContext +#endif diff --git a/stdlib/public/Backtracing/CoreSymbolication.swift b/stdlib/public/Backtracing/CoreSymbolication.swift new file mode 100644 index 0000000000000..ab090267301e1 --- /dev/null +++ b/stdlib/public/Backtracing/CoreSymbolication.swift @@ -0,0 +1,359 @@ +//===--- CoreSymbolication.swift - Shims for CoreSymbolication ------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// CoreSymbolication is a private framework, which makes it tricky to link +// with from here and also means there are no headers on customer builds. +// +//===----------------------------------------------------------------------===// + +#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) + +import Swift + +@_implementationOnly import Darwin +@_implementationOnly import CoreFoundation + +@_implementationOnly import _SwiftBacktracingShims + +// .. Dynamic binding .......................................................... + +private let coreSymbolicationPath = + "/System/Library/PrivateFrameworks/CoreSymbolication.framework/CoreSymbolication" +private let coreSymbolicationHandle = dlopen(coreSymbolicationPath, RTLD_LAZY)! + +private let crashReporterSupportPath = + "/System/Library/PrivateFrameworks/CrashReporterSupport.framework/CrashReporterSupport" + +private let crashReporterSupportHandle = dlopen(crashReporterSupportPath, RTLD_LAZY)! + +private func symbol(_ handle: UnsafeMutableRawPointer, _ name: String) -> T { + guard let result = dlsym(handle, name) else { + fatalError("Unable to look up \(name) in CoreSymbolication") + } + return unsafeBitCast(result, to: T.self) +} + +private enum Sym { + // CRCopySanitizedPath + static let CRCopySanitizedPath: @convention(c) (CFString, CFIndex) -> CFString = + symbol(crashReporterSupportHandle, "CRCopySanitizedPath") + + // Base functionality + static let CSRetain: @convention(c) (CSTypeRef) -> CSTypeRef = + symbol(coreSymbolicationHandle, "CSRetain") + static let CSRelease: @convention(c) (CSTypeRef) -> () = + symbol(coreSymbolicationHandle, "CSRelease") + static let CSEqual: @convention(c) (CSTypeRef, CSTypeRef) -> CBool = + symbol(coreSymbolicationHandle, "CSEqual") + static let CSIsNull: @convention(c) (CSTypeRef) -> CBool = + symbol(coreSymbolicationHandle, "CSIsNull") + + // CSSymbolicator + static let CSSymbolicatorCreateWithBinaryImageList: + @convention(c) (UnsafeMutablePointer, + UInt32, UInt32, CSNotificationBlock?) -> CSSymbolicatorRef = + symbol(coreSymbolicationHandle, "CSSymbolicatorCreateWithBinaryImageList") + + static let CSSymbolicatorGetSymbolOwnerWithAddressAtTime: + @convention(c) (CSSymbolicatorRef, mach_vm_address_t, + CSMachineTime) -> CSSymbolOwnerRef = + symbol(coreSymbolicationHandle, "CSSymbolicatorGetSymbolOwnerWithAddressAtTime") + static let CSSymbolicatorForeachSymbolOwnerAtTime: + @convention(c) (CSSymbolicatorRef, CSMachineTime, @convention(block) (CSSymbolOwnerRef) -> Void) -> UInt = + symbol(coreSymbolicationHandle, "CSSymbolicatorForeachSymbolOwnerAtTime") + + // CSSymbolOwner + static let CSSymbolOwnerGetName: + @convention(c) (CSSymbolOwnerRef) -> UnsafePointer? = + symbol(coreSymbolicationHandle, "CSSymbolOwnerGetName") + static let CSSymbolOwnerGetSymbolWithAddress: + @convention(c) (CSSymbolOwnerRef, mach_vm_address_t) -> CSSymbolRef = + symbol(coreSymbolicationHandle, "CSSymbolOwnerGetSymbolWithAddress") + static let CSSymbolOwnerGetSourceInfoWithAddress: + @convention(c) (CSSymbolOwnerRef, mach_vm_address_t) -> CSSourceInfoRef = + symbol(coreSymbolicationHandle, "CSSymbolOwnerGetSourceInfoWithAddress") + static let CSSymbolOwnerForEachStackFrameAtAddress: + @convention(c) (CSSymbolOwnerRef, mach_vm_address_t, CSStackFrameIterator) -> UInt = + symbol(coreSymbolicationHandle, "CSSymbolOwnerForEachStackFrameAtAddress") + static let CSSymbolOwnerGetBaseAddress: + @convention(c) (CSSymbolOwnerRef) -> mach_vm_address_t = + symbol(coreSymbolicationHandle, "CSSymbolOwnerGetBaseAddress") + + // CSSymbol + static let CSSymbolGetRange: + @convention(c) (CSSymbolRef) -> CSRange = + symbol(coreSymbolicationHandle, "CSSymbolGetRange") + static let CSSymbolGetName: + @convention(c) (CSSymbolRef) -> UnsafePointer? = + symbol(coreSymbolicationHandle, "CSSymbolGetName") + static let CSSymbolGetMangledName: + @convention(c) (CSSymbolRef) -> UnsafePointer? = + symbol(coreSymbolicationHandle, "CSSymbolGetMangledName") + + // CSSourceInfo + static let CSSourceInfoGetPath: + @convention(c) (CSSourceInfoRef) -> UnsafePointer? = + symbol(coreSymbolicationHandle, "CSSourceInfoGetPath") + static let CSSourceInfoGetLineNumber: + @convention(c) (CSSourceInfoRef) -> UInt32 = + symbol(coreSymbolicationHandle, "CSSourceInfoGetLineNumber") + static let CSSourceInfoGetColumn: + @convention(c) (CSSourceInfoRef) -> UInt32 = + symbol(coreSymbolicationHandle, "CSSourceInfoGetColumn") +} + +// .. Crash Reporter support ................................................... + +// We can't import swiftFoundation here, so there's no automatic bridging for +// CFString. As a result, we need to do the dance manually. + +private func toCFString(_ s: String) -> CFString! { + var s = s + return s.withUTF8 { + return CFStringCreateWithBytes(nil, + $0.baseAddress, + $0.count, + CFStringBuiltInEncodings.UTF8.rawValue, + false) + } +} + +private func fromCFString(_ cf: CFString) -> String { + let length = CFStringGetLength(cf) + if length == 0 { + return "" + } + + if let ptr = CFStringGetCStringPtr(cf, + CFStringBuiltInEncodings.ASCII.rawValue) { + return String(decoding: UnsafeRawBufferPointer(start: ptr, count: length), + as: UTF8.self) + } else { + var byteLen = CFIndex(0) + + CFStringGetBytes(cf, + CFRangeMake(0, length), + CFStringBuiltInEncodings.UTF8.rawValue, + 0, + false, + nil, + 0, + &byteLen) + + let buffer = UnsafeMutableBufferPointer.allocate(capacity: byteLen) + defer { + buffer.deallocate() + } + + CFStringGetBytes(cf, CFRangeMake(0, length), + CFStringBuiltInEncodings.UTF8.rawValue, + 0, false, buffer.baseAddress, buffer.count, nil) + + return String(decoding: buffer, as: UTF8.self) + } +} + +func CRCopySanitizedPath(_ path: String, _ options: Int) -> String { + return fromCFString(Sym.CRCopySanitizedPath(toCFString(path), CFIndex(options))) +} + +// .. Base functionality ....................................................... + +func CSRetain(_ obj: CSTypeRef) -> CSTypeRef { + return Sym.CSRetain(obj) +} + +func CSRelease(_ obj: CSTypeRef) { + Sym.CSRelease(obj) +} + +func CSEqual(_ a: CSTypeRef, _ b: CSTypeRef) -> Bool { + return Sym.CSEqual(a, b) +} + +func CSIsNull(_ obj: CSTypeRef) -> Bool { + return Sym.CSIsNull(obj) +} + +// .. CSSymbolicator ........................................................... + +let kCSSymbolicatorDisallowDaemonCommunication = UInt32(0x00000800) + +struct BinaryRelocationInformation { + var base: mach_vm_address_t + var extent: mach_vm_address_t + var name: String +} + +struct BinaryImageInformation { + var base: mach_vm_address_t + var extent: mach_vm_address_t + var uuid: CFUUIDBytes + var arch: CSArchitecture + var path: String + var relocations: [BinaryRelocationInformation] + var flags: UInt32 +} + +func CSSymbolicatorCreateWithBinaryImageList( + _ imageInfo: [BinaryImageInformation], + _ flags: UInt32, + _ notificationBlock: CSNotificationBlock?) -> CSSymbolicatorRef { + + // Convert the Swifty types above to suitable input for the C API + var pathBuf: [UInt8] = [] + let imageList = UnsafeMutableBufferPointer.allocate(capacity: imageInfo.count) + defer { + imageList.deallocate() + } + + var totalRelocations = 0 + for image in imageInfo { + totalRelocations += image.relocations.count + + pathBuf.insert(contentsOf: image.path.utf8, at: pathBuf.count) + pathBuf.append(0) + } + + let relocationList = UnsafeMutableBufferPointer.allocate(capacity: totalRelocations) + defer { + relocationList.deallocate() + } + + return pathBuf.withUnsafeBufferPointer { + $0.withMemoryRebound(to: CChar.self) { pathData in + var pathPtr = pathData.baseAddress! + var relocationPtr = relocationList.baseAddress! + + for (n, image) in imageInfo.enumerated() { + imageList[n].base = image.base + imageList[n].extent = image.extent + imageList[n].uuid = image.uuid + imageList[n].arch = image.arch + imageList[n].path = pathPtr + imageList[n].relocations = relocationPtr + imageList[n].relocationCount = UInt32(image.relocations.count) + imageList[n].flags = image.flags + + pathPtr += strlen(pathPtr) + 1 + + for relocation in image.relocations { + relocationPtr.pointee.base = relocation.base + relocationPtr.pointee.extent = relocation.extent + withUnsafeMutablePointer(to: &relocationPtr.pointee.name) { + $0.withMemoryRebound(to: CChar.self, capacity: 17) { buf in + var utf8Iterator = relocation.name.utf8.makeIterator() + var ndx = 0 + while let ch = utf8Iterator.next(), ndx < 16 { + buf[ndx] = CChar(bitPattern: ch) + ndx += 1 + } + buf[ndx] = 0 + } + } + + relocationPtr += 1 + } + } + + return Sym.CSSymbolicatorCreateWithBinaryImageList( + imageList.baseAddress!, + UInt32(imageList.count), + flags, + notificationBlock + ) + } + } +} + +func CSSymbolicatorGetSymbolOwnerWithAddressAtTime( + _ symbolicator: CSSymbolicatorRef, + _ addr: mach_vm_address_t, + _ time: CSMachineTime +) -> CSSymbolOwnerRef { + return Sym.CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator, + addr, time) +} + +func CSSymbolicatorForeachSymbolOwnerAtTime( + _ symbolicator: CSSymbolicatorRef, + _ time: CSMachineTime, + _ symbolIterator: (CSSymbolOwnerRef) -> Void + ) -> UInt { + return Sym.CSSymbolicatorForeachSymbolOwnerAtTime(symbolicator, time, + symbolIterator) +} + +// .. CSSymbolOwner ............................................................ + +func CSSymbolOwnerGetName(_ sym: CSTypeRef) -> String? { + Sym.CSSymbolOwnerGetName(sym) + .map(String.init(cString:)) +} + +func CSSymbolOwnerGetSymbolWithAddress( + _ owner: CSSymbolOwnerRef, + _ address: mach_vm_address_t +) -> CSSymbolRef { + return Sym.CSSymbolOwnerGetSymbolWithAddress(owner, address) +} + +func CSSymbolOwnerGetSourceInfoWithAddress( + _ owner: CSSymbolOwnerRef, + _ address: mach_vm_address_t +) -> CSSourceInfoRef { + return Sym.CSSymbolOwnerGetSourceInfoWithAddress(owner, address) +} + +func CSSymbolOwnerForEachStackFrameAtAddress( + _ owner: CSSymbolOwnerRef, + _ address: mach_vm_address_t, + _ iterator: CSStackFrameIterator +) -> UInt { + return Sym.CSSymbolOwnerForEachStackFrameAtAddress(owner, address, iterator) +} + +func CSSymbolOwnerGetBaseAddress( + _ owner: CSSymbolOwnerRef +) -> mach_vm_address_t { + return Sym.CSSymbolOwnerGetBaseAddress(owner) +} + +// .. CSSymbol ................................................................. + +func CSSymbolGetRange(_ symbol: CSSymbolRef) -> CSRange { + return Sym.CSSymbolGetRange(symbol) +} + +func CSSymbolGetName(_ symbol: CSSymbolRef) -> String? { + return Sym.CSSymbolGetName(symbol).map{ String(cString: $0) } +} + +func CSSymbolGetMangledName(_ symbol: CSSymbolRef) -> String? { + return Sym.CSSymbolGetMangledName(symbol).map{ String(cString: $0) } +} + +// .. CSSourceInfo ............................................................. + +func CSSourceInfoGetPath(_ sourceInfo: CSSourceInfoRef) -> String? { + return Sym.CSSourceInfoGetPath(sourceInfo).map{ String(cString: $0) } +} + +func CSSourceInfoGetLineNumber(_ sourceInfo: CSSourceInfoRef) -> UInt32 { + return Sym.CSSourceInfoGetLineNumber(sourceInfo) +} + +func CSSourceInfoGetColumn(_ sourceInfo: CSSourceInfoRef) -> UInt32 { + return Sym.CSSourceInfoGetColumn(sourceInfo) +} + +#endif // os(Darwin) diff --git a/stdlib/public/Backtracing/FramePointerUnwinder.swift b/stdlib/public/Backtracing/FramePointerUnwinder.swift new file mode 100644 index 0000000000000..d5cc7b25a61ea --- /dev/null +++ b/stdlib/public/Backtracing/FramePointerUnwinder.swift @@ -0,0 +1,152 @@ +//===--- FramePointerUnwinder.swift ---------------------------*- swift -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Unwind the stack by chasing the frame pointer. +// +//===----------------------------------------------------------------------===// + +import Swift + +// @available(SwiftStdlib 5.1, *) +@_silgen_name("swift_task_getCurrent") +func _getCurrentAsyncTask() -> UnsafeRawPointer? + +@_spi(Unwinders) +public struct FramePointerUnwinder: Sequence, IteratorProtocol { + public typealias Context = C + public typealias MemoryReader = M + public typealias Address = MemoryReader.Address + + var pc: Address + var fp: Address + var asyncContext: Address + var first: Bool + var isAsync: Bool + + var reader: MemoryReader + + public init(context: Context, memoryReader: MemoryReader) { + pc = Address(context.programCounter) + fp = Address(context.framePointer) + first = true + isAsync = false + asyncContext = 0 + reader = memoryReader + } + + private func isAsyncFrame(_ storedFp: Address) -> Bool { + #if (os(macOS) || os(iOS) || os(watchOS)) && (arch(arm64) || arch(arm64_32) || arch(x86_64)) + // On Darwin, we borrow a bit of the frame pointer to indicate async + // stack frames + return (storedFp & (1 << 60)) != 0 && _getCurrentAsyncTask() != nil + #else + return false + #endif + } + + private func stripPtrAuth(_ address: Address) -> Address { + return Address(Context.stripPtrAuth(address: Context.Address(address))) + } + + private mutating func fetchAsyncContext() -> Bool { + let strippedFp = stripPtrAuth(fp) + + do { + asyncContext = try reader.fetch(from: Address(strippedFp - 8), + as: Address.self) + return true + } catch { + return false + } + } + + public mutating func next() -> Backtrace.Frame? { + if first { + first = false + pc = stripPtrAuth(pc) + return .programCounter(Backtrace.Address(pc)) + } + + if !isAsync { + // Try to read the next fp/pc pair + var next: Address = 0 + let strippedFp = stripPtrAuth(fp) + + if strippedFp == 0 + || !Context.isAlignedForStack(framePointer: + Context.Address(strippedFp)) { + return nil + } + + do { + pc = stripPtrAuth(try reader.fetch(from: + strippedFp + Address(MemoryLayout
.size), + as: Address.self)) + next = try reader.fetch(from: Address(strippedFp), as: Address.self) + } catch { + return nil + } + + if next <= fp { + return nil + } + + if !isAsyncFrame(next) { + fp = next + return .returnAddress(Backtrace.Address(pc)) + } + + isAsync = true + if !fetchAsyncContext() { + return nil + } + } + + // If we get here, we're in async mode + + var next: Address = 0 + let strippedCtx = stripPtrAuth(asyncContext) + + if strippedCtx == 0 { + return nil + } + + #if arch(arm64_32) + + // On arm64_32, the two pointers at the start of the context are 32-bit, + // although the stack layout is identical to vanilla arm64 + do { + var next32 = try reader.fetch(from: strippedCtx, as: UInt32.self) + var pc32 = try reader.fetch(from: strippedCtx + 4, as: UInt32.self) + + next = Address(next32) + pc = stripPtrAuth(Address(pc32)) + } catch { + return nil + } + #else + + // Otherwise it's two 64-bit words + do { + next = try reader.fetch(from: strippedCtx, as: Address.self) + pc = stripPtrAuth(try reader.fetch(from: strippedCtx + 8, as: Address.self)) + } catch { + return nil + } + + #endif + + asyncContext = next + + return .asyncResumePoint(Backtrace.Address(pc)) + } +} diff --git a/stdlib/public/Backtracing/MemoryReader.swift b/stdlib/public/Backtracing/MemoryReader.swift new file mode 100644 index 0000000000000..ee1e5a23e2750 --- /dev/null +++ b/stdlib/public/Backtracing/MemoryReader.swift @@ -0,0 +1,118 @@ +//===--- MemoryReader.swift -----------------------------------*- swift -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Provides the ability to read memory, both in the current process and +// remotely. +// +//===----------------------------------------------------------------------===// + +import Swift + +@_implementationOnly import _SwiftBacktracingShims + +@_spi(MemoryReaders) public protocol MemoryReader { + associatedtype Address: FixedWidthInteger + + func fetch(from address: Address, + into buffer: UnsafeMutableBufferPointer) throws + + func fetch(from addr: Address, + into pointer: UnsafeMutablePointer) throws + + func fetch(from addr: Address, count: Int, as: T.Type) throws -> [T] + + func fetch(from addr: Address, as: T.Type) throws -> T +} + +extension MemoryReader { + + public func fetch(from addr: Address, + into pointer: UnsafeMutablePointer) throws { + try fetch(from: addr, + into: UnsafeMutableBufferPointer(start: pointer, count: 1)) + } + + public func fetch(from addr: Address, count: Int, as: T.Type) throws -> [T] { + let array = try Array(unsafeUninitializedCapacity: count){ + buffer, initializedCount in + + try fetch(from: addr, into: buffer) + + initializedCount = count + } + + return array + } + + public func fetch(from addr: Address, as: T.Type) throws -> T { + return try withUnsafeTemporaryAllocation(of: T.self, capacity: 1) { buf in + try fetch(from: addr, into: buf) + return buf[0] + } + } + +} + +@_spi(MemoryReaders) public struct UnsafeLocalMemoryReader: MemoryReader { + public typealias Address = UInt + + public func fetch(from address: Address, + into buffer: UnsafeMutableBufferPointer) throws { + buffer.baseAddress!.update(from: UnsafePointer(bitPattern: address)!, + count: buffer.count) + } +} + +#if os(macOS) +@_implementationOnly import Darwin.Mach + +@_spi(MemoryReaders) public struct MachError: Error { + var result: kern_return_t +} + +@_spi(MemoryReaders) public struct RemoteMemoryReader: MemoryReader { + public typealias Address = UInt64 + + private var task: task_t + + // Sadly we can't expose the type of this argument + public init(task: Any) { + self.task = task as! task_t + } + + public func fetch(from address: Address, + into buffer: UnsafeMutableBufferPointer) throws { + let size = mach_vm_size_t(MemoryLayout.stride * buffer.count) + var sizeOut = mach_vm_size_t(0) + let kr = mach_vm_read_overwrite(task, + mach_vm_address_t(address), + mach_vm_size_t(size), + unsafeBitCast(buffer.baseAddress, + to: mach_vm_address_t.self), + &sizeOut) + + if kr != KERN_SUCCESS { + throw MachError(result: kr) + } + } +} + +@_spi(MemoryReaders) public struct LocalMemoryReader: MemoryReader { + public typealias Address = UInt64 + + public func fetch(from address: Address, + into buffer: UnsafeMutableBufferPointer) throws { + let reader = RemoteMemoryReader(task: mach_task_self_) + return try reader.fetch(from: address, into: buffer) + } +} +#endif diff --git a/stdlib/public/Backtracing/Registers.swift b/stdlib/public/Backtracing/Registers.swift new file mode 100644 index 0000000000000..8b21697de0cbe --- /dev/null +++ b/stdlib/public/Backtracing/Registers.swift @@ -0,0 +1,586 @@ +//===--- Registers.swift - Dwarf register mapping -------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Holds enums that define DWARF register mappings for the architectures we +// care about. +// +//===----------------------------------------------------------------------===// + +import Swift + +// .. x86-64 ................................................................. + +// https://gitlab.com/x86-psABIs/x86-64-ABI +@_spi(Registers) public enum X86_64Register: Int, Strideable, Comparable { + + public func advanced(by n: Int) -> X86_64Register { + return X86_64Register(rawValue: self.rawValue + n)! + } + + public func distance(to other: X86_64Register) -> Int { + return other.rawValue - self.rawValue + } + + public static func < (lhs: Self, rhs: Self) -> Bool { + return lhs.rawValue < rhs.rawValue + } + +case rax = 0 +case rdx = 1 +case rcx = 2 +case rbx = 3 +case rsi = 4 +case rdi = 5 +case rbp = 6 +case rsp = 7 +case r8 = 8 +case r9 = 9 +case r10 = 10 +case r11 = 11 +case r12 = 12 +case r13 = 13 +case r14 = 14 +case r15 = 15 +case ra = 16 +case xmm0 = 17 +case xmm1 = 18 +case xmm2 = 19 +case xmm3 = 20 +case xmm4 = 21 +case xmm5 = 22 +case xmm6 = 23 +case xmm7 = 24 +case xmm8 = 25 +case xmm9 = 26 +case xmm10 = 27 +case xmm11 = 28 +case xmm12 = 29 +case xmm13 = 30 +case xmm14 = 31 +case xmm15 = 32 +case st0 = 33 +case st1 = 34 +case st2 = 35 +case st3 = 36 +case st4 = 37 +case st5 = 38 +case st6 = 39 +case st7 = 40 +case mm0 = 41 +case mm1 = 42 +case mm2 = 43 +case mm3 = 44 +case mm4 = 45 +case mm5 = 46 +case mm6 = 47 +case mm7 = 48 +case rflags = 49 +case es = 50 +case cs = 51 +case ss = 52 +case ds = 53 +case fs = 54 +case gs = 55 + // 56-57 are reserved +case fs_base = 58 +case gs_base = 59 + // 60-61 are reserved +case tr = 62 +case ldtr = 63 +case mxcsr = 64 +case fcw = 65 +case fsw = 66 +case xmm16 = 67 +case xmm17 = 68 +case xmm18 = 69 +case xmm19 = 70 +case xmm20 = 71 +case xmm21 = 72 +case xmm22 = 73 +case xmm23 = 74 +case xmm24 = 75 +case xmm25 = 76 +case xmm26 = 77 +case xmm27 = 78 +case xmm28 = 79 +case xmm29 = 80 +case xmm30 = 81 +case xmm31 = 82 + // 83-117 are reserved +case k0 = 118 +case k1 = 119 +case k2 = 120 +case k3 = 121 +case k4 = 122 +case k5 = 123 +case k6 = 124 +case k7 = 125 + // 126-129 are reserved +} + +// .. i386 ................................................................... + +// https://gitlab.com/x86-psABIs/i386-ABI +@_spi(Registers) public enum I386Register: Int, Strideable, Comparable { + + public func advanced(by n: Int) -> I386Register { + return I386Register(rawValue: self.rawValue + n)! + } + + public func distance(to other: I386Register) -> Int { + return other.rawValue - self.rawValue + } + + public static func < (lhs: Self, rhs: Self) -> Bool { + return lhs.rawValue < rhs.rawValue + } + +case eax = 0 +case ecx = 1 +case edx = 2 +case ebx = 3 +case esp = 4 +case ebp = 5 +case esi = 6 +case edi = 7 +case ra = 8 +case eflags = 9 + // 10 is reserved +case st0 = 11 +case st1 = 12 +case st2 = 13 +case st3 = 14 +case st4 = 15 +case st5 = 16 +case st6 = 17 +case st7 = 18 + // 19-20 are reserved +case xmm0 = 21 +case xmm1 = 22 +case xmm2 = 23 +case xmm3 = 24 +case xmm4 = 25 +case xmm5 = 26 +case xmm6 = 27 +case xmm7 = 28 +case mm0 = 29 +case mm1 = 30 +case mm2 = 31 +case mm3 = 32 +case mm4 = 33 +case mm5 = 34 +case mm6 = 35 +case mm7 = 36 + // 36-38 are reserved +case mxcsr = 39 +case es = 40 +case cs = 41 +case ss = 42 +case ds = 43 +case fs = 44 +case gs = 45 + // 46-47 are reserved +case tr = 48 +case ldtr = 49 + // 50-92 are reserved +case fs_base = 93 +case gs_base = 94 +} + +// .. arm64 .................................................................. + +// https://github.com/ARM-software/abi-aa/tree/main/aadwarf64 +@_spi(Registers) public enum ARM64Register: Int, Strideable, Comparable { + + public func advanced(by n: Int) -> ARM64Register { + return ARM64Register(rawValue: self.rawValue + n)! + } + + public func distance(to other: ARM64Register) -> Int { + return other.rawValue - self.rawValue + } + + public static func < (lhs: Self, rhs: Self) -> Bool { + return lhs.rawValue < rhs.rawValue + } + +case x0 = 0 +case x1 = 1 +case x2 = 2 +case x3 = 3 +case x4 = 4 +case x5 = 5 +case x6 = 6 +case x7 = 7 +case x8 = 8 +case x9 = 9 +case x10 = 10 +case x11 = 11 +case x12 = 12 +case x13 = 13 +case x14 = 14 +case x15 = 15 +case x16 = 16 +case x17 = 17 +case x18 = 18 +case x19 = 19 +case x20 = 20 +case x21 = 21 +case x22 = 22 +case x23 = 23 +case x24 = 24 +case x25 = 25 +case x26 = 26 +case x27 = 27 +case x28 = 28 +case x29 = 29 // fp +case x30 = 30 // lr +case sp = 31 // x31 +case pc = 32 +case elr_mode = 33 +case ra_sign_state = 34 +case tpidrro_el0 = 35 +case tpidr_el0 = 36 +case tpidr_el1 = 37 +case tpidr_el2 = 38 +case tpidr_el3 = 39 + // 40-45 are reserved +case vg = 46 +case ffr = 47 +case p0 = 48 +case p1 = 49 +case p2 = 50 +case p3 = 51 +case p4 = 52 +case p5 = 53 +case p6 = 54 +case p7 = 55 +case p8 = 56 +case p9 = 57 +case p10 = 58 +case p11 = 59 +case p12 = 60 +case p13 = 61 +case p14 = 62 +case p15 = 63 +case v0 = 64 +case v1 = 65 +case v2 = 66 +case v3 = 67 +case v4 = 68 +case v5 = 69 +case v6 = 70 +case v7 = 71 +case v8 = 72 +case v9 = 73 +case v10 = 74 +case v11 = 75 +case v12 = 76 +case v13 = 77 +case v14 = 78 +case v15 = 79 +case v16 = 80 +case v17 = 81 +case v18 = 82 +case v19 = 83 +case v20 = 84 +case v21 = 85 +case v22 = 86 +case v23 = 87 +case v24 = 88 +case v25 = 89 +case v26 = 90 +case v27 = 91 +case v28 = 92 +case v29 = 93 +case v30 = 94 +case v31 = 95 +case z0 = 96 +case z1 = 97 +case z2 = 98 +case z3 = 99 +case z4 = 100 +case z5 = 101 +case z6 = 102 +case z7 = 103 +case z8 = 104 +case z9 = 105 +case z10 = 106 +case z11 = 107 +case z12 = 108 +case z13 = 109 +case z14 = 110 +case z15 = 111 +case z16 = 112 +case z17 = 113 +case z18 = 114 +case z19 = 115 +case z20 = 116 +case z21 = 117 +case z22 = 118 +case z23 = 119 +case z24 = 120 +case z25 = 121 +case z26 = 122 +case z27 = 123 +case z28 = 124 +case z29 = 125 +case z30 = 126 +case z31 = 127 +} + +// .. arm .................................................................... + +// https://github.com/ARM-software/abi-aa/tree/main/aadwarf32 +@_spi(Registers) public enum ARMRegister: Int, Strideable, Comparable { + + public func advanced(by n: Int) -> ARMRegister { + return ARMRegister(rawValue: self.rawValue + n)! + } + + public func distance(to other: ARMRegister) -> Int { + return other.rawValue - self.rawValue + } + + public static func < (lhs: Self, rhs: Self) -> Bool { + return lhs.rawValue < rhs.rawValue + } + +case r0 = 0 +case r1 = 1 +case r2 = 2 +case r3 = 3 +case r4 = 4 +case r5 = 5 +case r6 = 6 +case r7 = 7 +case r8 = 8 +case r9 = 9 +case r10 = 10 +case r11 = 11 // fp +case r12 = 12 // ip - scratch register (NOT "instruction pointer") +case r13 = 13 // sp +case r14 = 14 // lr +case r15 = 15 // pc + + // Obsolescent, overlapping mappings for FPA and VFP +case old_f0_s0 = 16 +case old_f1_s1 = 17 +case old_f2_s2 = 18 +case old_f3_s3 = 19 +case old_f4_s4 = 20 +case old_f5_s5 = 21 +case old_f6_s6 = 22 +case old_f7_s7 = 23 +case old_s8 = 24 +case old_s9 = 25 +case old_s10 = 26 +case old_s11 = 27 +case old_s12 = 28 +case old_s13 = 29 +case old_s14 = 30 +case old_s15 = 31 +case old_s16 = 32 +case old_s17 = 33 +case old_s18 = 34 +case old_s19 = 35 +case old_s20 = 36 +case old_s21 = 37 +case old_s22 = 38 +case old_s23 = 39 +case old_s24 = 40 +case old_s25 = 41 +case old_s26 = 42 +case old_s27 = 43 +case old_s28 = 44 +case old_s29 = 45 +case old_s30 = 46 +case old_s31 = 47 + + // Legacy VFPv2 +case s0 = 64 +case s1 = 65 +case s2 = 66 +case s3 = 67 +case s4 = 68 +case s5 = 69 +case s6 = 70 +case s7 = 71 +case s8 = 72 +case s9 = 73 +case s10 = 74 +case s11 = 75 +case s12 = 76 +case s13 = 77 +case s14 = 78 +case s15 = 79 +case s16 = 80 +case s17 = 81 +case s18 = 82 +case s19 = 83 +case s20 = 84 +case s21 = 85 +case s22 = 86 +case s23 = 87 +case s24 = 88 +case s25 = 89 +case s26 = 90 +case s27 = 91 +case s28 = 92 +case s29 = 93 +case s30 = 94 +case s31 = 95 + + // Obsolescent FPA registers +case f0 = 96 +case f1 = 97 +case f2 = 98 +case f3 = 99 +case f4 = 100 +case f5 = 101 +case f6 = 102 +case f7 = 103 + + // Intel wireless MMX GPRs / XScale accumulators +case wcgr0_acc0 = 104 +case wcgr1_acc1 = 105 +case wcgr2_acc2 = 106 +case wcgr3_acc3 = 107 +case wcgr4_acc4 = 108 +case wcgr5_acc5 = 109 +case wcgr6_acc6 = 110 +case wcgr7_acc7 = 111 + + // Intel wireless MMX data registers +case wr0 = 112 +case wr1 = 113 +case wr2 = 114 +case wr3 = 115 +case wr4 = 116 +case wr5 = 117 +case wr6 = 118 +case wr7 = 119 +case wr8 = 120 +case wr9 = 121 +case wr10 = 122 +case wr11 = 123 +case wr12 = 124 +case wr13 = 125 +case wr14 = 126 +case wr15 = 127 + +case spsr = 128 +case spsr_fiq = 129 +case spsr_irq = 130 +case spsr_abt = 131 +case spsr_und = 132 +case spsr_svc = 133 + + // 134-142 are reserved + +case ra_auth_code = 143 + +case r8_usr = 144 +case r9_usr = 145 +case r10_usr = 146 +case r11_usr = 147 +case r12_usr = 148 +case r13_usr = 149 +case r14_usr = 150 + +case r8_fiq = 151 +case r9_fiq = 152 +case r10_fiq = 153 +case r11_fiq = 154 +case r12_fiq = 155 +case r13_fiq = 156 +case r14_fiq = 157 + +case r13_irq = 158 +case r14_irq = 159 + +case r13_abt = 160 +case r14_abt = 161 + +case r13_und = 162 +case r14_und = 163 + +case r13_svc = 164 +case r14_svc = 165 + + // 166-191 are reserved + + // Intel wqireless MMX control register +case wc0 = 192 +case wc1 = 193 +case wc2 = 194 +case wc3 = 195 +case wc4 = 196 +case wc5 = 197 +case wc6 = 198 +case wc7 = 199 + + // 200-255 are reserved + +case d0 = 256 +case d1 = 257 +case d2 = 258 +case d3 = 259 +case d4 = 260 +case d5 = 261 +case d6 = 262 +case d7 = 263 +case d8 = 264 +case d9 = 265 +case d10 = 266 +case d11 = 267 +case d12 = 268 +case d13 = 269 +case d14 = 270 +case d15 = 271 +case d16 = 272 +case d17 = 273 +case d18 = 274 +case d19 = 275 +case d20 = 276 +case d21 = 277 +case d22 = 278 +case d23 = 279 +case d24 = 280 +case d25 = 281 +case d26 = 282 +case d27 = 283 +case d28 = 284 +case d29 = 285 +case d30 = 286 +case d31 = 287 + + // 288-319 are reserved + +case tpidruro = 320 +case tpidrurw = 321 +case tpidpr = 322 +case htpidpr = 323 + + // 324-8191 are reserved + // 8192-16383 are for vendor co-processors +} + +#if arch(x86_64) +@_spi(Registers) public typealias HostRegister = X86_64Register +#elseif arch(i386) +@_spi(Registers) public typealias HostRegister = I386Register +#elseif arch(arm64) || arch(arm64_32) +@_spi(Registers) public typealias HostRegister = ARM64Register +#elseif arch(arm) +@_spi(Registers) public typealias HostRegister = ARMRegister +#endif diff --git a/stdlib/public/Backtracing/SymbolicatedBacktrace.swift b/stdlib/public/Backtracing/SymbolicatedBacktrace.swift new file mode 100644 index 0000000000000..c882bc5934721 --- /dev/null +++ b/stdlib/public/Backtracing/SymbolicatedBacktrace.swift @@ -0,0 +1,469 @@ +//===--- Backtrace.swift --------------------------------------*- swift -*-===// +// +// 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 `SymbolicatedBacktrace` struct that represents a captured +// backtrace with symbols. +// +//===----------------------------------------------------------------------===// + +import Swift + +@_implementationOnly import _SwiftBacktracingShims + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +@_implementationOnly import CoreFoundation +#endif + +@_silgen_name("_swift_isThunkFunction") +func _swift_isThunkFunction( + _ rawName: UnsafePointer? +) -> CBool + +/// A symbolicated backtrace +public struct SymbolicatedBacktrace: CustomStringConvertible { + /// The `Backtrace` from which this was constructed + public var backtrace: Backtrace + + /// Represents a location in source code. + /// + /// The information in this structure comes from compiler-generated + /// debug information and may not correspond to the current state of + /// the filesystem --- it might even hold a path that only works + /// from an entirely different machine. + public struct SourceLocation: CustomStringConvertible, Sendable, Hashable { + /// The path of the source file. + public var path: String + + /// The line number. + public var line: Int + + /// The column number. + public var column: Int + + /// Provide a textual description. + public var description: String { + if column > 0 && line > 0 { + return "\(path):\(line):\(column)" + } else if line > 0 { + return "\(path):\(line)" + } else { + return path + } + } + } + + /// Represents an individual frame in the backtrace. + public struct Frame: CustomStringConvertible { + /// The captured frame from the `Backtrace`. + public var captured: Backtrace.Frame + + /// The result of doing a symbol lookup for this frame. + public var symbol: Symbol? + + /// If `true`, then this frame was inlined + public var inlined: Bool = false + + /// `true` if this frame represents a Swift runtime failure. + public var isSwiftRuntimeFailure: Bool { + symbol?.isSwiftRuntimeFailure ?? false + } + + /// `true` if this frame represents a Swift thunk function. + public var isSwiftThunk: Bool { + symbol?.isSwiftThunk ?? false + } + + /// `true` if this frame is a system frame. + public var isSystem: Bool { + symbol?.isSystemFunction ?? false + } + + /// A textual description of this frame. + public var description: String { + if let symbol = symbol { + let isInlined = inlined ? " [inlined]" : "" + let isThunk = isSwiftThunk ? " [thunk]" : "" + return "\(captured)\(isInlined)\(isThunk) \(symbol)" + } else { + return captured.description + } + } + } + + /// Represents a symbol we've located + public class Symbol: CustomStringConvertible { + /// The index of the image in which the symbol for this address is located. + public var imageIndex: Int + + /// The name of the image in which the symbol for this address is located. + public var imageName: String + + /// The raw symbol name, before demangling. + public var rawName: String + + /// The demangled symbol name. + public lazy var name: String = demangleRawName() + + /// The offset from the symbol. + public var offset: Int + + /// The source location, if available. + public var sourceLocation: SourceLocation? + + /// True if this symbol represents a Swift runtime failure. + public var isSwiftRuntimeFailure: Bool { + guard let sourceLocation = sourceLocation else { + return false + } + + let symName: Substring + if rawName.hasPrefix("_") { + symName = rawName.dropFirst() + } else { + symName = rawName.dropFirst(0) + } + + return symName.hasPrefix("Swift runtime failure: ") + && sourceLocation.line == 0 + && sourceLocation.column == 0 + && sourceLocation.path.hasSuffix("") + } + + /// True if this symbol is a Swift thunk function. + public var isSwiftThunk: Bool { + return _swift_isThunkFunction(rawName) + } + + /// True if this symbol represents a system function. + /// + /// For instance, the `start` function from `dyld` on macOS is a system + /// function, and we don't need to display it under normal circumstances. + public var isSystemFunction: Bool { + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + if rawName == "start" && imageName == "dyld" { + return true + } + if let location = sourceLocation, + location.line == 0 && location.column == 0 { + return true + } + if rawName.hasSuffix("5$mainyyFZ") { + return true + } + #endif + return false + } + + /// Construct a new Symbol. + public init(imageIndex: Int, imageName: String, + rawName: String, offset: Int, sourceLocation: SourceLocation?) { + self.imageIndex = imageIndex + self.imageName = imageName + self.rawName = rawName + self.offset = offset + self.sourceLocation = sourceLocation + } + + /// Demangle the raw name, if possible. + private func demangleRawName() -> String { + // We don't actually need this function on macOS because we're using + // CoreSymbolication, which demangles the name when it does the lookup + // anyway. We will need it for Linux and Windows though. + return rawName + } + + /// A textual description of this symbol. + public var description: String { + let symPlusOffset: String + + if offset > 0 { + symPlusOffset = "\(name) + \(offset)" + } else if offset < 0 { + symPlusOffset = "\(name) - \(-offset)" + } else { + symPlusOffset = name + } + + let location: String + if let sourceLocation = sourceLocation { + location = " at \(sourceLocation)" + } else { + location = "" + } + + return "[\(imageIndex)] \(imageName) \(symPlusOffset)\(location)" + } + } + + /// A list of captured frame information. + public var frames: [Frame] + + /// A list of images found in the process. + public var images: [Backtrace.Image] + + /// Shared cache information. + public var sharedCacheInfo: Backtrace.SharedCacheInfo? + + /// True if this backtrace is a Swift runtime failure. + public var isSwiftRuntimeFailure: Bool { + guard let frame = frames.first else { return false } + return frame.isSwiftRuntimeFailure + } + + /// If this backtrace is a Swift runtime failure, return the description. + public var swiftRuntimeFailure: String? { + guard let frame = frames.first else { return nil } + if !frame.isSwiftRuntimeFailure { return nil } + + let symbolName = frame.symbol!.rawName + if symbolName.hasPrefix("_") { + return String(symbolName.dropFirst()) + } + return symbolName + } + + /// Construct a SymbolicatedBacktrace from a backtrace and a list of images. + private init(backtrace: Backtrace, images: [Backtrace.Image], + sharedCacheInfo: Backtrace.SharedCacheInfo?, + frames: [Frame]) { + self.backtrace = backtrace + self.images = images + self.sharedCacheInfo = sharedCacheInfo + self.frames = frames + } + + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + /// Convert a build ID to a CFUUIDBytes. + private static func uuidBytesFromBuildID(_ buildID: [UInt8]) -> CFUUIDBytes { + var result = CFUUIDBytes() + withUnsafeMutablePointer(to: &result) { + $0.withMemoryRebound(to: UInt8.self, + capacity: MemoryLayout.size) { + let bp = UnsafeMutableBufferPointer(start: $0, + count: MemoryLayout.size) + _ = bp.initialize(from: buildID) + } + } + return result + } + + /// Create a symbolicator. + private static func withSymbolicator(images: [Backtrace.Image], + sharedCacheInfo: Backtrace.SharedCacheInfo?, + useSymbolCache: Bool, + fn: (CSSymbolicatorRef) throws -> T) rethrows -> T { + let binaryImageList = images.map{ image in + BinaryImageInformation( + base: mach_vm_address_t(image.baseAddress), + extent: mach_vm_address_t(image.endOfText), + uuid: uuidBytesFromBuildID(image.buildID!), + arch: HostContext.coreSymbolicationArchitecture, + path: image.path, + relocations: [ + BinaryRelocationInformation( + base: mach_vm_address_t(image.baseAddress), + extent: mach_vm_address_t(image.endOfText), + name: "__TEXT" + ) + ], + flags: 0 + ) + } + + let symbolicator = CSSymbolicatorCreateWithBinaryImageList( + binaryImageList, + useSymbolCache ? 0 : kCSSymbolicatorDisallowDaemonCommunication, + nil + ) + + defer { CSRelease(symbolicator) } + + return try fn(symbolicator) + } + + /// Generate a frame from a symbol and source info pair + private static func buildFrame(from capturedFrame: Backtrace.Frame, + with owner: CSSymbolOwnerRef, + isInline: Bool, + symbol: CSSymbolRef, + sourceInfo: CSSourceInfoRef, + images: [Backtrace.Image]) -> Frame { + if CSIsNull(symbol) { + return Frame(captured: capturedFrame, symbol: nil) + } + + let address = capturedFrame.originalProgramCounter + let rawName = CSSymbolGetMangledName(symbol) ?? "" + let name = CSSymbolGetName(symbol) ?? rawName + let range = CSSymbolGetRange(symbol) + + let location: SourceLocation? + + if !CSIsNull(sourceInfo) { + let path = CSSourceInfoGetPath(sourceInfo) ?? "" + let line = CSSourceInfoGetLineNumber(sourceInfo) + let column = CSSourceInfoGetColumn(sourceInfo) + + location = SourceLocation( + path: path, + line: Int(line), + column: Int(column) + ) + } else { + location = nil + } + + let imageBase = CSSymbolOwnerGetBaseAddress(owner) + var imageIndex = -1 + var imageName = "" + for (ndx, image) in images.enumerated() { + if image.baseAddress == imageBase { + imageIndex = ndx + imageName = image.name + break + } + } + + let theSymbol = Symbol(imageIndex: imageIndex, + imageName: imageName, + rawName: rawName, + offset: Int(address - UInt(range.location)), + sourceLocation: location) + theSymbol.name = name + + return Frame(captured: capturedFrame, symbol: theSymbol, inlined: isInline) + } + #endif + + /// Actually symbolicate. + internal static func symbolicate(backtrace: Backtrace, + images: [Backtrace.Image]?, + sharedCacheInfo: Backtrace.SharedCacheInfo?, + showInlineFrames: Bool, + useSymbolCache: Bool) + -> SymbolicatedBacktrace? { + + let theImages: [Backtrace.Image] + if let images = images { + theImages = images + } else if let images = backtrace.images { + theImages = images + } else { + theImages = Backtrace.captureImages() + } + + let theCacheInfo: Backtrace.SharedCacheInfo? + if let sharedCacheInfo = sharedCacheInfo { + theCacheInfo = sharedCacheInfo + } else if let sharedCacheInfo = backtrace.sharedCacheInfo { + theCacheInfo = sharedCacheInfo + } else { + theCacheInfo = Backtrace.captureSharedCacheInfo() + } + + var frames: [Frame] = [] + + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + withSymbolicator(images: theImages, + sharedCacheInfo: theCacheInfo, + useSymbolCache: useSymbolCache) { symbolicator in + for frame in backtrace.frames { + switch frame { + case .omittedFrames(_), .truncated: + frames.append(Frame(captured: frame, symbol: nil)) + default: + let address = mach_vm_address_t(frame.adjustedProgramCounter) + let owner + = CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator, + address, + kCSBeginningOfTime) + + if CSIsNull(owner) { + frames.append(Frame(captured: frame, symbol: nil)) + } else if showInlineFrames { + // These present in *reverse* order (i.e. the real one first, + // then the inlined frames from callee to caller). + let pos = frames.count + var first = true + + _ = CSSymbolOwnerForEachStackFrameAtAddress(owner, address) { + symbol, sourceInfo in + + frames.insert(buildFrame(from: frame, + with: owner, + isInline: !first, + symbol: symbol, + sourceInfo: sourceInfo, + images: theImages), + at: pos) + + first = false + } + } else { + let symbol = CSSymbolOwnerGetSymbolWithAddress(owner, address) + let sourceInfo = CSSymbolOwnerGetSourceInfoWithAddress(owner, + address) + + frames.append(buildFrame(from: frame, + with: owner, + isInline: false, + symbol: symbol, + sourceInfo: sourceInfo, + images: theImages)) + } + } + } + } + #else + frames = backtrace.frames.map{ Frame(captured: $0, symbol: nil) } + #endif + + return SymbolicatedBacktrace(backtrace: backtrace, + images: theImages, + sharedCacheInfo: theCacheInfo, + frames: frames) + } + + /// Provide a textual version of the backtrace. + public var description: String { + var lines: [String] = [] + + var n = 0 + for frame in frames { + lines.append("\(n)\t\(frame)") + switch frame.captured { + case let .omittedFrames(count): + n += count + default: + n += 1 + } + } + + lines.append("") + lines.append("Images:") + lines.append("") + for (n, image) in images.enumerated() { + lines.append("\(n)\t\(image)") + } + + if let sharedCacheInfo = sharedCacheInfo { + lines.append("") + lines.append("Shared Cache:") + lines.append("") + lines.append(" UUID: \(hex(sharedCacheInfo.uuid))") + lines.append(" Base: \(hex(sharedCacheInfo.baseAddress))") + lines.append(" Active: \(!sharedCacheInfo.noCache)") + } + + return lines.joined(separator: "\n") + } +} diff --git a/stdlib/public/Backtracing/Utils.swift b/stdlib/public/Backtracing/Utils.swift new file mode 100644 index 0000000000000..5913f047c3843 --- /dev/null +++ b/stdlib/public/Backtracing/Utils.swift @@ -0,0 +1,32 @@ +//===--- Utils.swift - Utility functions ----------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Utility functions that are used in the backtracing library. +// +//===----------------------------------------------------------------------===// + +import Swift + +internal func hex(_ value: T, + withPrefix: Bool = true) -> String { + let digits = String(value, radix: 16) + let padTo = value.bitWidth / 4 + let padding = digits.count >= padTo ? "" : String(repeating: "0", + count: padTo - digits.count) + let prefix = withPrefix ? "0x" : "" + + return "\(prefix)\(padding)\(digits)" +} + +internal func hex(_ bytes: [UInt8]) -> String { + return bytes.map{ hex($0, withPrefix: false) }.joined(separator: "") +} diff --git a/stdlib/public/Backtracing/get-cpu-context.S b/stdlib/public/Backtracing/get-cpu-context.S new file mode 100644 index 0000000000000..7c574dfe5be95 --- /dev/null +++ b/stdlib/public/Backtracing/get-cpu-context.S @@ -0,0 +1,142 @@ +//===--- get-cpu-context.S - Low-level functions to capture registers -----===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Saves the necessary registers to an appropriate Context structure. +// +//===----------------------------------------------------------------------===// + + // On Apple platforms, we need an extra underscore +#if __APPLE__ + #define FN(x) _##x +#else + #define FN(x) x +#endif + + .text + .globl FN(_swift_get_cpu_context) + +// .. x86-64 ................................................................... + +#if defined(__x86_64__) + // On entry, rax contains the pointer to the x86_64_gprs +FN(_swift_get_cpu_context): + movq %rax, (%rax) + movq %rdx, 8(%rax) + movq %rcx, 16(%rax) + movq %rbx, 24(%rax) + movq %rsi, 32(%rax) + movq %rdi, 40(%rax) + movq %rbp, 48(%rax) + leaq 8(%rsp), %rdx + movq %rdx, 56(%rax) + movq %r8, 64(%rax) + movq %r9, 72(%rax) + movq %r10, 80(%rax) + movq %r11, 88(%rax) + movq %r12, 96(%rax) + movq %r13, 104(%rax) + movq %r14, 112(%rax) + movq %r15, 120(%rax) + pushf + pop %rdx + movq %rdx, 128(%rax) + movw %cs, %dx + movw %dx, 136(%rax) + movw %fs, %dx + movw %dx, 138(%rax) + movw %gs, %dx + movw %dx, 140(%rax) + movq (%rsp), %rdx + movq %rdx, 144(%rax) + movq $0x1fffff, 152(%rax) + ret +#endif + +// .. i386 ..................................................................... + +#if defined(__i386__) + // On entry, 8(%esp) contains the pointer to the i386_gprs +FN(_swift_get_cpu_context): + push %eax + movl 8(%esp), %eax + movl %ecx, 4(%eax) + movl %edx, 8(%eax) + movl %ebx, 12(%eax) + movl %esi, 16(%eax) + movl %edi, 20(%eax) + movl %ebp, 24(%eax) + leal 8(%esp), %edx + movl %edx, 28(%eax) + pushf + pop %edx + movl %edx, 32(%eax) + movw %ss, %dx + movw %dx, 36(%eax) + movw %cs, %dx + movw %dx, 38(%eax) + movw %ds, %dx + movw %dx, 40(%eax) + movw %es, %dx + movw %dx, 42(%eax) + movw %fs, %dx + movw %dx, 44(%eax) + movw %gs, %dx + movw %dx, 46(%eax) + movl (%esp), %edx + movl %edx, (%eax) + movl $0xffff, 48(%eax) + popl %eax + ret +#endif + +// .. ARM64 .................................................................... + +#if defined(__aarch64__) + .p2align 2 + // On entry, x8 contains a pointer to the arm64_gprs +FN(_swift_get_cpu_context): + stp x0, x1, [x8, #0x00] + stp x2, x3, [x8, #0x10] + stp x4, x5, [x8, #0x20] + stp x6, x7, [x8, #0x30] + stp x8, x9, [x8, #0x40] + stp x10, x11, [x8, #0x50] + stp x12, x13, [x8, #0x60] + stp x14, x15, [x8, #0x70] + stp x16, x17, [x8, #0x80] + stp x18, x19, [x8, #0x90] + stp x20, x21, [x8, #0xa0] + stp x22, x23, [x8, #0xb0] + stp x24, x25, [x8, #0xc0] + stp x26, x27, [x8, #0xd0] + stp x28, x29, [x8, #0xe0] + mov x1, sp + stp x30, x1, [x8, #0xf0] + str x30, [x8, #0x100] + mov x1, #0x1ffffffff + str x1, [x8, #0x108] + ret +#endif + +// .. 32-bit ARM ............................................................... + +#if defined(__arm__) + .p2align 2 + // On entry, r0 contains a pointer to the arm_gprs +FN(_swift_get_cpu_context): + stm r0, {r0-r14} + str lr, [r0, #0x3c] + mov r1, #0xffff + str r1, [r0, #0x40] + bx lr +#endif + diff --git a/stdlib/public/Backtracing/get-cpu-context.asm b/stdlib/public/Backtracing/get-cpu-context.asm new file mode 100644 index 0000000000000..3bc3d45993d97 --- /dev/null +++ b/stdlib/public/Backtracing/get-cpu-context.asm @@ -0,0 +1,4 @@ +;;; Placeholder for Windows assembly code +;;; This uses different syntax from everywhere else :-( + + end diff --git a/stdlib/public/CMakeLists.txt b/stdlib/public/CMakeLists.txt index 60a49ba4a9ff5..480ee45e65117 100644 --- a/stdlib/public/CMakeLists.txt +++ b/stdlib/public/CMakeLists.txt @@ -158,6 +158,8 @@ if(SWIFT_BUILD_STDLIB) if(SWIFT_ENABLE_EXPERIMENTAL_OBSERVATION) add_subdirectory(Observation) endif() + + add_subdirectory(Backtracing) endif() if(SWIFT_BUILD_STDLIB OR SWIFT_BUILD_REMOTE_MIRROR) @@ -177,3 +179,5 @@ if(SWIFT_BUILD_SDK_OVERLAY) add_subdirectory(Windows) endif() endif() + +add_subdirectory(libexec) diff --git a/stdlib/public/SwiftShims/swift/shims/CMakeLists.txt b/stdlib/public/SwiftShims/swift/shims/CMakeLists.txt index b0df12ada5d26..1e548f16071fa 100644 --- a/stdlib/public/SwiftShims/swift/shims/CMakeLists.txt +++ b/stdlib/public/SwiftShims/swift/shims/CMakeLists.txt @@ -21,6 +21,7 @@ set(sources ThreadLocalStorage.h UnicodeData.h Visibility.h + _SwiftBacktracing.h _SwiftConcurrency.h _SwiftDistributed.h _SwiftRuntime.h diff --git a/stdlib/public/SwiftShims/swift/shims/_SwiftBacktracing.h b/stdlib/public/SwiftShims/swift/shims/_SwiftBacktracing.h new file mode 100644 index 0000000000000..0dea31890b67c --- /dev/null +++ b/stdlib/public/SwiftShims/swift/shims/_SwiftBacktracing.h @@ -0,0 +1,279 @@ +//===--- _SwiftBacktracing.h - Swift Backtracing Support --------*- 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 types and support functions for the Swift backtracing code. +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_BACKTRACING_H +#define SWIFT_BACKTRACING_H + +#include + +#ifdef __APPLE__ +#include +#endif + +#if TARGET_OS_OSX || TARGET_OS_IPHONE +#include +#include + +#include +#include + +#if __has_include() +#include +#else +#ifdef __cplusplus +extern "C" { +#endif +extern int proc_name(int pid, void * buffer, uint32_t buffersize); +#ifdef __cplusplus +} +#endif +#endif + +#endif + +#ifdef __cplusplus +namespace swift { +extern "C" { +#endif + +struct CrashInfo { + uint64_t crashing_thread; + uint64_t signal; + uint64_t fault_address; + uint64_t mctx; +}; + +// .. Processor specifics ...................................................... + +struct x86_64_gprs { + uint64_t _r[16]; + uint64_t rflags; + uint16_t cs, fs, gs, _pad0; + uint64_t rip; + uint64_t valid; +}; + +struct i386_gprs { + uint32_t _r[8]; + uint32_t eflags; + uint16_t segreg[6]; + uint32_t eip; + uint32_t valid; +}; + +struct arm64_gprs { + uint64_t _x[32]; + uint64_t pc; + uint64_t valid; +}; + +struct arm_gprs { + uint32_t _r[16]; + uint32_t valid; +}; + +// .. Darwin specifics ......................................................... + +#if TARGET_OS_OSX || TARGET_OS_IPHONE + +/* Darwin thread states. We can't import these from the system header because + it uses all kinds of macros and the Swift importer can't cope with that. + So declare them here in a form it can understand. */ +#define ARM_THREAD_STATE64 6 +struct darwin_arm64_thread_state { + uint64_t _x[29]; + uint64_t fp; + uint64_t lr; + uint64_t sp; + uint64_t pc; + uint32_t cpsr; + uint32_t __pad; +}; + +struct darwin_arm64_exception_state { + uint64_t far; + uint32_t esr; + uint32_t exception; +}; + +struct darwin_arm64_mcontext { + struct darwin_arm64_exception_state es; + struct darwin_arm64_thread_state ss; + // followed by NEON state (which we don't care about) +}; + +#define x86_THREAD_STATE64 4 +struct darwin_x86_64_thread_state { + uint64_t rax; + uint64_t rbx; + uint64_t rcx; + uint64_t rdx; + uint64_t rdi; + uint64_t rsi; + uint64_t rbp; + uint64_t rsp; + uint64_t r8; + uint64_t r9; + uint64_t r10; + uint64_t r11; + uint64_t r12; + uint64_t r13; + uint64_t r14; + uint64_t r15; + uint64_t rip; + uint64_t rflags; + uint64_t cs; + uint64_t fs; + uint64_t gs; +}; + +struct darwin_x86_64_exception_state { + uint16_t trapno; + uint16_t cpu; + uint32_t err; + uint64_t faultvaddr; +}; + +struct darwin_x86_64_mcontext { + struct darwin_x86_64_exception_state es; + struct darwin_x86_64_thread_state ss; + // followed by FP/AVX/AVX512 state (which we don't care about) +}; + +/* DANGER! These are SPI. They may change (or vanish) at short notice, may + not work how you expect, and are generally dangerous to use. */ +// ###FIXME: Remove the 0 +#if 0 && __has_include() + #include +#else +struct dyld_process_cache_info { + uuid_t cacheUUID; + uint64_t cacheBaseAddress; + bool noCache; + bool privateCache; +}; +typedef struct dyld_process_cache_info dyld_process_cache_info; +typedef const struct dyld_process_info_base* dyld_process_info; + +extern dyld_process_info _dyld_process_info_create(task_t task, uint64_t timestamp, kern_return_t* kernelError); +extern void _dyld_process_info_release(dyld_process_info info); +extern void _dyld_process_info_retain(dyld_process_info info); +extern void _dyld_process_info_get_cache(dyld_process_info info, dyld_process_cache_info* cacheInfo); +extern void _dyld_process_info_for_each_image(dyld_process_info info, void (^callback)(uint64_t machHeaderAddress, const uuid_t uuid, const char* path)); +extern void _dyld_process_info_for_each_segment(dyld_process_info info, uint64_t machHeaderAddress, void (^callback)(uint64_t segmentAddress, uint64_t segmentSize, const char* segmentName)); +#endif + +/* DANGER! CoreSymbolication is a private framework. This is all SPI. */ +// ###FIXME: Remove the 0 +struct _CSArchitecture { + cpu_type_t cpu_type; + cpu_subtype_t cpu_subtype; +}; + +typedef struct _CSArchitecture CSArchitecture; + +static const CSArchitecture kCSArchitectureI386 = { CPU_TYPE_I386, CPU_SUBTYPE_I386_ALL }; +static const CSArchitecture kCSArchitectureX86_64 = { CPU_TYPE_I386|CPU_ARCH_ABI64, CPU_SUBTYPE_I386_ALL }; +static const CSArchitecture kCSArchitectureArm64 = { CPU_TYPE_ARM | CPU_ARCH_ABI64, CPU_SUBTYPE_ARM64_ALL }; +static const CSArchitecture kCSArchitectureArm64_32 = { CPU_TYPE_ARM | CPU_ARCH_ABI64_32, CPU_SUBTYPE_ARM64_ALL }; +static const CSArchitecture kCSArchitectureArmV7K = { CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7K }; + +typedef struct _CSBinaryRelocationInformation { + mach_vm_address_t base; + mach_vm_address_t extent; + char name[17]; +} CSBinaryRelocationInformation; + +typedef struct _CSBinaryImageInformation { + mach_vm_address_t base; + mach_vm_address_t extent; + CFUUIDBytes uuid; + CSArchitecture arch; + const char *path; + CSBinaryRelocationInformation *relocations; + uint32_t relocationCount; + uint32_t flags; +} CSBinaryImageInformation; + +typedef uint64_t CSMachineTime; + +static const CSMachineTime kCSBeginningOfTime = 0; +static const CSMachineTime kCSEndOfTime = INT64_MAX; +static const CSMachineTime kCSNow = INT64_MAX + 1ULL; +static const CSMachineTime kCSAllTimes = INT64_MAX + 2ULL; + +struct _CSTypeRef { + uintptr_t _opaque_1; + uintptr_t _opaque_2; +}; + +typedef struct _CSTypeRef CSTypeRef; + +typedef CSTypeRef CSNullRef; +typedef CSTypeRef CSSymbolicatorRef; +typedef CSTypeRef CSSymbolOwnerRef; +typedef CSTypeRef CSSymbolRef; +typedef CSTypeRef CSSourceInfoRef; + +static const CSNullRef kCSNull = { 0, 0 }; + +typedef void (^CSSymbolOwnerIterator)(CSSymbolOwnerRef owner); +typedef void (^CSStackFrameIterator)(CSSymbolRef symbol, CSSourceInfoRef info); + +typedef struct _CSNotificationData { + CSSymbolicatorRef symbolicator; + union { + struct Ping { + uint32_t value; + } ping; + + struct DyldLoad { + CSSymbolOwnerRef symbolOwner; + } dyldLoad; + + struct DyldUnload { + CSSymbolOwnerRef symbolOwner; + } dyldUnload; + } u; +} CSNotificationData; + +typedef void (^CSNotificationBlock)(uint32_t type, CSNotificationData data); + +struct _CSRange { + mach_vm_address_t location; + mach_vm_size_t length; +}; + +typedef struct _CSRange CSRange; + +/* DANGER! This is also SPI */ +enum { + kCRSanitizePathGlobLocalHomeDirectories = 1, + kCRSanitizePathGlobLocalVolumes = 2, + kCRSanitizePathGlobAllTypes = 0xff, + + kCRSanitizePathNormalize = 0x100 << 0, + kCRSanitizePathKeepFile = 0x100 << 1, +}; + +#endif // TARGET_OS_OSX || TARGET_OS_IPHONE + +#ifdef __cplusplus +} // extern "C" +} // namespace swift +#endif + +#endif // SWIFT_BACKTRACING_H diff --git a/stdlib/public/SwiftShims/swift/shims/module.modulemap b/stdlib/public/SwiftShims/swift/shims/module.modulemap index 57e872cd2594e..cfc4d3990ba36 100644 --- a/stdlib/public/SwiftShims/swift/shims/module.modulemap +++ b/stdlib/public/SwiftShims/swift/shims/module.modulemap @@ -26,6 +26,10 @@ module _SwiftConcurrencyShims { header "_SwiftConcurrency.h" } +module _SwiftBacktracingShims { + header "_SwiftBacktracing.h" +} + module SwiftOverlayShims { header "LibcOverlayShims.h" export * diff --git a/stdlib/public/libexec/CMakeLists.txt b/stdlib/public/libexec/CMakeLists.txt new file mode 100644 index 0000000000000..b3a3d713fd45d --- /dev/null +++ b/stdlib/public/libexec/CMakeLists.txt @@ -0,0 +1,4 @@ +# Make this macOS only for now. +if(OSX IN_LIST SWIFT_SDKS) +add_subdirectory(swift-backtrace) +endif() diff --git a/stdlib/public/libexec/README.txt b/stdlib/public/libexec/README.txt new file mode 100644 index 0000000000000..5ce4dd545b48f --- /dev/null +++ b/stdlib/public/libexec/README.txt @@ -0,0 +1,8 @@ +What is this? +============= + +The libexec directory contains the source code for auxiliary executables that +ship with the Swift runtime, as opposed to being part of the toolchain. + +On UNIX-like platforms, if the runtime is installed in /usr/lib/swift (for +instance), these are things that you would expect to find in /usr/libexec/swift. diff --git a/stdlib/public/libexec/swift-backtrace/AnsiColor.swift b/stdlib/public/libexec/swift-backtrace/AnsiColor.swift new file mode 100644 index 0000000000000..04fac53aa0143 --- /dev/null +++ b/stdlib/public/libexec/swift-backtrace/AnsiColor.swift @@ -0,0 +1,159 @@ +//===--- AnsiColor.swift - ANSI formatting control codes ------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Provides ANSI support via Swift string interpolation. +// +//===----------------------------------------------------------------------===// + +enum AnsiColor { + case normal + case black + case red + case green + case yellow + case blue + case magenta + case cyan + case white + case gray + case brightRed + case brightGreen + case brightYellow + case brightBlue + case brightMagenta + case brightCyan + case brightWhite + case rgb(r: Int, g: Int, b: Int) + case grayscale(Int) + + var foregroundCode: String { + switch self { + case .normal: return "39" + case .black: return "30" + case .red: return "31" + case .green: return "32" + case .yellow: return "33" + case .blue: return "34" + case .cyan: return "35" + case .magenta: return "36" + case .white: return "37" + case .gray: return "90" + case .brightRed: return "91" + case .brightGreen: return "92" + case .brightYellow: return "93" + case .brightBlue: return "94" + case .brightCyan: return "95" + case .brightMagenta: return "96" + case .brightWhite: return "97" + case let .rgb(r, g, b): + let ndx = 16 + 36 * r + 6 * g + b + return "38;5;\(ndx)" + case let .grayscale(g): + let ndx = 232 + g + return "38;5;\(ndx)" + } + } + + var backgroundCode: String { + switch self { + case .normal: return "49" + case .black: return "40" + case .red: return "41" + case .green: return "42" + case .yellow: return "43" + case .blue: return "44" + case .cyan: return "45" + case .magenta: return "46" + case .white: return "47" + case .gray: return "100" + case .brightRed: return "101" + case .brightGreen: return "102" + case .brightYellow: return "103" + case .brightBlue: return "104" + case .brightCyan: return "105" + case .brightMagenta: return "106" + case .brightWhite: return "107" + case let .rgb(r, g, b): + let ndx = 16 + 36 * r + 6 * g + b + return "48;5;\(ndx)" + case let .grayscale(g): + let ndx = 232 + g + return "48;5;\(ndx)" + } + } +} + +enum AnsiWeight { + case normal + case bold + case faint + + var code: String { + switch self { + case .normal: return "22" + case .bold: return "1" + case .faint: return "2" + } + } +} + +enum AnsiAttribute { + case fg(AnsiColor) + case bg(AnsiColor) + case weight(AnsiWeight) + case inverse(Bool) +} + +extension DefaultStringInterpolation { + mutating func appendInterpolation(ansi attrs: AnsiAttribute...) { + var code = "\u{1b}[" + var first = true + for attr in attrs { + if first { + first = false + } else { + code += ";" + } + + switch attr { + case let .fg(color): + code += color.foregroundCode + case let .bg(color): + code += color.backgroundCode + case let .weight(weight): + code += weight.code + case let .inverse(enabled): + if enabled { + code += "7" + } else { + code += "27" + } + } + } + code += "m" + + appendInterpolation(code) + } + + mutating func appendInterpolation(fg: AnsiColor) { + return appendInterpolation(ansi: .fg(fg)) + } + mutating func appendInterpolation(bg: AnsiColor) { + return appendInterpolation(ansi: .bg(bg)) + } + mutating func appendInterpolation(weight: AnsiWeight) { + return appendInterpolation(ansi: .weight(weight)) + } + mutating func appendInterpolation(inverse: Bool) { + return appendInterpolation(ansi: .inverse(inverse)) + } +} diff --git a/stdlib/public/libexec/swift-backtrace/CMakeLists.txt b/stdlib/public/libexec/swift-backtrace/CMakeLists.txt new file mode 100644 index 0000000000000..59fb989aa4ed8 --- /dev/null +++ b/stdlib/public/libexec/swift-backtrace/CMakeLists.txt @@ -0,0 +1,14 @@ +add_swift_target_executable(swift-backtrace BUILD_WITH_STDLIB + main.swift + AnsiColor.swift + Target.swift + Themes.swift + Utils.swift + + SWIFT_MODULE_DEPENDS + _Backtracing + + INSTALL_IN_COMPONENT stdlib + COMPILE_FLAGS -parse-as-library + + TARGET_SDKS OSX) diff --git a/stdlib/public/libexec/swift-backtrace/Target.swift b/stdlib/public/libexec/swift-backtrace/Target.swift new file mode 100644 index 0000000000000..b4a8e5e872ee7 --- /dev/null +++ b/stdlib/public/libexec/swift-backtrace/Target.swift @@ -0,0 +1,348 @@ +//===--- Target.swift - Represents a process we are inspecting ------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Defines `Target`, which represents the process we are inspecting. +// There are a lot of system specifics in this file! +// +//===----------------------------------------------------------------------===// + +#if os(macOS) + +import Darwin +import Darwin.Mach + +import _Backtracing +@_spi(Internal) import _Backtracing +@_spi(Contexts) import _Backtracing +@_spi(MemoryReaders) import _Backtracing + +import _SwiftBacktracingShims + +#if arch(x86_64) +typealias MContext = darwin_x86_64_mcontext +#elseif arch(arm64) || arch(arm64_32) +typealias MContext = darwin_arm64_mcontext +#else +#error("You need to define MContext for this architecture") +#endif + +extension thread_extended_info { + var pth_swiftName: String { + withUnsafePointer(to: pth_name) { ptr in + let len = strnlen(ptr, Int(MAXTHREADNAMESIZE)) + return String(decoding: UnsafeRawBufferPointer(start: ptr, count: Int(len)), + as: UTF8.self) + } + } +} + +struct TargetThread { + typealias ThreadID = UInt64 + + var id: ThreadID + var context: HostContext? + var name: String + var backtrace: SymbolicatedBacktrace +} + +class Target { + typealias Address = UInt64 + + var pid: pid_t + var name: String + var signal: UInt64 + var faultAddress: Address + var crashingThread: TargetThread.ThreadID + + var task: task_t + var images: [Backtrace.Image] = [] + var sharedCacheInfo: Backtrace.SharedCacheInfo? + + var threads: [TargetThread] = [] + var crashingThreadNdx: Int = -1 + + var signalName: String { + switch signal { + case UInt64(SIGQUIT): return "SIGQUIT" + case UInt64(SIGABRT): return "SIGABRT" + case UInt64(SIGBUS): return "SIGBUS" + case UInt64(SIGFPE): return "SIGFPE" + case UInt64(SIGILL): return "SIGILL" + case UInt64(SIGSEGV): return "SIGSEGV" + case UInt64(SIGTRAP): return "SIGTRAP" + default: return "\(signal)" + } + } + + var signalDescription: String { + switch signal { + case UInt64(SIGQUIT): return "Terminated" + case UInt64(SIGABRT): return "Aborted" + case UInt64(SIGBUS): return "Bus error" + case UInt64(SIGFPE): return "Floating point exception" + case UInt64(SIGILL): return "Illegal instruction" + case UInt64(SIGSEGV): return "Bad pointer dereference" + case UInt64(SIGTRAP): return "System trap" + default: + return "Signal \(signal)" + } + } + + var reader: RemoteMemoryReader + + var mcontext: MContext + + static func getParentTask() -> task_t? { + var ports: mach_port_array_t? = nil + var portCount: mach_msg_type_number_t = 0 + + // For some reason, we can't pass a task read port this way, but we + // *can* pass the control port. So do that and then ask for a read port + // before immediately dropping the control port from this process. + + let kr = mach_ports_lookup(mach_task_self_, &ports, &portCount) + if kr != KERN_SUCCESS { + return nil + } + + if let ports = ports, portCount != 0 { + var taskPort: mach_port_t = 0 + let kr = task_get_special_port(ports[0], TASK_READ_PORT, &taskPort) + if kr != KERN_SUCCESS { + mach_port_deallocate(mach_task_self_, ports[0]) + return nil + } + mach_port_deallocate(mach_task_self_, ports[0]) + return task_t(taskPort) + } else { + return nil + } + } + + static func getProcessName(pid: pid_t) -> String { + let buffer = UnsafeMutableBufferPointer.allocate(capacity: 4096) + defer { + buffer.deallocate() + } + let ret = proc_name(pid, buffer.baseAddress, UInt32(buffer.count)) + if ret <= 0 { + return "" + } else { + return String(decoding: buffer[0.. ()) throws { + #if os(macOS) + return try withTemporaryDirectory(pattern: "/tmp/backtrace.XXXXXXXX") { + tmpdir in + + let cmdfile = "\(tmpdir)/lldb.command" + guard let fp = fopen(cmdfile, "wt") else { + throw PosixError(errno: errno) + } + if fputs(""" + #!/bin/bash + clear + echo "Once LLDB has attached, return to the other window and press any key" + echo "" + xcrun lldb --attach-pid \(pid) -o c + """, fp) == EOF { + throw PosixError(errno: errno) + } + if fclose(fp) != 0 { + throw PosixError(errno: errno) + } + if chmod(cmdfile, S_IXUSR|S_IRUSR) != 0 { + throw PosixError(errno: errno) + } + + try spawn("/usr/bin/open", args: ["open", cmdfile]) + + body() + } + #else + print(""" + From another shell, please run + + lldb --attach-pid \(target.pid) -o c + """) + body() + #endif + } +} + +private func mach_thread_info(_ thread: thread_t, + _ flavor: CInt, + _ result: inout T) -> kern_return_t { + var count: mach_msg_type_number_t + = mach_msg_type_number_t(MemoryLayout.stride + / MemoryLayout.stride) + + return withUnsafeMutablePointer(to: &result) { ptr in + ptr.withMemoryRebound(to: natural_t.self, capacity: Int(count)) { intPtr in + return thread_info(thread, + thread_flavor_t(flavor), + intPtr, + &count) + } + } +} + +#endif // os(macOS) diff --git a/stdlib/public/libexec/swift-backtrace/Themes.swift b/stdlib/public/libexec/swift-backtrace/Themes.swift new file mode 100644 index 0000000000000..f9addbc90287f --- /dev/null +++ b/stdlib/public/libexec/swift-backtrace/Themes.swift @@ -0,0 +1,172 @@ +//===--- Themes.swift - Represents a process we are inspecting ------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Defines the `Theme` struct that we use for color support. +// +//===----------------------------------------------------------------------===// + +@_spi(Formatting) import _Backtracing + +protocol ErrorAndWarningTheme { + func crashReason(_ s: String) -> String + func error(_ s: String) -> String + func warning(_ s: String) -> String + func info(_ s: String) -> String +} + +extension ErrorAndWarningTheme { + public func crashReason(_ s: String) -> String { return "*** \(s) ***" } + public func error(_ s: String) -> String { return "!!! error: \(s)" } + public func warning(_ s: String) -> String { return "/!\\ warning: \(s)" } + public func info(_ s: String) -> String { return "(i) \(s)" } +} + +protocol PromptTheme { + func prompt(_ s: String) -> String +} + +extension PromptTheme { + public func prompt(_ s: String) -> String { return s } +} + +protocol MemoryDumpTheme { + func address(_ s: String) -> String + func data(_ s: String) -> String + func printable(_ s: String) -> String + func nonPrintable(_ s: String) -> String +} + +extension MemoryDumpTheme { + public func address(_ s: String) -> String { return s } + public func data(_ s: String) -> String { return s } + public func printable(_ s: String) -> String { return s } + public func nonPrintable(_ s: String) -> String { return s } +} + +protocol RegisterDumpTheme : MemoryDumpTheme { + func register(_ s: String) -> String + func hexValue(_ s: String) -> String + func decimalValue(_ s: String) -> String + func flags(_ s: String) -> String +} + +extension RegisterDumpTheme { + public func register(_ s: String) -> String { return s } + public func hexValue(_ s: String) -> String { return s } + public func decimalValue(_ s: String) -> String { return s } + public func flags(_ s: String) -> String { return s } +} + +typealias Theme = BacktraceFormattingTheme & ErrorAndWarningTheme & + PromptTheme & MemoryDumpTheme & RegisterDumpTheme + +enum Themes { + + struct Plain: Theme { + } + + struct Color: Theme { + public func frameIndex(_ s: String) -> String { + return "\(fg: .gray)\(s)\(fg: .normal)" + } + public func programCounter(_ s: String) -> String { + return "\(fg: .green)\(s)\(fg: .normal)" + } + public func frameAttribute(_ s: String) -> String { + return "\(fg: .blue)[\(s)]\(fg: .normal)" + } + + public func symbol(_ s: String) -> String { + return "\(fg: .brightMagenta)\(s)\(fg: .normal)" + } + public func offset(_ s: String) -> String { + return "\(fg: .white)\(s)\(fg: .normal)" + } + public func sourceLocation(_ s: String) -> String { + return "\(fg: .yellow)\(s)\(fg: .normal)" + } + public func lineNumber(_ s: String) -> String { + return "\(fg: .gray)\(s)\(fg: .normal)│" + } + public func code(_ s: String) -> String { + return "\(s)" + } + public func crashedLineNumber(_ s: String) -> String { + return "\(fg: .gray)\(s)\(fg: .brightWhite)│" + } + public func crashedLine(_ s: String) -> String { + return "\(bg: .grayscale(2))\(fg: .brightWhite)\(s)\(fg: .normal)\(bg: .normal)" + } + public func crashLocation() -> String { + return "\(fg: .brightRed)▲\(fg: .normal)" + } + public func imageName(_ s: String) -> String { + return "\(fg: .cyan)\(s)\(fg: .normal)" + } + public func imageAddressRange(_ s: String) -> String { + return "\(fg: .green)\(s)\(fg: .normal)" + } + public func imageBuildID(_ s: String) -> String { + return "\(fg: .white)\(s)\(fg: .normal)" + } + public func imagePath(_ s: String) -> String { + return "\(fg: .gray)\(s)\(fg: .normal)" + } + + public func prompt(_ s: String) -> String { + return "\(fg: .gray)\(s)\(fg: .normal)" + } + + public func address(_ s: String) -> String { + return "\(fg: .green)\(s)\(fg: .normal)" + } + public func data(_ s: String) -> String { + return s + } + public func printable(_ s: String) -> String { + return s + } + public func nonPrintable(_ s: String) -> String { + return "\(fg: .gray)\(s)\(fg: .normal)" + } + + public func register(_ s: String) -> String { + return s + } + public func hexValue(_ s: String) -> String { + return "\(fg: .green)\(s)\(fg: .normal)" + } + public func decimalValue(_ s: String) -> String { + return "\(fg: .green)\(s)\(fg: .normal)" + } + public func flags(_ s: String) -> String { + return "\(fg: .magenta)\(s)\(fg: .normal)" + } + + public func crashReason(_ s: String) -> String { + return "💣 \(fg: .brightRed)\(s)\(fg: .normal)" + } + public func error(_ s: String) -> String { + return "🛑 \(fg: .brightRed)error: \(s)\(fg: .normal)" + } + public func warning(_ s: String) -> String { + return "⚠️ \(fg: .brightYellow)warning: \(s)\(fg: .normal)" + } + public func info(_ s: String) -> String { + return "ℹ️ \(s)" + } + } + + static let plain = Plain() + static let color = Color() + +} diff --git a/stdlib/public/libexec/swift-backtrace/Utils.swift b/stdlib/public/libexec/swift-backtrace/Utils.swift new file mode 100644 index 0000000000000..9601a46b0541a --- /dev/null +++ b/stdlib/public/libexec/swift-backtrace/Utils.swift @@ -0,0 +1,142 @@ +//===--- Utils.swift - Utility functions ----------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Utility functions that are used in the swift-backtrace program. +// +//===----------------------------------------------------------------------===// + +#if canImport(Darwin) +import Darwin.C +#elseif canImport(Glibc) +import Glibc +#elseif canImport(CRT) +import CRT +#endif + +import Swift + +internal func hex(_ value: T, + withPrefix: Bool = true) -> String { + let digits = String(value, radix: 16) + let padTo = value.bitWidth / 4 + let padding = digits.count >= padTo ? "" : String(repeating: "0", + count: padTo - digits.count) + let prefix = withPrefix ? "0x" : "" + + return "\(prefix)\(padding)\(digits)" +} + +internal func hex(_ bytes: [UInt8]) -> String { + return bytes.map{ hex($0, withPrefix: false) }.joined(separator: "") +} + +internal func parseUInt64(_ s: S) -> UInt64? { + if s.hasPrefix("0x") { + return UInt64(s.dropFirst(2), radix: 16) + } else if s.hasPrefix("0b") { + return UInt64(s.dropFirst(2), radix: 2) + } else if s.hasPrefix("0o") { + return UInt64(s.dropFirst(2), radix: 8) + } else { + return UInt64(s, radix: 10) + } +} + +#if os(macOS) + +struct PosixError: Error { + var errno: Int32 + + var desription: String { + return String(cString: strerror(self.errno)) + } +} + +internal func recursiveRemoveContents(_ dir: String) throws { + guard let dirp = opendir(dir) else { + throw PosixError(errno: errno) + } + defer { + closedir(dirp) + } + while let dp = readdir(dirp) { + let name: String = + withUnsafePointer(to: &dp.pointee.d_name) { +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + let len = Int(dp.pointee.d_namlen) +#else + let len = Int(strlen($0)) +#endif + return String(decoding: UnsafeRawBufferPointer(start: $0, + count: len), + as: UTF8.self) + } + if name == "." || name == ".." { + continue + } + let fullPath = "\(dir)/\(name)" + if dp.pointee.d_type == DT_DIR { + try recursiveRemove(fullPath) + } else { + if unlink(fullPath) != 0 { + throw PosixError(errno: errno) + } + } + } +} + +internal func recursiveRemove(_ dir: String) throws { + try recursiveRemoveContents(dir) + + if rmdir(dir) != 0 { + throw PosixError(errno: errno) + } +} + +internal func withTemporaryDirectory(pattern: String, shouldDelete: Bool = true, + body: (String) throws -> ()) throws { + var buf = Array(pattern.utf8) + buf.append(0) + + guard let dir = buf.withUnsafeMutableBufferPointer({ + if let ptr = mkdtemp($0.baseAddress!) { + return String(cString: ptr) + } + return nil + }) else { + throw PosixError(errno: errno) + } + + defer { + if shouldDelete { + try? recursiveRemove(dir) + } + } + + try body(dir) +} + +internal func spawn(_ path: String, args: [String]) throws { + var cargs = args.map{ strdup($0) } + cargs.append(nil) + let result = cargs.withUnsafeBufferPointer{ + posix_spawn(nil, path, nil, nil, $0.baseAddress!, nil) + } + for arg in cargs { + free(arg) + } + if result != 0 { + throw PosixError(errno: errno) + } +} + +#endif // os(macOS) diff --git a/stdlib/public/libexec/swift-backtrace/main.swift b/stdlib/public/libexec/swift-backtrace/main.swift new file mode 100644 index 0000000000000..26394ddf9b356 --- /dev/null +++ b/stdlib/public/libexec/swift-backtrace/main.swift @@ -0,0 +1,1121 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#if os(macOS) + +#if canImport(Darwin) +import Darwin.C +#elseif canImport(Glibc) +import Glibc +#elseif canImport(CRT) +import CRT +#endif + +@_spi(Formatting) import _Backtracing +@_spi(Contexts) import _Backtracing +@_spi(Registers) import _Backtracing +@_spi(MemoryReaders) import _Backtracing + +@main +internal struct SwiftBacktrace { + enum UnwindAlgorithm { + case fast + case precise + } + + enum Preset { + case none + case friendly + case medium + case full + } + + enum ImagesToShow { + case none + case all + case mentioned + } + + enum RegistersToShow { + case none + case all + case crashedOnly + } + + struct Arguments { + var unwindAlgorithm: UnwindAlgorithm = .precise + var demangle = false + var interactive = false + var color = false + var timeout = 30 + var preset: Preset = .none + var threads: Bool? = nil + var registers: RegistersToShow? = nil + var crashInfo: UInt64? = nil + var showImages: ImagesToShow? = nil + var limit: Int? = 64 + var top = 16 + var sanitize: Bool? = nil + var cache = true + } + + static var args = Arguments() + static var formattingOptions = BacktraceFormattingOptions() + + static var target: Target? = nil + static var currentThread: Int = 0 + + static var theme: any Theme { + if args.color { + return Themes.color + } else { + return Themes.plain + } + } + + static func usage() { + print(""" +usage: swift-backtrace [--unwind ] [--demangle []] [--interactive []] [--color []] [--timeout ] [--preset ] [--threads []] [--registers ] [--images ] [--cache []] --crashinfo + +Generate a backtrace for the parent process. + +--unwind +-u Set the unwind algorithm to use. Supported algorithms + are "fast" and "precise". + +--demangle [] +-d [] Set whether or not to demangle identifiers. + +--interactive [] +-i [] Set whether to be interactive. + +--color [] +-c [] Set whether to use ANSI color in the output. + +--timeout +-t Set how long to wait for interaction. + +--preset +-p Set the backtrace format (by preset). Options are + "friendly", "medium" and "full". + +--threads [] +-h [] Set whether or not to show all threads. + +--registers +-r Set which registers dumps to show. Options are "none", + "all" and "crashed". + +--images +-m Set which images to list. Options are "none", "all" + and "mentioned". + +--limit +-l Set the limit on the number of frames to capture. + Can be set to "none" to disable the limit. + +--top +-T Set the minimum number of frames to capture at the top + of the stack. This is used with limit to ensure that + you capture sufficient frames to understand deep traces. + +--sanitize [] +-s [] Set whether or not to sanitize paths. + +--cache [] Set whether or not to use the symbol cache, if any. + +--crashinfo +-a Provide a pointer to a platform specific CrashInfo + structure. should be in hexadecimal. +""") + } + + static func parseBool(_ s: some StringProtocol) -> Bool { + let lowered = s.lowercased() + return lowered == "yes" || lowered == "y" || + lowered == "true" || lowered == "t" || lowered == "on" + } + + static func handleArgument(_ arg: String, value: String?) { + switch arg { + case "-?", "--help": + usage() + exit(0) + case "-u", "--unwind": + if let v = value { + switch v.lowercased() { + case "fast": + args.unwindAlgorithm = .fast + case "precise": + args.unwindAlgorithm = .precise + default: + print("swift-backtrace: unknown unwind algorithm '\(v)'") + usage() + exit(1) + } + } else { + print("swift-backtrace: missing unwind algorithm") + usage() + exit(1) + } + case "-d", "--demangle": + if let v = value { + args.demangle = parseBool(v) + } else { + args.demangle = true + } + case "-i", "--interactive": + if let v = value { + args.interactive = parseBool(v) + } else { + args.interactive = true + } + case "-c", "--color": + if let v = value { + args.color = parseBool(v) + } else { + args.color = true + } + case "-t", "--timeout": + if let v = value { + if let secs = Int(v), secs >= 0 { + args.timeout = secs + } else { + print("swift-backtrace: bad timeout '\(v)'") + } + } else { + print("swift-backtrace: missing timeout value") + usage() + exit(1) + } + case "-p", "--preset": + if let v = value { + switch v.lowercased() { + case "friendly": + args.preset = .friendly + case "medium": + args.preset = .medium + case "full": + args.preset = .full + default: + print("swift-backtrace: unknown preset '\(v)'") + usage() + exit(1) + } + } else { + print("swift-backtrace: missing preset name") + usage() + exit(1) + } + case "-h", "--threads": + if let v = value { + if v.lowercased() != "preset" { + args.threads = parseBool(v) + } + } else { + args.threads = true + } + case "-r", "--registers": + if let v = value { + switch v.lowercased() { + case "preset": + break + case "none": + args.registers = RegistersToShow.none + case "all": + args.registers = .all + case "crashed": + args.registers = .crashedOnly + default: + print("swift-backtrace: unknown registers setting '\(v)'") + usage() + exit(1) + } + } else { + print("swift-backtrace: missing registers setting") + usage() + exit(1) + } + case "-m", "--images": + if let v = value { + switch v.lowercased() { + case "preset": + break + case "none": + args.showImages = ImagesToShow.none + case "all": + args.showImages = .all + case "mentioned": + args.showImages = .mentioned + default: + print("swift-backtrace: unknown images setting '\(v)'") + usage() + exit(1) + } + } else { + print("swift-backtrace: missing images setting") + usage() + exit(1) + } + case "-l", "--limit": + if let v = value { + if v.lowercased() == "none" { + args.limit = nil + } else if let limit = Int(v), limit > 0 { + args.limit = limit + } else { + print("swift-backtrace: bad limit value \(v)") + } + } else { + print("swift-backtrace: missing limit value") + usage() + exit(1) + } + case "-T", "--top": + if let v = value { + if let top = Int(v), top >= 0 { + args.top = top + } else { + print("swift-backtrace: bad top value \(v)") + } + } else { + print("swift-backtrace: missing top value") + usage() + exit(1) + } + case "-s", "--sanitize": + if let v = value { + args.sanitize = parseBool(v) + } else { + args.sanitize = true + } + case "--cache": + if let v = value { + args.cache = parseBool(v) + } else { + args.cache = true + } + case "-a", "--crashinfo": + if let v = value { + if let a = UInt64(v, radix: 16) { + args.crashInfo = a + } else { + print("swift-backtrace: bad pointer '\(v)'") + usage() + exit(1) + } + } else { + print("swift-backtrace: missing pointer value") + usage() + exit(1) + } + default: + print("swift-backtrace: unknown argument '\(arg)'") + usage() + exit(1) + } + } + + static func main() { + parseArguments() + + guard let crashInfoAddr = args.crashInfo else { + print("swift-backtrace: --crashinfo is not optional") + usage() + exit(1) + } + + // Set-up the backtrace formatting options + switch args.preset { + case .friendly, .none: + formattingOptions = + .skipRuntimeFailures(true) + .showAddresses(false) + .showSourceCode(true) + .showFrameAttributes(false) + .sanitizePaths(args.sanitize ?? false) + if args.threads == nil { + args.threads = false + } + if args.registers == nil { + args.registers = RegistersToShow.none + } + if args.showImages == nil { + args.showImages = ImagesToShow.none + } + case .medium: + formattingOptions = + .skipRuntimeFailures(true) + .showSourceCode(true) + .showFrameAttributes(true) + .sanitizePaths(args.sanitize ?? true) + if args.threads == nil { + args.threads = false + } + if args.registers == nil { + args.registers = .crashedOnly + } + if args.showImages == nil { + args.showImages = .mentioned + } + case .full: + formattingOptions = + .skipRuntimeFailures(false) + .skipThunkFunctions(false) + .skipSystemFrames(false) + .sanitizePaths(args.sanitize ?? true) + if args.threads == nil { + args.threads = true + } + if args.registers == nil { + args.registers = .crashedOnly + } + if args.showImages == nil { + args.showImages = .mentioned + } + } + formattingOptions = formattingOptions.demangle(args.demangle) + + // We never use the showImages option; if we're going to show images, we + // want to do it *once* for all the backtraces we showed. + formattingOptions = formattingOptions.showImages(.none) + + target = Target(crashInfoAddr: crashInfoAddr, + limit: args.limit, top: args.top, + cache: args.cache) + + currentThread = target!.crashingThreadNdx + + printCrashLog() + + print("") + + if args.interactive { + // Make sure we're line buffered + setvbuf(stdout, nil, _IOLBF, 0) + + while let ch = waitForKey("Press space to interact, D to debug, or any other key to quit", + timeout: args.timeout) { + switch UInt8(ch) { + case UInt8(ascii: " "): + interactWithUser() + exit(0) + case UInt8(ascii: "D"), UInt8(ascii: "d"): + startDebugger() + default: + exit(0) + } + } + } + + } + + // Parse the command line arguments; we can't use swift-argument-parser + // from here because that would create a dependency problem, so we do + // it manually. + static func parseArguments() { + var currentArg: String? = nil + for arg in CommandLine.arguments[1...] { + if arg.hasPrefix("-") { + if let key = currentArg { + handleArgument(key, value: nil) + } + currentArg = arg + } else { + if let key = currentArg { + handleArgument(key, value: arg) + currentArg = nil + } else if arg != "" { + print("swift-backtrace: unexpected argument '\(arg)'") + usage() + exit(1) + } + } + } + if let key = currentArg { + handleArgument(key, value: nil) + } + } + + #if os(Linux) || os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + static func setRawMode() -> termios { + var oldAttrs = termios() + tcgetattr(0, &oldAttrs) + + var newAttrs = oldAttrs + newAttrs.c_lflag &= ~(UInt(ICANON) | UInt(ECHO)) + tcsetattr(0, TCSANOW, &newAttrs) + + return oldAttrs + } + + static func resetInputMode(mode: termios) { + var theMode = mode + tcsetattr(0, TCSANOW, &theMode) + } + + static func waitForKey(_ message: String, timeout: Int?) -> Int32? { + let oldMode = setRawMode() + + defer { + print("\r\u{1b}[0K", terminator: "") + fflush(stdout) + resetInputMode(mode: oldMode) + } + + if let timeout = timeout { + var remaining = timeout + + while true { + print("\r\(message) (\(remaining)s) ", terminator: "") + fflush(stdout) + + var pfd = pollfd(fd: 0, events: Int16(POLLIN), revents: 0) + + let ret = poll(&pfd, 1, 1000) + if ret == 0 { + remaining -= 1 + if remaining == 0 { + break + } + continue + } else if ret < 0 { + break + } + + return getchar() + } + } else { + print("\r\(message)", terminator: "") + fflush(stdout) + return getchar() + } + + return nil + } + #elseif os(Windows) + static func waitForKey(_ message: String, timeout: Int?) -> Int32? { + // ###TODO + return nil + } + #endif + + static func backtraceFormatter() -> BacktraceFormatter { + var terminalSize = winsize(ws_row: 24, ws_col: 80, + ws_xpixel: 1024, ws_ypixel: 768) + _ = ioctl(0, TIOCGWINSZ, &terminalSize) + + return BacktraceFormatter(formattingOptions + .theme(theme) + .width(Int(terminalSize.ws_col))) + } + + static func printCrashLog() { + guard let target = target else { + print("swift-backtrace: unable to get target") + return + } + + let crashingThread = target.threads[target.crashingThreadNdx] + + let description: String + + if let failure = crashingThread.backtrace.swiftRuntimeFailure { + description = failure + } else { + description = "Program crashed: \(target.signalDescription) at \(hex(target.faultAddress))" + } + + print("") + print(theme.crashReason(description)) + print("") + + var mentionedImages = Set() + let formatter = backtraceFormatter() + + func dump(ndx: Int, thread: TargetThread) { + let crashed = thread.id == target.crashingThread ? " crashed" : "" + let name = !thread.name.isEmpty ? " \"\(thread.name)\"" : "" + print("Thread \(ndx)\(name)\(crashed):\n") + + if args.registers! == .all { + if let context = thread.context { + showRegisters(context) + } else { + print(" " + theme.info("no context for thread \(ndx)")) + } + print("") + } + + let formatted = formatter.format(backtrace: thread.backtrace) + + print(formatted) + + if args.showImages! == .mentioned { + for frame in thread.backtrace.frames { + if formatter.shouldSkip(frame) { + continue + } + if let symbol = frame.symbol, symbol.imageIndex >= 0 { + mentionedImages.insert(symbol.imageIndex) + } + } + } + } + + if args.threads! { + for (ndx, thread) in target.threads.enumerated() { + dump(ndx: ndx, thread: thread) + } + } else { + dump(ndx: target.crashingThreadNdx, thread: crashingThread) + } + + if args.registers! == .crashedOnly { + print("\n\nRegisters:\n") + + if let context = target.threads[target.crashingThreadNdx].context { + showRegisters(context) + } else { + print(theme.info("no context for thread \(target.crashingThreadNdx)")) + } + } + + switch args.showImages! { + case .none: + break + case .mentioned: + let images = mentionedImages.sorted().map{ target.images[$0] } + let omitted = target.images.count - images.count + if omitted > 0 { + print("\n\nImages (\(omitted) omitted):\n") + } else { + print("\n\nImages:\n") + } + print(formatter.format(images: images)) + case .all: + print("\n\nImages:\n") + print(formatter.format(images: target.images)) + } + } + + static func startDebugger() { + guard let target = target else { + return + } + + do { + try target.withDebugger { + + if let ch = waitForKey("Press any key once LLDB is attached, or A to abort", timeout: nil), + ch != UInt8(ascii: "A") && ch != UInt8(ascii: "a") { + exit(0) + } + } + } catch { + print(theme.error("unable to spawn debugger")) + } + } + + static func interactWithUser() { + guard let target = target else { + return + } + + while true { + fflush(stdout) + print(theme.prompt(">>> "), terminator: "") + guard let input = readLine() else { + print("") + break + } + + let cmd = input.split(whereSeparator: { $0.isWhitespace }) + + if cmd.count < 1 { + continue + } + + // ###TODO: We should really replace this with something a little neater + switch cmd[0].lowercased() { + case "exit", "quit": + return + case "debug": + startDebugger() + case "bt", "backtrace": + let formatter = backtraceFormatter() + let backtrace = target.threads[currentThread].backtrace + let formatted = formatter.format(backtrace: backtrace) + + print(formatted) + case "thread": + if cmd.count >= 2 { + if let newThreadNdx = Int(cmd[1]), + newThreadNdx >= 0 && newThreadNdx < target.threads.count { + currentThread = newThreadNdx + } else { + print(theme.error("Bad thread index '\(cmd[1])'")) + break + } + } + + let crashed: String + if currentThread == target.crashingThreadNdx { + crashed = " (crashed)" + } else { + crashed = "" + } + + let thread = target.threads[currentThread] + let backtrace = thread.backtrace + let name = thread.name.isEmpty ? "" : " \(thread.name)" + print("Thread \(currentThread) id=\(thread.id)\(name)\(crashed)\n") + + if let frame = backtrace.frames.drop(while: { + $0.isSwiftRuntimeFailure + }).first { + let formatter = backtraceFormatter() + let formatted = formatter.format(frame: frame) + print("\(formatted)") + } + break + case "reg", "registers": + if let context = target.threads[currentThread].context { + showRegisters(context) + } else { + print(theme.info("no context for thread \(currentThread)")) + } + break + case "mem", "memory": + if cmd.count != 2 && cmd.count != 3 { + print("memory [|+]") + break + } + + guard let startAddress = parseUInt64(cmd[1]) else { + print(theme.error("bad start address \(cmd[1])")) + break + } + + let count: UInt64 + if cmd.count == 3 { + if cmd[2].hasPrefix("+") { + guard let theCount = parseUInt64(cmd[2].dropFirst()) else { + print(theme.error("bad byte count \(cmd[2])")) + break + } + count = theCount + } else { + guard let addr = parseUInt64(cmd[2]) else { + print(theme.error("bad end address \(cmd[2])")) + break + } + if addr < startAddress { + print("End address must be after start address") + break + } + count = addr - startAddress + } + } else { + count = 256 + } + + dumpMemory(at: startAddress, count: count) + break + case "process", "threads": + print("Process \(target.pid) \"\(target.name)\" has \(target.threads.count) thread(s):\n") + + let formatter = backtraceFormatter() + + var rows: [BacktraceFormatter.TableRow] = [] + for (n, thread) in target.threads.enumerated() { + let backtrace = thread.backtrace + + let crashed: String + if n == target.crashingThreadNdx { + crashed = " (crashed)" + } else { + crashed = "" + } + + let selected = currentThread == n ? "▶︎" : " " + let name = thread.name.isEmpty ? "" : " \(thread.name)" + + rows.append(.columns([ selected, + "\(n)", + "id=\(thread.id)\(name)\(crashed)" ])) + if let frame = backtrace.frames.drop(while: { + $0.isSwiftRuntimeFailure + }).first { + + rows += formatter.formatRows(frame: frame).map{ row in + switch row { + case let .columns(columns): + return .columns([ "", "" ] + columns) + default: + return row + } + } + } + } + + let output = BacktraceFormatter.formatTable(rows, + alignments: [ + .left, + .right + ]) + print(output) + case "images": + let formatter = backtraceFormatter() + let images = target.threads[currentThread].backtrace.images + let output = formatter.format(images: images) + + print(output) + case "set": + if cmd.count == 1 { + let limit: String + if let lim = args.limit { + limit = "\(lim)" + } else { + limit = "none" + } + let top = "\(args.top)" + + print(""" + addresses = \(formattingOptions.shouldShowAddresses) + demangle = \(formattingOptions.shouldDemangle) + frame-attrs = \(formattingOptions.shouldShowFrameAttributes) + image-names = \(formattingOptions.shouldShowImageNames) + limit = \(limit) + sanitize = \(formattingOptions.shouldSanitizePaths) + source = \(formattingOptions.shouldShowSourceCode) + source-context = \(formattingOptions.sourceContextLines) + system-frames = \(!formattingOptions.shouldSkipSystemFrames) + thunks = \(!formattingOptions.shouldSkipThunkFunctions) + top = \(top) + """) + } else { + for optval in cmd[1...] { + let parts = optval.split(separator: "=", maxSplits: 1, + omittingEmptySubsequences: false) + if parts.count == 1 { + let option = parts[0] + + switch option { + case "addresses": + print("addresses = \(formattingOptions.shouldShowAddresses)") + case "demangle": + print("demangle = \(formattingOptions.shouldDemangle)") + case "frame-attrs": + print("frame-attrs = \(formattingOptions.shouldShowFrameAttributes)") + case "image-names": + print("image-names = \(formattingOptions.shouldShowImageNames)") + case "limit": + if let limit = args.limit { + print("limit = \(limit)") + } else { + print("limit = none") + } + case "sanitize": + print("sanitize = \(formattingOptions.shouldSanitizePaths)") + case "source": + print("source = \(formattingOptions.shouldShowSourceCode)") + case "source-context": + print("source-context = \(formattingOptions.sourceContextLines)") + case "system-frames": + print("system-frames = \(!formattingOptions.shouldSkipSystemFrames)") + case "thunks": + print("thunks = \(!formattingOptions.shouldSkipThunkFunctions)") + case "top": + print("top = \(args.top)") + + default: + print(theme.error("unknown option '\(option)'")) + } + } else { + let option = parts[0] + let value = parts[1] + var changedBacktrace = false + + switch option { + case "limit": + if value == "none" { + args.limit = nil + changedBacktrace = true + } else if let limit = Int(value), limit > 0 { + args.limit = limit + changedBacktrace = true + } else { + print(theme.error("bad limit value '\(value)'")) + } + + case "top": + if let top = Int(value), top >= 0 { + args.top = top + changedBacktrace = true + } else { + print(theme.error("bad top value '\(value)'")) + } + + case "source": + formattingOptions = + formattingOptions.showSourceCode(parseBool(value), + contextLines: formattingOptions.sourceContextLines) + + case "source-context": + if let lines = Int(value), lines >= 0 { + formattingOptions = + formattingOptions.showSourceCode(formattingOptions.shouldShowSourceCode, + contextLines: lines) + } else { + print(theme.error("bad source-context value '\(value)'")) + } + + case "thunks": + formattingOptions = + formattingOptions.skipThunkFunctions(!parseBool(value)) + + case "system-frames": + formattingOptions = + formattingOptions.skipSystemFrames(!parseBool(value)) + + case "frame-attrs": + formattingOptions = + formattingOptions.showFrameAttributes(parseBool(value)) + + case "addresses": + formattingOptions = + formattingOptions.showAddresses(parseBool(value)) + + case "sanitize": + formattingOptions = + formattingOptions.sanitizePaths(parseBool(value)) + + case "demangle": + formattingOptions = + formattingOptions.demangle(parseBool(value)) + + case "image-names": + formattingOptions = + formattingOptions.showImageNames(parseBool(value)) + + default: + print(theme.error("unknown option '\(option)'")) + } + + if changedBacktrace { + target.redoBacktraces(limit: args.limit, + top: args.top, + cache: args.cache) + } + } + } + } + case "help": + print(""" + Available commands: + + backtrace Display a backtrace. + bt Synonym for backtrace. + debug Attach the debugger. + exit Exit interaction, allowing program to crash normally. + help Display help. + images List images loaded by the program. + mem Synonym for memory. + memory Inspect memory. + process Show information about the process. + quit Synonym for exit. + reg Synonym for registers. + registers Display the registers. + set Set or show options. + thread Show or set the current thread. + threads Synonym for process. + """) + default: + print(theme.error("unknown command '\(cmd[0])'")) + } + + print("") + } + } + + static func printableBytes(from bytes: some Sequence) -> String { + // It would be nice to join these with ZWNJs to prevent ligature processing, + // but sadly Terminal displays ZWNJ as a space character. + return bytes.map{ byte in + switch byte { + case 0..<32, 127, 0x80..<0xa0: + return theme.nonPrintable("·") + default: + return theme.printable(String(Unicode.Scalar(byte))) + } + }.joined(separator:"") + } + + static func dumpMemory(at address: UInt64, count: UInt64) { + guard let bytes = try? target!.reader.fetch( + from: RemoteMemoryReader.Address(address), + count: Int(count), + as: UInt8.self) else { + print("Unable to read memory") + return + } + + let startAddress = HostContext.stripPtrAuth(address: address) + var ndx = 0 + while ndx < bytes.count { + let addr = startAddress + UInt64(ndx) + let remaining = bytes.count - ndx + let lineChunk = 16 + let todo = min(remaining, lineChunk) + let formattedBytes = theme.data(bytes[ndx..(name: String, value: T) { + let hexValue = theme.hexValue(hex(value)) + + // Pad the register name + let regPad = String(repeating: " ", count: max(3 - name.count, 0)) + let reg = theme.register(regPad + name) + + // Grab 16 bytes at each address if possible + if let bytes = try? target!.reader.fetch( + from: RemoteMemoryReader.Address(value), + count: 16, + as: UInt8.self) { + let formattedBytes = theme.data(bytes.map{ + hex($0, withPrefix: false) + }.joined(separator: " ")) + let printedBytes = printableBytes(from: bytes) + print("\(reg) \(hexValue) \(formattedBytes) \(printedBytes)") + } else { + let decValue = theme.decimalValue("\(value)") + print("\(reg) \(hexValue) \(decValue)") + } + } + + static func showGPR(name: String, context: C, register: C.Register) { + // Get the register contents + let value = context.getRegister(register)! + + showRegister(name: name, value: value) + } + + static func showGPRs(_ context: C, range: Rs) where Rs.Element == C.Register { + for reg in range { + showGPR(name: "\(reg)", context: context, register: reg) + } + } + + static func x86StatusFlags(_ flags: T) -> String { + var status: [String] = [] + + if (flags & 0x400) != 0 { + status.append("OF") + } + if (flags & 0x80) != 0 { + status.append("SF") + } + if (flags & 0x40) != 0 { + status.append("ZF") + } + if (flags & 0x10) != 0 { + status.append("AF") + } + if (flags & 0x4) != 0 { + status.append("PF") + } + if (flags & 0x1) != 0 { + status.append("CF") + } + + return status.joined(separator: " ") + } + + static func showRegisters(_ context: X86_64Context) { + showGPRs(context, range: .rax ... .r15) + showRegister(name: "rip", value: context.programCounter) + + let rflags = context.getRegister(.rflags)! + let cs = theme.hexValue(hex(UInt16(context.getRegister(.cs)!))) + let fs = theme.hexValue(hex(UInt16(context.getRegister(.fs)!))) + let gs = theme.hexValue(hex(UInt16(context.getRegister(.gs)!))) + + let hexFlags = theme.hexValue(hex(rflags)) + let status = theme.flags(x86StatusFlags(rflags)) + + print("") + print("\(theme.register("rflags")) \(hexFlags) \(status)") + print("") + print("\(theme.register("cs")) \(cs) \(theme.register("fs")) \(fs) \(theme.register("gs")) \(gs)") + } + + static func showRegisters(_ context: I386Context) { + showGPRs(context, range: .eax ... .edi) + showRegister(name: "eip", value: context.programCounter) + + let eflags = UInt32(context.getRegister(.eflags)!) + let es = theme.hexValue(hex(UInt16(context.getRegister(.es)!))) + let cs = theme.hexValue(hex(UInt16(context.getRegister(.cs)!))) + let ss = theme.hexValue(hex(UInt16(context.getRegister(.ss)!))) + let ds = theme.hexValue(hex(UInt16(context.getRegister(.ds)!))) + let fs = theme.hexValue(hex(UInt16(context.getRegister(.fs)!))) + let gs = theme.hexValue(hex(UInt16(context.getRegister(.gs)!))) + + let hexFlags = theme.hexValue(hex(eflags)) + let status = theme.flags(x86StatusFlags(eflags)) + + print("") + print("\(theme.register("eflags")) \(hexFlags) \(status)") + print("") + print("\(theme.register("es")): \(es) \(theme.register("cs")): \(cs) \(theme.register("ss")): \(ss) \(theme.register("ds")): \(ds) \(theme.register("fs")): \(fs)) \(theme.register("gs")): \(gs)") + } + + static func showRegisters(_ context: ARM64Context) { + showGPRs(context, range: .x0 ..< .x29) + showGPR(name: "fp", context: context, register: .x29) + showGPR(name: "lr", context: context, register: .x30) + showGPR(name: "sp", context: context, register: .sp) + showGPR(name: "pc", context: context, register: .pc) + } + + static func showRegisters(_ context: ARMContext) { + showGPRs(context, range: .r0 ... .r10) + showGPR(name: "fp", context: context, register: .r11) + showGPR(name: "ip", context: context, register: .r12) + showGPR(name: "sp", context: context, register: .r13) + showGPR(name: "lr", context: context, register: .r14) + showGPR(name: "pc", context: context, register: .r15) + } +} + +#else + +@main +internal struct SwiftBacktrace { + static public func main() { + print("swift-backtrace: not supported on this platform.") + } +} + +#endif // os(macOS) diff --git a/stdlib/public/runtime/Backtrace.cpp b/stdlib/public/runtime/Backtrace.cpp new file mode 100644 index 0000000000000..971f152d8e493 --- /dev/null +++ b/stdlib/public/runtime/Backtrace.cpp @@ -0,0 +1,798 @@ +//===--- Backtrace.cpp - Swift crash catching and backtracing support ---- ===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Crash catching and backtracing support routines. +// +//===----------------------------------------------------------------------===// + +#include + +#include "llvm/ADT/StringRef.h" + +#include "swift/Runtime/Config.h" +#include "swift/Runtime/Backtrace.h" +#include "swift/Runtime/Debug.h" +#include "swift/Runtime/Paths.h" +#include "swift/Runtime/EnvironmentVariables.h" +#include "swift/Runtime/Win32.h" + +#include "swift/Demangling/Demangler.h" + +#ifdef __linux__ +#include +#endif + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +#include +#include +#include + +#define DEBUG_BACKTRACING_SETTINGS 0 + +#ifndef lengthof +#define lengthof(x) (sizeof(x) / sizeof(x[0])) +#endif + +using namespace swift::runtime::backtrace; + +namespace swift { +namespace runtime { +namespace backtrace { + +SWIFT_RUNTIME_STDLIB_INTERNAL BacktraceSettings _swift_backtraceSettings = { + UnwindAlgorithm::Auto, + + // enabled +#if TARGET_OS_OSX + OnOffTty::TTY, +#elif 0 // defined(__linux__) || defined(_WIN32) + OnOffTty::On, +#else + OnOffTty::Off, +#endif + + // demangle + true, + + // interactive +#if TARGET_OS_OSX // || defined(__linux__) || defined(_WIN32) + OnOffTty::TTY, +#else + OnOffTty::Off, +#endif + + // color + OnOffTty::TTY, + + // timeout + 30, + + // threads + ThreadsToShow::Preset, + + // registers + RegistersToShow::Preset, + + // images + ImagesToShow::Preset, + + // limit + 64, + + // top + 16, + + // sanitize, + SanitizePaths::Preset, + + // preset + Preset::Auto, + + // cache + true, + + // swiftBacktracePath + NULL, +}; + +} +} +} + +namespace { + +class BacktraceInitializer { +public: + BacktraceInitializer(); +}; + +SWIFT_ALLOWED_RUNTIME_GLOBAL_CTOR_BEGIN + +BacktraceInitializer backtraceInitializer; + +SWIFT_ALLOWED_RUNTIME_GLOBAL_CTOR_END + +#if TARGET_OS_OSX +posix_spawnattr_t backtraceSpawnAttrs; +posix_spawn_file_actions_t backtraceFileActions; +#endif + +#if SWIFT_BACKTRACE_ON_CRASH_SUPPORTED + +// We need swiftBacktracePath to be aligned on a page boundary, and it also +// needs to be a multiple of the system page size. +#define SWIFT_BACKTRACE_BUFFER_SIZE 16384 + +static_assert((SWIFT_BACKTRACE_BUFFER_SIZE % SWIFT_PAGE_SIZE) == 0, + "The backtrace path buffer must be a multiple of the system " + "page size. If it isn't, you'll get weird crashes in other " + "code because we'll protect more than just the buffer."); + +// The same goes for swiftBacktraceEnvironment +#define SWIFT_BACKTRACE_ENVIRONMENT_SIZE 32768 + +static_assert((SWIFT_BACKTRACE_ENVIRONMENT_SIZE % SWIFT_PAGE_SIZE) == 0, + "The environment buffer must be a multiple of the system " + "page size. If it isn't, you'll get weird crashes in other " + "code because we'll protect more than just the buffer."); + +#if _WIN32 +#pragma section(SWIFT_BACKTRACE_SECTION, read, write) +__declspec(allocate(SWIFT_BACKTRACE_SECTION)) WCHAR swiftBacktracePath[SWIFT_BACKTRACE_BUFFER_SIZE]; +__declspec(allocate(SWIFT_BACKTRACE_SECTION)) CHAR swiftBacktraceEnv[SWIFT_BACKTRACE_ENVIRONMENT_SIZE]; +#elif defined(__linux__) || TARGET_OS_OSX +char swiftBacktracePath[SWIFT_BACKTRACE_BUFFER_SIZE] __attribute__((section(SWIFT_BACKTRACE_SECTION), aligned(SWIFT_PAGE_SIZE))); +char swiftBacktraceEnv[SWIFT_BACKTRACE_ENVIRONMENT_SIZE] __attribute__((section(SWIFT_BACKTRACE_SECTION), aligned(SWIFT_PAGE_SIZE))); +#endif + +void _swift_backtraceSetupEnvironment(); + +bool isStdoutATty() +{ +#ifndef _WIN32 + return isatty(STDOUT_FILENO); +#else + DWORD dwMode; + return GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &dwMode); +#endif +} + +bool isStdinATty() +{ +#ifndef _WIN32 + return isatty(STDIN_FILENO); +#else + DWORD dwMode; + return GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &dwMode); +#endif +} + +#endif // SWIFT_BACKTRACE_ON_CRASH_SUPPORTED + +void _swift_processBacktracingSetting(llvm::StringRef key, llvm::StringRef value); +void _swift_parseBacktracingSettings(const char *); + +#if DEBUG_BACKTRACING_SETTINGS +const char *algorithmToString(UnwindAlgorithm algorithm) { + switch (algorithm) { + case UnwindAlgorithm::Auto: return "Auto"; + case UnwindAlgorithm::Fast: return "Fast"; + case UnwindAlgorithm::Precise: return "Precise"; + } +} + +const char *onOffTtyToString(OnOffTty oot) { + switch (oot) { + case OnOffTty::On: return "On"; + case OnOffTty::Off: return "Off"; + case OnOffTty::TTY: return "TTY"; + } +} + +const char *boolToString(bool b) { + return b ? "true" : "false"; +} + +const char *presetToString(Preset preset) { + switch (preset) { + case Preset::Auto: return "Auto"; + case Preset::Friendly: return "Friendly"; + case Preset::Medium: return "Medium"; + case Preset::Full: return Full; + } +} +#endif + +#ifdef __linux__ +bool isPrivileged() { + return getauxval(AT_SECURE); +} +#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) +bool isPrivileged() { + return issetugid(); +} +#elif _WIN32 +bool isPrivileged() { + return false; +} +#endif + +} // namespace + +BacktraceInitializer::BacktraceInitializer() { + const char *backtracing = swift::runtime::environment::SWIFT_BACKTRACE(); + + // Force off for setuid processes. + if (isPrivileged()) { + _swift_backtraceSettings.enabled = OnOffTty::Off; + } + + if (backtracing) + _swift_parseBacktracingSettings(backtracing); + +#if TARGET_OS_OSX + // Make sure that all fds are closed except for stdin/stdout/stderr. + posix_spawnattr_init(&backtraceSpawnAttrs); + posix_spawnattr_setflags(&backtraceSpawnAttrs, POSIX_SPAWN_CLOEXEC_DEFAULT); + + posix_spawn_file_actions_init(&backtraceFileActions); + posix_spawn_file_actions_addinherit_np(&backtraceFileActions, STDIN_FILENO); + posix_spawn_file_actions_addinherit_np(&backtraceFileActions, STDOUT_FILENO); + posix_spawn_file_actions_addinherit_np(&backtraceFileActions, STDERR_FILENO); +#endif + +#if !SWIFT_BACKTRACE_ON_CRASH_SUPPORTED + if (_swift_backtraceSettings.enabled != OnOffTty::Off) { + swift::warning(0, + "swift runtime: backtrace-on-crash is not supported on " + "this platform.\n"); + _swift_backtraceSettings.enabled = OnOffTty::Off; + } +#else + + if (isPrivileged() && _swift_backtraceSettings.enabled != OnOffTty::Off) { + // You'll only see this warning if you do e.g. + // + // SWIFT_BACKTRACE=enable=on /path/to/some/setuid/binary + // + // as opposed to + // + // /path/to/some/setuid/binary + // + // i.e. when you're trying to force matters. + swift::warning(0, + "swift runtime: backtrace-on-crash is not supported for " + "privileged executables.\n"); + _swift_backtraceSettings.enabled = OnOffTty::Off; + } + + if (_swift_backtraceSettings.enabled == OnOffTty::TTY) + _swift_backtraceSettings.enabled = + isStdoutATty() ? OnOffTty::On : OnOffTty::Off; + + if (_swift_backtraceSettings.interactive == OnOffTty::TTY) { + _swift_backtraceSettings.interactive = + (isStdoutATty() && isStdinATty()) ? OnOffTty::On : OnOffTty::Off; + } + + if (_swift_backtraceSettings.color == OnOffTty::TTY) + _swift_backtraceSettings.color = + isStdoutATty() ? OnOffTty::On : OnOffTty::Off; + + if (_swift_backtraceSettings.preset == Preset::Auto) { + if (_swift_backtraceSettings.interactive == OnOffTty::On) + _swift_backtraceSettings.preset = Preset::Friendly; + else + _swift_backtraceSettings.preset = Preset::Full; + } + + if (_swift_backtraceSettings.enabled == OnOffTty::On + && !_swift_backtraceSettings.swiftBacktracePath) { + _swift_backtraceSettings.swiftBacktracePath + = swift_copyAuxiliaryExecutablePath("swift-backtrace"); + + if (!_swift_backtraceSettings.swiftBacktracePath) { + swift::warning(0, + "swift runtime: unable to locate swift-backtrace; " + "disabling backtracing.\n"); + _swift_backtraceSettings.enabled = OnOffTty::Off; + } + } + + if (_swift_backtraceSettings.enabled == OnOffTty::On) { + // Copy the path to swift-backtrace into swiftBacktracePath, then write + // protect it so that it can't be overwritten easily at runtime. We do + // this to avoid creating a massive security hole that would allow an + // attacker to overwrite the path and then cause a crash to get us to + // execute an arbitrary file. + +#if _WIN32 + if (_swift_backtraceSettings.algorithm == UnwindAlgorithm::Auto) + _swift_backtraceSettings.algorithm = UnwindAlgorithm::Precise; + + int len = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + _swift_backtraceSettings.swiftBacktracePath, -1, + swiftBacktracePath, + SWIFT_BACKTRACE_BUFFER_SIZE); + if (!len) { + swift::warning(0, + "swift runtime: unable to convert path to " + "swift-backtrace: %08lx; disabling backtracing.\n", + ::GetLastError()); + _swift_backtraceSettings.enabled = OnOffTty::Off; + } else if (!VirtualProtect(swiftBacktracePath, + sizeof(swiftBacktracePath), + PAGE_READONLY, + NULL)) { + swift::warning(0, + "swift runtime: unable to protect path to " + "swift-backtrace: %08lx; disabling backtracing.\n", + ::GetLastError()); + _swift_backtraceSettings.enabled = OnOffTty::Off; + } + + _swift_backtraceSetupEnvironment(); + + if (!VirtualProtect(swiftBacktraceEnv, + sizeof(swiftBacktraceEnv), + PAGE_READONLY, + NULL)) { + swift::warning(0, + "swift runtime: unable to protect environment " + "for swift-backtrace: %08lx; disabling backtracing.\n", + ::GetLastError()); + _swift_backtraceSettings.enabled = OnOffTty::Off; + } +#else + if (_swift_backtraceSettings.algorithm == UnwindAlgorithm::Auto) + _swift_backtraceSettings.algorithm = UnwindAlgorithm::Precise; + + size_t len = strlen(_swift_backtraceSettings.swiftBacktracePath); + if (len > SWIFT_BACKTRACE_BUFFER_SIZE - 1) { + swift::warning(0, + "swift runtime: path to swift-backtrace is too long; " + "disabling backtracing.\n"); + _swift_backtraceSettings.enabled = OnOffTty::Off; + } else { + memcpy(swiftBacktracePath, + _swift_backtraceSettings.swiftBacktracePath, + len + 1); + + if (mprotect(swiftBacktracePath, + sizeof(swiftBacktracePath), + PROT_READ) < 0) { + swift::warning(0, + "swift runtime: unable to protect path to " + "swift-backtrace at %p: %d; disabling backtracing.\n", + swiftBacktracePath, + errno); + _swift_backtraceSettings.enabled = OnOffTty::Off; + } + } + + _swift_backtraceSetupEnvironment(); + + if (mprotect(swiftBacktraceEnv, + sizeof(swiftBacktraceEnv), + PROT_READ) < 0) { + swift::warning(0, + "swift runtime: unable to protect environment for " + "swift-backtrace at %p: %d; disabling backtracing.\n", + swiftBacktraceEnv, + errno); + _swift_backtraceSettings.enabled = OnOffTty::Off; + } +#endif + } + + if (_swift_backtraceSettings.enabled == OnOffTty::On) { + ErrorCode err = _swift_installCrashHandler(); + if (err != 0) { + swift::warning(0, + "swift runtime: crash handler installation failed; " + "disabling backtracing.\n"); + } + } +#endif + +#if DEBUG_BACKTRACING_SETTINGS + printf("\nBACKTRACING SETTINGS\n" + "\n" + "algorithm: %s\n" + "enabled: %s\n" + "demangle: %s\n" + "interactive: %s\n" + "color: %s\n" + "timeout: %u\n" + "preset: %s\n" + "swiftBacktracePath: %s\n", + algorithmToString(_swift_backtraceSettings.algorithm), + onOffTtyToString(_swift_backtraceSettings.enabled), + boolToString(_swift_backtraceSettings.demangle), + onOffTtyToString(_swift_backtraceSettings.interactive), + onOffTtyToString(_swift_backtraceSettings.color), + _swift_backtraceSettings.timeout, + presetToString(_swift_backtraceSettings.preset), + swiftBacktracePath); + + printf("\nBACKTRACING ENV\n"); + + const char *ptr = swiftBacktraceEnv; + while (*ptr) { + size_t len = std::strlen(ptr); + printf("%s\n", ptr); + ptr += len + 1; + } + printf("\n"); +#endif +} + +namespace { + +OnOffTty +parseOnOffTty(llvm::StringRef value) +{ + if (value.equals_insensitive("on") + || value.equals_insensitive("true") + || value.equals_insensitive("yes") + || value.equals_insensitive("y") + || value.equals_insensitive("t") + || value.equals_insensitive("1")) + return OnOffTty::On; + if (value.equals_insensitive("tty") + || value.equals_insensitive("auto")) + return OnOffTty::TTY; + return OnOffTty::Off; +} + +bool +parseBoolean(llvm::StringRef value) +{ + return (value.equals_insensitive("on") + || value.equals_insensitive("true") + || value.equals_insensitive("yes") + || value.equals_insensitive("y") + || value.equals_insensitive("t") + || value.equals_insensitive("1")); +} + +void +_swift_processBacktracingSetting(llvm::StringRef key, + llvm::StringRef value) + +{ + if (key.equals_insensitive("enable")) { + _swift_backtraceSettings.enabled = parseOnOffTty(value); + } else if (key.equals_insensitive("demangle")) { + _swift_backtraceSettings.demangle = parseBoolean(value); + } else if (key.equals_insensitive("interactive")) { + _swift_backtraceSettings.interactive = parseOnOffTty(value); + } else if (key.equals_insensitive("color")) { + _swift_backtraceSettings.color = parseOnOffTty(value); + } else if (key.equals_insensitive("timeout")) { + int count; + llvm::StringRef valueCopy = value; + + if (value.equals_insensitive("none")) { + _swift_backtraceSettings.timeout = 0; + } else if (!valueCopy.consumeInteger(0, count)) { + // Yes, consumeInteger() really does return *false* for success + llvm::StringRef unit = valueCopy.trim(); + + if (unit.empty() + || unit.equals_insensitive("s") + || unit.equals_insensitive("seconds")) + _swift_backtraceSettings.timeout = count; + else if (unit.equals_insensitive("m") + || unit.equals_insensitive("minutes")) + _swift_backtraceSettings.timeout = count * 60; + else if (unit.equals_insensitive("h") + || unit.equals_insensitive("hours")) + _swift_backtraceSettings.timeout = count * 3600; + + if (_swift_backtraceSettings.timeout < 0) { + swift::warning(0, + "swift runtime: bad backtracing timeout %ds\n", + _swift_backtraceSettings.timeout); + _swift_backtraceSettings.timeout = 0; + } + } else { + swift::warning(0, + "swift runtime: bad backtracing timeout '%.*s'\n", + static_cast(value.size()), value.data()); + } + } else if (key.equals_insensitive("unwind")) { + if (value.equals_insensitive("auto")) + _swift_backtraceSettings.algorithm = UnwindAlgorithm::Auto; + else if (value.equals_insensitive("fast")) + _swift_backtraceSettings.algorithm = UnwindAlgorithm::Fast; + else if (value.equals_insensitive("precise")) + _swift_backtraceSettings.algorithm = UnwindAlgorithm::Precise; + else { + swift::warning(0, + "swift runtime: unknown unwind algorithm '%.*s'\n", + static_cast(value.size()), value.data()); + } + } else if (key.equals_insensitive("sanitize")) { + _swift_backtraceSettings.sanitize + = parseBoolean(value) ? SanitizePaths::On : SanitizePaths::Off; + } else if (key.equals_insensitive("preset")) { + if (value.equals_insensitive("auto")) + _swift_backtraceSettings.preset = Preset::Auto; + else if (value.equals_insensitive("friendly")) + _swift_backtraceSettings.preset = Preset::Friendly; + else if (value.equals_insensitive("medium")) + _swift_backtraceSettings.preset = Preset::Medium; + else if (value.equals_insensitive("full")) + _swift_backtraceSettings.preset = Preset::Full; + else { + swift::warning(0, + "swift runtime: unknown backtracing preset '%.*s'\n", + static_cast(value.size()), value.data()); + } + } else if (key.equals_insensitive("threads")) { + if (value.equals_insensitive("all")) + _swift_backtraceSettings.threads = ThreadsToShow::All; + else if (value.equals_insensitive("crashed")) + _swift_backtraceSettings.threads = ThreadsToShow::Crashed; + else { + swift::warning(0, + "swift runtime: unknown threads setting '%.*s'\n", + static_cast(value.size()), value.data()); + } + } else if (key.equals_insensitive("registers")) { + if (value.equals_insensitive("none")) + _swift_backtraceSettings.registers = RegistersToShow::None; + else if (value.equals_insensitive("all")) + _swift_backtraceSettings.registers = RegistersToShow::All; + else if (value.equals_insensitive("crashed")) + _swift_backtraceSettings.registers = RegistersToShow::Crashed; + else { + swift::warning(0, + "swift runtime: unknown registers setting '%.*s'\n", + static_cast(value.size()), value.data()); + } + } else if (key.equals_insensitive("images")) { + if (value.equals_insensitive("none")) + _swift_backtraceSettings.images = ImagesToShow::None; + else if (value.equals_insensitive("all")) + _swift_backtraceSettings.images = ImagesToShow::All; + else if (value.equals_insensitive("mentioned")) + _swift_backtraceSettings.images = ImagesToShow::Mentioned; + else { + swift::warning(0, + "swift runtime: unknown registers setting '%.*s'\n", + static_cast(value.size()), value.data()); + } + } else if (key.equals_insensitive("limit")) { + int limit; + // Yes, getAsInteger() returns false for success. + if (value.equals_insensitive("none")) + _swift_backtraceSettings.limit = -1; + else if (!value.getAsInteger(0, limit) && limit > 0) + _swift_backtraceSettings.limit = limit; + else { + swift::warning(0, + "swift runtime: bad backtrace limit '%.*s'\n", + static_cast(value.size()), value.data()); + } + } else if (key.equals_insensitive("top")) { + int top; + // (If you think the next line is wrong, see above.) + if (!value.getAsInteger(0, top) && top >= 0) + _swift_backtraceSettings.top = top; + else { + swift::warning(0, + "swift runtime: bad backtrace top count '%.*s'\n", + static_cast(value.size()), value.data()); + } + } else if (key.equals_insensitive("cache")) { + _swift_backtraceSettings.cache = parseBoolean(value); + } else if (key.equals_insensitive("swift-backtrace")) { + size_t len = value.size(); + char *path = (char *)std::malloc(len + 1); + std::copy(value.begin(), value.end(), path); + path[len] = 0; + + std::free(const_cast(_swift_backtraceSettings.swiftBacktracePath)); + _swift_backtraceSettings.swiftBacktracePath = path; + } else { + swift::warning(0, + "swift runtime: unknown backtracing setting '%.*s'\n", + static_cast(key.size()), key.data()); + } +} + +void +_swift_parseBacktracingSettings(const char *settings) +{ + const char *ptr = settings; + const char *key = ptr; + const char *keyEnd; + const char *value; + const char *valueEnd; + enum { + ScanningKey, + ScanningValue + } state = ScanningKey; + int ch; + + while ((ch = *ptr++)) { + switch (state) { + case ScanningKey: + if (ch == '=') { + keyEnd = ptr - 1; + value = ptr; + state = ScanningValue; + continue; + } + break; + case ScanningValue: + if (ch == ',') { + valueEnd = ptr - 1; + + _swift_processBacktracingSetting(llvm::StringRef(key, keyEnd - key), + llvm::StringRef(value, + valueEnd - value)); + + key = ptr; + state = ScanningKey; + continue; + } + break; + } + } + + if (state == ScanningValue) { + valueEnd = ptr - 1; + _swift_processBacktracingSetting(llvm::StringRef(key, keyEnd - key), + llvm::StringRef(value, + valueEnd - value)); + } +} + +#if SWIFT_BACKTRACE_ON_CRASH_SUPPORTED +// These are the only environment variables that are passed through to +// the swift-backtrace process. They're copied at program start, and then +// write protected so they can't be manipulated by an attacker using a buffer +// overrun. +const char * const environmentVarsToPassThrough[] = { + "LD_LIBRARY_PATH", + "DYLD_LIBRARY_PATH", + "DYLD_FRAMEWORK_PATH", + "PATH", + "TERM", + "LANG", + "HOME" +}; + +#define BACKTRACE_MAX_ENV_VARS lengthof(environmentVarsToPassThrough) + +void +_swift_backtraceSetupEnvironment() +{ + size_t remaining = sizeof(swiftBacktraceEnv); + char *penv = swiftBacktraceEnv; + + std::memset(swiftBacktraceEnv, 0, sizeof(swiftBacktraceEnv)); + + // We definitely don't want this on in the swift-backtrace program + const char * const disable = "SWIFT_BACKTRACE=enable=no"; + const size_t disableLen = std::strlen(disable) + 1; + std::memcpy(penv, disable, disableLen); + penv += disableLen; + remaining -= disableLen; + + for (unsigned n = 0; n < BACKTRACE_MAX_ENV_VARS; ++n) { + const char *name = environmentVarsToPassThrough[n]; + const char *value = getenv(name); + if (!value) + continue; + + size_t nameLen = std::strlen(name); + size_t valueLen = std::strlen(value); + size_t totalLen = nameLen + 1 + valueLen + 1; + + if (remaining > totalLen) { + std::memcpy(penv, name, nameLen); + penv += nameLen; + *penv++ = '='; + std::memcpy(penv, value, valueLen); + penv += valueLen; + *penv++ = 0; + + remaining -= totalLen; + } + } + + *penv = 0; +} + +#endif // SWIFT_BACKTRACE_ON_CRASH_SUPPORTED + +} // namespace + +namespace swift { +namespace runtime { +namespace backtrace { + +/// Test if a Swift symbol name represents a thunk function. +/// +/// In backtraces, it is often desirable to omit thunk frames as they usually +/// just clutter up the backtrace unnecessarily. +/// +/// @param mangledName is the symbol name to be tested. +/// +/// @returns `true` if `mangledName` represents a thunk function. +SWIFT_RUNTIME_STDLIB_SPI SWIFT_CC(swift) bool +_swift_isThunkFunction(const char *mangledName) { + swift::Demangle::Context ctx; + + return ctx.isThunkSymbol(mangledName); +} + +// 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 +_swift_spawnBacktracer(const ArgChar * const *argv) +{ +#if TARGET_OS_OSX + pid_t child; + const char *env[BACKTRACE_MAX_ENV_VARS + 1]; + + // Set-up the environment array + const char *ptr = swiftBacktraceEnv; + unsigned nEnv = 0; + while (*ptr && nEnv < lengthof(env) - 1) { + env[nEnv++] = ptr; + ptr += std::strlen(ptr) + 1; + }; + env[nEnv] = 0; + + // SUSv3 says argv and envp are "completely constant" and that the reason + // posix_spawn() et al use char * const * is for compatibility. + int ret = posix_spawn(&child, swiftBacktracePath, + &backtraceFileActions, &backtraceSpawnAttrs, + const_cast(argv), + const_cast(env)); + if (ret < 0) + return false; + + int wstatus; + + do { + ret = waitpid(child, &wstatus, 0); + } while (ret < 0 && errno == EINTR); + + if (WIFEXITED(wstatus)) + return WEXITSTATUS(wstatus) == 0; + + return false; + + // ###TODO: Linux + // ###TODO: Windows +#else + return false; +#endif +} + +} // namespace backtrace +} // namespace runtime +} // namespace swift diff --git a/stdlib/public/runtime/CMakeLists.txt b/stdlib/public/runtime/CMakeLists.txt index 87ad25f452ff9..3331b6871eee3 100644 --- a/stdlib/public/runtime/CMakeLists.txt +++ b/stdlib/public/runtime/CMakeLists.txt @@ -30,9 +30,11 @@ set(swift_runtime_sources AnyHashableSupport.cpp Array.cpp AutoDiffSupport.cpp + Backtrace.cpp Bincompat.cpp BytecodeLayouts.cpp Casting.cpp + CrashHandlerMacOS.cpp CrashReporter.cpp Demangle.cpp DynamicCast.cpp diff --git a/stdlib/public/runtime/CrashHandlerMacOS.cpp b/stdlib/public/runtime/CrashHandlerMacOS.cpp new file mode 100644 index 0000000000000..12719b6786fc5 --- /dev/null +++ b/stdlib/public/runtime/CrashHandlerMacOS.cpp @@ -0,0 +1,466 @@ +//===--- 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 + +#if TARGET_OS_OSX + +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "swift/Runtime/Backtrace.h" + +#include + +#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); + +swift::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); + + /* Start the backtracer; this will suspend the process, so there's no need + to try to suspend other threads from here. */ + run_backtracer(); + + // 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 + 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() +{ + // Forward our task port to the backtracer; we use the same technique that + // libxpc uses to forward one of its ports on fork(), except that we aren't + // going to call fork() so libxpc's atfork handler won't run and we'll get + // to send the task port to the child. + // + // I would very much like to send a task *read* port, but for some reason + // that doesn't work here. As a result, what we do instead is send the + // control port but have the backtracer use it to get the read port and + // immediately drop the control port. + // + // That *should* be safe enough in practice; if someone could replace the + // backtracer, then they can also replace libswiftCore, and since we do + // this early on in backtracer start-up, the control port won't be valid + // by the time anyone gets to try anything nefarious. + mach_port_t ports[] = { + mach_task_self(), + }; + + mach_ports_register(mach_task_self(), ports, 1); + + // 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); +} + +} // namespace + +#endif // TARGET_OS_OSX + +#endif // __APPLE__ + diff --git a/stdlib/public/runtime/EnvironmentVariables.def b/stdlib/public/runtime/EnvironmentVariables.def index 48b85a762f48d..1bf01ec7415af 100644 --- a/stdlib/public/runtime/EnvironmentVariables.def +++ b/stdlib/public/runtime/EnvironmentVariables.def @@ -86,4 +86,9 @@ VARIABLE(SWIFT_ROOT, string, "", "This is used to locate auxiliary files relative to the runtime " "itself.") +VARIABLE(SWIFT_BACKTRACE, string, "", + "A comma-separated list of key=value pairs that controls the " + "crash catching and backtracing support in the runtime. " + "See docs/Backtracing.rst in the Swift repository for details.") + #undef VARIABLE diff --git a/test/Backtracing/BacktraceWithLimit.swift b/test/Backtracing/BacktraceWithLimit.swift new file mode 100644 index 0000000000000..99683c9e5f351 --- /dev/null +++ b/test/Backtracing/BacktraceWithLimit.swift @@ -0,0 +1,45 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -parse-as-library -Onone -o %t/BacktraceWithLimit +// RUN: %target-codesign %t/BacktraceWithLimit +// RUN: %target-run %t/BacktraceWithLimit | %FileCheck %s + +// REQUIRES: executable_test +// REQUIRES: OS=macosx + +import _Backtracing + +func doFrames(_ count: Int) { + if count <= 0 { + let backtrace = try! Backtrace.capture(limit: 10, top: 0) + + print(backtrace) + } else { + doFrames(count - 1) + } +} + +@main +struct BacktraceWithLimit { + static func main() { + // CHECK: 0{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 1{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 2{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 3{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 4{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 5{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 6{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 7{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 8{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 9{{[ \t]+}}... + doFrames(1000) + + print("") + + // CHECK: 0{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 1{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 2{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 3{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 4{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + doFrames(5) + } +} diff --git a/test/Backtracing/BacktraceWithLimitAndTop.swift b/test/Backtracing/BacktraceWithLimitAndTop.swift new file mode 100644 index 0000000000000..a14288cdf924a --- /dev/null +++ b/test/Backtracing/BacktraceWithLimitAndTop.swift @@ -0,0 +1,60 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -parse-as-library -Onone -o %t/BacktraceWithLimitAndTop +// RUN: %target-codesign %t/BacktraceWithLimitAndTop +// RUN: %target-run %t/BacktraceWithLimitAndTop | %FileCheck %s + +// REQUIRES: executable_test +// REQUIRES: OS=macosx + +import _Backtracing + +func doFrames(_ count: Int, limit: Int, top: Int) { + if count <= 0 { + let backtrace = try! Backtrace.capture(limit: limit, top: top) + + print(backtrace) + } else { + doFrames(count - 1, limit: limit, top: top) + } +} + +@main +struct BacktraceWithTop { + static func main() { + // CHECK: 0{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 1{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 2{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 3{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 4{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 5{{[ \t]+}}... + // CHECK-NEXT: {{[0-9]+[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: {{[0-9]+[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: {{[0-9]+[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: {{[0-9]+[ \t]+}}0x{{[0-9a-f]+}} [ra] + doFrames(1000, limit: 10, top: 4) + + print("") + + // CHECK: 0{{[ \t]+}}... + // CHECK-NEXT: {{[0-9]+[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: {{[0-9]+[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: {{[0-9]+[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: {{[0-9]+[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: {{[0-9]+[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: {{[0-9]+[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: {{[0-9]+[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: {{[0-9]+[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: {{[0-9]+[ \t]+}}0x{{[0-9a-f]+}} [ra] + doFrames(1000, limit: 10, top: 10) + + print("") + + // CHECK: 0{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 1{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 2{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 3{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 4{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 5{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + doFrames(6, limit: 30, top: 4) + } +} diff --git a/test/Backtracing/Crash.swift b/test/Backtracing/Crash.swift new file mode 100644 index 0000000000000..525a3e77678ac --- /dev/null +++ b/test/Backtracing/Crash.swift @@ -0,0 +1,174 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -parse-as-library -Onone -g -o %t/Crash +// RUN: %target-build-swift %s -parse-as-library -Onone -o %t/CrashNoDebug +// RUN: %target-build-swift %s -parse-as-library -O -g -o %t/CrashOpt +// RUN: %target-build-swift %s -parse-as-library -O -o %t/CrashOptNoDebug +// RUN: %target-codesign %t/Crash +// RUN: %target-codesign %t/CrashNoDebug +// RUN: %target-codesign %t/CrashOpt +// RUN: %target-codesign %t/CrashOptNoDebug +// RUN: (env SWIFT_BACKTRACE=enable=yes,cache=no %target-run %t/Crash || true) | %FileCheck %s +// RUN: (env SWIFT_BACKTRACE=preset=friendly,enable=yes,cache=no %target-run %t/Crash || true) | %FileCheck %s --check-prefix FRIENDLY +// RUN: (env SWIFT_BACKTRACE=enable=yes,cache=no %target-run %t/CrashNoDebug || true) | %FileCheck %s --check-prefix NODEBUG +// RUN: (env SWIFT_BACKTRACE=enable=yes,cache=no %target-run %t/CrashOpt || true) | %FileCheck %s --check-prefix OPTIMIZED +// RUN: (env SWIFT_BACKTRACE=enable=yes,cache=no %target-run %t/CrashOptNoDebug || true) | %FileCheck %s --check-prefix OPTNODEBUG + +// REQUIRES: executable_test +// REQUIRES: OS=macosx + +func level1() { + level2() +} + +func level2() { + level3() +} + +func level3() { + level4() +} + +func level4() { + level5() +} + +func level5() { + print("About to crash") + let ptr = UnsafeMutablePointer(bitPattern: 4)! + ptr.pointee = 42 +} + +@main +struct Crash { + static func main() { + level1() + } +} + +// CHECK: *** Program crashed: Bad pointer dereference at 0x{{0+}}4 *** + +// CHECK: Thread 0 crashed: + +// CHECK: 0 0x{{[0-9a-f]+}} level5() + {{[0-9]+}} in Crash at {{.*}}/Crash.swift:38:15 +// CHECK-NEXT: 1 [ra] 0x{{[0-9a-f]+}} level4() + {{[0-9]+}} in Crash at {{.*}}/Crash.swift:32:3 +// CHECK-NEXT: 2 [ra] 0x{{[0-9a-f]+}} level3() + {{[0-9]+}} in Crash at {{.*}}/Crash.swift:28:3 +// CHECK-NEXT: 3 [ra] 0x{{[0-9a-f]+}} level2() + {{[0-9]+}} in Crash at {{.*}}/Crash.swift:24:3 +// CHECK-NEXT: 4 [ra] 0x{{[0-9a-f]+}} level1() + {{[0-9]+}} in Crash at {{.*}}/Crash.swift:20:3 +// CHECK-NEXT: 5 [ra] 0x{{[0-9a-f]+}} static Crash.main() + {{[0-9]+}} in Crash at {{.*}}/Crash.swift:44:5 +// CHECK-NEXT: 6 [ra] [system] 0x{{[0-9a-f]+}} static Crash.$main() + {{[0-9]+}} in Crash at {{.*}}/Crash.swift:41:1 +// CHECK-NEXT: 7 [ra] [system] 0x{{[0-9a-f]+}} main + {{[0-9]+}} in Crash at {{.*}}/Crash.swift + +// CHECK: Registers: + +// CHECK: Images ({{[0-9]+}} omitted): + +// CHECK: {{0x[0-9a-f]+}}–{{0x[0-9a-f]+}}{{ +}}{{[0-9a-f]+}}{{ +}}Crash{{ +}}{{.*}}/Crash + +// FRIENDLY: *** Program crashed: Bad pointer dereference at 0x{{0+}}4 *** + +// FRIENDLY: Thread 0 crashed: + +// FRIENDLY: 0 level5() + {{[0-9]+}} in Crash at {{.*}}/Crash.swift:38:15 + +// FRIENDLY: 36| print("About to crash") +// FRIENDLY-NEXT: 37| let ptr = UnsafeMutablePointer(bitPattern: 4)! +// FRIENDLY-NEXT: 38| ptr.pointee = 42 +// FRIENDLY-NEXT: | ^ +// FRIENDLY-NEXT: 39| } +// FRIENDLY-NEXT: 40| + +// FRIENDLY: 1 level4() + {{[0-9]+}} in Crash at {{.*}}/Crash.swift:32:3 + +// FRIENDLY: 30| +// FRIENDLY-NEXT: 31| func level4() { +// FRIENDLY-NEXT: 32| level5() +// FRIENDLY-NEXT: | ^ +// FRIENDLY-NEXT: 33| } +// FRIENDLY-NEXT: 34| + +// FRIENDLY: 2 level3() + {{[0-9]+}} in Crash at {{.*}}/Crash.swift:28:3 + +// FRIENDLY: 26| +// FRIENDLY-NEXT: 27| func level3() { +// FRIENDLY-NEXT: 28| level4() +// FRIENDLY-NEXT: | ^ +// FRIENDLY-NEXT: 29| } +// FRIENDLY-NEXT: 30| + +// FRIENDLY: 3 level2() + {{[0-9]+}} in Crash at {{.*}}/Crash.swift:24:3 + +// FRIENDLY: 22| +// FRIENDLY-NEXT: 23| func level2() { +// FRIENDLY-NEXT: 24| level3() +// FRIENDLY-NEXT: | ^ +// FRIENDLY-NEXT: 25| } +// FRIENDLY-NEXT: 26| + +// FRIENDLY: 4 level1() + {{[0-9]+}} in Crash at {{.*}}/Crash.swift:20:3 + +// FRIENDLY: 18| +// FRIENDLY-NEXT: 19| func level1() { +// FRIENDLY-NEXT: 20| level2() +// FRIENDLY-NEXT: | ^ +// FRIENDLY-NEXT: 21| } +// FRIENDLY-NEXT: 22| + +// FRIENDLY: 5 static Crash.main() + {{[0-9]+}} in Crash at {{.*}}/Crash.swift:44:5 + +// FRIENDLY: 42| struct Crash { +// FRIENDLY-NEXT: 43| static func main() { +// FRIENDLY-NEXT: 44| level1() +// FRIENDLY-NEXT: | ^ +// FRIENDLY-NEXT: 45| } +// FRIENDLY-NEXT: 46| } + +// NODEBUG: *** Program crashed: Bad pointer dereference at 0x{{0*}}4 *** + +// NODEBUG: Thread 0 crashed: + +// NODEBUG: 0 0x{{[0-9a-f]+}} level5() + {{[0-9]+}} in CrashNoDebug +// NODEBUG: 1 [ra] 0x{{[0-9a-f]+}} level4() + {{[0-9]+}} in CrashNoDebug +// NODEBUG: 2 [ra] 0x{{[0-9a-f]+}} level3() + {{[0-9]+}} in CrashNoDebug +// NODEBUG: 3 [ra] 0x{{[0-9a-f]+}} level2() + {{[0-9]+}} in CrashNoDebug +// NODEBUG: 4 [ra] 0x{{[0-9a-f]+}} level1() + {{[0-9]+}} in CrashNoDebug +// NODEBUG: 5 [ra] 0x{{[0-9a-f]+}} static Crash.main() + {{[0-9]+}} in CrashNoDebug +// NODEBUG: 6 [ra] [system] 0x{{[0-9a-f]+}} static Crash.$main() + {{[0-9]+}} in CrashNoDebug +// NODEBUG: 7 [ra] 0x{{[0-9a-f]+}} main + {{[0-9]+}} in CrashNoDebug + +// NODEBUG: Registers: + +// NODEBUG: Images ({{[0-9]+}} omitted): + +// NODEBUG: {{0x[0-9a-f]+}}–{{0x[0-9a-f]+}}{{ +}}{{[0-9a-f]+}}{{ +}}CrashNoDebug{{ +}}{{.*}}/CrashNoDebug + +// OPTIMIZED: *** Program crashed: Bad pointer dereference at 0x{{0+}}4 *** + +// OPTIMIZED: Thread 0 crashed: + +// OPTIMIZED: 0 [inlined] 0x{{[0-9a-f]+}} level5() in CrashOpt at {{.*}}/Crash.swift:38:15 +// OPTIMIZED-NEXT: 1 [inlined] 0x{{[0-9a-f]+}} level4() in CrashOpt at {{.*}}/Crash.swift:32:3 +// OPTIMIZED-NEXT: 2 [inlined] 0x{{[0-9a-f]+}} level3() in CrashOpt at {{.*}}/Crash.swift:28:3 +// OPTIMIZED-NEXT: 3 [inlined] 0x{{[0-9a-f]+}} level2() in CrashOpt at {{.*}}/Crash.swift:24:3 +// OPTIMIZED-NEXT: 4 [inlined] 0x{{[0-9a-f]+}} level1() in CrashOpt at {{.*}}/Crash.swift:20:3 +// OPTIMIZED-NEXT: 5 [inlined] 0x{{[0-9a-f]+}} static Crash.main() in CrashOpt at {{.*}}/Crash.swift:44:5 +// OPTIMIZED-NEXT: 6 [inlined] [system] 0x{{[0-9a-f]+}} static Crash.$main() in CrashOpt at {{.*}}/Crash.swift:41:1 +// OPTIMIZED-NEXT: 7 [system] 0x{{[0-9a-f]+}} main + {{[0-9]+}} in CrashOpt at {{.*}}/Crash.swift + +// OPTIMIZED: Registers: + +// OPTIMIZED: Images ({{[0-9]+}} omitted): + +// OPTIMIZED: {{0x[0-9a-f]+}}–{{0x[0-9a-f]+}}{{ +}}{{[0-9a-f]+}}{{ +}}CrashOpt{{ +}}{{.*}}/CrashOpt + +// OPTNODEBUG: *** Program crashed: Bad pointer dereference at 0x{{0*}}4 *** + +// OPTNODEBUG: Thread 0 crashed: + +// OPTNODEBUG: 0 0x{{[0-9a-f]+}} main + {{[0-9]+}} in CrashOptNoDebug + +// OPTNODEBUG: Registers: + +// OPTNODEBUG: Images ({{[0-9]+}} omitted): + +// OPTNODEBUG: {{0x[0-9a-f]+}}–{{0x[0-9a-f]+}}{{ +}}{{[0-9a-f]+}}{{ +}}CrashOptNoDebug{{ +}}{{.*}}/CrashOptNoDebug + diff --git a/test/Backtracing/CrashWithThunk.swift b/test/Backtracing/CrashWithThunk.swift new file mode 100644 index 0000000000000..479bd63a7ece5 --- /dev/null +++ b/test/Backtracing/CrashWithThunk.swift @@ -0,0 +1,65 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -parse-as-library -Onone -g -o %t/CrashWithThunk +// RUN: %target-codesign %t/CrashWithThunk +// RUN: (env SWIFT_BACKTRACE=enable=yes,cache=no %target-run %t/CrashWithThunk || true) | %FileCheck %s +// RUN: (env SWIFT_BACKTRACE=preset=friendly,enable=yes,cache=no %target-run %t/CrashWithThunk || true) | %FileCheck %s --check-prefix FRIENDLY + +// REQUIRES: executable_test +// REQUIRES: OS=macosx + +struct Foo { + var value: T +} + +func crash() { + print("I'm going to crash here") + let ptr = UnsafeMutablePointer(bitPattern: 4)! + ptr.pointee = 42 +} + +@main +struct CrashWithThunk { + static func main() { + let foo = Foo(value: crash) + + foo.value() + } +} + +// CHECK: *** Program crashed: Bad pointer dereference at 0x{{0+}}4 *** + +// CHECK: Thread 0 crashed: + +// CHECK: 0 0x{{[0-9a-f]+}} crash() + {{[0-9]+}} in CrashWithThunk at {{.*}}/CrashWithThunk.swift:17:15 +// CHECK-NEXT: 1 [ra] [thunk] [system] 0x{{[0-9a-f]+}} thunk for @escaping @callee_guaranteed () -> () + {{[0-9]+}} in CrashWithThunk at {{.*}}/Backtracing/ +// CHECK-NEXT: 2 [ra] 0x{{[0-9a-f]+}} static CrashWithThunk.main() + {{[0-9]+}} in CrashWithThunk at {{.*}}/CrashWithThunk.swift:25:9 +// CHECK-NEXT: 3 [ra] [system] 0x{{[0-9a-f]+}} static CrashWithThunk.$main() + {{[0-9]+}} in CrashWithThunk at {{.*}}/CrashWithThunk.swift:20:1 +// CHECK-NEXT: 4 [ra] [system] 0x{{[0-9a-f]+}} main + {{[0-9]+}} in CrashWithThunk at {{.*}}/CrashWithThunk.swift + +// CHECK: Registers: + +// CHECK: Images ({{[0-9]+}} omitted): + +// CHECK: {{0x[0-9a-f]+}}–{{0x[0-9a-f]+}}{{ +}}{{[0-9a-f]+}}{{ +}}CrashWithThunk{{ +}}{{.*}}/CrashWithThunk + +// FRIENDLY: *** Program crashed: Bad pointer dereference at 0x{{0+}}4 *** + +// FRIENDLY: Thread 0 crashed: + +// FRIENDLY: 0 crash() + {{[0-9]+}} in CrashWithThunk at {{.*}}/CrashWithThunk.swift:17:15 + +// FRIENDLY: 15| print("I'm going to crash here") +// FRIENDLY-NEXT: 16| let ptr = UnsafeMutablePointer(bitPattern: 4)! +// FRIENDLY-NEXT: 17| ptr.pointee = 42 +// FRIENDLY-NEXT: | ^ +// FRIENDLY-NEXT: 18| } +// FRIENDLY-NEXT: 19| + +// FRIENDLY: 1 static CrashWithThunk.main() + {{[0-9]+}} in CrashWithThunk at {{.*}}/CrashWithThunk.swift:25:9 + +// FRIENDLY: 23| let foo = Foo(value: crash) +// FRIENDLY-NEXT: 24| +// FRIENDLY-NEXT: 25| foo.value() +// FRIENDLY-NEXT: | ^ +// FRIENDLY-NEXT: 26| } +// FRIENDLY-NEXT: 27| } diff --git a/test/Backtracing/NotImportedByDefault.swift b/test/Backtracing/NotImportedByDefault.swift new file mode 100644 index 0000000000000..3160c35ed0cac --- /dev/null +++ b/test/Backtracing/NotImportedByDefault.swift @@ -0,0 +1,9 @@ +// RUN: %empty-directory(%t) +// RUN: ( %target-build-swift %s -o %t/NotImportedByDefault || true ) 2>&1 | %FileCheck %s + +// Windows chokes on the parens in the above expression +// UNSUPPORTED: OS=windows-msvc + +let backtrace = try! Backtrace.capture() + +// CHECK: error: cannot find 'Backtrace' in scope diff --git a/test/Backtracing/Overflow.swift b/test/Backtracing/Overflow.swift new file mode 100644 index 0000000000000..177215991b8a0 --- /dev/null +++ b/test/Backtracing/Overflow.swift @@ -0,0 +1,109 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -parse-as-library -Onone -g -o %t/Overflow +// RUN: %target-codesign %t/Overflow +// RUN: (env SWIFT_BACKTRACE=enable=yes,cache=no %target-run %t/Overflow || true) | %FileCheck %s +// RUN: (env SWIFT_BACKTRACE=preset=friendly,enable=yes,cache=no %target-run %t/Overflow || true) | %FileCheck %s --check-prefix FRIENDLY + +// REQUIRES: executable_test +// REQUIRES: OS=macosx +var x: UInt = 0 + +func level1() { + level2() +} + +func level2() { + level3() +} + +func level3() { + level4() +} + +func level4() { + level5() +} + +func level5() { + print("About to overflow") + + x -= 1 +} + +@main +struct Overflow { + static func main() { + level1() + } +} + +// CHECK: *** Swift runtime failure: arithmetic overflow *** + +// CHECK: Thread 0 crashed: + +// CHECK: 0 [inlined] [system] 0x{{[0-9a-f]+}} Swift runtime failure: arithmetic overflow in Overflow at {{.*}}/ +// CHECK-NEXT: 1 0x{{[0-9a-f]+}} level5() + {{[0-9]+}} in Overflow at {{.*}}/Overflow.swift:30:5 +// CHECK-NEXT: 2 [ra] 0x{{[0-9a-f]+}} level4() + {{[0-9]+}} in Overflow at {{.*}}/Overflow.swift:24:3 +// CHECK-NEXT: 3 [ra] 0x{{[0-9a-f]+}} level3() + {{[0-9]+}} in Overflow at {{.*}}/Overflow.swift:20:3 +// CHECK-NEXT: 4 [ra] 0x{{[0-9a-f]+}} level2() + {{[0-9]+}} in Overflow at {{.*}}/Overflow.swift:16:3 +// CHECK-NEXT: 5 [ra] 0x{{[0-9a-f]+}} level1() + {{[0-9]+}} in Overflow at {{.*}}/Overflow.swift:12:3 +// CHECK-NEXT: 6 [ra] 0x{{[0-9a-f]+}} static Overflow.main() + {{[0-9]+}} in Overflow at {{.*}}/Overflow.swift:36:5 +// CHECK-NEXT: 7 [ra] [system] 0x{{[0-9a-f]+}} static Overflow.$main() + {{[0-9]+}} in Overflow at {{.*}}/Overflow.swift:33:1 +// CHECK-NEXT: 8 [ra] [system] 0x{{[0-9a-f]+}} main + {{[0-9]+}} in Overflow at {{.*}}/Overflow.swift + +// CHECK: Registers: + +// CHECK: Images ({{[0-9]+}} omitted): + +// CHECK: {{0x[0-9a-f]+}}–{{0x[0-9a-f]+}}{{ +}}{{[0-9a-f]+}}{{ +}}Overflow{{ +}}{{.*}}/Overflow + +// FRIENDLY: *** Swift runtime failure: arithmetic overflow *** + +// FRIENDLY: Thread 0 crashed: + +// FRIENDLY: 0 level5() + {{[0-9]+}} in Overflow at {{.*}}/Overflow.swift:30:5 + +// FRIENDLY: 28| print("About to overflow") +// FRIENDLY-NEXT: 29| +// FRIENDLY-NEXT: 30| x -= 1 +// FRIENDLY-NEXT: | ^ +// FRIENDLY-NEXT: 31| } + +// FRIENDLY: 1 level4() + {{[0-9]+}} in Overflow at {{.*}}/Overflow.swift:24:3 + +// FRIENDLY: 22| +// FRIENDLY-NEXT: 23| func level4() { +// FRIENDLY-NEXT: 24| level5() +// FRIENDLY-NEXT: | ^ +// FRIENDLY-NEXT: 25| } +// FRIENDLY-NEXT: 26| + +// FRIENDLY: 2 level3() + {{[0-9]+}} in Overflow at {{.*}}/Overflow.swift:20:3 + +// FRIENDLY: 18| +// FRIENDLY-NEXT: 19| func level3() { +// FRIENDLY-NEXT: 20| level4() +// FRIENDLY-NEXT: | ^ +// FRIENDLY-NEXT: 21| } +// FRIENDLY-NEXT: 22| + +// FRIENDLY: 3 level2() + {{[0-9]+}} in Overflow at {{.*}}/Overflow.swift:16:3 + +// FRIENDLY: 14| +// FRIENDLY-NEXT: 15| func level2() { +// FRIENDLY-NEXT: 16| level3() +// FRIENDLY-NEXT: | ^ +// FRIENDLY-NEXT: 17| } +// FRIENDLY-NEXT: 18| + +// FRIENDLY: 4 level1() + {{[0-9]+}} in Overflow at {{.*}}/Overflow.swift:12:3 + +// FRIENDLY: 10| +// FRIENDLY-NEXT: 11| func level1() { +// FRIENDLY-NEXT: 12| level2() +// FRIENDLY-NEXT: | ^ +// FRIENDLY-NEXT: 13| } +// FRIENDLY-NEXT: 14| + +// FRIENDLY: 5 static Overflow.main() + {{[0-9]+}} in Overflow at {{.*}}/Overflow.swift + diff --git a/test/Backtracing/SimpleAsyncBacktrace.swift b/test/Backtracing/SimpleAsyncBacktrace.swift new file mode 100644 index 0000000000000..363a9010edf3a --- /dev/null +++ b/test/Backtracing/SimpleAsyncBacktrace.swift @@ -0,0 +1,54 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -g -parse-as-library -Onone -o %t/SimpleAsyncBacktrace +// RUN: %target-codesign %t/SimpleAsyncBacktrace +// RUN: %target-run %t/SimpleAsyncBacktrace | %FileCheck %s + +// REQUIRES: concurrency +// REQUIRES: executable_test +// REQUIRES: OS=macosx +// REQUIRES: concurrency_runtime +// UNSUPPORTED: back_deployment_runtime + +import _Backtracing + +@available(SwiftStdlib 5.1, *) +func level1() async { + await level2() +} + +@available(SwiftStdlib 5.1, *) +func level2() async { + level3() +} + +@available(SwiftStdlib 5.1, *) +func level3() { + level4() +} + +@available(SwiftStdlib 5.1, *) +func level4() { + level5() +} + +@available(SwiftStdlib 5.1, *) +func level5() { + let backtrace = try! Backtrace.capture() + + // CHECK: 0{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 1{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 2{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 3{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 4{{[ \t]+}}0x{{[0-9a-f]+}} [async] + // CHECK-NEXT: 5{{[ \t]+}}0x{{[0-9a-f]+}} [async] + + print(backtrace) +} + +@available(SwiftStdlib 5.1, *) +@main +struct SimpleAsyncBacktrace { + static func main() async { + await level1() + } +} diff --git a/test/Backtracing/SimpleBacktrace.swift b/test/Backtracing/SimpleBacktrace.swift new file mode 100644 index 0000000000000..dfe39af4e6d96 --- /dev/null +++ b/test/Backtracing/SimpleBacktrace.swift @@ -0,0 +1,43 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -Xfrontend -enable-implicit-backtracing-module-import -parse-as-library -Onone -o %t/SimpleBacktrace +// RUN: %target-codesign %t/SimpleBacktrace +// RUN: %target-run %t/SimpleBacktrace | %FileCheck %s + +// REQUIRES: executable_test +// REQUIRES: OS=macosx + +func level1() { + level2() +} + +func level2() { + level3() +} + +func level3() { + level4() +} + +func level4() { + level5() +} + +func level5() { + let backtrace = try! Backtrace.capture() + + // CHECK: 0{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 1{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 2{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 3{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 4{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + // CHECK-NEXT: 5{{[ \t]+}}0x{{[0-9a-f]+}} [ra] + + print(backtrace) +} + +@main +struct SimpleBacktrace { + static func main() { + level1() + } +} diff --git a/test/Backtracing/StackOverflow.swift b/test/Backtracing/StackOverflow.swift new file mode 100644 index 0000000000000..29ec2d5e9e97a --- /dev/null +++ b/test/Backtracing/StackOverflow.swift @@ -0,0 +1,210 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -parse-as-library -Onone -g -o %t/StackOverflow +// RUN: %target-codesign %t/StackOverflow +// RUN: (env SWIFT_BACKTRACE=enable=yes,cache=no %target-run %t/StackOverflow || true) | %FileCheck %s +// RUN: (env SWIFT_BACKTRACE=limit=16,top=4,enable=yes,cache=no %target-run %t/StackOverflow || true) | %FileCheck %s --check-prefix LIMITED +// RUN: (env SWIFT_BACKTRACE=preset=friendly,enable=yes,cache=no %target-run %t/StackOverflow || true) | %FileCheck %s --check-prefix FRIENDLY + +// REQUIRES: executable_test +// REQUIRES: OS=macosx + +func recurse(_ level: Int) { + if level % 100000 == 0 { + print(level) + } + recurse(level + 1) +} + +@main +struct StackOverflow { + static func main() { + recurse(1) + } +} + +// FIXME: We have to allow all the line numbers below to be off-by-one because +// of a CoreSymbolication bug. This also means we have to skip checking the +// source code output because currently, it's pointing at the wrong line :-( + +// CHECK: *** Program crashed: Bad pointer dereference at 0x{{[0-9a-f]+}} *** + +// CHECK: Thread 0 crashed: + +// CHECK: 0 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:{{[0-9]+}} +// CHECK-NEXT: 1 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 2 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 3 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 4 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 5 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 6 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 7 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 8 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 9 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 10 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 11 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 12 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 13 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 14 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 15 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 16 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 17 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 18 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 19 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 20 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 21 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 22 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 23 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 24 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 25 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 26 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 27 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 28 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 29 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 30 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 31 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 32 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 33 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 34 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 35 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 36 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 37 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 38 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 39 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 40 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 41 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 42 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 43 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 44 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 45 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: 46 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: ... +// CHECK-NEXT: {{[0-9]+}} [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: {{[0-9]+}} [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: {{[0-9]+}} [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: {{[0-9]+}} [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: {{[0-9]+}} [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: {{[0-9]+}} [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: {{[0-9]+}} [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: {{[0-9]+}} [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: {{[0-9]+}} [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: {{[0-9]+}} [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: {{[0-9]+}} [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: {{[0-9]+}} [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// CHECK-NEXT: {{[0-9]+}} [ra] 0x{{[0-9a-f]+}} static StackOverflow.main() + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:{{21|20}}:5 +// CHECK-NEXT: {{[0-9]+}} [ra] [system] 0x{{[0-9a-f]+}} static StackOverflow.$main() + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:{{18|17}}:1 +// CHECK-NEXT: {{[0-9]+}} [ra] [system] 0x{{[0-9a-f]+}} main + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift + +// CHECK: Registers: + +// CHECK: Images ({{[0-9]+}} omitted): + +// CHECK: {{0x[0-9a-f]+}}–{{0x[0-9a-f]+}}{{ +}}{{[0-9a-f]+}}{{ +}}StackOverflow{{ +}}{{.*}}/StackOverflow + +// LIMITED: *** Program crashed: Bad pointer dereference at 0x{{[0-9a-f]+}} *** + +// LIMITED: Thread 0 crashed: + +// LIMITED: 0 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:{{[0-9]+}} +// LIMITED-NEXT: 1 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// LIMITED-NEXT: 2 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// LIMITED-NEXT: 3 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// LIMITED-NEXT: 4 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// LIMITED-NEXT: 5 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// LIMITED-NEXT: 6 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// LIMITED-NEXT: 7 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// LIMITED-NEXT: 8 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// LIMITED-NEXT: 9 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// LIMITED-NEXT: 10 [ra] 0x{{[0-9a-f]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// LIMITED-NEXT: ... +// LIMITED-NEXT: {{[0-9]+}} [ra] 0x{{[0-9a-f]+}} static StackOverflow.main() + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:{{21|20}}:5 +// LIMITED-NEXT: {{[0-9]+}} [ra] [system] 0x{{[0-9a-f]+}} static StackOverflow.$main() + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:{{18|17}}:1 +// LIMITED-NEXT: {{[0-9]+}} [ra] [system] 0x{{[0-9a-f]+}} main + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift + +// FRIENDLY: *** Program crashed: Bad pointer dereference at 0x{{[0-9a-f]+}} *** + +// FRIENDLY: Thread 0 crashed: + +// FRIENDLY: 0 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:{{[0-9]+}} + +// SKIP-FRIENDLY: 8│ // REQUIRES: executable_test +// SKIP-FRIENDLY-NEXT: 9│ // REQUIRES: OS=macosx +// SKIP-FRIENDLY-NEXT: 10│ +// SKIP-FRIENDLY-NEXT: 11│ func recurse(_ level: Int) { +// SKIP-FRIENDLY-NEXT: │ ▲ +// SKIP-FRIENDLY-NEXT: 12│ if level % 100000 == 0 { + +// FRIENDLY: 1 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 + +// SKIP-FRIENDLY: 12│ if level % 100000 == 0 { +// SKIP-FRIENDLY-NEXT: 13│ print(level) +// SKIP-FRIENDLY-NEXT: 14│ } +// SKIP-FRIENDLY-NEXT: 15│ recurse(level + 1) +// SKIP-FRIENDLY-NEXT: │ ▲ +// SKIP-FRIENDLY-NEXT: 16│ } + +// FRIENDLY: 2 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 3 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 4 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 5 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 6 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 7 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 8 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 9 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 10 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 11 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 12 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 13 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 14 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 15 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 16 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 17 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 18 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 19 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 20 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 21 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 22 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 23 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 24 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 25 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 26 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 27 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 28 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 29 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 30 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 31 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 32 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 33 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 34 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 35 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 36 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 37 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 38 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 39 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 40 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 41 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 42 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 43 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 44 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 45 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: 46 recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: ... +// FRIENDLY-NEXT: {{[0-9]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: {{[0-9]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: {{[0-9]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: {{[0-9]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: {{[0-9]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: {{[0-9]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: {{[0-9]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: {{[0-9]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: {{[0-9]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: {{[0-9]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: {{[0-9]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: {{[0-9]+}} recurse(_:) + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:15:3 +// FRIENDLY-NEXT: {{[0-9]+}} static StackOverflow.main() + {{[0-9]+}} in StackOverflow at {{.*}}/StackOverflow.swift:{{21|20}}:5 + +// SKIP-FRIENDLY: 18│ @main +// SKIP-FRIENDLY-NEXT: 19│ struct StackOverflow { +// SKIP-FRIENDLY-NEXT: 20│ static func main() { +// SKIP-FRIENDLY-NEXT: 21│ recurse(1) +// SKIP-FRIENDLY-NEXT: │ ▲ +// SKIP-FRIENDLY-NEXT: 22│ } diff --git a/test/Backtracing/SymbolicatedBacktrace.swift b/test/Backtracing/SymbolicatedBacktrace.swift new file mode 100644 index 0000000000000..0773d7aa42db6 --- /dev/null +++ b/test/Backtracing/SymbolicatedBacktrace.swift @@ -0,0 +1,50 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -parse-as-library -g -Onone -o %t/SymbolicatedBacktrace +// RUN: %target-codesign %t/SymbolicatedBacktrace +// RUN: %target-run %t/SymbolicatedBacktrace | %FileCheck %s + +// REQUIRES: executable_test +// REQUIRES: OS=macosx + +import _Backtracing + +func kablam() { + kerpow() +} + +func kerpow() { + whap() +} + +func whap() { + zonk() +} + +func zonk() { + splat() +} + +func splat() { + pow() +} + +func pow() { + let backtrace = try! Backtrace.capture().symbolicated(useSymbolCache: false)! + + // CHECK: 0{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [0] SymbolicatedBacktrace pow() + // CHECK: 1{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [0] SymbolicatedBacktrace splat() + // CHECK: 2{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [0] SymbolicatedBacktrace zonk() + // CHECK: 3{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [0] SymbolicatedBacktrace whap() + // CHECK: 4{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [0] SymbolicatedBacktrace kerpow() + // CHECK: 5{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [0] SymbolicatedBacktrace kablam() + // CHECK: 6{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [0] SymbolicatedBacktrace static SymbolicatedBacktrace.main() + + print(backtrace) +} + +@main +struct SymbolicatedBacktrace { + static func main() { + kablam() + } +} diff --git a/test/Backtracing/SymbolicatedBacktraceInline.swift b/test/Backtracing/SymbolicatedBacktraceInline.swift new file mode 100644 index 0000000000000..0998a0e717dcd --- /dev/null +++ b/test/Backtracing/SymbolicatedBacktraceInline.swift @@ -0,0 +1,50 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -parse-as-library -g -O -o %t/SymbolicatedBacktraceInline +// RUN: %target-codesign %t/SymbolicatedBacktraceInline +// RUN: %target-run %t/SymbolicatedBacktraceInline | %FileCheck %s + +// REQUIRES: executable_test +// REQUIRES: OS=macosx + +import _Backtracing + +func kablam() { + kerpow() +} + +func kerpow() { + whap() +} + +func whap() { + zonk() +} + +func zonk() { + splat() +} + +func splat() { + pow() +} + +func pow() { + let backtrace = try! Backtrace.capture().symbolicated(useSymbolCache: false)! + + // CHECK: 0{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [0] SymbolicatedBacktraceInline pow() + // CHECK: 1{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [inlined] [0] SymbolicatedBacktraceInline splat() + // CHECK: 2{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [inlined] [0] SymbolicatedBacktraceInline zonk() + // CHECK: 3{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [inlined] [0] SymbolicatedBacktraceInline whap() + // CHECK: 4{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [inlined] [0] SymbolicatedBacktraceInline kerpow() + // CHECK: 5{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [inlined] [0] SymbolicatedBacktraceInline kablam() + // CHECK: 6{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [inlined] [0] SymbolicatedBacktraceInline static SymbolicatedBacktraceInline.main() + + print(backtrace) +} + +@main +struct SymbolicatedBacktraceInline { + static func main() { + kablam() + } +} diff --git a/test/lit.cfg b/test/lit.cfg index 6b5b3f6af1a08..a3146263f50ad 100644 --- a/test/lit.cfg +++ b/test/lit.cfg @@ -1093,7 +1093,8 @@ if run_vendor == 'apple': "swiftStdlibCollectionUnittest", "swiftSwiftPrivateLibcExtras", "swiftSwiftPrivate", "swiftDarwin", "swiftSwiftPrivateThreadExtras", - "swiftSwiftOnoneSupport", "swift_Concurrency"]: + "swiftSwiftOnoneSupport", "swift_Concurrency", + "swift_Backtracing"]: swift_execution_tests_extra_flags += ' -Xlinker -l%s'% library swift_native_clang_tools_path = lit_config.params.get('swift_native_clang_tools_path', None) @@ -1853,6 +1854,13 @@ config.substitutions.append(('%concurrency_module', concurrency_module)) config.substitutions.append(('%/concurrency_module', '/'.join(os.path.normpath(concurrency_module).split(os.sep)))) +# Add 'backtracing_module' as the path to the _Backtracing .swiftmodule file +backtracing_module = os.path.join(stdlib_dir, "_Backtracing.swiftmodule", + target_specific_module_triple + ".swiftmodule") +config.substitutions.append(('%backtracing_module', backtracing_module)) +config.substitutions.append(('%/backtracing_module', + '/'.join(os.path.normpath(backtracing_module).split(os.sep)))) + # Add 'distributed_module' as the path to the Distributed .swiftmodule file distributed_module = os.path.join(stdlib_dir, "Distributed.swiftmodule", target_specific_module_triple + ".swiftmodule") diff --git a/tools/SourceKit/tools/sourcekitd-test/Options.td b/tools/SourceKit/tools/sourcekitd-test/Options.td index a1168c38029c5..32bca04bff954 100644 --- a/tools/SourceKit/tools/sourcekitd-test/Options.td +++ b/tools/SourceKit/tools/sourcekitd-test/Options.td @@ -116,6 +116,14 @@ def disable_implicit_string_processing_module_import : Flag<["-"], "disable-implicit-string-processing-module-import">, HelpText<"Disable implicit import of the _StringProcessing module">; +def enable_implicit_backtracing_module_import : Flag<["-"], + "enable-implicit-backtracing-module-import">, + HelpText<"Enable implicit import of the _Backtracing module">; + +def disable_implicit_backtracing_module_import : Flag<["-"], + "disable-implicit-backtracing-module-import">, + HelpText<"Disable implicit import of the _Backtracing module">; + def end_pos : Separate<["-"], "end-pos">, HelpText<"line:col">; def end_pos_EQ : Joined<["-"], "end-pos=">, Alias; diff --git a/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp b/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp index ed394f3cd3797..e92982d18ea0a 100644 --- a/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp +++ b/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp @@ -455,6 +455,10 @@ bool TestOptions::parseArgs(llvm::ArrayRef Args) { DisableImplicitStringProcessingModuleImport = true; break; + case OPT_disable_implicit_backtracing_module_import: + DisableImplicitBacktracingModuleImport = true; + break; + case OPT_UNKNOWN: llvm::errs() << "error: unknown argument: " << InputArg->getAsString(ParsedArgs) << '\n' diff --git a/tools/SourceKit/tools/sourcekitd-test/TestOptions.h b/tools/SourceKit/tools/sourcekitd-test/TestOptions.h index 2b080fa12acba..d8b9868dc28c9 100644 --- a/tools/SourceKit/tools/sourcekitd-test/TestOptions.h +++ b/tools/SourceKit/tools/sourcekitd-test/TestOptions.h @@ -133,6 +133,8 @@ struct TestOptions { bool measureInstructions = false; bool DisableImplicitConcurrencyModuleImport = false; bool DisableImplicitStringProcessingModuleImport = false; + bool EnableImplicitBacktracingModuleImport = false; + bool DisableImplicitBacktracingModuleImport = false; llvm::Optional CompletionCheckDependencyInterval; unsigned repeatRequest = 1; struct VFSFile { diff --git a/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp b/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp index 003da68497855..22aef88e5ba81 100644 --- a/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp +++ b/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp @@ -1161,6 +1161,20 @@ static int handleTestInvocation(TestOptions Opts, TestOptions &InitOpts) { sourcekitd_request_array_set_string(Args, SOURCEKITD_ARRAY_APPEND, "-disable-implicit-string-processing-module-import"); } + if (Opts.EnableImplicitBacktracingModuleImport && + !compilerArgsAreClang) { + sourcekitd_request_array_set_string(Args, SOURCEKITD_ARRAY_APPEND, + "-Xfrontend"); + sourcekitd_request_array_set_string(Args, SOURCEKITD_ARRAY_APPEND, + "-enable-implicit-backtracing-module-import"); + } + if (Opts.DisableImplicitBacktracingModuleImport && + !compilerArgsAreClang) { + sourcekitd_request_array_set_string(Args, SOURCEKITD_ARRAY_APPEND, + "-Xfrontend"); + sourcekitd_request_array_set_string(Args, SOURCEKITD_ARRAY_APPEND, + "-disable-implicit-backtracing-module-import"); + } for (auto Arg : Opts.CompilerArgs) sourcekitd_request_array_set_string(Args, SOURCEKITD_ARRAY_APPEND, Arg); diff --git a/tools/swift-ide-test/swift-ide-test.cpp b/tools/swift-ide-test/swift-ide-test.cpp index cff8c9fd2caf5..2d87297ea00c6 100644 --- a/tools/swift-ide-test/swift-ide-test.cpp +++ b/tools/swift-ide-test/swift-ide-test.cpp @@ -807,6 +807,16 @@ DisableImplicitStringProcessingImport("disable-implicit-string-processing-module llvm::cl::desc("Disable implicit import of _StringProcessing module"), llvm::cl::init(false)); +static llvm::cl::opt +EnableImplicitBacktracingImport("enable-implicit-backtracing-module-import", + llvm::cl::desc("Enable implicit import of _Backtracing module"), + llvm::cl::init(false)); + +static llvm::cl::opt +DisableImplicitBacktracingImport("disable-implicit-backtracing-module-import", + llvm::cl::desc("Disable implicit import of _Backtracing module"), + llvm::cl::init(false)); + static llvm::cl::opt EnableExperimentalNamedOpaqueTypes( "enable-experimental-named-opaque-types", llvm::cl::desc("Enable experimental support for named opaque result types"), @@ -4345,6 +4355,14 @@ int main(int argc, char *argv[]) { if (options::DisableImplicitStringProcessingImport) { InitInvok.getLangOptions().DisableImplicitStringProcessingModuleImport = true; } + if (options::DisableImplicitBacktracingImport) { + InitInvok.getLangOptions().DisableImplicitBacktracingModuleImport = true; + } else if (options::EnableImplicitBacktracingImport) { + InitInvok.getLangOptions().DisableImplicitBacktracingModuleImport = false; + } else { + InitInvok.getLangOptions().DisableImplicitBacktracingModuleImport = true; + } + if (options::EnableExperimentalNamedOpaqueTypes) { InitInvok.getLangOptions().Features.insert(Feature::NamedOpaqueTypes); }