diff --git a/CMakeLists.txt b/CMakeLists.txt index ddfd8670c75..ff36d12836a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -695,10 +695,6 @@ 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) @@ -731,6 +727,10 @@ option(SWIFT_ENABLE_SYNCHRONIZATION "Enable build of the Swift Synchronization module" FALSE) +option(SWIFT_ENABLE_RUNTIME_MODULE + "Build the Swift Runtime module" + FALSE) + option(SWIFT_ENABLE_VOLATILE "Enable build of the Swift Volatile module" FALSE) @@ -865,11 +865,6 @@ if (CMAKE_Swift_COMPILER) SWIFT_SUPPORTS_DISABLE_IMPLICIT_STRING_PROCESSING_MODULE_IMPORT) message(STATUS " Implicit 'string-processing' import: ${SWIFT_SUPPORTS_DISABLE_IMPLICIT_STRING_PROCESSING_MODULE_IMPORT}") - # Same for _Backtracing. - swift_supports_implicit_module("backtracing" - SWIFT_SUPPORTS_DISABLE_IMPLICIT_BACKTRACING_MODULE_IMPORT) - message(STATUS " Implicit 'backtracing' import: ${SWIFT_SUPPORTS_DISABLE_IMPLICIT_BACKTRACING_MODULE_IMPORT}") - swift_get_package_cmo_support( Swift_COMPILER_PACKAGE_CMO_SUPPORT) message(STATUS " Package CMO: ${Swift_COMPILER_PACKAGE_CMO_SUPPORT}") @@ -1393,6 +1388,8 @@ if(SWIFT_BUILD_STDLIB OR SWIFT_BUILD_SDK_OVERLAY) message(STATUS "Observation Support: ${SWIFT_ENABLE_EXPERIMENTAL_OBSERVATION}") message(STATUS "Synchronization Support: ${SWIFT_ENABLE_SYNCHRONIZATION}") message(STATUS "Volatile Support: ${SWIFT_ENABLE_VOLATILE}") + message(STATUS "Pointer Bounds Support: ${SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS}") + message(STATUS "Runtime Support: ${SWIFT_ENABLE_RUNTIME_MODULE}") message(STATUS "") else() message(STATUS "Not building Swift standard library, SDK overlays, and runtime") diff --git a/Runtimes/Core/CMakeLists.txt b/Runtimes/Core/CMakeLists.txt index 2c861811044..85e2b03ff05 100644 --- a/Runtimes/Core/CMakeLists.txt +++ b/Runtimes/Core/CMakeLists.txt @@ -173,7 +173,6 @@ add_compile_options( "$<$:SHELL:-Xfrontend -enable-experimental-concise-pound-file>" "$<$:SHELL:-Xfrontend -enable-lexical-lifetimes=false>" "$<$:SHELL:-Xfrontend -disable-implicit-concurrency-module-import>" - "$<$:SHELL:-Xfrontend -disable-implicit-backtracing-module-import>" "$<$:SHELL:-Xfrontend -disable-implicit-string-processing-module-import>" "$<$:SHELL:-Xfrontend -enforce-exclusivity=unchecked>" "$<$:SHELL:-Xfrontend -enable-ossa-modules>" diff --git a/cmake/modules/AddPureSwift.cmake b/cmake/modules/AddPureSwift.cmake index 28e496a1ba4..647247a0570 100644 --- a/cmake/modules/AddPureSwift.cmake +++ b/cmake/modules/AddPureSwift.cmake @@ -28,13 +28,7 @@ function(_add_host_swift_compile_options name) "$<$:SHELL:-Xfrontend -disable-implicit-string-processing-module-import>") endif() - # Same for backtracing - if (SWIFT_SUPPORTS_DISABLE_IMPLICIT_BACKTRACING_MODULE_IMPORT) - target_compile_options(${name} PRIVATE - "$<$:SHELL:-Xfrontend -disable-implicit-backtracing-module-import>") - endif() - - if(SWIFT_ANALYZE_CODE_COVERAGE) + if(SWIFT_ANALYZE_CODE_COVERAGE) set(_cov_flags $<$:-profile-generate -profile-coverage-mapping>) target_compile_options(${name} PRIVATE ${_cov_flags}) target_link_options(${name} PRIVATE ${_cov_flags}) diff --git a/docs/Backtracing.rst b/docs/Backtracing.rst index 352e7b09997..7543ceadbaf 100644 --- a/docs/Backtracing.rst +++ b/docs/Backtracing.rst @@ -319,3 +319,14 @@ of the backtracer using If the runtime is unable to locate the backtracer, it will allow your program to crash as it would have done anyway. + +Backtrace Storage +----------------- + +Backtraces are stored internally in a format called :download:`Compact Backtrace +Format `. This provides us with a way to store a +large number of frames in a much smaller space than would otherwise be possible. + +Similarly, where we need to store address to image mappings, we +use :download:`Compact ImageMap Format ` to minimise +storage requirements. diff --git a/docs/CompactBacktraceFormat.md b/docs/CompactBacktraceFormat.md new file mode 100644 index 00000000000..eef81c3a889 --- /dev/null +++ b/docs/CompactBacktraceFormat.md @@ -0,0 +1,167 @@ +Compact Backtrace Format +======================== + +We would like to be able to efficiently store and access backtraces, +but we also wish to minimise the memory used to store them. Since +backtraces typically contain a good deal of redundancy, it should be +possible to compress the data. + +Compact Backtrace Format (CBF) is a binary format for holding a +backtrace; this specification addresses only the storage of the actual +stack backtrace, and it does not consider storage of ancillary data +(register contents, image lists and so on). Those will be dealt with +separately elsewhere. + +## General Format + +Compact Backtrace Format data is byte aligned and starts with an +information byte: + +~~~ + 7 6 5 4 3 2 1 0 + ┌───────────────────────┬───────┐ + │ version │ size │ + └───────────────────────┴───────┘ +~~~ + +The `version` field identifies the version of CBF that is in use; this +document describes version `0`. The `size` field is encqoded as +follows: + +| `size` | Machine word size | +| :----: | :---------------- | +| 00 | 16-bit | +| 01 | 32-bit | +| 10 | 64-bit | +| 11 | Reserved | + +This is followed by a series of instructions that tell the reader how +to decode subsequent data. + +The first instruction that computes an address _must_ specify an +absolute address (the `a` bit must be set). + +## Instructions + +The following instructions are currently defined + +| `opcode` | Mnemonic | Meaning | +| :--------: | :------- | :---------------------------------------- | +| `00000000` | `end` | Marks the end of the backtrace | +| `00000001` | `trunc` | As above, but the backtrace was truncated | +| `0000xxxx` | reserved | Reserved for future expansion | +| `0001axxx` | `pc` | A program counter value follows | +| `0010axxx` | `ra` | A return address value follows | +| `0011axxx` | `async` | An async resume point follows | +| `01xxxxxx` | `omit` | Indicates frames have been omitted | +| `1000xxxx` | `rep` | Repeat the previous frame | +| `1xxxxxxx` | reserved | Reserved for future expansion | + +If the bit labelled `a` is set, it means that the address computation +is absolute rather than being relative to the previously computed +address. + +### `end`/`trunc` + +#### Encoding + +~~~ + 7 6 5 4 3 2 1 0 + ┌───────────────────────────┬───┐ + │ 0 0 0 0 0 0 0 │ t │ end (or trunc if t is 1) + └───────────────────────────┴───┘ +~~~ + +#### Meaning + +Marks the end of the backtrace data. If `t` is set, it indicates that +the backtrace was truncated at this point (for instance because we hit +a frame limit while capturing). + +It is not strictly necessary to use the `end` instruction if the +CBF data is of a known length. + +### `pc`, `ra`, `async` + +#### Encoding + +~~~ + 7 6 5 4 3 2 1 0 + ┌────────────────┬───┬──────────┐ + │ 0 0 0 1 │ a │ count │ pc + └────────────────┴───┴──────────┘ + ┌────────────────┬───┬──────────┐ + │ 0 0 1 0 │ a │ count │ ra + └────────────────┴───┴──────────┘ + ┌────────────────┬───┬──────────┐ + │ 0 0 1 1 │ a │ count │ async + └────────────────┴───┴──────────┘ +~~~ + +#### Meaning + +Each of these instructions represents a frame on the stack. For `pc` +frames, the computed address is an actual program counter (aka +instruction pointer) value. `ra` instructions instead represent a +_return address_, the difference being that the program has not yet +executed that instruction. `async` instructions point at the entry +point of an async resume function, and are used when walking stacks on +systems that support `async`/`await` primitives that are implemented +by function splitting (typically an `async` instruction will point at +the start of a function containing the code immediately following an +`await`). + +The next `count + 1` bytes following the instruction are an address +value. If `a` is set, the computed address is equal to the address +value. If `a` is not set, the computed address is equal to the +preceding computed address *plus* the address value. + +Address values are sign-extended to the machine word width before +processing. Thus a single address byte with value `0xff` on a 32-bit +backtrace represents the address value `0xffffffff`. + +### `omit` + +#### Encoding + +~~~ + 7 6 5 4 3 2 1 0 + ┌───────┬───┬───────────────────┐ + │ 0 1 │ x │ count │ omit + └───────┴───┴───────────────────┘ +~~~ + +#### Meaning + +Indicates that a number of frames were skipped when capturing the +backtrace. This is used to allow a backtrace to include both the top +and bottom of the stack, without carrying every intervening frame, and +is useful to prevent the data from exploding where recursion has taken +place. + +If `x` is `1`, the instruction is followed by `count + 1` bytes (up to the +machine word length) that are zero-extended to machine word length and +that represent a count of the number of frames that were omitted. + +If `x` is `0`, `count + 1` is the number of frames that were omitted. + +### `rep` + +#### Encoding + +~~~ + 7 6 5 4 3 2 1 0 + ┌────────────────┬───┬──────────┐ + │ 1 0 0 0 │ x │ count │ repeat + └────────────────┴───┴──────────┘ +~~~ + +#### Meaning + +Repeat the previous frame. + +If `x` is `1`, the instruction is followed by `count + 1` bytes that are zero +extended to machine word length and that represent a count of the number of +times to repeat the preceding frame. + +If `x` is `0`, the previous frame should be repeated `count + 1` times. diff --git a/docs/CompactImageMapFormat.md b/docs/CompactImageMapFormat.md new file mode 100644 index 00000000000..a7a92bff85d --- /dev/null +++ b/docs/CompactImageMapFormat.md @@ -0,0 +1,230 @@ +Compact ImageMap Format +======================= + +A process' address space contains (among other things) the set of +dynamically loaded images that have been mapped into that address +space. When generating crash logs or symbolicating backtraces, we +need to be able to capture and potentially store the list of images +that has been loaded, as well as some of the attributes of those +images, including each image's + +- Path +- Build ID (aka UUID) +- Base address +- End-of-text address + +Compact ImageMap Format (CIF) is a binary format for holding this +information. + +### General Format + +Compact ImageMap Format data is byte aligned and starts with an +information byte: + +~~~ + 7 6 5 4 3 2 1 0 + ┌───────────────────────┬───────┐ + │ version │ size │ + └───────────────────────┴───────┘ +~~~ + +The `version` field identifies the version of CIF that is in use; this +document describes version `0`. The `size` field is encoded as +follows: + +| `size` | Machine word size | +| :----: | :---------------- | +| 00 | 16-bit | +| 01 | 32-bit | +| 10 | 64-bit | +| 11 | Reserved | + +This is followed immediately by a field containing the name of the platform +that generated this image map. This field consists of a single byte length +followed by a UTF-8 string of that length. + +After that is a field encoding the number of images in the image map; +this field is encoded as a sequence of bytes, each holding seven bits +of data, with the top bit clear for the final byte. The most +significant byte is the first. e.g. + +| `count` | Encoding | +| ------: | :---------- | +| 0 | 00 | +| 1 | 01 | +| 127 | 7f | +| 128 | 81 00 | +| 129 | 81 01 | +| 700 | 85 3c | +| 1234 | 89 52 | +| 16384 | 81 80 00 | +| 65535 | 83 ff 7f | +| 2097152 | 81 80 80 00 | + +This in turn is followed by the list of images, stored in order of +increasing base address. For each image, we start with a header byte: + +~~~ + 7 6 5 4 3 2 1 0 + ┌───┬───┬───────────┬───────────┐ + │ r │ 0 │ acount │ ecount │ + └───┴───┴───────────┴───────────┘ +~~~ + +If `r` is set, then the base address is understood to be relative to +the previously computed base address. + +This byte is followed by `acount + 1` bytes of base address, then +`ecount + 1` bytes of offset to the end of text. + +Following this is an encoded count of bytes in the build ID, +encoded using the 7-bit scheme we used to encode the image count, and +then after that come the build ID bytes themselves. + +Finally, we encode the path string using the scheme below. + +### String Encoding + +Image paths contain a good deal of redundancy; paths are therefore +encoded using a prefix compression scheme. The basic idea here is +that while generating or reading the data, we maintain a mapping from +small integers to path prefix segments. + +The mapping is initialised with the following fixed list that never +need to be stored in CIF data: + +| code | Path prefix | +| :--: | :---------------------------------- | +| 0 | `/lib` | +| 1 | `/usr/lib` | +| 2 | `/usr/local/lib` | +| 3 | `/opt/lib` | +| 4 | `/System/Library/Frameworks` | +| 5 | `/System/Library/PrivateFrameworks` | +| 6 | `/System/iOSSupport` | +| 7 | `/Library/Frameworks` | +| 8 | `/System/Applications` | +| 9 | `/Applications` | +| 10 | `C:\Windows\System32` | +| 11 | `C:\Program Files\` | + +Codes below 32 are reserved for future expansion of the fixed list. + +Strings are encoded as a sequence of bytes, as follows: + +| `opcode` | Mnemonic | Meaning | +| :--------: | :-------- | :---------------------------------------- | +| `00000000` | `end` | Marks the end of the string | +| `00xxxxxx` | `str` | Raw string data | +| `01xxxxxx` | `framewk` | Names a framework | +| `1exxxxxx` | `expand` | Identifies a prefix in the table | + +#### `end` + +##### Encoding + +~~~ + 7 6 5 4 3 2 1 0 + ┌───────────────────────────────┐ + │ 0 0 0 0 0 0 0 0 │ end + └───────────────────────────────┘ +~~~ + +#### Meaning + +Marks the end of the string + +#### `str` + +##### Encoding + +~~~ + 7 6 5 4 3 2 1 0 + ┌───────┬───────────────────────┐ + │ 0 0 │ count │ str + └───────┴───────────────────────┘ +~~~ + +##### Meaning + +The next `count` bytes are included in the string verbatim. +Additionally, all path prefixes of this string data will be added to +the current prefix table. For instance, if the string data is +`/swift/linux/x86_64/libfoo.so`, then the prefix `/swift` will be +assigned the next available code, `/swift/linux` the code after that, +and `/swift/linux/x86_64` the code following that one. + +#### `framewk` + +##### Encoding + +~~~ + 7 6 5 4 3 2 1 0 + ┌───────┬───────────────────────┐ + │ 0 1 │ count │ framewk + └───────┴───────────────────────┘ +~~~ + +##### Meaning + +The next byte is a version character (normally `A`, but some +frameworks use higher characters), after which there are `count + 1` +bytes of name. + +This is expanded using the pattern +`/.framework/Versions//`. This also marks the +end of the string. + +#### `expand` + +##### Encoding + +~~~ + 7 6 5 4 3 2 1 0 + ┌───┬───┬───────────────────────┐ + │ 1 │ e │ code │ expand + └───┴───┴───────────────────────┘ +~~~ + +##### Meaning + +If `e` is `0`, `code` is the index into the prefix table for the +prefix that should be appended to the string at this point. + +If `e` is `1`, this opcode is followed by `code + 1` bytes that give +a value `v` such that `v + 64` is the index into the prefix table for +the prefix that should be appended to the string at this point. + +#### Example + +Let's say we wish to encode the following strings: + + /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit + /System/Library/Frameworks/Photos.framework/Versions/A/Photos + /usr/lib/libobjc.A.dylib + /usr/lib/libz.1.dylib + /usr/lib/swift/libswiftCore.dylib + /usr/lib/libSystem.B.dylib + /usr/lib/libc++.1.dylib + +We would encode + + <84> <45> CAppKit <00> + +We then follow with + + <84> <45> APhotos <00> + +Next we have + + <81> <10> /libobjc.A.dylib <00> + <81> <0d> /libz.1.dylib <00> + <81> <19> /swift/libswiftCore.dylib <00> + +assigning code 32 to `/swift`, then + + <81> <12> /libSystem.B.dylib <00> + <81> <0f> /libc++.1.dylib <00> + +In total the original data would have taken up 256 bytes. Instead, we +have used 122 bytes, a saving of over 50%. diff --git a/include/swift/AST/DiagnosticsFrontend.def b/include/swift/AST/DiagnosticsFrontend.def index b5c6c481c4c..95be9de0ff0 100644 --- a/include/swift/AST/DiagnosticsFrontend.def +++ b/include/swift/AST/DiagnosticsFrontend.def @@ -188,8 +188,6 @@ 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_bad_module_name,none, "module name \"%0\" is not a valid identifier" diff --git a/include/swift/Basic/LangOptions.h b/include/swift/Basic/LangOptions.h index f7ec4f15538..1ca5e89f485 100644 --- a/include/swift/Basic/LangOptions.h +++ b/include/swift/Basic/LangOptions.h @@ -421,10 +421,6 @@ 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; - /// Disable the implicit import of the Cxx module. bool DisableImplicitCxxModuleImport = false; diff --git a/include/swift/Config.h.in b/include/swift/Config.h.in index b0e8282aac0..1884d0f95bc 100644 --- a/include/swift/Config.h.in +++ b/include/swift/Config.h.in @@ -10,8 +10,6 @@ #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 83386ce3275..6918fb89cbc 100644 --- a/include/swift/Frontend/Frontend.h +++ b/include/swift/Frontend/Frontend.h @@ -425,10 +425,6 @@ public: /// imported. bool shouldImportSwiftStringProcessing() const; - /// Whether the Swift Backtracing support library should be implicitly - /// imported. - bool shouldImportSwiftBacktracing() const; - /// Whether the CXX module should be implicitly imported. bool shouldImportCxx() const; @@ -679,14 +675,6 @@ public: /// 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 Cxx library can be imported bool canImportCxx() const; diff --git a/include/swift/Option/FrontendOptions.td b/include/swift/Option/FrontendOptions.td index 382151cae85..85f1c9faf76 100644 --- a/include/swift/Option/FrontendOptions.td +++ b/include/swift/Option/FrontendOptions.td @@ -527,14 +527,6 @@ 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_implicit_cxx_module_import : Flag<["-"], "disable-implicit-cxx-module-import">, HelpText<"Disable the implicit import of the C++ Standard Library module.">; diff --git a/include/swift/Strings.h b/include/swift/Strings.h index be4791f9a31..f1c2dba3461 100644 --- a/include/swift/Strings.h +++ b/include/swift/Strings.h @@ -33,8 +33,6 @@ constexpr static const StringLiteral SWIFT_MODULE_ABI_NAME_PREFIX = "Compiler"; 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 f5e7e36b79c..ef8d3c02322 100644 --- a/lib/AST/NameLookup.cpp +++ b/lib/AST/NameLookup.cpp @@ -651,22 +651,6 @@ 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; - } - } - // Next, prefer any other module over the Observation module. if (auto obsModule = ctx.getLoadedModule(ctx.Id_Observation)) { if ((firstModule == obsModule) != (secondModule == obsModule)) { diff --git a/lib/DriverTool/autolink_extract_main.cpp b/lib/DriverTool/autolink_extract_main.cpp index 79d63e6ea5d..7bcc1251d82 100644 --- a/lib/DriverTool/autolink_extract_main.cpp +++ b/lib/DriverTool/autolink_extract_main.cpp @@ -230,9 +230,9 @@ int autolink_extract_main(ArrayRef Args, const char *Argv0, "-lswift_StringProcessing", "-lswiftRegexBuilder", "-lswift_RegexParser", - "-lswift_Backtracing", "-lswift_Builtin_float", "-lswift_math", + "-lswiftRuntime", "-lswiftSynchronization", "-lswiftGlibc", "-lswiftAndroid", diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index e53a1d5abc5..f4a904b54a9 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -948,11 +948,6 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args, Opts.DisableImplicitCxxModuleImport |= Args.hasArg(OPT_disable_implicit_cxx_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 efb0685d850..530bbc74f8b 100644 --- a/lib/Frontend/Frontend.cpp +++ b/lib/Frontend/Frontend.cpp @@ -1109,20 +1109,6 @@ 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; -} - bool CompilerInvocation::shouldImportCxx() const { // C++ Interop is disabled if (!getLangOptions().EnableCXXInterop) @@ -1209,21 +1195,6 @@ bool CompilerInstance::canImportSwiftStringProcessing() const { return getASTContext().testImportModule(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().testImportModule(modulePath); -} - bool CompilerInstance::canImportCxx() const { ImportPath::Module::Builder builder( getASTContext().getIdentifier(CXX_MODULE_NAME)); @@ -1321,19 +1292,6 @@ 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) { if (Invocation.shouldImportCxx() && canImportCxx()) pushImport(CXX_MODULE_NAME); diff --git a/stdlib/CMakeLists.txt b/stdlib/CMakeLists.txt index 1d8e818e793..9768a59a21f 100644 --- a/stdlib/CMakeLists.txt +++ b/stdlib/CMakeLists.txt @@ -228,12 +228,6 @@ swift_create_stdlib_targets("swift-test-stdlib" "" FALSE) swift_create_stdlib_targets("swift-libexec" "" TRUE) swift_create_stdlib_targets("swift-test-libexec" "" FALSE) -# Check whether the Swift compiler we're using supports -# -disable-implicit-backtracing-module-import -include(SwiftCompilerCapability) - -swift_supports_implicit_module("backtracing" SWIFT_COMPILER_SUPPORTS_BACKTRACING) - # FIXME: Include the toolchain directory before the public directory. Otherwise # the clang resource directory symlink stops installing correctly. add_subdirectory(toolchain) diff --git a/stdlib/cmake/modules/AddSwiftStdlib.cmake b/stdlib/cmake/modules/AddSwiftStdlib.cmake index 130a1470393..c76cf96aa91 100644 --- a/stdlib/cmake/modules/AddSwiftStdlib.cmake +++ b/stdlib/cmake/modules/AddSwiftStdlib.cmake @@ -2091,11 +2091,6 @@ function(add_swift_target_library name) # Turn off implicit import of _StringProcessing when building libraries list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-Xfrontend;-disable-implicit-string-processing-module-import") - # Turn off implicit import of _Backtracing when building libraries - if(SWIFT_COMPILER_SUPPORTS_BACKTRACING) - list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-Xfrontend;-disable-implicit-backtracing-module-import") - endif() - if(SWIFTLIB_IS_STDLIB AND SWIFT_STDLIB_ENABLE_PRESPECIALIZATION) list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-Xfrontend;-prespecialize-generic-metadata") endif() @@ -3126,10 +3121,6 @@ function(add_swift_target_executable name) "-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() - if(SWIFT_BUILD_STDLIB) # All Swift executables depend on the standard library. list(APPEND SWIFTEXE_TARGET_SWIFT_MODULE_DEPENDS Core) diff --git a/stdlib/public/Backtracing/ArrayImageSource.swift b/stdlib/public/Backtracing/ArrayImageSource.swift deleted file mode 100644 index e9266806cb2..00000000000 --- a/stdlib/public/Backtracing/ArrayImageSource.swift +++ /dev/null @@ -1,48 +0,0 @@ -//===--- ArrayImageSource.swift - An image source backed by an Array -------===// -// -// 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 ArrayImageSource, an image source that is backed by a Swift Array. -// -//===----------------------------------------------------------------------===// - -import Swift - -enum ArrayImageSourceError: Error { - case outOfBoundsRead(UInt64, UInt64) -} - -struct ArrayImageSource: ImageSource { - private var array: Array - - public init(array: Array) { - self.array = array - } - - public var isMappedImage: Bool { return false } - public var path: String? { return nil } - public var bounds: Bounds? { - return Bounds(base: 0, size: Size(array.count * MemoryLayout.stride)) - } - - public func fetch(from addr: Address, - into buffer: UnsafeMutableRawBufferPointer) throws { - try array.withUnsafeBytes{ - let size = Size($0.count) - let requested = Size(buffer.count) - if addr > size || requested > size - addr { - throw ArrayImageSourceError.outOfBoundsRead(addr, requested) - } - - buffer.copyBytes(from: $0[Int(addr).. String { - switch self { - case let .programCounter(addr): - return "\(hex(addr, width: width))" - case let .returnAddress(addr): - return "\(hex(addr, width: width)) [ra]" - case let .asyncResumePoint(addr): - return "\(hex(addr, width: width)) [async]" - case .omittedFrames(_), .truncated: - return "..." - } - } - - /// A textual description of this frame. - public var description: String { - return description(width: MemoryLayout
.size * 2) - } - } - - /// 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 func description(width: Int) -> String { - if let buildID = self.buildID { - return "\(hex(baseAddress, width: width))-\(hex(endOfText, width: width)) \(hex(buildID)) \(name) \(path)" - } else { - return "\(hex(baseAddress, width: width))-\(hex(endOfText, width: width)) \(name) \(path)" - } - } - - /// A textual description of an Image. - public var description: String { - return description(width: MemoryLayout
.size * 2) - } - } - - /// The architecture of the system that captured this backtrace. - public var architecture: String - - /// The width of an address in this backtrace, in bits. - public var addressWidth: Int - - /// 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? - - /// Format an address according to the addressWidth. - /// - /// @param address The address to format. - /// @param prefix Whether to include a "0x" prefix. - /// - /// @returns A String containing the formatted Address. - public func formatAddress(_ address: Address, - prefix: Bool = true) -> String { - return hex(address, prefix: prefix, width: (addressWidth + 3) / 4) - } - - /// 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) - @_semantics("use_frame_pointer") - public static func capture(algorithm: UnwindAlgorithm = .auto, - limit: Int? = 64, - offset: Int = 0, - top: Int = 16) throws -> Backtrace { - #if os(Linux) - let images = captureImages() - #else - let images: [Image]? = nil - #endif - - // 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(), - images: images, - algorithm: algorithm, - limit: limit, - offset: offset + 1, - top: top) - } - } - - @_spi(Internal) - public static func capture( - from context: Ctx, - using memoryReader: Rdr, - images: [Image]?, - algorithm: UnwindAlgorithm = .auto, - limit: Int? = 64, - offset: Int = 0, - top: Int = 16 - ) throws -> Backtrace { - let addressWidth = 8 * MemoryLayout.size - - 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, - images: images, - memoryReader: memoryReader) - .dropFirst(offset) - - if let limit = limit { - if limit <= 0 { - return Backtrace(architecture: context.architecture, - addressWidth: addressWidth, - 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(architecture: context.architecture, - addressWidth: addressWidth, - 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 captureImages(using: UnsafeLocalMemoryReader()) - #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 - - #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) - @_spi(Internal) - public static func captureImages(for process: Any) -> [Image] { - var images: [Image] = [] - 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(validatingCString: name!), name == "__TEXT" { - endOfText = address + size - } - } - - images.append(Image(name: name, - path: pathString, - buildID: theUUID, - baseAddress: Address(machHeaderAddress), - endOfText: Address(endOfText))) - } - } - } - - return images.sorted(by: { $0.baseAddress < $1.baseAddress }) - } - #else // !(os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) - private struct AddressRange { - var low: Address = 0 - var high: Address = 0 - } - - @_spi(Internal) - public static func captureImages(using reader: M, - forProcess pid: Int? = nil) -> [Image] { - var images: [Image] = [] - - #if os(Linux) - let path: String - if let pid = pid { - path = "/proc/\(pid)/maps" - } else { - path = "/proc/self/maps" - } - - guard let procMaps = readString(from: path) else { - return [] - } - - // Find all the mapped files and get high/low ranges - var mappedFiles: [Substring:AddressRange] = [:] - for match in ProcMapsScanner(procMaps) { - let path = stripWhitespace(match.pathname) - if match.inode == "0" || path == "" { - continue - } - guard let start = Address(match.start, radix: 16), - let end = Address(match.end, radix: 16) else { - continue - } - - if let range = mappedFiles[path] { - mappedFiles[path] = AddressRange(low: min(start, range.low), - high: max(end, range.high)) - } else { - mappedFiles[path] = AddressRange(low: start, - high: end) - } - } - - // Look for ELF headers in the process' memory - typealias Source = MemoryImageSource - let source = Source(with: reader) - for match in ProcMapsScanner(procMaps) { - let path = stripWhitespace(match.pathname) - if match.inode == "0" || path == "" { - continue - } - - guard let start = Address(match.start, radix: 16), - let end = Address(match.end, radix: 16), - let offset = Address(match.offset, radix: 16) else { - continue - } - - if offset != 0 || end - start < EI_NIDENT { - continue - } - - // Extract the filename from path - let name: Substring - if let slashIndex = path.lastIndex(of: "/") { - name = path.suffix(from: path.index(after: slashIndex)) - } else { - name = path - } - - // Inspect the image and extract the UUID and end of text - let range = mappedFiles[path]! - let subSource = SubImageSource(parent: source, - baseAddress: Source.Address(range.low), - length: Source.Size(range.high - - range.low)) - var theUUID: [UInt8]? = nil - var endOfText: Address = range.low - - if let image = try? Elf32Image(source: subSource) { - theUUID = image.uuid - - for hdr in image.programHeaders { - if hdr.p_type == .PT_LOAD && (hdr.p_flags & PF_X) != 0 { - endOfText = max(endOfText, range.low + Address(hdr.p_vaddr - + hdr.p_memsz)) - } - } - } else if let image = try? Elf64Image(source: subSource) { - theUUID = image.uuid - - for hdr in image.programHeaders { - if hdr.p_type == .PT_LOAD && (hdr.p_flags & PF_X) != 0 { - endOfText = max(endOfText, range.low + Address(hdr.p_vaddr - + hdr.p_memsz)) - } - } - } else { - // Not a valid ELF image - continue - } - - let image = Image(name: String(name), - path: String(path), - buildID: theUUID, - baseAddress: range.low, - endOfText: endOfText) - - images.append(image) - } - #endif - - return images.sorted(by: { $0.baseAddress < $1.baseAddress }) - } - #endif - - /// 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 showSourceLocation If `true`, look up the source location for - /// each address. - /// - /// @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, - showSourceLocations: Bool = true, - useSymbolCache: Bool = true) - -> SymbolicatedBacktrace? { - return SymbolicatedBacktrace.symbolicate( - backtrace: self, - images: images, - sharedCacheInfo: sharedCacheInfo, - showInlineFrames: showInlineFrames, - showSourceLocations: showSourceLocations, - useSymbolCache: useSymbolCache - ) - } - - /// Provide a textual version of the backtrace. - public var description: String { - var lines: [String] = [] - let addressChars = (addressWidth + 3) / 4 - - var n = 0 - for frame in frames { - lines.append("\(n)\t\(frame.description(width: addressChars))") - 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.description(width: addressChars))") - } - } - - #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, width: addressChars))") - lines.append(" Active: \(!sharedCacheInfo.noCache)") - } - #endif - - return lines.joined(separator: "\n") - } -} diff --git a/stdlib/public/Backtracing/FileImageSource.swift b/stdlib/public/Backtracing/FileImageSource.swift deleted file mode 100644 index 7a5e36709e2..00000000000 --- a/stdlib/public/Backtracing/FileImageSource.swift +++ /dev/null @@ -1,81 +0,0 @@ -//===--- FileImageSource.swift - An image source that reads from a file ---===// -// -// 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 FileImageSource, an image source that reads data from a file. -// -//===----------------------------------------------------------------------===// - -import Swift - -#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) -internal import Darwin -#elseif os(Windows) -internal import ucrt -#elseif canImport(Glibc) -internal import Glibc -#elseif canImport(Musl) -internal import Musl -#endif - -enum FileImageSourceError: Error { - case posixError(Int32) - case outOfRangeRead -} - -class FileImageSource: ImageSource { - private var _mapping: UnsafeRawBufferPointer - - public var isMappedImage: Bool { return false } - - private var _path: String - public var path: String? { return _path } - - public var bounds: Bounds? { - return Bounds(base: 0, size: Size(_mapping.count)) - } - - public init(path: String) throws { - _path = path - let fd = open(path, O_RDONLY, 0) - if fd < 0 { - throw FileImageSourceError.posixError(errno) - } - defer { close(fd) } - let size = lseek(fd, 0, SEEK_END) - if size < 0 { - throw FileImageSourceError.posixError(errno) - } - let base = mmap(nil, Int(size), PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0) - if base == nil || base! == UnsafeRawPointer(bitPattern: -1)! { - throw FileImageSourceError.posixError(errno) - } - _mapping = UnsafeRawBufferPointer(start: base, count: Int(size)) - } - - deinit { - munmap(UnsafeMutableRawPointer(mutating: _mapping.baseAddress), - _mapping.count) - } - - public func fetch(from addr: Address, - into buffer: UnsafeMutableRawBufferPointer) throws { - let start = Int(addr) - guard _mapping.indices.contains(start) else { - throw FileImageSourceError.outOfRangeRead - } - let slice = _mapping[start...] - guard slice.count >= buffer.count else { - throw FileImageSourceError.outOfRangeRead - } - buffer.copyBytes(from: slice[start.. { - var base: Address - var size: Size - var end: Address { - return base + Address(size) - } - - func adjusted(by offset: some FixedWidthInteger) -> Self { - return Self(base: base + Address(offset), size: size - Size(offset)) - } -} - -protocol ImageSource: MemoryReader { - typealias Bounds = ImageBounds - - /// Says whether we are looking at a loaded image in memory or not. - /// The layout in memory may differ from the on-disk layout; in particular, - /// some information may not be available when the image is mapped into - /// memory (an example is ELF section headers). - var isMappedImage: Bool { get } - - /// If this ImageSource knows its path, this will be non-nil. - var path: String? { get } - - /// If this ImageSource knows its bounds, this will be non-nil. - var bounds: Bounds? { get } -} - -struct ImageSourceCursor { - typealias Address = UInt64 - typealias Size = UInt64 - typealias Bounds = ImageBounds - - var source: any ImageSource - var pos: Address - - init(source: any ImageSource, offset: Address = 0) { - self.source = source - self.pos = offset - } - - public mutating func read(into buffer: UnsafeMutableRawBufferPointer) throws { - try source.fetch(from: pos, into: buffer) - pos += UInt64(buffer.count) - } - - public mutating func read(into buffer: UnsafeMutableBufferPointer) throws { - try source.fetch(from: pos, into: buffer) - pos += UInt64(MemoryLayout.stride * buffer.count) - } - - public mutating func read(into pointer: UnsafeMutablePointer) throws { - try source.fetch(from: pos, into: pointer) - pos += UInt64(MemoryLayout.stride) - } - - public mutating func read(as type: T.Type) throws -> T { - let stride = MemoryLayout.stride - let result = try source.fetch(from: pos, as: type) - pos += UInt64(stride) - return result - } - - public mutating func read(count: Int, as type: T.Type) throws -> [T] { - let stride = MemoryLayout.stride - let result = try source.fetch(from: pos, count: count, as: type) - pos += UInt64(stride * count) - return result - } - - public mutating func readString() throws -> String? { - var bytes: [UInt8] = [] - while true { - let ch = try read(as: UInt8.self) - if ch == 0 { - break - } - bytes.append(ch) - } - - return String(decoding: bytes, as: UTF8.self) - } - -} - -extension ImageSource { - /// Fetch all the data from this image source (which must be bounded) - func fetchAllBytes() -> [UInt8]? { - guard let bounds = self.bounds else { - return nil - } - if let data = try? fetch(from: bounds.base, - count: Int(bounds.size), - as: UInt8.self) { - return data - } - return nil - } -} - -enum SubImageSourceError: Error { - case outOfRangeFetch(UInt64, Int) -} - -struct SubImageSource: ImageSource { - var parent: S - var baseAddress: Address - var length: Size - var path: String? { return parent.path } - - var bounds: Bounds? { - return Bounds(base: 0, size: length) - } - - public init(parent: S, baseAddress: Address, length: Size) { - self.parent = parent - self.baseAddress = baseAddress - self.length = length - } - - public var isMappedImage: Bool { - return parent.isMappedImage - } - - public func fetch(from addr: Address, - into buffer: UnsafeMutableRawBufferPointer) throws { - let toFetch = buffer.count - if addr < 0 || addr > length { - throw SubImageSourceError.outOfRangeFetch(UInt64(addr), toFetch) - } - if Address(length) - addr < toFetch { - throw SubImageSourceError.outOfRangeFetch(UInt64(addr), toFetch) - } - - return try parent.fetch(from: baseAddress + addr, into: buffer) - } -} diff --git a/stdlib/public/Backtracing/MemoryImageSource.swift b/stdlib/public/Backtracing/MemoryImageSource.swift deleted file mode 100644 index c22bb27307e..00000000000 --- a/stdlib/public/Backtracing/MemoryImageSource.swift +++ /dev/null @@ -1,35 +0,0 @@ -//===--- MemoryImageSource.swift - An image source that reads from a file ---===// -// -// 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 MemoryImageSource, an image source that reads data using a -// MemoryReader. -// -//===----------------------------------------------------------------------===// - -import Swift - -class MemoryImageSource: ImageSource { - private var reader: M - - public var isMappedImage: Bool { return true } - public var path: String? { return nil } - public var bounds: Bounds? { return nil } - - public init(with reader: M) { - self.reader = reader - } - - public func fetch(from addr: Address, - into buffer: UnsafeMutableRawBufferPointer) throws { - try reader.fetch(from: addr, into: buffer) - } -} diff --git a/stdlib/public/CMakeLists.txt b/stdlib/public/CMakeLists.txt index a33c675ae22..618de91625f 100644 --- a/stdlib/public/CMakeLists.txt +++ b/stdlib/public/CMakeLists.txt @@ -301,8 +301,8 @@ if(SWIFT_BUILD_STDLIB AND NOT SWIFT_STDLIB_BUILD_ONLY_CORE_MODULES) add_subdirectory(Observation) endif() - if(SWIFT_ENABLE_BACKTRACING) - add_subdirectory(Backtracing) + if(SWIFT_ENABLE_RUNTIME_MODULE) + add_subdirectory(RuntimeModule) endif() if(SWIFT_ENABLE_SYNCHRONIZATION) diff --git a/stdlib/public/CompatibilityOverride/CompatibilityOverride.h b/stdlib/public/CompatibilityOverride/CompatibilityOverride.h index 3c2e7517c5e..0e4517fdea3 100644 --- a/stdlib/public/CompatibilityOverride/CompatibilityOverride.h +++ b/stdlib/public/CompatibilityOverride/CompatibilityOverride.h @@ -123,7 +123,7 @@ namespace swift { // Include path computation. Code that includes this file can write `#include // "..CompatibilityOverride/CompatibilityOverrideIncludePath.h"` to include the // appropriate .def file for the current library. -#define COMPATIBILITY_OVERRIDE_INCLUDE_PATH_swiftRuntime \ +#define COMPATIBILITY_OVERRIDE_INCLUDE_PATH_swiftRuntimeCore \ "../CompatibilityOverride/CompatibilityOverrideRuntime.def" #define COMPATIBILITY_OVERRIDE_INCLUDE_PATH_swift_Concurrency \ "../CompatibilityOverride/CompatibilityOverrideConcurrency.def" @@ -155,7 +155,7 @@ namespace swift { // resolve to string literal containing the appropriate section name for the // current library. // Turns into '__swift_hooks' -#define COMPATIBILITY_OVERRIDE_SECTION_NAME_swiftRuntime "__swift" \ +#define COMPATIBILITY_OVERRIDE_SECTION_NAME_swiftRuntimeCore "__swift" \ SWIFT_VERSION_MAJOR \ SWIFT_VERSION_MINOR \ "_hooks" diff --git a/stdlib/public/RuntimeModule/Address.swift b/stdlib/public/RuntimeModule/Address.swift new file mode 100644 index 00000000000..24a8a0a7557 --- /dev/null +++ b/stdlib/public/RuntimeModule/Address.swift @@ -0,0 +1,258 @@ +//===--- Address.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.Address` struct that represents addresses in a +// captured backtrace. This type is *not* used for storage; rather, it's +// used as an interface type. +// +//===----------------------------------------------------------------------===// + +import Swift + +// .. Comparable ............................................................. + +extension Backtrace.Address { + fileprivate var widestRepresentation: UInt64 { + switch representation { + case .null: + return 0 + case let .sixteenBit(addr): + return UInt64(addr) + case let .thirtyTwoBit(addr): + return UInt64(addr) + case let .sixtyFourBit(addr): + return addr + } + } +} + +extension Backtrace.Address: Comparable { + /// Return true if `lhs` is lower than `rhs` + public static func < (lhs: Backtrace.Address, rhs: Backtrace.Address) -> Bool { + return lhs.widestRepresentation < rhs.widestRepresentation + } + /// Return true if `lhs` is equal to `rhs` + public static func == (lhs: Backtrace.Address, rhs: Backtrace.Address) -> Bool { + return lhs.widestRepresentation == rhs.widestRepresentation + } +} + +// .. LosslessStringConvertible .............................................. + +extension Backtrace.Address: LosslessStringConvertible { + /// Create an Backtrace.Address from its string representation + public init?(_ s: String) { + self.init(s[...]) + } + + public init?(_ s: Substring) { + let unprefixed: Substring + + // Explicit support for null + if s == "null" { + self.representation = .null + return + } + + // Drop the prefix, if any + if s.hasPrefix("0x") { + unprefixed = s[s.index(s.startIndex, offsetBy: 2)...] + } else { + unprefixed = Substring(s) + } + + // Work out whether it's 64-bit or 32-bit and parse it + if unprefixed.count > 8 && unprefixed.count <= 16 { + guard let addr = UInt64(unprefixed, radix: 16) else { + return nil + } + if addr == 0 { + self.representation = .null + } else { + self.representation = .sixtyFourBit(addr) + } + } else if unprefixed.count <= 8 { + guard let addr = UInt32(unprefixed, radix: 16) else { + return nil + } + if addr == 0 { + self.representation = .null + } else { + self.representation = .thirtyTwoBit(addr) + } + } else { + return nil + } + } + + /// A textual representation of this address + public var description: String { + switch representation { + case .null: + return "null" + case let .sixteenBit(addr): + if addr == 0 { + return "null" + } + return hex(addr) + case let .thirtyTwoBit(addr): + if addr == 0 { + return "null" + } + return hex(addr) + case let .sixtyFourBit(addr): + if addr == 0 { + return "null" + } + return hex(addr) + } + } +} + +// .. ExpressibleByIntegerLiteral ............................................ + +extension Backtrace.Address: ExpressibleByIntegerLiteral { + public typealias IntegerLiteralType = UInt64 + + /// Convert from an integer literal. + public init(integerLiteral: Self.IntegerLiteralType) { + if integerLiteral == 0 { + self.representation = .null + } else if integerLiteral < 0x10000 { + self.representation = .sixteenBit(UInt16(truncatingIfNeeded: integerLiteral)) + } else if integerLiteral < 0x100000000 { + self.representation = .thirtyTwoBit(UInt32(truncatingIfNeeded: integerLiteral)) + } else { + self.representation = .sixtyFourBit(integerLiteral) + } + } +} + +// .. FixedWidthInteger conversions .......................................... + +extension Backtrace.Address { + fileprivate func toFixedWidth( + type: T.Type = T.self + ) -> T? { + switch representation { + case .null: + return T(0) + case let .sixteenBit(addr): + guard T.bitWidth >= 16 else { return nil } + return T(truncatingIfNeeded: addr) + case let .thirtyTwoBit(addr): + guard T.bitWidth >= 32 else { return nil } + return T(truncatingIfNeeded: addr) + case let .sixtyFourBit(addr): + guard T.bitWidth >= 64 else { return nil } + return T(truncatingIfNeeded: addr) + } + } +} + +extension FixedWidthInteger { + /// Convert from an Backtrace.Address. + /// + /// This initializer will return nil if the address width is larger than the + /// type you are attempting to convert into. + public init?(_ address: Backtrace.Address) { + guard let result = address.toFixedWidth(type: Self.self) else { + return nil + } + self = result + } +} + +extension Backtrace.Address { + /// Convert from a UInt16. + public init(_ value: UInt16) { + if value == 0 { + self.representation = .null + return + } + self.representation = .sixteenBit(value) + } + + /// Convert from a UInt32. + public init(_ value: UInt32) { + if value == 0 { + self.representation = .null + return + } + self.representation = .thirtyTwoBit(value) + } + + /// Convert from a UInt64. + public init(_ value: UInt64) { + if value == 0 { + self.representation = .null + return + } + self.representation = .sixtyFourBit(value) + } + + /// Convert from a FixedWidthInteger + public init?(_ value: T) { + switch T.bitWidth { + case 16: + self.init(UInt16(truncatingIfNeeded: value)) + case 32: + self.init(UInt32(truncatingIfNeeded: value)) + case 64: + self.init(UInt64(truncatingIfNeeded: value)) + default: + return nil + } + } +} + +// -- Arithmetic ------------------------------------------------------------- + +extension Backtrace.Address { + static func - (lhs: Backtrace.Address, rhs: Backtrace.Address) -> Int64 { + let ulhs = UInt64(lhs)! + let urhs = UInt64(rhs)! + return Int64(bitPattern: ulhs - urhs) + } + + static func - (lhs: Backtrace.Address, rhs: Int64) -> Backtrace.Address { + switch lhs.representation { + case .null: + return Backtrace.Address(0) + case let .sixteenBit(addr): + let newAddr = addr &- UInt16(bitPattern: Int16(truncatingIfNeeded: rhs)) + return Backtrace.Address(newAddr) + case let .thirtyTwoBit(addr): + let newAddr = addr &- UInt32(bitPattern: Int32(truncatingIfNeeded: rhs)) + return Backtrace.Address(newAddr) + case let .sixtyFourBit(addr): + let newAddr = addr &- UInt64(bitPattern: rhs) + return Backtrace.Address(newAddr) + } + } + + static func + (lhs: Backtrace.Address, rhs: Int64) -> Backtrace.Address { + switch lhs.representation { + case .null: + return Backtrace.Address(0) + case let .sixteenBit(addr): + let newAddr = addr &+ UInt16(bitPattern: Int16(truncatingIfNeeded: rhs)) + return Backtrace.Address(newAddr) + case let .thirtyTwoBit(addr): + let newAddr = addr &+ UInt32(bitPattern: Int32(truncatingIfNeeded: rhs)) + return Backtrace.Address(newAddr) + case let .sixtyFourBit(addr): + let newAddr = addr &+ UInt64(bitPattern: rhs) + return Backtrace.Address(newAddr) + } + } +} diff --git a/stdlib/public/RuntimeModule/Backtrace+Codable.swift b/stdlib/public/RuntimeModule/Backtrace+Codable.swift new file mode 100644 index 00000000000..6a7df75e710 --- /dev/null +++ b/stdlib/public/RuntimeModule/Backtrace+Codable.swift @@ -0,0 +1,69 @@ +//===--- Backtrace+Codable.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 Codable conformance for Backtrace. +// +//===----------------------------------------------------------------------===// + +import Swift + +func stringFrom(sequence: some Sequence) -> String? { + if #available(macOS 15.0, *) { + return String(validating: sequence, as: UTF8.self) + } else { + let bytes = Array(sequence) + return String(decoding: bytes, as: UTF8.self) + } +} + +@available(macOS 15.0, *) +extension Backtrace: Codable { + + enum CodingKeys: CodingKey { + case architecture + case backtrace + case imageMap + } + + public init(from decoder: any Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + self.architecture = try values.decode(String.self, forKey: .architecture) + + let backtraceB64 = try values.decode(String.self, forKey: .backtrace) + self.representation = Array(Base64Decoder(source: backtraceB64.utf8)) + + if let imageMapB64 = try values.decodeIfPresent(String.self, + forKey: .imageMap) { + self.images = ImageMap(compactImageMapData: + Base64Decoder(source: imageMapB64.utf8)) + } else { + self.images = nil + } + } + + public func encode(to encoder: any Encoder) throws { + var values = encoder.container(keyedBy: CodingKeys.self) + try values.encode(architecture, forKey: .architecture) + + let backtraceB64 = stringFrom(sequence: + Base64Encoder(source: self.representation)) + try values.encode(backtraceB64, forKey: .backtrace) + + if let imageMap = self.images { + let encoder = CompactImageMapFormat.Encoder(imageMap) + let imageMapB64 = stringFrom(sequence: + Base64Encoder(source: encoder)) + try values.encode(imageMapB64, forKey: .imageMap) + } + } + +} diff --git a/stdlib/public/RuntimeModule/Backtrace.swift b/stdlib/public/RuntimeModule/Backtrace.swift new file mode 100644 index 00000000000..a6e682e576d --- /dev/null +++ b/stdlib/public/RuntimeModule/Backtrace.swift @@ -0,0 +1,433 @@ +//===--- 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(tvOS) || os(watchOS) +// internal import Darwin +// #elseif os(Windows) +// internal import ucrt +// #elseif canImport(Glibc) +// internal import Glibc +// #elseif canImport(Musl) +// internal import Musl +// #endif + +/// Holds a backtrace. +public struct Backtrace: CustomStringConvertible, Sendable { + /// The type of an address. + /// + /// This is used as an opaque type; if you have some Address, you + /// can ask if it's NULL, and you can attempt to convert it to a + /// FixedWidthInteger. + /// + /// This is intentionally _not_ a pointer, because you shouldn't be + /// dereferencing them; they may refer to some other process, for + /// example. + public struct Address: Hashable, Sendable { + enum Representation: Hashable, Sendable { + case null + case sixteenBit(UInt16) + case thirtyTwoBit(UInt32) + case sixtyFourBit(UInt64) + } + + var representation: Representation + + /// The width of this address, in bits + public var bitWidth: Int { + switch representation { + case .null: + return 0 + case .sixteenBit(_): + return 16 + case .thirtyTwoBit(_): + return 32 + case .sixtyFourBit(_): + return 64 + } + } + + /// True if this address is a NULL pointer + public var isNull: Bool { + switch representation { + case .null: + return true + case let .sixteenBit(addr): + return addr == 0 + case let .thirtyTwoBit(addr): + return addr == 0 + case let .sixtyFourBit(addr): + return addr == 0 + } + } + } + + /// 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. + /// + /// This can only be present at the end of a backtrace; in other cases + /// we will know how many frames we have omitted. For instance, + /// + /// 0: frame 100 <----- bottom of call stack + /// 1: frame 99 + /// 2: frame 98 + /// 3: frame 97 + /// 4: frame 96 + /// 5: ... <----- truncated + 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 "\(addr)" + case let .returnAddress(addr): + return "\(addr) [ra]" + case let .asyncResumePoint(addr): + return "\(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). + private(set) public var name: String? + + /// The full path to the image (e.g. /usr/lib/swift/libswiftCore.dylib). + private(set) public var path: String? + + /// The unique 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 unique ID). + /// + /// On Darwin systems, this is the LC_UUID value; on Linux this is the + /// build ID, which may take one of a number of forms or may not even + /// be present. + private(set) public var uniqueID: [UInt8]? + + /// The base address of the image. + private(set) public var baseAddress: Backtrace.Address + + /// The end of the text segment in this image. + private(set) public var endOfText: Backtrace.Address + + /// Provide a textual description of an Image. + public var description: String { + if let uniqueID = self.uniqueID { + return "\(baseAddress)-\(endOfText) \(hex(uniqueID)) \(name ?? "") \(path ?? "")" + } else { + return "\(baseAddress)-\(endOfText) \(name ?? "") \(path ?? "")" + } + } + } + + /// The architecture of the system that captured this backtrace. + public internal(set) var architecture: String + + /// The actual backtrace data, stored in Compact Backtrace Format. + var representation: [UInt8] + + /// A list of captured frame information. + @available(macOS 10.15, *) + public var frames: some Sequence { + return CompactBacktraceFormat.Decoder(representation) + } + + /// 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 `ImageMap.capture()`. + public var images: ImageMap? + + /// 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. + /// + /// Parameters: + /// + /// - algorithm: Specifies which unwind mechanism to use. If this + /// is set to `.auto`, we will use the platform default. + /// - limit: The backtrace will include at most this number of + /// frames; you can set this to `nil` to remove the + /// limit completely if required. + /// - 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. + /// - top: Sets the minimum number of frames to capture at the + /// top of the stack. + /// - images: (Optional) A list of captured images. This allows you + /// to capture images once, and then generate multiple + /// backtraces using a single set of captured images. + @inline(never) + @_semantics("use_frame_pointer") + public static func capture(algorithm: UnwindAlgorithm = .auto, + limit: Int? = 64, + offset: Int = 0, + top: Int = 16, + images: ImageMap? = nil) throws -> Backtrace { + #if os(Linux) + // On Linux, we need the captured images to resolve async functions + let theImages = images ?? ImageMap.capture() + #else + let theImages = images + #endif + + // 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(), + images: theImages, + algorithm: algorithm, + limit: limit, + offset: offset + 1, + top: top) + } + } + + /// Specifies options for the `symbolicated` method. + public struct SymbolicationOptions: OptionSet { + public let rawValue: Int + + /// Add virtual frames to show inline function calls. + public static let showInlineFrames: SymbolicationOptions = + SymbolicationOptions(rawValue: 1 << 0) + + /// Look up source locations. + /// + /// This may be expensive in some cases; it may be desirable to turn + /// this off e.g. in Kubernetes so that pods restart promptly on crash. + public static let showSourceLocations: SymbolicationOptions = + SymbolicationOptions(rawValue: 1 << 1) + + /// Use a symbol cache, if one is available. + public static let useSymbolCache: SymbolicationOptions = + SymbolicationOptions(rawValue: 1 << 2) + + public static let `default`: SymbolicationOptions = [.showInlineFrames, + .showSourceLocations, + .useSymbolCache] + + public init(rawValue: Int) { + self.rawValue = rawValue + } + } + + /// Return a symbolicated version of the backtrace. + /// + /// - 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. + /// + /// - options: Symbolication options; see `SymbolicationOptions`. + public func symbolicated(with images: ImageMap? = nil, + options: SymbolicationOptions = .default) + -> SymbolicatedBacktrace? { + return SymbolicatedBacktrace.symbolicate( + backtrace: self, + images: images, + options: options + ) + } + + /// Provide a textual version of the backtrace. + public var description: String { + var lines: [String] = ["Architecture: \(architecture)", ""] + + var n = 0 + for frame in frames { + lines.append("\(n)\t\(frame.description)") + 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.description)") + } + } + + return lines.joined(separator: "\n") + } + + /// Initialise a Backtrace from a sequence of `RichFrame`s + @_spi(Internal) + public init(architecture: String, + frames: some Sequence>, + images: ImageMap?) { + self.architecture = architecture + self.representation = Array(CompactBacktraceFormat.Encoder(frames)) + self.images = images + } +} + +// -- Capture Implementation ------------------------------------------------- + +extension Backtrace { + + // ###FIXME: There is a problem with @_specialize here that results in the + // arguments not lining up properly when this gets used from + // swift-backtrace. + + @_spi(Internal) + //@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == UnsafeLocalMemoryReader) + //@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == RemoteMemoryReader) + //#if os(Linux) + //@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == MemserverMemoryReader) + //#endif + @inlinable + public static func capture( + from context: Ctx, + using memoryReader: Rdr, + images: ImageMap?, + algorithm: UnwindAlgorithm, + 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, + images: images, + memoryReader: memoryReader) + + if let limit = limit { + let limited = LimitSequence(unwinder, + limit: limit, + offset: offset, + top: top) + + return Backtrace(architecture: context.architecture, + frames: limited, + images: images) + } + + return Backtrace(architecture: context.architecture, + frames: unwinder.dropFirst(offset), + images: images) + } + } +} diff --git a/stdlib/public/Backtracing/BacktraceFormatter.swift b/stdlib/public/RuntimeModule/BacktraceFormatter.swift similarity index 91% rename from stdlib/public/Backtracing/BacktraceFormatter.swift rename to stdlib/public/RuntimeModule/BacktraceFormatter.swift index cfa6cd886fb..71a6fa87195 100644 --- a/stdlib/public/Backtracing/BacktraceFormatter.swift +++ b/stdlib/public/RuntimeModule/BacktraceFormatter.swift @@ -527,24 +527,22 @@ public struct BacktraceFormatter { /// Format an individual frame into a list of columns. /// /// @param frame The frame to format. - /// @param addressWidth The width, in characters, of an address. /// @param index The frame index, if required. /// /// @result An array of strings, one per column. public func formatColumns(frame: Backtrace.Frame, - addressWidth: Int, index: Int? = nil) -> [String] { let pc: String var attrs: [String] = [] switch frame { case let .programCounter(address): - pc = "\(hex(address, width: addressWidth))" + pc = "\(address)" case let .returnAddress(address): - pc = "\(hex(address, width: addressWidth))" + pc = "\(address)" attrs.append("ra") case let .asyncResumePoint(address): - pc = "\(hex(address, width: addressWidth))" + pc = "\(address)" attrs.append("async") case .omittedFrames(_), .truncated: pc = "..." @@ -567,30 +565,24 @@ public struct BacktraceFormatter { /// Format a frame into a list of rows. /// /// @param frame The frame to format. - /// @param addressWidth The width, in characters, of an address. /// @param index The frame index, if required. /// /// @result An array of table rows. public func formatRows(frame: Backtrace.Frame, - addressWidth: Int, index: Int? = nil) -> [TableRow] { return [.columns(formatColumns(frame: frame, - addressWidth: addressWidth, index: index))] } /// Format just one frame. /// /// @param frame The frame to format. - /// @param addressWidth The width, in characters, of an address. /// @param index The frame index, if required. /// /// @result A `String` containing the formatted data. public func format(frame: Backtrace.Frame, - addressWidth: Int, index: Int? = nil) -> String { let rows = formatRows(frame: frame, - addressWidth: addressWidth, index: index) return BacktraceFormatter.formatTable(rows, alignments: [.right]) } @@ -598,16 +590,14 @@ public struct BacktraceFormatter { /// Format the frame list from a backtrace. /// /// @param frames The frames to format. - /// @param addressWidth The width, in characters, of an address. /// /// @result A `String` containing the formatted data. - public func format(frames: some Sequence, - addressWidth: Int) -> String { + public func format(frames: some Sequence) -> String { var rows: [TableRow] = [] var n = 0 for frame in frames { - rows += formatRows(frame: frame, addressWidth: addressWidth, index: n) + rows += formatRows(frame: frame, index: n) if case let .omittedFrames(count) = frame { n += count @@ -625,8 +615,7 @@ public struct BacktraceFormatter { /// /// @result A `String` containing the formatted data. public func format(backtrace: Backtrace) -> String { - return format(frames: backtrace.frames, - addressWidth: (backtrace.addressWidth + 3) / 4) + return format(frames: backtrace.frames) } /// Grab source lines for a symbolicated backtrace. @@ -743,19 +732,18 @@ public struct BacktraceFormatter { /// /// @result An array of strings, one per column. public func formatColumns(frame: SymbolicatedBacktrace.Frame, - addressWidth: Int, index: Int? = nil) -> [String] { let pc: String var attrs: [String] = [] switch frame.captured { case let .programCounter(address): - pc = "\(hex(address, width: addressWidth))" + pc = "\(address)" case let .returnAddress(address): - pc = "\(hex(address, width: addressWidth))" + pc = "\(address)" attrs.append("ra") case let .asyncResumePoint(address): - pc = "\(hex(address, width: addressWidth))" + pc = "\(address)" attrs.append("async") case .omittedFrames(_), .truncated: pc = "" @@ -855,16 +843,13 @@ public struct BacktraceFormatter { /// Format a frame into a list of rows. /// /// @param frame The frame to format. - /// @param addressWidth The width, in characters, of an address. /// @param index The frame index, if required. /// /// @result An array of table rows. public func formatRows(frame: SymbolicatedBacktrace.Frame, - addressWidth: Int, index: Int? = nil, showSource: Bool = true) -> [TableRow] { let columns = formatColumns(frame: frame, - addressWidth: addressWidth, index: index) var rows: [TableRow] = [.columns(columns)] @@ -884,16 +869,13 @@ public struct BacktraceFormatter { /// Format just one frame. /// /// @param frame The frame to format. - /// @param addressWidth The width, in characters, of an address. /// @param index The frame index, if required. /// /// @result A `String` containing the formatted data. public func format(frame: SymbolicatedBacktrace.Frame, - addressWidth: Int, index: Int? = nil, showSource: Bool = true) -> String { - let rows = formatRows(frame: frame, addressWidth: addressWidth, - index: index, showSource: showSource) + let rows = formatRows(frame: frame, index: index, showSource: showSource) return BacktraceFormatter.formatTable(rows, alignments: [.right]) } @@ -907,11 +889,11 @@ public struct BacktraceFormatter { /// Format the frame list from a symbolicated backtrace. /// /// @param frames The frames to format. - /// @param addressWidth The width, in characters, of an address. /// /// @result A `String` containing the formatted data. - public func format(frames: some Sequence, - addressWidth: Int) -> String { + public func format( + frames: some Sequence + ) -> String { var rows: [TableRow] = [] var sourceLocationsShown = Set() @@ -931,8 +913,7 @@ public struct BacktraceFormatter { } } - rows += formatRows(frame: frame, addressWidth: addressWidth, - index: n, showSource: showSource) + rows += formatRows(frame: frame, index: n, showSource: showSource) if case let .omittedFrames(count) = frame.captured { n += count @@ -950,16 +931,14 @@ public struct BacktraceFormatter { /// /// @result A `String` containing the formatted data. public func format(backtrace: SymbolicatedBacktrace) -> String { - let addressChars = (backtrace.addressWidth + 3) / 4 - var result = format(frames: backtrace.frames, addressWidth: addressChars) + var result = format(frames: backtrace.frames) switch options._showImages { case .none: break case .all: result += "\n\nImages:\n" - result += format(images: backtrace.images, - addressWidth: addressChars) + result += format(images: backtrace.images) case .mentioned: var mentionedImages = Set() for frame in backtrace.frames { @@ -978,7 +957,7 @@ public struct BacktraceFormatter { } else { result += "\n\nImages (only mentioned):\n" } - result += format(images: images, addressWidth: addressChars) + result += format(images: images) } return result @@ -987,28 +966,31 @@ public struct BacktraceFormatter { /// Format a `Backtrace.Image` into a list of columns. /// /// @param image The `Image` object to format. - /// @param addressWidth The width of an address, in characters. /// /// @result An array of strings, one per column. - public func formatColumns(image: Backtrace.Image, - addressWidth: Int) -> [String] { - let addressRange = "\(hex(image.baseAddress, width: addressWidth))–\(hex(image.endOfText, width: addressWidth))" + public func formatColumns(image: Backtrace.Image) -> [String] { + let addressRange = "\(image.baseAddress)–\(image.endOfText)" let buildID: String - if let bytes = image.buildID { + if let bytes = image.uniqueID { buildID = hex(bytes) } else { buildID = "" } let imagePath: String - if options._sanitizePaths { - imagePath = sanitizePath(image.path) + if let path = image.path { + if options._sanitizePaths { + imagePath = sanitizePath(path) + } else { + imagePath = path + } } else { - imagePath = image.path + imagePath = "" } + let imageName = image.name ?? "" return [ options._theme.imageAddressRange(addressRange), options._theme.imageBuildID(buildID), - options._theme.imageName(image.name), + options._theme.imageName(imageName), options._theme.imagePath(imagePath) ] } @@ -1016,15 +998,12 @@ public struct BacktraceFormatter { /// Format an array of `Backtrace.Image`s. /// /// @param images The array of `Image` objects to format. - /// @param addressWidth The width of an address, in characters. /// /// @result A string containing the formatted data. - public func format(images: some Sequence, - addressWidth: Int) -> String { + public func format(images: some Sequence) -> String { let rows = images.map{ TableRow.columns( - formatColumns(image: $0, - addressWidth: addressWidth) + formatColumns(image: $0) ) } diff --git a/stdlib/public/RuntimeModule/Base64.swift b/stdlib/public/RuntimeModule/Base64.swift new file mode 100644 index 00000000000..608ce60eb99 --- /dev/null +++ b/stdlib/public/RuntimeModule/Base64.swift @@ -0,0 +1,333 @@ +//===--- Base64.swift -----------------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// +// +// Encode and decode sequences of bytes as base64. +// +//===----------------------------------------------------------------------===// + +import Swift + +// Forward mapping (we encode normal base64) +fileprivate let forwardMapping: ( + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, + UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit +) = ( + // A B C D E F G H + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + // I J K L M N O P + 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + // Q R S T U V W X + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + // Y Z a b c d e f + 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + // g h i j k l m n + 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, + // o p q r s t u v + 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, + // w x y z 0 1 2 3 + 0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, + // 4 5 6 7 8 9 + / + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f +) + +fileprivate func forward(at ndx: Int) -> UTF8.CodeUnit { + precondition(ndx >= 0 && ndx < 64) + return withUnsafePointer(to: forwardMapping) { + $0.withMemoryRebound(to: UTF8.CodeUnit.self, + capacity: 64) { table in + return table[ndx] + } + } +} + +// Reverse (we support URL-safe and normal base64) +fileprivate let reverseMapping: ( + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, + Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8 +) = ( +// + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +// + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +// + - / + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, +// 0 1 2 3 4 5 6 7 8 9 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, +// A B C D E F G H I J K L M N O + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, +// P Q R S T U V W X Y Z _ + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, +// a b c d e f g h i j k l m n o + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, +// p q r s t u v w x y z + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 +) + +fileprivate func reverse(at char: UTF8.CodeUnit) -> UInt8? { + if char >= 128 { + return nil + } + return withUnsafePointer(to: reverseMapping) { + $0.withMemoryRebound(to: Int8.self, + capacity: 128) { + (table: UnsafePointer) -> UInt8? in + + let value = table[Int(char)] + guard value >= 0 else { + return nil + } + return UInt8(truncatingIfNeeded: value) + } + } +} + +@_spi(Base64) +public struct Base64Encoder: Sequence + where S.Element == UInt8 +{ + public typealias Element = UTF8.CodeUnit + + var source: S + + public init(source: S) { + self.source = source + } + + public func makeIterator() -> Iterator { + return Iterator(source: source) + } + + public struct Iterator: IteratorProtocol { + public typealias Element = UTF8.CodeUnit + + var sourceIterator: S.Iterator + var output: (UTF8.CodeUnit, UTF8.CodeUnit, UTF8.CodeUnit) + var ndx: Int + var buffer: UInt32 + var count: Int + + init(source: S) { + sourceIterator = source.makeIterator() + output = (0, 0, 0) + buffer = 0 + count = 0 + ndx = 3 + } + + public mutating func next() -> UTF8.CodeUnit? { + // If we have bytes, output those first + switch ndx { + case 0: + ndx += 1 + return output.0 + case 1: + ndx += 1 + return output.1 + case 2: + ndx += 1 + return output.2 + default: + break + } + + // Now try to refill the buffer with up to three bytes + buffer = 0 + count = 0 + while count < 3 { + if let byte = sourceIterator.next() { + buffer = (buffer << 8) | UInt32(truncatingIfNeeded: byte) + } else { + break + } + count += 1 + } + + switch count { + case 0: + return nil + case 1: + let first = Int(buffer >> 2) + let second = Int((buffer << 4) & 0x3f) + output.2 = forward(at: second) + ndx = 2 + return forward(at: first) + case 2: + let first = Int(buffer >> 10) + let second = Int((buffer >> 4) & 0x3f) + let third = Int((buffer << 2) & 0x3f) + output.1 = forward(at: second) + output.2 = forward(at: third) + ndx = 1 + return forward(at: first) + case 3: + let first = Int(buffer >> 18) + let second = Int((buffer >> 12) & 0x3f) + let third = Int((buffer >> 6) & 0x3f) + let fourth = Int(buffer & 0x3f) + output.0 = forward(at: second) + output.1 = forward(at: third) + output.2 = forward(at: fourth) + ndx = 0 + return forward(at: first) + default: + fatalError("count has an impossible value") + } + } + } +} + +@_spi(Base64) +public struct Base64Decoder: Sequence + where S.Element == UTF8.CodeUnit +{ + public typealias Element = UInt8 + + var source: S + + public init(source: S) { + self.source = source + } + + public func makeIterator() -> Iterator { + return Iterator(source: source) + } + + public struct Iterator: IteratorProtocol { + public typealias Element = UInt8 + + var sourceIterator: S.Iterator + var output: (UInt8, UInt8) + var ndx: Int + var buffer: UInt32 + var count: Int + var bad: Bool + var done: Bool + + init(source: S) { + sourceIterator = source.makeIterator() + output = (0, 0) + ndx = 2 + buffer = 0 + count = 0 + bad = false + done = false + } + + public mutating func next() -> UInt8? { + if bad { + return nil + } + + // If we have bytes, output those first + switch ndx { + case 0: + ndx += 1 + return output.0 + case 1: + ndx += 1 + return output.1 + default: + break + } + + // If we've finished, stop + if done { + return nil + } + + // Now try to refill the buffer + count = 0 + while count < 4 { + if let encoded = sourceIterator.next() { + if encoded >= 128 { + bad = true + return nil + } + + // '=' + if encoded == 0x3d { + break + } + + guard let value = reverse(at: encoded) else { + bad = true + return nil + } + + buffer = (buffer << 6) | UInt32(truncatingIfNeeded: value) + count += 1 + } else { + break + } + } + + switch count { + case 0: + return nil + case 1: + bad = true + return nil + case 2: + // 12 bits + done = true + return UInt8(truncatingIfNeeded: buffer >> 4) + case 3: + // 18 bits + done = true + let first = UInt8(truncatingIfNeeded: buffer >> 10) + let second = UInt8(truncatingIfNeeded: buffer >> 2) + output.1 = second + ndx = 1 + return first + case 4: + // 24 bits + let first = UInt8(truncatingIfNeeded: buffer >> 16) + let second = UInt8(truncatingIfNeeded: buffer >> 8) + let third = UInt8(truncatingIfNeeded: buffer) + output.0 = second + output.1 = third + ndx = 0 + return first + default: + fatalError("count has an impossible value") + } + } + } +} diff --git a/stdlib/public/Backtracing/ByteSwapping.swift b/stdlib/public/RuntimeModule/ByteSwapping.swift similarity index 100% rename from stdlib/public/Backtracing/ByteSwapping.swift rename to stdlib/public/RuntimeModule/ByteSwapping.swift diff --git a/stdlib/public/Backtracing/CMakeLists.txt b/stdlib/public/RuntimeModule/CMakeLists.txt similarity index 69% rename from stdlib/public/Backtracing/CMakeLists.txt rename to stdlib/public/RuntimeModule/CMakeLists.txt index 2a61525fb01..dc72ae13c8e 100644 --- a/stdlib/public/Backtracing/CMakeLists.txt +++ b/stdlib/public/RuntimeModule/CMakeLists.txt @@ -1,4 +1,4 @@ -#===--- CMakeLists.txt - Backtracing support library -----------------------===# +#===--- CMakeLists.txt - Runtime module ------------------------------------===# # # This source file is part of the Swift.org open source project # @@ -9,8 +9,10 @@ # See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors # #===------------------------------------------------------------------------===# - -set(swift_backtracing_link_libraries +# +# The Runtime module isn't the runtime itself; that lives in libswiftCore; +# rather, it's a high level Swift interface to things +set(swift_runtime_link_libraries swiftCore swift_Concurrency ) @@ -20,27 +22,37 @@ if(SWIFT_BUILD_STDLIB AND SWIFT_ENABLE_EXPERIMENTAL_CONCURRENCY) set(concurrency _Concurrency) endif() -set(BACKTRACING_SOURCES - ArrayImageSource.swift +set(RUNTIME_SOURCES + Address.swift Backtrace.swift + Backtrace+Codable.swift BacktraceFormatter.swift + Base64.swift ByteSwapping.swift CachingMemoryReader.swift - Context.swift + CompactBacktrace.swift + CompactImageMap.swift Compression.swift + Context.swift CoreSymbolication.swift Dwarf.swift + EightByteBuffer.swift Elf.swift - FileImageSource.swift + ElfImageCache.swift FramePointerUnwinder.swift Image.swift + ImageMap.swift + ImageMap+Darwin.swift + ImageMap+Linux.swift ImageSource.swift - MemoryImageSource.swift Libc.swift + LimitSequence.swift MemoryReader.swift + OSReleaseScanner.swift ProcMapsScanner.swift Registers.swift Runtime.swift + RichFrame.swift SymbolicatedBacktrace.swift Utils.swift Win32Extras.cpp @@ -48,24 +60,24 @@ set(BACKTRACING_SOURCES get-cpu-context.${SWIFT_ASM_EXT} ) -set(BACKTRACING_COMPILE_FLAGS +set(RUNTIME_COMPILE_FLAGS "-cxx-interoperability-mode=default" "-Xfrontend;-experimental-spi-only-imports" "-Xcc;-I${SWIFT_SOURCE_DIR}/include" "-Xcc;-I${CMAKE_BINARY_DIR}/include" - "-Xcc;-I${SWIFT_STDLIB_SOURCE_DIR}/public/Backtracing/modules" + "-Xcc;-I${SWIFT_STDLIB_SOURCE_DIR}/public/RuntimeModule/modules" "-disable-upcoming-feature;MemberImportVisibility") ###TODO: Add these when we add static linking support # -#list(APPEND BACKTRACING_COMPILE_FLAGS +#list(APPEND RUNTIME_COMPILE_FLAGS # "-Xcc;-I${SWIFT_PATH_TO_ZLIB_SOURCE}" # "-Xcc;-I${SWIFT_PATH_TO_ZSTD_SOURCE}/lib" # "-Xcc;-I${SWIFT_PATH_TO_LIBLZMA_SOURCE}/src/liblzma/api") if(SWIFT_ASM_AVAILABLE) - list(APPEND BACKTRACING_SOURCES get-cpu-context.${SWIFT_ASM_EXT}) - list(APPEND BACKTRACING_COMPILE_FLAGS "-DSWIFT_ASM_AVAILABLE") + list(APPEND RUNTIME_SOURCES get-cpu-context.${SWIFT_ASM_EXT}) + list(APPEND RUNTIME_COMPILE_FLAGS "-DSWIFT_ASM_AVAILABLE") else() message(warning "Assembly language not available on this platform; backtracing will fail.") endif() @@ -75,8 +87,8 @@ set(LLVM_OPTIONAL_SOURCES get-cpu-context.asm ) -add_swift_target_library(swift_Backtracing ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_STDLIB - ${BACKTRACING_SOURCES} +add_swift_target_library(swiftRuntime ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_STDLIB + ${RUNTIME_SOURCES} SWIFT_MODULE_DEPENDS ${concurrency} @@ -89,11 +101,11 @@ add_swift_target_library(swift_Backtracing ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I SWIFT_MODULE_DEPENDS_HAIKU Glibc SWIFT_MODULE_DEPENDS_WINDOWS CRT - PRIVATE_LINK_LIBRARIES ${swift_backtracing_link_libraries} + PRIVATE_LINK_LIBRARIES ${swift_runtime_link_libraries} SWIFT_COMPILE_FLAGS ${SWIFT_STANDARD_LIBRARY_SWIFT_FLAGS} - ${BACKTRACING_COMPILE_FLAGS} + ${RUNTIME_COMPILE_FLAGS} -parse-stdlib LINK_FLAGS diff --git a/stdlib/public/Backtracing/CachingMemoryReader.swift b/stdlib/public/RuntimeModule/CachingMemoryReader.swift similarity index 64% rename from stdlib/public/Backtracing/CachingMemoryReader.swift rename to stdlib/public/RuntimeModule/CachingMemoryReader.swift index 967f3a5394b..33c8d10aeb8 100644 --- a/stdlib/public/Backtracing/CachingMemoryReader.swift +++ b/stdlib/public/RuntimeModule/CachingMemoryReader.swift @@ -18,18 +18,17 @@ import Swift // The size of the pages in the page cache (must be a power of 2) fileprivate let pageSize = 4096 - fileprivate let pageMask = pageSize - 1 // The largest chunk we will try to cache data for fileprivate let maxCachedSize = pageSize * 8 @_spi(MemoryReaders) -public class CachingMemoryReader: MemoryReader { - private var reader: T +public class CachingMemoryReader: MemoryReader { + private var reader: Reader private var cache: [Address:UnsafeRawBufferPointer] - public init(for reader: T) { + public init(for reader: Reader) { self.reader = reader self.cache = [:] } @@ -40,7 +39,7 @@ public class CachingMemoryReader: MemoryReader { } } - private func getPage(at address: Address) throws -> UnsafeRawBufferPointer { + func getPage(at address: Address) throws -> UnsafeRawBufferPointer { precondition((address & Address(pageMask)) == 0) if let page = cache[address] { @@ -84,3 +83,39 @@ public class CachingMemoryReader: MemoryReader { } } } + +#if os(Linux) +@_spi(MemoryReaders) +public typealias MemserverMemoryReader + = CachingMemoryReader + +extension CachingMemoryReader where Reader == UncachedMemserverMemoryReader { + convenience public init(fd: CInt) { + self.init(for: UncachedMemserverMemoryReader(fd: fd)) + } +} +#endif + +@_spi(MemoryReaders) +public typealias RemoteMemoryReader = CachingMemoryReader + +extension CachingMemoryReader where Reader == UncachedRemoteMemoryReader { + #if os(macOS) + convenience public init(task: Any) { + self.init(for: UncachedRemoteMemoryReader(task: task)) + } + #elseif os(Linux) + convenience public init(pid: Any) { + self.init(for: UncachedRemoteMemoryReader(pid: pid)) + } + #endif +} + +@_spi(MemoryReaders) +public typealias LocalMemoryReader = CachingMemoryReader + +extension CachingMemoryReader where Reader == UncachedLocalMemoryReader { + convenience public init() { + self.init(for: UncachedLocalMemoryReader()) + } +} diff --git a/stdlib/public/RuntimeModule/CompactBacktrace.swift b/stdlib/public/RuntimeModule/CompactBacktrace.swift new file mode 100644 index 00000000000..7a8fc3559a6 --- /dev/null +++ b/stdlib/public/RuntimeModule/CompactBacktrace.swift @@ -0,0 +1,660 @@ +//===--- CompactBacktrace.swift -------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 for Compact Backtrace Format +// +//===----------------------------------------------------------------------===// + +import Swift + +enum CompactBacktraceFormat { + /// Tells us what size of machine words were used when generating the + /// backtrace. + enum WordSize: UInt8 { + case sixteenBit = 0 + case thirtyTwoBit = 1 + case sixtyFourBit = 2 + } + + // Instruction encodings + struct Instruction: RawRepresentable { + typealias RawValue = UInt8 + + private(set) var rawValue: UInt8 + + init?(rawValue: Self.RawValue) { + self.rawValue = rawValue + } + + static let end = Instruction(rawValue: 0b00000000)! + static let trunc = Instruction(rawValue: 0b00000001)! + + static let pc_first = Instruction(rawValue: 0b00010000)! + static let pc_last = Instruction(rawValue: 0b00011111)! + static let ra_first = Instruction(rawValue: 0b00100000)! + static let ra_last = Instruction(rawValue: 0b00101111)! + static let async_first = Instruction(rawValue: 0b00110000)! + static let async_last = Instruction(rawValue: 0b00111111)! + + static let omit_first = Instruction(rawValue: 0b01000000)! + static let omit_last = Instruction(rawValue: 0b01111111)! + + static let rep_first = Instruction(rawValue: 0b10000000)! + static let rep_last = Instruction(rawValue: 0b10001111)! + + private static func addressInstr( + _ code: UInt8, _ absolute: Bool, _ count: Int + ) -> Instruction { + return Instruction(rawValue: code + | (absolute ? 0b00001000 : 0) + | UInt8(count - 1))! + } + + static func pc(absolute: Bool, count: Int) -> Instruction { + return addressInstr(0b00010000, absolute, count) + } + static func ra(absolute: Bool, count: Int) -> Instruction { + return addressInstr(0b00100000, absolute, count) + } + static func `async`(absolute: Bool, count: Int) -> Instruction { + return addressInstr(0b00110000, absolute, count) + } + + static func omit(external: Bool, count: Int) -> Instruction { + return Instruction(rawValue: 0b01000000 + | (external ? 0b00100000 : 0) + | UInt8(count - 1))! + } + + static func rep(external: Bool, count: Int) -> Instruction { + return Instruction(rawValue: 0b10000000 + | (external ? 0b00001000 : 0) + | UInt8(count - 1))! + } + } + + // Represents a decoded instruction + enum DecodedInstruction { + case end + case trunc + case pc(absolute: Bool, count: Int) + case ra(absolute: Bool, count: Int) + case `async`(absolute: Bool, count: Int) + case omit(external: Bool, count: Int) + case rep(external: Bool, count: Int) + } + + + /// Accumulates bytes until the end of a Compact Backtrace Format + /// sequence is detected. + public struct Accumulator>: Sequence { + public typealias Element = UInt8 + typealias Source = S + + private var source: S + + public init(_ source: S) { + self.source = source + } + + public func makeIterator() -> Iterator { + return Iterator(source.makeIterator()) + } + + public struct Iterator: IteratorProtocol { + var iterator: Source.Iterator? + + enum State { + case infoByte + case instruction + case argumentData(Int) + } + + var state: State + + init(_ iterator: Source.Iterator?) { + self.iterator = iterator + self.state = .infoByte + } + + private mutating func finished() { + iterator = nil + } + + private mutating func fail() { + iterator = nil + } + + public mutating func next() -> UInt8? { + if iterator == nil { + return nil + } + + switch state { + case .infoByte: + guard let infoByte = iterator!.next() else { + fail() + return nil + } + let version = infoByte >> 2 + guard let _ = WordSize(rawValue: infoByte & 0x3) else { + fail() + return nil + } + guard version == 0 else { + fail() + return nil + } + + state = .instruction + + return infoByte + + case .instruction: + guard let instr = iterator!.next() else { + finished() + return nil + } + + guard let decoded = Instruction(rawValue: instr)?.decoded() else { + fail() + return nil + } + + switch decoded { + case .end, .trunc: + finished() + return instr + case let .pc(_, count), let .ra(_, count), let .async(_, count): + state = .argumentData(count) + return instr + case let .omit(external, count), let .rep(external, count): + if external { + state = .argumentData(count) + } + return instr + } + + case let .argumentData(count): + guard let byte = iterator!.next() else { + fail() + return nil + } + + let newCount = count - 1 + if newCount == 0 { + state = .instruction + } else { + state = .argumentData(newCount) + } + + return byte + } + } + } + } + + /// Adapts a Sequence containing Compact Backtrace Format data into a + /// Sequence of `Backtrace.Frame`s. + struct Decoder>: Sequence { + typealias Frame = Backtrace.Frame + typealias Address = Backtrace.Address + typealias Storage = S + + private var storage: Storage + + init(_ storage: S) { + self.storage = storage + } + + public func makeIterator() -> Iterator { + var iterator = storage.makeIterator() + guard let infoByte = iterator.next() else { + return Iterator(nil, .sixtyFourBit) + } + let version = infoByte >> 2 + guard let size = WordSize(rawValue: infoByte & 0x3) else { + return Iterator(nil, .sixtyFourBit) + } + guard version == 0 else { + return Iterator(nil, .sixtyFourBit) + } + return Iterator(iterator, size) + } + + struct Iterator: IteratorProtocol { + var iterator: Storage.Iterator? + let wordSize: WordSize + let wordMask: UInt64 + var lastAddress: UInt64 + var lastFrame: Backtrace.Frame? + var repeatCount: Int = 0 + + init(_ iterator: Storage.Iterator?, _ size: WordSize) { + self.iterator = iterator + self.wordSize = size + + switch size { + case .sixteenBit: + self.wordMask = 0xff00 + case .thirtyTwoBit: + self.wordMask = 0xffffff00 + case .sixtyFourBit: + self.wordMask = 0xffffffffffffff00 + } + + self.lastAddress = 0 + } + + private mutating func decodeAddress( + _ absolute: Bool, _ count: Int + ) -> Address? { + var word: UInt64 + guard let firstByte = iterator!.next() else { + return nil + } + if (firstByte & 0x80) != 0 { + word = wordMask | UInt64(firstByte) + } else { + word = UInt64(firstByte) + } + for _ in 1.. Int? { + var word: Int = 0 + for _ in 0.. Backtrace.Frame? { + if repeatCount > 0 { + repeatCount -= 1 + return lastFrame + } + + if iterator == nil { + return nil + } + + guard let instr = iterator!.next() else { + finished() + return .truncated + } + + guard let decoded = Instruction(rawValue: instr)?.decoded() else { + fail() + return .truncated + } + + let result: Backtrace.Frame + switch decoded { + case .end: + finished() + return nil + case .trunc: + finished() + return .truncated + case let .pc(absolute, count): + guard let addr = decodeAddress(absolute, count) else { + finished() + return .truncated + } + result = .programCounter(addr) + case let .ra(absolute, count): + guard let addr = decodeAddress(absolute, count) else { + finished() + return .truncated + } + result = .returnAddress(addr) + case let .async(absolute, count): + guard let addr = decodeAddress(absolute, count) else { + finished() + return .truncated + } + result = .asyncResumePoint(addr) + case let .omit(external, count): + if !external { + result = .omittedFrames(count) + } else { + guard let word = decodeWord(count) else { + finished() + return .truncated + } + result = .omittedFrames(word) + } + case let .rep(external, count): + if lastFrame == nil { + finished() + return .truncated + } + if !external { + repeatCount = count - 1 + } else { + guard let word = decodeWord(count) else { + finished() + return .truncated + } + repeatCount = word - 1 + } + result = lastFrame! + } + + lastFrame = result + + return result + } + } + + } + + /// Adapts a Sequence of RichFrames into a sequence containing Compact + /// Backtrace Format data. + struct Encoder>>: Sequence { + typealias Element = UInt8 + typealias Frame = Backtrace.Frame + typealias SourceFrame = RichFrame + typealias Address = A + typealias Source = S + + private var source: Source + + init(_ source: Source) { + self.source = source + } + + public func makeIterator() -> Iterator { + return Iterator(source.makeIterator()) + } + + struct Iterator: IteratorProtocol { + var iterator: Source.Iterator + var lastAddress: Address = 0 + + enum State { + case start + case ready + case emittingBytes(Int, SourceFrame?) + case stashedFrame(SourceFrame) + case done + } + var bytes = EightByteBuffer() + var state: State = .start + var lastFrame: SourceFrame? = nil + + init(_ iterator: Source.Iterator) { + self.iterator = iterator + } + + /// Set up to emit the bytes of `address`, returning the number of bytes + /// we will need to emit + private mutating func emitNext( + address: Address + ) -> (absolute: Bool, count: Int) { + let delta = address &- lastAddress + + let absCount: Int + if address & (1 << (Address.bitWidth - 1)) != 0 { + let ones = ((~address).leadingZeroBitCount - 1) >> 3 + absCount = (Address.bitWidth >> 3) - ones + } else { + let zeroes = (address.leadingZeroBitCount - 1) >> 3 + absCount = (Address.bitWidth >> 3) - zeroes + } + + let deltaCount: Int + if delta & (1 << (Address.bitWidth - 1)) != 0 { + let ones = ((~delta).leadingZeroBitCount - 1) >> 3 + deltaCount = (Address.bitWidth >> 3) - ones + } else { + let zeroes = (delta.leadingZeroBitCount - 1) >> 3 + deltaCount = (Address.bitWidth >> 3) - zeroes + } + + lastAddress = address + + if absCount < deltaCount { + bytes = EightByteBuffer(address) + state = .emittingBytes(8 - absCount, nil) + return (absolute: true, count: absCount) + } else { + bytes = EightByteBuffer(delta) + state = .emittingBytes(8 - deltaCount, nil) + return (absolute: false, count: deltaCount) + } + } + + /// Set up to emit the bytes of `count`, returning the number of bytes + /// we will need to emit + private mutating func emitNext( + externalCount count: Int + ) -> Int { + let ucount = UInt64(count) + let zeroes = ucount.leadingZeroBitCount >> 3 + let byteCount = 8 - zeroes + bytes = EightByteBuffer(ucount) + state = .emittingBytes(zeroes, nil) + return byteCount + } + + private mutating func emitNext( + frame: SourceFrame?, + externalCount count: Int? = nil + ) -> Int { + if let count { + let ucount = UInt64(count) + let zeroes = ucount.leadingZeroBitCount >> 3 + let byteCount = 8 - zeroes + bytes = EightByteBuffer(ucount) + state = .emittingBytes(zeroes, frame) + return byteCount + } else if let frame { + state = .stashedFrame(frame) + } else { + state = .ready + } + return 0 + } + + private mutating func emit(frame: SourceFrame) -> UInt8 { + lastFrame = frame + + switch frame { + case let .programCounter(addr): + let (absolute, count) = emitNext(address: addr) + return Instruction.pc(absolute: absolute, + count: count).rawValue + case let .returnAddress(addr): + let (absolute, count) = emitNext(address: addr) + return Instruction.ra(absolute: absolute, + count: count).rawValue + case let .asyncResumePoint(addr): + let (absolute, count) = emitNext(address: addr) + return Instruction.async(absolute: absolute, + count: count).rawValue + case let .omittedFrames(count): + if count <= 0x1f { + return Instruction.omit(external: false, + count: count).rawValue + } + let countCount = emitNext(externalCount: count) + return Instruction.omit(external: true, + count: countCount).rawValue + case .truncated: + self.state = .done + return Instruction.trunc.rawValue + } + } + + public mutating func next() -> UInt8? { + switch state { + case .done: + return nil + + case .start: + // The first thing we emit is the info byte + let size: WordSize + switch Address.bitWidth { + case 16: + size = .sixteenBit + case 32: + size = .thirtyTwoBit + case 64: + size = .sixtyFourBit + default: + state = .done + return nil + } + + state = .ready + + let version: UInt8 = 0 + let infoByte = (version << 2) | size.rawValue + return infoByte + + case let .emittingBytes(ndx, frame): + + let byte = bytes[ndx] + if ndx + 1 == 8 { + if let frame { + state = .stashedFrame(frame) + } else { + state = .ready + } + } else { + state = .emittingBytes(ndx + 1, frame) + } + return byte + + case .ready: + + // Grab a rich frame and encode it + guard let frame = iterator.next() else { + state = .done + return nil + } + + if let lastFrame, lastFrame == frame { + var count = 1 + var nextFrame: SourceFrame? = nil + while let frame = iterator.next() { + if frame != lastFrame { + nextFrame = frame + break + } else { + count += 1 + } + } + + if count <= 8 { + _ = emitNext(frame: nextFrame) + return Instruction.rep(external: false, + count: count).rawValue + } else { + let countCount = emitNext(frame: nextFrame, + externalCount: count) + return Instruction.rep(external: true, + count: countCount).rawValue + } + } + + return emit(frame: frame) + + case let .stashedFrame(frame): + + state = .ready + + return emit(frame: frame) + } + } + } + } +} + +extension CompactBacktraceFormat.Instruction: Comparable { + public static func < (lhs: Self, rhs: Self) -> Bool { + return lhs.rawValue < rhs.rawValue + } + public static func == (lhs: Self, rhs: Self) -> Bool { + return lhs.rawValue == rhs.rawValue + } +} + +extension CompactBacktraceFormat.Instruction { + func decoded() -> CompactBacktraceFormat.DecodedInstruction? { + switch self { + case .end: + return .end + case .trunc: + return .trunc + case .pc_first ... .pc_last: + let count = Int((self.rawValue & 0x7) + 1) + let absolute = (self.rawValue & 0x8) != 0 + return .pc(absolute: absolute, count: count) + case .ra_first ... .ra_last: + let count = Int((self.rawValue & 0x7) + 1) + let absolute = (self.rawValue & 0x8) != 0 + return .ra(absolute: absolute, count: count) + case .async_first ... .async_last: + let count = Int((self.rawValue & 0x7) + 1) + let absolute = (self.rawValue & 0x8) != 0 + return .async(absolute: absolute, count: count) + case .omit_first ... .omit_last: + let count = Int((self.rawValue & 0x1f) + 1) + let external = (self.rawValue & 0x20) != 0 + return .omit(external: external, count: count) + case .rep_first ... .rep_last: + let count = Int((self.rawValue & 0x7) + 1) + let external = (self.rawValue & 0x8) != 0 + return .rep(external: external, count: count) + default: + return nil + } + } +} diff --git a/stdlib/public/RuntimeModule/CompactImageMap.swift b/stdlib/public/RuntimeModule/CompactImageMap.swift new file mode 100644 index 00000000000..f76233ebbdd --- /dev/null +++ b/stdlib/public/RuntimeModule/CompactImageMap.swift @@ -0,0 +1,804 @@ +//===--- CompactImageMap.swift -------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 for Compact ImageMap Format +// +//===----------------------------------------------------------------------===// + +import Swift + +private let slash = UInt8(ascii: "/") +private let backslash = UInt8(ascii: "\\") + +@_spi(Internal) +public enum CompactImageMapFormat { + + /// The list of fixed prefixes used to encode paths. + static let fixedPathPrefixes = [ + // Traditional UNIX + (0, "/lib"), + (1, "/usr/lib"), + (2, "/usr/local/lib"), + (3, "/opt/lib"), + + // NeXT/Darwin + (4, "/System/Library/Frameworks"), + (5, "/System/Library/PrivateFrameworks"), + (6, "/System/iOSSupport"), + (7, "/Library/Frameworks"), + (8, "/System/Applications"), + (9, "/Applications"), + + // Windows + (10, "C:\\Windows\\System32"), + (11, "C:\\Program Files") + ] + + /// Tells us what size of machine words were used when generating the + /// image map. + enum WordSize: UInt8 { + case sixteenBit = 0 + case thirtyTwoBit = 1 + case sixtyFourBit = 2 + } + + /// Run a closure for each prefix of the specified string + static func forEachPrefix(of str: String.UTF8View.SubSequence, + body: (String) -> ()) { + let base = str.startIndex + let end = str.endIndex + var pos = base + + while pos < end { + let ch = str[pos] + + if pos > base && (ch == slash || ch == backslash) { + let range = base..> { + var sequence: S + var iterator: S.Iterator + var imageCount: Int = 0 + var wordSize: WordSize = .sixtyFourBit + var wordMask: UInt64 = 0 + var pathPrefixes = Dictionary(uniqueKeysWithValues: fixedPathPrefixes) + var nextCode = 32 + + public init(_ sequence: S) { + self.sequence = sequence + self.iterator = sequence.makeIterator() + } + + mutating func decodeCount() -> Int? { + var value: Int = 0 + while true { + guard let byte = iterator.next() else { + return nil + } + + value = (value << 7) | Int(byte & 0x7f) + + if (byte & 0x80) == 0 { + break + } + } + return value + } + + mutating func decodeString() -> String? { + guard let utf8Length = iterator.next() else { + return nil + } + + var bytes: [UInt8] = [] + bytes.reserveCapacity(Int(utf8Length)) + + for _ in 0.. UInt64? { + var word: UInt64 + guard let firstByte = iterator.next() else { + return nil + } + + // Sign extend + if (firstByte & 0x80) != 0 { + word = wordMask | UInt64(firstByte) + } else { + word = UInt64(firstByte) + } + + for _ in 1.. String? { + var byte: UInt8 + + guard let b = iterator.next() else { + return nil + } + + byte = b + + // `end` here means no string at all + if byte == 0x00 { + return nil + } + + var resultBytes: [UInt8] = [] + var stringBase: Int? = nil + + while true { + if byte == 0x00 { + // `end` + #if DEBUG_COMPACT_IMAGE_MAP + print("end") + #endif + return String(decoding: resultBytes, as: UTF8.self) + } else if byte < 0x40 { + // `str` + let count = Int(byte) + resultBytes.reserveCapacity(resultBytes.count + count) + let base = resultBytes.count + if stringBase == nil { + stringBase = base + } + for n in 0.. stringBase! && (char == slash + || char == backslash) { + let prefix = String(decoding: resultBytes[stringBase!.. (String, [ImageMap.Image], ImageMap.WordSize)? { + // Check the version and decode the size + guard let infoByte = iterator.next() else { + return nil + } + let version = infoByte >> 2 + guard let size = WordSize(rawValue: infoByte & 0x3) else { + return nil + } + wordSize = size + guard version == 0 else { + return nil + } + + // Set up the word mask + switch wordSize { + case .sixteenBit: + wordMask = 0xff00 + case .thirtyTwoBit: + wordMask = 0xffffff00 + case .sixtyFourBit: + wordMask = 0xffffffffffffff00 + } + + // Now decode the platform + guard let platform = decodeString() else { + return nil + } + + // Next is the image count + guard let count = decodeCount() else { + return nil + } + + imageCount = count + + // Now decode all of the images + var images: [ImageMap.Image] = [] + var lastAddress: UInt64 = 0 + + images.reserveCapacity(count) + + for _ in 0..> 3) & 0x7) + 1) + let ecount = Int((header & 0x7) + 1) + + #if DEBUG_COMPACT_IMAGE_MAP + print("r = \(relative), acount = \(acount), ecount = \(ecount)") + #endif + + // Now the base and end of text addresses + guard let address = decodeAddress(acount) else { + return nil + } + let baseAddress: UInt64 + if relative { + baseAddress = lastAddress &+ address + } else { + baseAddress = address + } + + lastAddress = baseAddress + + guard let eotOffset = decodeAddress(ecount) else { + return nil + } + let endOfText = baseAddress &+ eotOffset + + #if DEBUG_COMPACT_IMAGE_MAP + print("address = \(hex(address)), eotOffset = \(hex(eotOffset))") + print("baseAddress = \(hex(baseAddress)), endOfText = \(hex(endOfText))") + #endif + + // Next, get the build ID byte count + guard let buildIdBytes = decodeCount() else { + return nil + } + + #if DEBUG_COMPACT_IMAGE_MAP + print("buildIdBytes = \(buildIdBytes)") + #endif + + // Read the build ID + var buildId: [UInt8]? = nil + + if buildIdBytes > 0 { + buildId = [] + buildId!.reserveCapacity(buildIdBytes) + + for _ in 0.. + @_spi(Internal) + public struct Encoder: Sequence { + public typealias Element = UInt8 + + private var source: ImageMap + + public init(_ source: ImageMap) { + self.source = source + } + + public func makeIterator() -> Iterator { + return Iterator(source) + } + + public struct Iterator: IteratorProtocol { + enum State { + case start + case platform(Int) + case count(Int) + case image + case baseAddress(Int) + case endOfText(Int) + case uniqueID(Int) + case uniqueIDBytes(Int) + case path + case pathCode(Int) + case pathString + case pathStringChunk(Int) + case version + case framework + case done + } + + var abytes = EightByteBuffer() + var ebytes = EightByteBuffer() + var acount: Int = 0 + var ecount: Int = 0 + var version: UInt8 = 0 + var lastAddress: UInt64 = 0 + var ndx: Int = 0 + var state: State = .start + var source: ImageMap + var pathPrefixes = fixedPathPrefixes + var nextCode = 32 + var remainingPath: String.UTF8View.SubSequence? + + func signExtend(_ value: UInt64) -> UInt64 { + let mask: UInt64 + let topBit: UInt64 + switch source.wordSize { + case .sixteenBit: + topBit = 0x8000 + mask = 0xffffffffffff0000 + case .thirtyTwoBit: + topBit = 0x80000000 + mask = 0xffffffff00000000 + case .sixtyFourBit: + return value + } + + if (value & topBit) != 0 { + return value | mask + } + return value + } + + init(_ source: ImageMap) { + self.source = source + } + + public mutating func next() -> UInt8? { + switch state { + case .done: + return nil + + case .start: + // The first thing we emit is the info byte + let size: WordSize + switch source.wordSize { + case .sixteenBit: + size = .sixteenBit + case .thirtyTwoBit: + size = .thirtyTwoBit + case .sixtyFourBit: + size = .sixtyFourBit + } + + state = .platform(-1) + + let version: UInt8 = 0 + let infoByte = (version << 2) | size.rawValue + return infoByte + + case let .platform(ndx): + let length = UInt8(source.platform.utf8.count) + let byte: UInt8 + + if ndx == -1 { + // The length byte comes first + byte = length + } else { + byte = source.platform.utf8[ + source.platform.utf8.index( + source.platform.utf8.startIndex, + offsetBy: ndx + ) + ] + } + + // If we're done, move to the .count state + if ndx + 1 == length { + let count = source.images.count + let bits = Int.bitWidth - count.leadingZeroBitCount + state = .count(7 * (bits / 7)) + } else { + state = .platform(ndx + 1) + } + + return byte + + case let .count(ndx): + let count = source.images.count + let byte = UInt8(truncatingIfNeeded:(count >> ndx) & 0x7f) + if ndx == 0 { + state = .image + return byte + } else { + state = .count(ndx - 7) + return 0x80 | byte + } + + case .image: + if ndx == source.images.count { + state = .done + return nil + } + + let baseAddress = signExtend(source.images[ndx].baseAddress) + let delta = baseAddress &- lastAddress + + let endOfText = signExtend(source.images[ndx].endOfText) + let endOfTextOffset = endOfText - baseAddress + + let eotCount: Int + if endOfTextOffset & (1 << 63) != 0 { + let ones = ((~endOfTextOffset).leadingZeroBitCount - 1) >> 3 + eotCount = 8 - ones + } else { + let zeroes = (endOfTextOffset.leadingZeroBitCount - 1) >> 3 + eotCount = 8 - zeroes + } + + ebytes = EightByteBuffer(endOfTextOffset) + ecount = eotCount + + let absCount: Int + if baseAddress & (1 << 63) != 0 { + let ones = ((~baseAddress).leadingZeroBitCount - 1) >> 3 + absCount = 8 - ones + } else { + let zeroes = (baseAddress.leadingZeroBitCount - 1) >> 3 + absCount = 8 - zeroes + } + + let deltaCount: Int + if delta & (1 << 63) != 0 { + let ones = ((~delta).leadingZeroBitCount - 1) >> 3 + deltaCount = 8 - ones + } else { + let zeroes = (delta.leadingZeroBitCount - 1) >> 3 + deltaCount = 8 - zeroes + } + + lastAddress = baseAddress + + let relativeFlag: UInt8 + if absCount <= deltaCount { + abytes = EightByteBuffer(baseAddress) + acount = absCount + relativeFlag = 0 + } else { + abytes = EightByteBuffer(delta) + acount = deltaCount + relativeFlag = 0x80 + } + + state = .baseAddress(8 - acount) + return relativeFlag + | UInt8(truncatingIfNeeded: (acount - 1) << 3) + | UInt8(truncatingIfNeeded: ecount - 1) + + case let .baseAddress(ndx): + let byte = abytes[ndx] + if ndx + 1 == 8 { + state = .endOfText(8 - ecount) + } else { + state = .baseAddress(ndx + 1) + } + return byte + + case let .endOfText(ndx): + let byte = ebytes[ndx] + if ndx + 1 == 8 { + let count = source.images[self.ndx].uniqueID?.count ?? 0 + let bits = Int.bitWidth - count.leadingZeroBitCount + state = .uniqueID(7 * (bits / 7)) + } else { + state = .endOfText(ndx + 1) + } + return byte + + case let .uniqueID(cndx): + guard let count = source.images[self.ndx].uniqueID?.count else { + state = .path + if let path = source.images[self.ndx].path { + remainingPath = path.utf8[...] + } else { + remainingPath = nil + } + return 0 + } + let byte = UInt8(truncatingIfNeeded: (count >> cndx) & 0x7f) + if cndx == 0 { + state = .uniqueIDBytes(0) + return byte + } else { + state = .uniqueID(cndx - 7) + return 0x80 | byte + } + + case let .uniqueIDBytes(byteNdx): + let uniqueID = source.images[self.ndx].uniqueID! + let byte = uniqueID[byteNdx] + if byteNdx + 1 == uniqueID.count { + state = .path + if let path = source.images[self.ndx].path { + remainingPath = path.utf8[...] + } else { + remainingPath = nil + } + } else { + state = .uniqueIDBytes(byteNdx + 1) + } + return byte + + case .path: + guard let remainingPath = remainingPath, + remainingPath.count > 0 else { + ndx += 1 + state = .image + return 0x00 + } + + // Find the longest prefix match + var longestMatchLen = 0 + var matchedPrefix: Int? = nil + for (ndx, (_, prefix)) in pathPrefixes.enumerated() { + let prefixUTF8 = prefix.utf8 + if prefixUTF8.count > remainingPath.count { + continue + } + if prefixUTF8.count > longestMatchLen + && remainingPath.starts(with: prefixUTF8) { + longestMatchLen = prefixUTF8.count + matchedPrefix = ndx + } + } + + if let ndx = matchedPrefix { + let (code, prefix) = pathPrefixes[ndx] + self.remainingPath = remainingPath.dropFirst(prefix.utf8.count) + if code <= 0x3f { + return 0x80 | UInt8(exactly: code)! + } + + let theCode = UInt64(exactly: code - 0x40)! + abytes = EightByteBuffer(theCode) + + let codeBytes = Swift.max( + (64 - theCode.leadingZeroBitCount) >> 3, 1 + ) + + state = .pathCode(8 - codeBytes) + + return 0xc0 | UInt8(exactly: codeBytes - 1)! + } + + // Check for /.framework/Versions// + if let name = source.images[ndx].name, !name.isEmpty { + let nameCount = name.utf8.count + let expectedLen = 1 // '/' + + nameCount // + + 20 // .framework/Versions/ + + 1 // + + 1 // '/' + + nameCount // + if remainingPath.count == expectedLen { + let framework = "/\(name).framework/Versions/" + if remainingPath.starts(with: framework.utf8) { + var verNdx = remainingPath.startIndex + remainingPath.formIndex(&verNdx, offsetBy: framework.utf8.count) + + version = remainingPath[verNdx] + + let slashNdx = remainingPath.index(after: verNdx) + if remainingPath[slashNdx] == slash { + let nameNdx = remainingPath.index(after: slashNdx) + if remainingPath[nameNdx...].elementsEqual(name.utf8) { + self.remainingPath = remainingPath[nameNdx...] + + state = .version + return 0x40 | UInt8(exactly: nameCount - 1)! + } + } + } + } + } + + // Add any new prefixes + forEachPrefix(of: remainingPath) { prefix in + #if DEBUG_COMPACT_IMAGE_MAP + print("defining \(nextCode) as \"\(prefix)\"") + #endif + pathPrefixes.append((nextCode, prefix)) + nextCode += 1 + } + + fallthrough + + case .pathString: + if remainingPath!.count == 0 { + ndx += 1 + state = .image + return 0x00 + } + + let chunkLength = Swift.min(remainingPath!.count, 0x3f) + state = .pathStringChunk(chunkLength) + return UInt8(truncatingIfNeeded: chunkLength) + + case let .pathStringChunk(length): + let byte = remainingPath!.first! + remainingPath = remainingPath!.dropFirst() + if length == 1 { + state = .pathString + } else { + state = .pathStringChunk(length - 1) + } + return byte + + case .version: + state = .framework + return version + + case .framework: + let byte = remainingPath!.first! + remainingPath = remainingPath!.dropFirst() + if remainingPath!.count == 0 { + ndx += 1 + state = .image + } + return byte + + case let .pathCode(ndx): + let byte = abytes[ndx] + if ndx + 1 == 8 { + state = .path + } else { + state = .pathCode(ndx + 1) + } + return byte + } + } + } + } + +} diff --git a/stdlib/public/Backtracing/Compression.swift b/stdlib/public/RuntimeModule/Compression.swift similarity index 57% rename from stdlib/public/Backtracing/Compression.swift rename to stdlib/public/RuntimeModule/Compression.swift index 0b0f0c0e7e2..575e7b7a58f 100644 --- a/stdlib/public/Backtracing/Compression.swift +++ b/stdlib/public/RuntimeModule/Compression.swift @@ -25,8 +25,6 @@ // //===----------------------------------------------------------------------===// -#if os(Linux) - import Swift #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) @@ -56,18 +54,26 @@ let lzma_stream_init = swift.runtime.lzma_stream_init // .. CompressedStream ......................................................... protocol CompressedStream { - typealias InputSource = () throws -> UnsafeBufferPointer + typealias InputSource = () throws -> UnsafeRawBufferPointer typealias OutputSink = (_ used: UInt, _ done: Bool) throws - -> UnsafeMutableBufferPointer? + -> UnsafeMutableRawBufferPointer? func decompress(input: InputSource, output: OutputSink) throws -> UInt } // .. Compression library bindings ............................................. +#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) +private var lzmaHandle = dlopen("liblzma.dylib", RTLD_LAZY) +private var zlibHandle = dlopen("libz.dylib", RTLD_LAZY) +private var zstdHandle = dlopen("libzstd.dylib", RTLD_LAZY) +#elseif os(Linux) private var lzmaHandle = dlopen("liblzma.so.5", RTLD_LAZY) private var zlibHandle = dlopen("libz.so.1", RTLD_LAZY) private var zstdHandle = dlopen("libzstd.so.1", RTLD_LAZY) +#elseif os(Windows) +// ###TODO +#endif private func symbol(_ handle: UnsafeMutableRawPointer?, _ name: String) -> T? { guard let handle = handle, let result = dlsym(handle, name) else { @@ -152,7 +158,9 @@ struct ZLibStream: CompressedStream { let buffer = try input() // Not really mutable; this is just an issue with z_const - stream.next_in = UnsafeMutablePointer(mutating: buffer.baseAddress) + stream.next_in = UnsafeMutablePointer( + mutating: buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) + ) stream.avail_in = CUnsignedInt(buffer.count) } @@ -161,7 +169,7 @@ struct ZLibStream: CompressedStream { throw CompressedImageSourceError.outputOverrun } - stream.next_out = buffer.baseAddress + stream.next_out = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) stream.avail_out = CUnsignedInt(buffer.count) outputBufferSize = UInt(buffer.count) } @@ -211,7 +219,7 @@ struct ZStdStream: CompressedStream { if inBuffer.size == inBuffer.pos { let buffer = try input() - inBuffer.src = UnsafeRawPointer(buffer.baseAddress) + inBuffer.src = buffer.baseAddress inBuffer.size = buffer.count inBuffer.pos = 0 } @@ -225,7 +233,7 @@ struct ZStdStream: CompressedStream { throw CompressedImageSourceError.outputOverrun } - outBuffer.dst = UnsafeMutableRawPointer(buffer.baseAddress) + outBuffer.dst = buffer.baseAddress outBuffer.size = buffer.count outBuffer.pos = 0 } @@ -280,7 +288,7 @@ struct LZMAStream: CompressedStream { while true { if stream.avail_in == 0 { let buffer = try input() - stream.next_in = buffer.baseAddress + stream.next_in = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) stream.avail_in = buffer.count } @@ -289,7 +297,7 @@ struct LZMAStream: CompressedStream { throw CompressedImageSourceError.outputOverrun } - stream.next_out = buffer.baseAddress + stream.next_out = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) stream.avail_out = buffer.count outputBufferSize = UInt(buffer.count) } @@ -310,230 +318,130 @@ struct LZMAStream: CompressedStream { // .. Image Sources ............................................................ -fileprivate func decompress( - stream: S, source: I, dataBounds: I.Bounds, uncompressedSize: UInt? = nil) - throws -> [UInt8] { +fileprivate func decompress( + stream: S, + source: ImageSource, + offset: Int, + output: inout ImageSource +) throws { + let totalBytes = try stream.decompress( + input: { + () throws -> UnsafeRawBufferPointer in - var pos = dataBounds.base - var remaining = dataBounds.size + return UnsafeRawBufferPointer(rebasing: source.bytes[offset...]) + }, + output: { + (used: UInt, done: Bool) throws -> UnsafeMutableRawBufferPointer? in - let bufSize = 65536 - - if let uncompressedSize = uncompressedSize { - // If we know the uncompressed size, we can decompress directly into the - // array. - - let inputBuffer - = UnsafeMutableBufferPointer.allocate(capacity: bufSize) - defer { - inputBuffer.deallocate() - } - - return try [UInt8].init(unsafeUninitializedCapacity: Int(uncompressedSize)) { - (outputBuffer: inout UnsafeMutableBufferPointer, - count: inout Int) in - - count = Int(try stream.decompress( - input: { () throws -> UnsafeBufferPointer in - - let chunkSize = min(Int(remaining), inputBuffer.count) - let slice = inputBuffer[0.. UnsafeMutableBufferPointer? in - - if used == 0 { - return outputBuffer - } else { - return nil - } - } - )) - } - } else { - // Otherwise, we decompress in chunks and append them to the array. - - let buffer - = UnsafeMutableBufferPointer.allocate(capacity: 2 * bufSize) - defer { - buffer.deallocate() - } - - let inputBuffer = UnsafeMutableBufferPointer(rebasing: buffer[0.. UnsafeBufferPointer in - - let chunkSize = min(Int(remaining), inputBuffer.count) - let slice = inputBuffer[0.. UnsafeMutableBufferPointer? in - - data.append(contentsOf: outputBuffer[..: ImageSource { +fileprivate func decompressChunked( + stream: S, + source: ImageSource, + offset: Int, + output: inout ImageSource +) throws { + let bufSize = 65536 + let outputBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: bufSize, + alignment: 16) + defer { + outputBuffer.deallocate() + } - private var data: [UInt8] + let _ = try stream.decompress( + input: { + () throws -> UnsafeRawBufferPointer in - var isMappedImage: Bool { return false } - var path: String? { return nil } - var bounds: Bounds? { return Bounds(base: Address(0), size: Size(data.count)) } + return UnsafeRawBufferPointer(rebasing: source.bytes[offset...]) + }, + output: { + (used: UInt, done: Bool) throws -> UnsafeMutableRawBufferPointer? in - init(source: some ImageSource) throws { - guard let bounds = source.bounds else { - throw CompressedImageSourceError.unboundedImageSource + output.append( + bytes: UnsafeRawBufferPointer(rebasing: outputBuffer[...size { +extension ImageSource { + @_specialize(kind: full, where Traits == Elf32Traits) + @_specialize(kind: full, where Traits == Elf64Traits) + init(elfCompressedImageSource source: ImageSource, + traits: Traits.Type) throws { + if source.bytes.count < MemoryLayout.size { throw CompressedImageSourceError.badCompressedData } - let chdr = try source.fetch(from: bounds.base, - as: Traits.Chdr.self) - let dataBounds = bounds.adjusted(by: MemoryLayout.stride) + let rawChdr = try source.fetch(from: 0, as: Traits.Chdr.self) + let chdr: Traits.Chdr + switch rawChdr.ch_type { + case .ELFCOMPRESS_ZLIB.byteSwapped, .ELFCOMPRESS_ZSTD.byteSwapped: + chdr = rawChdr.byteSwapped + default: + chdr = rawChdr + } + let uncompressedSize = UInt(chdr.ch_size) + self.init(capacity: Int(uncompressedSize), isMappedImage: false, path: nil) + switch chdr.ch_type { case .ELFCOMPRESS_ZLIB: - data = try decompress(stream: ZLibStream(), - source: source, dataBounds: dataBounds, - uncompressedSize: uncompressedSize) + try decompress(stream: ZLibStream(), + source: source, offset: MemoryLayout.stride, + output: &self) case .ELFCOMPRESS_ZSTD: - data = try decompress(stream: ZStdStream(), - source: source, dataBounds: dataBounds, - uncompressedSize: uncompressedSize) + try decompress(stream: ZStdStream(), + source: source, offset: MemoryLayout.stride, + output: &self) default: throw CompressedImageSourceError.unsupportedFormat } } - public func fetch(from addr: Address, - into buffer: UnsafeMutableRawBufferPointer) throws { - let toFetch = buffer.count - if addr < 0 || addr > data.count || data.count - Int(addr) < toFetch { - throw CompressedImageSourceError.outOfRangeFetch(addr, toFetch) - } - - buffer.withMemoryRebound(to: UInt8.self) { outBuf in - for n in 0.. data.count || data.count - Int(addr) < toFetch { - throw CompressedImageSourceError.outOfRangeFetch(addr, toFetch) - } + init(lzmaCompressedImageSource source: ImageSource) throws { + self.init(isMappedImage: false, path: nil) - buffer.withMemoryRebound(to: UInt8.self) { outBuf in - for n in 0.. data.count || data.count - Int(addr) < toFetch { - throw CompressedImageSourceError.outOfRangeFetch(addr, toFetch) - } - - buffer.withMemoryRebound(to: UInt8.self) { outBuf in - for n in 0..(_ handle: UnsafeMutableRawPointer, _ name: String) -> T { return unsafeBitCast(result, to: T.self) } +// Define UniChar +typealias UniChar = UInt16 + private enum Sym { // CRCopySanitizedPath static let CRCopySanitizedPath: @convention(c) (CFString, CFIndex) -> CFString = @@ -130,6 +133,9 @@ private enum Sym { UnsafeMutableRawPointer?, CFIndex, UnsafeMutablePointer?) -> CFIndex = symbol(coreFoundationHandle, "CFStringGetBytes") + static let CFStringGetCharactersPtr: + @convention(c) (CFString) -> UnsafePointer? = + symbol(coreFoundationHandle, "CFStringGetCharactersPtr") } // .. Core Foundation miscellany ............................................... @@ -161,6 +167,11 @@ internal func CFStringGetCStringPtr(_ s: CFString, return Sym.CFStringGetCStringPtr(s, encoding) } +internal func CFStringGetCharactersPtr(_ s: CFString) + -> UnsafePointer? { + return Sym.CFStringGetCharactersPtr(s); +} + internal func CFStringGetBytes(_ s: CFString, _ range: CFRange, _ encoding: CFStringEncoding, @@ -199,8 +210,15 @@ private func fromCFString(_ cf: CFString) -> String { if let ptr = CFStringGetCStringPtr(cf, CFStringBuiltInEncodings.ASCII.rawValue) { - return String(decoding: UnsafeRawBufferPointer(start: ptr, count: length), - as: UTF8.self) + let buffer = UnsafeRawBufferPointer(start: ptr, count: length) + return String(decoding: buffer, as: UTF8.self) + } else if let ptr = CFStringGetCharactersPtr(cf) { + let buffer = UnsafeBufferPointer(start: ptr, count: length) + return String(decoding: buffer, as: UTF16.self) + } else if let ptr = CFStringGetCStringPtr(cf, + CFStringBuiltInEncodings.UTF8.rawValue) { + let buffer = UnsafeRawBufferPointer(start: ptr, count: length) + return String(decoding: buffer, as: UTF8.self) } else { var byteLen = CFIndex(0) diff --git a/stdlib/public/Backtracing/Dwarf.swift b/stdlib/public/RuntimeModule/Dwarf.swift similarity index 86% rename from stdlib/public/Backtracing/Dwarf.swift rename to stdlib/public/RuntimeModule/Dwarf.swift index 15a5e0f30a2..85d8082f449 100644 --- a/stdlib/public/Backtracing/Dwarf.swift +++ b/stdlib/public/RuntimeModule/Dwarf.swift @@ -15,8 +15,6 @@ // //===----------------------------------------------------------------------===// -#if os(Linux) - import Swift internal import BacktracingImpl.ImageFormats.Dwarf @@ -500,7 +498,7 @@ enum DwarfSection { protocol DwarfSource { - func getDwarfSection(_ section: DwarfSection) -> (any ImageSource)? + func getDwarfSection(_ section: DwarfSection) -> ImageSource? } @@ -523,14 +521,14 @@ struct DwarfReader { var attributes: [(Dwarf_Attribute, Dwarf_Form, Int64?)] } - var infoSection: any ImageSource - var abbrevSection: any ImageSource - var lineSection: (any ImageSource)? - var addrSection: (any ImageSource)? - var strSection: (any ImageSource)? - var lineStrSection: (any ImageSource)? - var strOffsetsSection: (any ImageSource)? - var rangesSection: (any ImageSource)? + var infoSection: ImageSource + var abbrevSection: ImageSource + var lineSection: ImageSource? + var addrSection: ImageSource? + var strSection: ImageSource? + var lineStrSection: ImageSource? + var strOffsetsSection: ImageSource? + var rangesSection: ImageSource? var shouldSwap: Bool typealias DwarfAbbrev = UInt64 @@ -557,263 +555,9 @@ struct DwarfReader { var attributes: [Dwarf_Attribute:DwarfValue] = [:] } - struct FileInfo { - var path: String - var directoryIndex: Int? - var timestamp: Int? - var size: UInt64? - var md5sum: [UInt8]? - } - - struct LineNumberState: CustomStringConvertible { - var address: Address - var opIndex: UInt - var file: Int - var path: String - var line: Int - var column: Int - var isStmt: Bool - var basicBlock: Bool - var endSequence: Bool - var prologueEnd: Bool - var epilogueBegin: Bool - var isa: UInt - var discriminator: UInt - - var description: String { - var flags: [String] = [] - if isStmt { - flags.append("is_stmt") - } - if basicBlock { - flags.append("basic_block") - } - if endSequence { - flags.append("end_sequence") - } - if prologueEnd { - flags.append("prologue_end") - } - if epilogueBegin { - flags.append("epilogue_begin") - } - - let flagsString = flags.joined(separator:" ") - - return """ - \(hex(address)) \(pad(line, 6)) \(pad(column, 6)) \(pad(file, 6)) \ - \(pad(isa, 3)) \(pad(discriminator, 13)) \(flagsString) - """ - } - } - - struct LineNumberInfo { - var baseOffset: Address - var version: Int - var addressSize: Int? - var selectorSize: Int? - var headerLength: UInt64 - var minimumInstructionLength: UInt - var maximumOpsPerInstruction: UInt - var defaultIsStmt: Bool - var lineBase: Int8 - var lineRange: UInt8 - var opcodeBase: UInt8 - var standardOpcodeLengths: [UInt64] - var directories: [String] = [] - var files: [FileInfo] = [] - var program: [UInt8] = [] - var shouldSwap: Bool - - /// Compute the full path for a file, given its index in the file table. - func fullPathForFile(index: Int) -> String { - if index >= files.count { - return "" - } - - let info = files[index] - if info.path.hasPrefix("/") { - return info.path - } - - let dirName: String - if let dirIndex = info.directoryIndex, - dirIndex < directories.count { - dirName = directories[dirIndex] - } else { - dirName = "" - } - - return "\(dirName)/\(info.path)" - } - - /// Execute the line number program, calling a closure for every line - /// table entry. - mutating func executeProgram( - line: (LineNumberState, inout Bool) -> () - ) throws { - let source = ArrayImageSource(array: program) - let bounds = source.bounds! - var cursor = ImageSourceCursor(source: source) - - func maybeSwap(_ x: T) -> T { - if shouldSwap { - return x.byteSwapped - } - return x - } - - // Table 6.4: Line number program initial state - let initialState = LineNumberState( - address: 0, - opIndex: 0, - file: 1, - path: fullPathForFile(index: 1), - line: 1, - column: 0, - isStmt: defaultIsStmt, - basicBlock: false, - endSequence: false, - prologueEnd: false, - epilogueBegin: false, - isa: 0, - discriminator: 0 - ) - - var state = initialState - - // Flag to allow fast exit - var done = false - - while !done && cursor.pos < bounds.end { - let opcode = try cursor.read(as: Dwarf_LNS_Opcode.self) - - if opcode.rawValue >= opcodeBase { - // Special opcode - let adjustedOpcode = UInt(opcode.rawValue - opcodeBase) - let advance = adjustedOpcode / UInt(lineRange) - let lineAdvance = adjustedOpcode % UInt(lineRange) - let instrAdvance - = (state.opIndex + advance) / maximumOpsPerInstruction - let newOp = (state.opIndex + advance) % maximumOpsPerInstruction - state.address += Address(instrAdvance) - state.opIndex = newOp - state.line += Int(lineBase) + Int(lineAdvance) - - line(state, &done) - - state.discriminator = 0 - state.basicBlock = false - state.prologueEnd = false - state.epilogueBegin = false - } else if opcode == .DW_LNS_extended { - // Extended opcode - let length = try cursor.readULEB128() - let opcode = try cursor.read(as: Dwarf_LNE_Opcode.self) - - switch opcode { - case .DW_LNE_end_sequence: - state.endSequence = true - line(state, &done) - state = initialState - case .DW_LNE_set_address: - let address: UInt64 - guard let addressSize = addressSize else { - throw DwarfError.unspecifiedAddressSize - } - switch addressSize { - case 4: - address = UInt64(maybeSwap(try cursor.read(as: UInt32.self))) - case 8: - address = maybeSwap(try cursor.read(as: UInt64.self)) - default: - throw DwarfError.badAddressSize(addressSize) - } - state.address = Address(address) - case .DW_LNE_define_file: - guard let path = try cursor.readString() else { - throw DwarfError.badString - } - let directoryIndex = try cursor.readULEB128() - let timestamp = try cursor.readULEB128() - let size = try cursor.readULEB128() - files.append(FileInfo( - path: path, - directoryIndex: Int(directoryIndex), - timestamp: timestamp != 0 ? Int(timestamp) : nil, - size: size != 0 ? size : nil, - md5sum: nil - )) - case .DW_LNE_set_discriminator: - let discriminator = try cursor.readULEB128() - state.discriminator = UInt(discriminator) - default: - cursor.pos += length - 1 - } - } else { - // Standard opcode - switch opcode { - case .DW_LNS_copy: - line(state, &done) - state.discriminator = 0 - state.basicBlock = false - state.prologueEnd = false - state.epilogueBegin = false - case .DW_LNS_advance_pc: - let advance = UInt(try cursor.readULEB128()) - let instrAdvance - = (state.opIndex + advance) / maximumOpsPerInstruction - let newOp = (state.opIndex + advance) % maximumOpsPerInstruction - state.address += Address(instrAdvance) - state.opIndex = newOp - case .DW_LNS_advance_line: - let advance = try cursor.readSLEB128() - state.line += Int(advance) - case .DW_LNS_set_file: - let file = Int(try cursor.readULEB128()) - state.file = file - state.path = fullPathForFile(index: state.file) - case .DW_LNS_set_column: - let column = Int(try cursor.readULEB128()) - state.column = column - case .DW_LNS_negate_stmt: - state.isStmt = !state.isStmt - case .DW_LNS_set_basic_block: - state.basicBlock = true - case .DW_LNS_const_add_pc: - let adjustedOpcode = UInt(255 - opcodeBase) - let advance = adjustedOpcode / UInt(lineRange) - let instrAdvance - = (state.opIndex + advance) / maximumOpsPerInstruction - let newOp = (state.opIndex + advance) % maximumOpsPerInstruction - state.address += Address(instrAdvance) - state.opIndex = newOp - case .DW_LNS_fixed_advance_pc: - let advance = try cursor.read(as: Dwarf_Half.self) - state.address += Address(advance) - state.opIndex = 0 - case .DW_LNS_set_prologue_end: - state.prologueEnd = true - case .DW_LNS_set_epilogue_begin: - state.epilogueBegin = true - case .DW_LNS_set_isa: - let isa = UInt(try cursor.readULEB128()) - state.isa = isa - default: - // Skip this unknown opcode - let length = standardOpcodeLengths[Int(opcode.rawValue)] - for _ in 0.. { var rangeListInfo: RangeListInfo? + @_specialize(kind: full, where S == Elf32Image) + @_specialize(kind: full, where S == Elf64Image) init(source: Source, shouldSwap: Bool = false) throws { // ###TODO: This should be optional, because we can have just line number // information. We should test that, too. @@ -873,7 +619,7 @@ struct DwarfReader { } lineNumberInfo[n].directories[0] = dirname - lineNumberInfo[n].files[0] = FileInfo( + lineNumberInfo[n].files[0] = DwarfFileInfo( path: filename, directoryIndex: 0, timestamp: nil, @@ -896,14 +642,11 @@ struct DwarfReader { } private func readUnits() throws -> [Unit] { - guard let bounds = infoSection.bounds else { - return [] - } - + let end = infoSection.bytes.count var units: [Unit] = [] var cursor = ImageSourceCursor(source: infoSection) - while cursor.pos < bounds.end { + while cursor.pos < end { // See 7.5.1.1 Full and Partial Compilation Unit Headers let base = cursor.pos @@ -1053,16 +796,16 @@ struct DwarfReader { return units } - private func readLineNumberInfo() throws -> [LineNumberInfo] { - guard let lineSection = lineSection, - let bounds = lineSection.bounds else { + private func readLineNumberInfo() throws -> [DwarfLineNumberInfo] { + guard let lineSection = lineSection else { return [] } - var result: [LineNumberInfo] = [] + let end = lineSection.bytes.count + var result: [DwarfLineNumberInfo] = [] var cursor = ImageSourceCursor(source: lineSection, offset: 0) - while cursor.pos < bounds.end { + while cursor.pos < end { // 6.2.4 The Line Number Program Header // .1 unit_length @@ -1127,7 +870,7 @@ struct DwarfReader { } var dirNames: [String] = [] - var fileInfo: [FileInfo] = [] + var fileInfo: [DwarfFileInfo] = [] if version == 3 || version == 4 { // .11 include_directories @@ -1152,7 +895,7 @@ struct DwarfReader { // Prior to version 5, the compilation unit's filename is not included; // put a placeholder here for now, which we'll fix up later. - fileInfo.append(FileInfo( + fileInfo.append(DwarfFileInfo( path: "", directoryIndex: 0, timestamp: nil, @@ -1172,7 +915,7 @@ struct DwarfReader { let timestamp = try cursor.readULEB128() let size = try cursor.readULEB128() - fileInfo.append(FileInfo( + fileInfo.append(DwarfFileInfo( path: path, directoryIndex: Int(dirIndex), timestamp: timestamp != 0 ? Int(timestamp) : nil, @@ -1275,7 +1018,7 @@ struct DwarfReader { md5sum = nil } - fileInfo.append(FileInfo( + fileInfo.append(DwarfFileInfo( path: path, directoryIndex: dirIndex, timestamp: timestamp, @@ -1285,12 +1028,10 @@ struct DwarfReader { } // The actual program comes next - let program = try cursor.read(count: Int(nextOffset - cursor.pos), - as: UInt8.self) - + let program = cursor.source[cursor.pos.. { if tag != .DW_TAG_subprogram { return } - + refAttrs = try readDieAttributes( at: &cursor, unit: unit, @@ -2005,11 +1746,268 @@ struct DwarfReader { } +struct DwarfFileInfo { + var path: String + var directoryIndex: Int? + var timestamp: Int? + var size: UInt64? + var md5sum: [UInt8]? +} + +struct DwarfLineNumberState: CustomStringConvertible { + typealias Address = UInt64 + + var address: Address + var opIndex: UInt + var file: Int + var path: String + var line: Int + var column: Int + var isStmt: Bool + var basicBlock: Bool + var endSequence: Bool + var prologueEnd: Bool + var epilogueBegin: Bool + var isa: UInt + var discriminator: UInt + + var description: String { + var flags: [String] = [] + if isStmt { + flags.append("is_stmt") + } + if basicBlock { + flags.append("basic_block") + } + if endSequence { + flags.append("end_sequence") + } + if prologueEnd { + flags.append("prologue_end") + } + if epilogueBegin { + flags.append("epilogue_begin") + } + + let flagsString = flags.joined(separator:" ") + + return """ + \(hex(address)) \(pad(line, 6)) \(pad(column, 6)) \(pad(file, 6)) \ + \(pad(isa, 3)) \(pad(discriminator, 13)) \(flagsString) + """ + } +} + +struct DwarfLineNumberInfo { + typealias Address = UInt64 + + var baseOffset: Address + var version: Int + var addressSize: Int? + var selectorSize: Int? + var headerLength: UInt64 + var minimumInstructionLength: UInt + var maximumOpsPerInstruction: UInt + var defaultIsStmt: Bool + var lineBase: Int8 + var lineRange: UInt8 + var opcodeBase: UInt8 + var standardOpcodeLengths: [UInt64] + var directories: [String] = [] + var files: [DwarfFileInfo] = [] + var program: ImageSource + var shouldSwap: Bool + + /// Compute the full path for a file, given its index in the file table. + func fullPathForFile(index: Int) -> String { + if index >= files.count { + return "" + } + + let info = files[index] + if info.path.hasPrefix("/") { + return info.path + } + + let dirName: String + if let dirIndex = info.directoryIndex, + dirIndex < directories.count { + dirName = directories[dirIndex] + } else { + dirName = "" + } + + return "\(dirName)/\(info.path)" + } + + /// Execute the line number program, calling a closure for every line + /// table entry. + mutating func executeProgram( + line: (DwarfLineNumberState, inout Bool) -> () + ) throws { + let end = program.bytes.count + var cursor = ImageSourceCursor(source: program) + + func maybeSwap(_ x: T) -> T { + if shouldSwap { + return x.byteSwapped + } + return x + } + + // Table 6.4: Line number program initial state + let initialState = DwarfLineNumberState( + address: 0, + opIndex: 0, + file: 1, + path: fullPathForFile(index: 1), + line: 1, + column: 0, + isStmt: defaultIsStmt, + basicBlock: false, + endSequence: false, + prologueEnd: false, + epilogueBegin: false, + isa: 0, + discriminator: 0 + ) + + var state = initialState + + // Flag to allow fast exit + var done = false + + while !done && cursor.pos < end { + let opcode = try cursor.read(as: Dwarf_LNS_Opcode.self) + + if opcode.rawValue >= opcodeBase { + // Special opcode + let adjustedOpcode = UInt(opcode.rawValue - opcodeBase) + let advance = adjustedOpcode / UInt(lineRange) + let lineAdvance = adjustedOpcode % UInt(lineRange) + let instrAdvance + = (state.opIndex + advance) / maximumOpsPerInstruction + let newOp = (state.opIndex + advance) % maximumOpsPerInstruction + state.address += Address(instrAdvance) + state.opIndex = newOp + state.line += Int(lineBase) + Int(lineAdvance) + + line(state, &done) + + state.discriminator = 0 + state.basicBlock = false + state.prologueEnd = false + state.epilogueBegin = false + } else if opcode == .DW_LNS_extended { + // Extended opcode + let length = try cursor.readULEB128() + let opcode = try cursor.read(as: Dwarf_LNE_Opcode.self) + + switch opcode { + case .DW_LNE_end_sequence: + state.endSequence = true + line(state, &done) + state = initialState + case .DW_LNE_set_address: + let address: UInt64 + guard let addressSize = addressSize else { + throw DwarfError.unspecifiedAddressSize + } + switch addressSize { + case 4: + address = UInt64(maybeSwap(try cursor.read(as: UInt32.self))) + case 8: + address = maybeSwap(try cursor.read(as: UInt64.self)) + default: + throw DwarfError.badAddressSize(addressSize) + } + state.address = Address(address) + case .DW_LNE_define_file: + guard let path = try cursor.readString() else { + throw DwarfError.badString + } + let directoryIndex = try cursor.readULEB128() + let timestamp = try cursor.readULEB128() + let size = try cursor.readULEB128() + files.append(DwarfFileInfo( + path: path, + directoryIndex: Int(directoryIndex), + timestamp: timestamp != 0 ? Int(timestamp) : nil, + size: size != 0 ? size : nil, + md5sum: nil + )) + case .DW_LNE_set_discriminator: + let discriminator = try cursor.readULEB128() + state.discriminator = UInt(discriminator) + default: + cursor.pos += length - 1 + } + } else { + // Standard opcode + switch opcode { + case .DW_LNS_copy: + line(state, &done) + state.discriminator = 0 + state.basicBlock = false + state.prologueEnd = false + state.epilogueBegin = false + case .DW_LNS_advance_pc: + let advance = UInt(try cursor.readULEB128()) + let instrAdvance + = (state.opIndex + advance) / maximumOpsPerInstruction + let newOp = (state.opIndex + advance) % maximumOpsPerInstruction + state.address += Address(instrAdvance) + state.opIndex = newOp + case .DW_LNS_advance_line: + let advance = try cursor.readSLEB128() + state.line += Int(advance) + case .DW_LNS_set_file: + let file = Int(try cursor.readULEB128()) + state.file = file + state.path = fullPathForFile(index: state.file) + case .DW_LNS_set_column: + let column = Int(try cursor.readULEB128()) + state.column = column + case .DW_LNS_negate_stmt: + state.isStmt = !state.isStmt + case .DW_LNS_set_basic_block: + state.basicBlock = true + case .DW_LNS_const_add_pc: + let adjustedOpcode = UInt(255 - opcodeBase) + let advance = adjustedOpcode / UInt(lineRange) + let instrAdvance + = (state.opIndex + advance) / maximumOpsPerInstruction + let newOp = (state.opIndex + advance) % maximumOpsPerInstruction + state.address += Address(instrAdvance) + state.opIndex = newOp + case .DW_LNS_fixed_advance_pc: + let advance = try cursor.read(as: Dwarf_Half.self) + state.address += Address(advance) + state.opIndex = 0 + case .DW_LNS_set_prologue_end: + state.prologueEnd = true + case .DW_LNS_set_epilogue_begin: + state.epilogueBegin = true + case .DW_LNS_set_isa: + let isa = UInt(try cursor.readULEB128()) + state.isa = isa + default: + // Skip this unknown opcode + let length = standardOpcodeLengths[Int(opcode.rawValue)] + for _ in 0.. Bool { - guard let source = try? FileImageSource(path: path) else { + guard let source = try? ImageSource(path: path) else { print("\(path) was not accessible") return false } @@ -2017,7 +2015,7 @@ public func testDwarfReaderFor(path: String) -> Bool { if let elfImage = try? Elf32Image(source: source) { print("\(path) is a 32-bit ELF image") - var reader: DwarfReader> + var reader: DwarfReader do { reader = try DwarfReader(source: elfImage) } catch { @@ -2034,7 +2032,7 @@ public func testDwarfReaderFor(path: String) -> Bool { } else if let elfImage = try? Elf64Image(source: source) { print("\(path) is a 64-bit ELF image") - var reader: DwarfReader> + var reader: DwarfReader do { reader = try DwarfReader(source: elfImage) } catch { @@ -2060,5 +2058,3 @@ public func testDwarfReaderFor(path: String) -> Bool { return false } } - -#endif // os(Linux) diff --git a/stdlib/public/RuntimeModule/EightByteBuffer.swift b/stdlib/public/RuntimeModule/EightByteBuffer.swift new file mode 100644 index 00000000000..e1cfcc8ba42 --- /dev/null +++ b/stdlib/public/RuntimeModule/EightByteBuffer.swift @@ -0,0 +1,60 @@ +//===--- EightByteBuffer.swift --------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// +// +// A statically allocated buffer for holding a small number of bytes. +// +//===----------------------------------------------------------------------===// + +import Swift + +struct EightByteBuffer { + var word: UInt64 + + init() { + word = 0 + } + + init(_ qword: UInt64) { + word = qword.bigEndian + } + + init(_ qword: Int64) { + self.init(UInt64(bitPattern: qword)) + } + + init(_ value: T) where T: SignedInteger { + self.init(Int64(value)) + } + + init(_ value: T) { + self.init(UInt64(value)) + } + + subscript(ndx: Int) -> UInt8 { + get { + if ndx < 0 || ndx >= 8 { + fatalError("Index out of range") + } + return withUnsafeBytes(of: word) { buffer in + return buffer[ndx] + } + } + set(newValue) { + if ndx < 0 || ndx >= 8 { + fatalError("Index out of range") + } + withUnsafeMutableBytes(of: &word) { buffer in + buffer[ndx] = newValue + } + } + } +} diff --git a/stdlib/public/Backtracing/Elf.swift b/stdlib/public/RuntimeModule/Elf.swift similarity index 71% rename from stdlib/public/Backtracing/Elf.swift rename to stdlib/public/RuntimeModule/Elf.swift index 3c37ad47c0d..d2501b33d25 100644 --- a/stdlib/public/Backtracing/Elf.swift +++ b/stdlib/public/RuntimeModule/Elf.swift @@ -17,8 +17,6 @@ // ###FIXME: We shouldn't really use String for paths. -#if os(Linux) - import Swift #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) @@ -75,7 +73,6 @@ let EI_VERSION = swift.runtime.EI_VERSION let EI_OSABI = swift.runtime.EI_OSABI let EI_ABIVERSION = swift.runtime.EI_ABIVERSION let EI_PAD = swift.runtime.EI_PAD -let EI_NIDENT = BacktracingImpl.EI_NIDENT let ELFMAG0 = swift.runtime.ELFMAG0 let ELFMAG1 = swift.runtime.ELFMAG1 @@ -83,109 +80,105 @@ let ELFMAG2 = swift.runtime.ELFMAG2 let ELFMAG3 = swift.runtime.ELFMAG3 typealias Elf_Ehdr_Class = swift.runtime.Elf_Ehdr_Class -typealias Elf_Ehdr_Data = swift.runtime.Elf_Ehdr_Data +typealias Elf_Ehdr_Data = swift.runtime.Elf_Ehdr_Data typealias Elf_Ehdr_OsAbi = swift.runtime.Elf_Ehdr_OsAbi -let SHN_UNDEF = swift.runtime.SHN_UNDEF +let SHN_UNDEF = swift.runtime.SHN_UNDEF let SHN_LORESERVE = swift.runtime.SHN_LORESERVE -let SHN_LOPROC = swift.runtime.SHN_LOPROC -let SHN_HIPROC = swift.runtime.SHN_HIPROC -let SHN_LOOS = swift.runtime.SHN_LOOS -let SHN_HIOS = swift.runtime.SHN_HIOS -let SHN_ABS = swift.runtime.SHN_ABS -let SHN_COMMON = swift.runtime.SHN_COMMON -let SHN_XINDEX = swift.runtime.SHN_XINDEX +let SHN_LOPROC = swift.runtime.SHN_LOPROC +let SHN_HIPROC = swift.runtime.SHN_HIPROC +let SHN_LOOS = swift.runtime.SHN_LOOS +let SHN_HIOS = swift.runtime.SHN_HIOS +let SHN_ABS = swift.runtime.SHN_ABS +let SHN_COMMON = swift.runtime.SHN_COMMON +let SHN_XINDEX = swift.runtime.SHN_XINDEX let SHN_HIRESERVE = swift.runtime.SHN_HIRESERVE typealias Elf_Shdr_Type = swift.runtime.Elf_Shdr_Type -let SHF_WRITE = swift.runtime.SHF_WRITE -let SHF_ALLOC = swift.runtime.SHF_ALLOC -let SHF_EXECINSTR = swift.runtime.SHF_EXECINSTR -let SHF_MERGE = swift.runtime.SHF_MERGE -let SHF_STRINGS = swift.runtime.SHF_STRINGS -let SHF_INFO_LINK = swift.runtime.SHF_INFO_LINK -let SHF_LINK_ORDER = swift.runtime.SHF_LINK_ORDER +let SHF_WRITE = swift.runtime.SHF_WRITE +let SHF_ALLOC = swift.runtime.SHF_ALLOC +let SHF_EXECINSTR = swift.runtime.SHF_EXECINSTR +let SHF_MERGE = swift.runtime.SHF_MERGE +let SHF_STRINGS = swift.runtime.SHF_STRINGS +let SHF_INFO_LINK = swift.runtime.SHF_INFO_LINK +let SHF_LINK_ORDER = swift.runtime.SHF_LINK_ORDER let SHF_OS_NONCONFORMING = swift.runtime.SHF_OS_NONCONFORMING -let SHF_GROUP = swift.runtime.SHF_GROUP -let SHF_TLS = swift.runtime.SHF_TLS -let SHF_COMPRESSED = swift.runtime.SHF_COMPRESSED -let SHF_MASKOS = swift.runtime.SHF_MASKOS -let SHF_MASKPROC = swift.runtime.SHF_MASKPROC +let SHF_GROUP = swift.runtime.SHF_GROUP +let SHF_TLS = swift.runtime.SHF_TLS +let SHF_COMPRESSED = swift.runtime.SHF_COMPRESSED +let SHF_MASKOS = swift.runtime.SHF_MASKOS +let SHF_MASKPROC = swift.runtime.SHF_MASKPROC -let GRP_COMDAT = swift.runtime.GRP_COMDAT -let GRP_MASKOS = swift.runtime.GRP_MASKOS +let GRP_COMDAT = swift.runtime.GRP_COMDAT +let GRP_MASKOS = swift.runtime.GRP_MASKOS let GRP_MASKPROC = swift.runtime.GRP_MASKPROC typealias Elf_Chdr_Type = swift.runtime.Elf_Chdr_Type -typealias Elf_Sym_Binding = swift.runtime.Elf_Sym_Binding -typealias Elf_Sym_Type = swift.runtime.Elf_Sym_Type +typealias Elf_Sym_Binding = swift.runtime.Elf_Sym_Binding +typealias Elf_Sym_Type = swift.runtime.Elf_Sym_Type typealias Elf_Sym_Visibility = swift.runtime.Elf_Sym_Visibility -typealias Elf_Phdr_Type = swift.runtime.Elf_Phdr_Type +typealias Elf_Phdr_Type = swift.runtime.Elf_Phdr_Type typealias Elf_Phdr_Flags = swift.runtime.Elf_Phdr_Flags let PF_X = swift.runtime.PF_X let PF_W = swift.runtime.PF_W let PF_R = swift.runtime.PF_R -let PF_MASKOS = swift.runtime.PF_MASKOS +let PF_MASKOS = swift.runtime.PF_MASKOS let PF_MASKPROC = swift.runtime.PF_MASKPROC -let DT_NULL = swift.runtime.DT_NULL -let DT_NEEDED = swift.runtime.DT_NEEDED -let DT_PLTRELSZ = swift.runtime.DT_PLTRELSZ -let DT_PLTGOT = swift.runtime.DT_PLTGOT -let DT_HASH = swift.runtime.DT_HASH -let DT_STRTAB = swift.runtime.DT_STRTAB -let DT_SYMTAB = swift.runtime.DT_SYMTAB -let DT_RELA = swift.runtime.DT_RELA -let DT_RELASZ = swift.runtime.DT_RELASZ -let DT_RELAENT = swift.runtime.DT_RELAENT -let DT_STRSZ = swift.runtime.DT_STRSZ -let DT_SYMENT = swift.runtime.DT_SYMENT -let DT_INIT = swift.runtime.DT_INIT -let DT_FINI = swift.runtime.DT_FINI -let DT_SONAME = swift.runtime.DT_SONAME -let DT_RPATH = swift.runtime.DT_RPATH -let DT_SYMBOLIC = swift.runtime.DT_SYMBOLIC -let DT_REL = swift.runtime.DT_REL -let DT_RELSZ = swift.runtime.DT_RELSZ -let DT_RELENT = swift.runtime.DT_RELENT -let DT_PLTREL = swift.runtime.DT_PLTREL -let DT_DEBUG = swift.runtime.DT_DEBUG -let DT_TEXTREL = swift.runtime.DT_TEXTREL -let DT_JMPREL = swift.runtime.DT_JMPREL -let DT_BIND_NOW = swift.runtime.DT_BIND_NOW -let DT_INIT_ARRAY = swift.runtime.DT_INIT_ARRAY -let DT_FINI_ARRAY = swift.runtime.DT_FINI_ARRAY -let DT_INIT_ARRAYSZ = swift.runtime.DT_INIT_ARRAYSZ -let DT_FINI_ARRAYSZ = swift.runtime.DT_FINI_ARRAYSZ -let DT_RUNPATH = swift.runtime.DT_RUNPATH -let DT_FLAGS = swift.runtime.DT_FLAGS - -let DT_ENCODING = swift.runtime.DT_ENCODING - -let DT_PREINIT_ARRAY = swift.runtime.DT_PREINIT_ARRAY +let DT_NULL = swift.runtime.DT_NULL +let DT_NEEDED = swift.runtime.DT_NEEDED +let DT_PLTRELSZ = swift.runtime.DT_PLTRELSZ +let DT_PLTGOT = swift.runtime.DT_PLTGOT +let DT_HASH = swift.runtime.DT_HASH +let DT_STRTAB = swift.runtime.DT_STRTAB +let DT_SYMTAB = swift.runtime.DT_SYMTAB +let DT_RELA = swift.runtime.DT_RELA +let DT_RELASZ = swift.runtime.DT_RELASZ +let DT_RELAENT = swift.runtime.DT_RELAENT +let DT_STRSZ = swift.runtime.DT_STRSZ +let DT_SYMENT = swift.runtime.DT_SYMENT +let DT_INIT = swift.runtime.DT_INIT +let DT_FINI = swift.runtime.DT_FINI +let DT_SONAME = swift.runtime.DT_SONAME +let DT_RPATH = swift.runtime.DT_RPATH +let DT_SYMBOLIC = swift.runtime.DT_SYMBOLIC +let DT_REL = swift.runtime.DT_REL +let DT_RELSZ = swift.runtime.DT_RELSZ +let DT_RELENT = swift.runtime.DT_RELENT +let DT_PLTREL = swift.runtime.DT_PLTREL +let DT_DEBUG = swift.runtime.DT_DEBUG +let DT_TEXTREL = swift.runtime.DT_TEXTREL +let DT_JMPREL = swift.runtime.DT_JMPREL +let DT_BIND_NOW = swift.runtime.DT_BIND_NOW +let DT_INIT_ARRAY = swift.runtime.DT_INIT_ARRAY +let DT_FINI_ARRAY = swift.runtime.DT_FINI_ARRAY +let DT_INIT_ARRAYSZ = swift.runtime.DT_INIT_ARRAYSZ +let DT_FINI_ARRAYSZ = swift.runtime.DT_FINI_ARRAYSZ +let DT_RUNPATH = swift.runtime.DT_RUNPATH +let DT_FLAGS = swift.runtime.DT_FLAGS +let DT_ENCODING = swift.runtime.DT_ENCODING +let DT_PREINIT_ARRAY = swift.runtime.DT_PREINIT_ARRAY let DT_PREINIT_ARRAYSZ = swift.runtime.DT_PREINIT_ARRAYSZ +let DT_LOOS = swift.runtime.DT_LOOS +let DT_HIOS = swift.runtime.DT_HIOS +let DT_LOPROC = swift.runtime.DT_LOPROC +let DT_HIPROC = swift.runtime.DT_HIPROC -let DT_LOOS = swift.runtime.DT_LOOS -let DT_HIOS = swift.runtime.DT_HIOS - -let DT_LOPROC = swift.runtime.DT_LOPROC -let DT_HIPROC = swift.runtime.DT_HIPROC - -let DF_ORIGIN = swift.runtime.DF_ORIGIN -let DF_SYMBOLIC = swift.runtime.DF_SYMBOLIC -let DF_TEXTREL = swift.runtime.DF_TEXTREL -let DF_BIND_NOW = swift.runtime.DF_BIND_NOW +let DF_ORIGIN = swift.runtime.DF_ORIGIN +let DF_SYMBOLIC = swift.runtime.DF_SYMBOLIC +let DF_TEXTREL = swift.runtime.DF_TEXTREL +let DF_BIND_NOW = swift.runtime.DF_BIND_NOW let DF_STATIC_TLS = swift.runtime.DF_STATIC_TLS -let NT_GNU_ABI_TAG = swift.runtime.NT_GNU_ABI_TAG -let NT_GNU_HWCAP = swift.runtime.NT_GNU_HWCAP -let NT_GNU_BUILD_ID = swift.runtime.NT_GNU_BUILD_ID -let NT_GNU_GOLD_VERSION = swift.runtime.NT_GNU_GOLD_VERSION +let NT_GNU_ABI_TAG = swift.runtime.NT_GNU_ABI_TAG +let NT_GNU_HWCAP = swift.runtime.NT_GNU_HWCAP +let NT_GNU_BUILD_ID = swift.runtime.NT_GNU_BUILD_ID +let NT_GNU_GOLD_VERSION = swift.runtime.NT_GNU_GOLD_VERSION let NT_GNU_PROPERTY_TYPE_0 = swift.runtime.NT_GNU_PROPERTY_TYPE_0 typealias Elf32_Ehdr = swift.runtime.Elf32_Ehdr @@ -200,32 +193,35 @@ typealias Elf64_Chdr = swift.runtime.Elf64_Chdr typealias Elf32_Sym = swift.runtime.Elf32_Sym typealias Elf64_Sym = swift.runtime.Elf64_Sym -let ELF32_ST_BIND = swift.runtime.ELF32_ST_BIND -let ELF32_ST_TYPE = swift.runtime.ELF32_ST_TYPE -let ELF32_ST_INFO = swift.runtime.ELF32_ST_INFO +let ELF32_ST_BIND = swift.runtime.ELF32_ST_BIND +let ELF32_ST_TYPE = swift.runtime.ELF32_ST_TYPE +let ELF32_ST_INFO = swift.runtime.ELF32_ST_INFO let ELF32_ST_VISIBILITY = swift.runtime.ELF32_ST_VISIBILITY -let ELF64_ST_BIND = swift.runtime.ELF64_ST_BIND -let ELF64_ST_TYPE = swift.runtime.ELF64_ST_TYPE -let ELF64_ST_INFO = swift.runtime.ELF64_ST_INFO +let ELF64_ST_BIND = swift.runtime.ELF64_ST_BIND +let ELF64_ST_TYPE = swift.runtime.ELF64_ST_TYPE +let ELF64_ST_INFO = swift.runtime.ELF64_ST_INFO let ELF64_ST_VISIBILITY = swift.runtime.ELF64_ST_VISIBILITY -typealias Elf32_Rel = swift.runtime.Elf32_Rel +typealias Elf32_Rel = swift.runtime.Elf32_Rel typealias Elf32_Rela = swift.runtime.Elf32_Rela -typealias Elf64_Rel = swift.runtime.Elf64_Rel +typealias Elf64_Rel = swift.runtime.Elf64_Rel typealias Elf64_Rela = swift.runtime.Elf64_Rela -let ELF32_R_SYM = swift.runtime.ELF32_R_SYM +let ELF32_R_SYM = swift.runtime.ELF32_R_SYM let ELF32_R_TYPE = swift.runtime.ELF32_R_TYPE let ELF32_R_INFO = swift.runtime.ELF32_R_INFO -let ELF64_R_SYM = swift.runtime.ELF64_R_SYM +let ELF64_R_SYM = swift.runtime.ELF64_R_SYM let ELF64_R_TYPE = swift.runtime.ELF64_R_TYPE let ELF64_R_INFO = swift.runtime.ELF64_R_INFO typealias Elf32_Phdr = swift.runtime.Elf32_Phdr typealias Elf64_Phdr = swift.runtime.Elf64_Phdr +typealias Elf32_Nhdr = swift.runtime.Elf32_Nhdr +typealias Elf64_Nhdr = swift.runtime.Elf64_Nhdr + typealias Elf32_Dyn = swift.runtime.Elf32_Dyn typealias Elf64_Dyn = swift.runtime.Elf64_Dyn @@ -302,7 +298,7 @@ private let crc32Table: [UInt32] = [ ] private func updateCrc(_ crc: UInt32, - _ bytes: UnsafeBufferPointer) -> UInt32 { + _ bytes: UnsafeRawBufferPointer) -> UInt32 { var theCrc = ~crc for byte in bytes { theCrc = crc32Table[Int(UInt8(truncatingIfNeeded: theCrc) @@ -420,6 +416,12 @@ extension Elf64_Chdr: Elf_Chdr { } } +extension Elf_Chdr_Type: ByteSwappable { + var byteSwapped: Self { + return Elf_Chdr_Type(rawValue: rawValue.byteSwapped)! + } +} + extension Elf32_Sym: ByteSwappable { var byteSwapped: Self { return Elf32_Sym( @@ -970,19 +972,17 @@ struct Elf64Traits: ElfTraits { // .. ElfStringSection ......................................................... struct ElfStringSection { - let bytes: [UInt8] + let source: ImageSource func getStringAt(index: Int) -> String? { - if index < 0 || index >= bytes.count { + if index < 0 || index >= source.bytes.count { return nil } - let slice = bytes[index...] + let slice = UnsafeRawBufferPointer(rebasing: source.bytes[index...]) var len: Int = 0 - slice.withUnsafeBufferPointer{ ptr in - len = strnlen(ptr.baseAddress!, ptr.count) - } - return String(decoding: bytes[index.. (any ImageSource)? - func getSection(_ name: String) -> (any ImageSource)? -} - -extension ElfGetSectionProtocol { - func getSection(_ name: String) -> (any ImageSource)? { - return getSection(name, debug: false) - } -} - protocol ElfSymbolProtocol: Equatable { associatedtype Address: FixedWidthInteger associatedtype Size: FixedWidthInteger @@ -1027,22 +1016,14 @@ protocol ElfSymbolTableProtocol { func lookupSymbol(address: Traits.Address) -> Symbol? } -protocol ElfImageProtocol: Image, ElfGetSectionProtocol, DwarfSource { +protocol ElfSymbolLookupProtocol { associatedtype Traits: ElfTraits - associatedtype SymbolTable: ElfSymbolTableProtocol - where SymbolTable.Traits == Traits + typealias CallSiteInfo = DwarfReader>.CallSiteInfo + typealias SourceLocation = SymbolicatedBacktrace.SourceLocation - var header: Traits.Ehdr { get } - var programHeaders: [Traits.Phdr] { get } - var sectionHeaders: [Traits.Shdr]? { get } - - var imageName: String { get } - var debugImage: (any ElfImageProtocol)? { get } - var debugLinkCRC: UInt32? { get } - - var symbolTable: SymbolTable { get } - - func _getSymbolTable(debug: Bool) -> SymbolTable + func lookupSymbol(address: Traits.Address) -> ImageSymbol? + func inlineCallSites(at address: Traits.Address) -> ArraySlice + func sourceLocation(for address: Traits.Address) throws -> SourceLocation? } struct ElfSymbolTable: ElfSymbolTableProtocol { @@ -1065,43 +1046,41 @@ struct ElfSymbolTable: ElfSymbolTableProtocol { init() {} - init?(image: ElfImage) { + @_specialize(kind: full, where SomeElfTraits == Elf32Traits) + @_specialize(kind: full, where SomeElfTraits == Elf64Traits) + init?(image: ElfImage) { guard let strtab = image.getSection(".strtab", debug: false), - let symtab = image.getSection(".symtab", debug: false), - let strings = strtab.fetchAllBytes(), - let symdata = symtab.fetchAllBytes() else { + let symtab = image.getSection(".symtab", debug: false) else { return nil } - let stringSect = ElfStringSection(bytes: strings) + let stringSect = ElfStringSection(source: strtab) // Extract all the data - symdata.withUnsafeBufferPointer{ - $0.withMemoryRebound(to: Traits.Sym.self) { symbols in - for symbol in symbols { - // Ignore things that are not functions - if symbol.st_type != .STT_FUNC { - continue - } - - // Ignore anything undefined - if symbol.st_shndx == SHN_UNDEF { - continue - } - - _symbols.append( - Symbol( - name: (stringSect.getStringAt(index: Int(symbol.st_name)) - ?? ""), - value: symbol.st_value, - size: symbol.st_size, - sectionIndex: Int(symbol.st_shndx), - binding: symbol.st_binding, - type: symbol.st_type, - visibility: symbol.st_visibility - ) - ) + symtab.bytes.withMemoryRebound(to: Traits.Sym.self) { symbols in + for symbol in symbols { + // Ignore things that are not functions + if symbol.st_type != .STT_FUNC { + continue } + + // Ignore anything undefined + if symbol.st_shndx == SHN_UNDEF { + continue + } + + _symbols.append( + Symbol( + name: (stringSect.getStringAt(index: Int(symbol.st_name)) + ?? ""), + value: symbol.st_value, + size: symbol.st_size, + sectionIndex: Int(symbol.st_shndx), + binding: symbol.st_binding, + type: symbol.st_type, + visibility: symbol.st_visibility + ) + ) } } @@ -1117,6 +1096,8 @@ struct ElfSymbolTable: ElfSymbolTableProtocol { _symbols = sortedSymbols } + @_specialize(kind: full, where SomeElfTraits == Elf32Traits) + @_specialize(kind: full, where SomeElfTraits == Elf64Traits) public func merged(with other: ElfSymbolTable) -> ElfSymbolTable { var merged: [Symbol] = [] @@ -1159,6 +1140,8 @@ struct ElfSymbolTable: ElfSymbolTableProtocol { return ElfSymbolTable(sortedSymbols: merged) } + @_specialize(kind: full, where SomeElfTraits == Elf32Traits) + @_specialize(kind: full, where SomeElfTraits == Elf64Traits) public func lookupSymbol(address: Traits.Address) -> Symbol? { var min = 0 var max = _symbols.count @@ -1179,7 +1162,7 @@ struct ElfSymbolTable: ElfSymbolTableProtocol { ndx -= 1 } return _symbols[ndx] - } else if symbol.value < address { + } else if symbol.value <= address { min = mid + 1 } else if symbol.value > address { max = mid @@ -1190,27 +1173,28 @@ struct ElfSymbolTable: ElfSymbolTableProtocol { } } -class ElfImage: ElfImageProtocol { +final class ElfImage + : DwarfSource, ElfSymbolLookupProtocol { typealias Traits = SomeElfTraits - typealias Source = SomeImageSource typealias SymbolTable = ElfSymbolTable // This is arbitrary and it isn't in the spec let maxNoteNameLength = 256 - var baseAddress: Source.Address - var endAddress: Source.Address + var baseAddress: ImageSource.Address + var endAddress: ImageSource.Address - var source: SomeImageSource + var source: ImageSource var header: Traits.Ehdr var programHeaders: [Traits.Phdr] var sectionHeaders: [Traits.Shdr]? var shouldByteSwap: Bool { return header.shouldByteSwap } - required init(source: SomeImageSource, - baseAddress: Source.Address = 0, - endAddress: Source.Address = 0) throws { + @_specialize(kind: full, where SomeElfTraits == Elf32Traits) + @_specialize(kind: full, where SomeElfTraits == Elf64Traits) + required init(source: ImageSource, + baseAddress: ImageSource.Address = 0, + endAddress: ImageSource.Address = 0) throws { self.source = source self.baseAddress = baseAddress self.endAddress = endAddress @@ -1237,11 +1221,11 @@ class ElfImage + var image: ElfImage struct NoteIterator: IteratorProtocol { - var image: ElfImage + var image: ElfImage var hdrNdx = -1 - var noteAddr = Source.Address() - var noteEnd = Source.Address() + var noteAddr = ImageSource.Address() + var noteEnd = ImageSource.Address() - init(image: ElfImage) { + @_specialize(kind: full, where SomeElfTraits == Elf32Traits) + @_specialize(kind: full, where SomeElfTraits == Elf64Traits) + init(image: ElfImage) { self.image = image } @@ -1287,15 +1273,25 @@ class ElfImage Note? { + let byteSwap = image.shouldByteSwap + func maybeSwap(_ x: T) -> T { + if byteSwap { + return x.byteSwapped + } + return x + } + if hdrNdx >= image.programHeaders.count { return nil } @@ -1311,9 +1307,10 @@ class ElfImage.size) + noteAddr += ImageSource.Address(MemoryLayout.size) if noteEnd - noteAddr < nhdr.n_namesz { // The segment is probably corrupted @@ -1322,12 +1319,15 @@ class ElfImage 0 ? nhdr.n_namesz - 1 : 0 - let nameBytes = try image.fetch(from: noteAddr, - count: Int(nameLen), - as: UInt8.self) - let name = String(decoding: nameBytes, as: UTF8.self) + guard let name = try image.source.fetchString(from: noteAddr, + length: Int(nameLen)) + else { + // Bad note name + noteAddr = noteEnd + continue + } - noteAddr += Source.Address(nhdr.n_namesz) + noteAddr += ImageSource.Address(nhdr.n_namesz) if (noteAddr & 3) != 0 { noteAddr += 4 - (noteAddr & 3) } @@ -1338,11 +1338,11 @@ class ElfImage.allocate(capacity: bufSize) - defer { - buffer.deallocate() - } - - var pos = bounds.base - var remaining = bounds.size - var crc: UInt32 = 0 - do { - while remaining > 0 { - let todo = min(bufSize, Int(remaining)) - let slice = buffer[..(rebasing: slice) - - try fetch(from: pos, into: chunk) - - crc = updateCrc(crc, UnsafeBufferPointer(chunk)) - - remaining -= Source.Size(todo) - pos += Source.Address(todo) - } - } catch { - return nil - } - + let crc = updateCrc(0, source.bytes) + _debugLinkCRC = crc return crc } struct Range { - var base: Source.Address - var size: Source.Size + var base: ImageSource.Address + var size: ImageSource.Size } struct EHFrameInfo { @@ -1442,19 +1414,21 @@ class ElfImage.size) { continue } - guard let ehdr = try? fetch(from: Source.Address(ehFrameHdrRange.base), - as: EHFrameHdr.self) else { + guard let ehdr = try? source.fetch( + from: ImageSource.Address(ehFrameHdrRange.base), + as: EHFrameHdr.self + ) else { continue } @@ -1462,11 +1436,11 @@ class ElfImage.size) + let pc = ehFrameHdrRange.base + ImageSource.Address(MemoryLayout.size) guard let (_, eh_frame_ptr) = - try? source.fetchEHValue(from: Source.Address(pc), + try? source.fetchEHValue(from: ImageSource.Address(pc), with: ehdr.eh_frame_ptr_enc, - pc: Source.Address(pc)) else { + pc: ImageSource.Address(pc)) else { continue } @@ -1476,30 +1450,27 @@ class ElfImage? + var debugImage: ElfImage? { if let checked = _checkedDebugImage, checked { return _debugImage } - let tryPath = { [self] (_ path: String) -> (any ElfImageProtocol)? in + let tryPath = { [self] (_ path: String) -> ElfImage? in do { - let fileSource = try FileImageSource(path: path) - let image = try ElfImage(source: fileSource) + let fileSource = try ImageSource(path: path) + let image = try ElfImage(source: fileSource) _debugImage = image return image } catch { @@ -1562,7 +1533,7 @@ class ElfImage (any ElfImageProtocol)? in + let tryLink = { (_ link: String) -> ElfImage? in if let image = tryPath("\(imageDir)/\(link)") { return image } @@ -1592,9 +1563,8 @@ class ElfImage(source: source) + let source = try ImageSource(lzmaCompressedImageSource: debugData) + _debugImage = try ElfImage(source: source) _checkedDebugImage = true return _debugImage } catch let CompressedImageSourceError.libraryNotFound(library) { @@ -1617,15 +1587,17 @@ class ElfImage (any ImageSource)? { + @_specialize(kind: full, where SomeElfTraits == Elf32Traits) + @_specialize(kind: full, where SomeElfTraits == Elf64Traits) + func getSection(_ name: String, debug: Bool = false) -> ImageSource? { if let sectionHeaders = sectionHeaders { let zname = ".z" + name.dropFirst() let stringShdr = sectionHeaders[Int(header.e_shstrndx)] do { - let bytes = try source.fetch(from: Source.Address(stringShdr.sh_offset), - count: Int(stringShdr.sh_size), - as: UInt8.self) - let stringSect = ElfStringSection(bytes: bytes) + let base = ImageSource.Address(stringShdr.sh_offset) + let end = base + ImageSource.Size(stringShdr.sh_size) + let stringSource = source[base..(source: subSource) + return try ImageSource(elfCompressedImageSource: subSource, + traits: Traits.self) } else { return subSource } } if zname == sname { - let subSource = SubImageSource(parent: source, - baseAddress: Source.Address(shdr.sh_offset), - length: Source.Size(shdr.sh_size)) - return try ElfGNUCompressedImageSource(source: subSource) + let base = ImageSource.Address(shdr.sh_offset) + let end = base + ImageSource.Size(shdr.sh_size) + let subSource = source[base.. ImageSymbol? { - let relativeAddress = Traits.Address(address - baseAddress) + public func lookupSymbol(address: Traits.Address) -> ImageSymbol? { + let relativeAddress = address - Traits.Address(baseAddress) guard let symbol = symbolTable.lookupSymbol(address: relativeAddress) else { return nil } @@ -1800,7 +1766,7 @@ class ElfImage (any ImageSource)? { + func getDwarfSection(_ section: DwarfSection) -> ImageSource? { switch section { case .debugAbbrev: return getSection(".debug_abbrev") case .debugAddr: return getSection(".debug_addr") @@ -1832,7 +1798,9 @@ class ElfImage.CallSiteInfo - func inlineCallSites(at address: Address) -> ArraySlice { + func inlineCallSites( + at address: Traits.Address + ) -> ArraySlice { guard let callSiteInfo = dwarfReader?.inlineCallSites else { return [][0..<0] } @@ -1870,9 +1838,11 @@ class ElfImage SourceLocation? { + func sourceLocation( + for address: Traits.Address + ) throws -> SourceLocation? { var result: SourceLocation? = nil - var prevState: DwarfReader.LineNumberState? = nil + var prevState: DwarfLineNumberState? = nil guard let dwarfReader = dwarfReader else { return nil } @@ -1901,14 +1871,157 @@ class ElfImage = ElfImage -typealias Elf64Image = ElfImage +typealias Elf32Image = ElfImage +typealias Elf64Image = ElfImage + +// .. Checking for ELF images .................................................. + +/// Test if there is a valid ELF image at the specified address; if there is, +/// extract the address range for the text segment and the UUID, if any. +@_specialize(kind: full, where R == UnsafeLocalMemoryReader) +@_specialize(kind: full, where R == RemoteMemoryReader) +#if os(Linux) +@_specialize(kind: full, where R == MemserverMemoryReader) +#endif +func getElfImageInfo(at address: R.Address, + using reader: R) + -> (endOfText: R.Address, uuid: [UInt8]?)? +{ + do { + // Check the magic number first + let magic = try reader.fetch(from: address, as: Elf_Magic.self) + + if magic != ElfMagic { + return nil + } + + // Read the class from the next byte + let elfClass = Elf_Ehdr_Class(rawValue: try reader.fetch(from: address + 4, + as: UInt8.self)) + + if elfClass == .ELFCLASS32 { + return try getElfImageInfo(at: address, using: reader, + traits: Elf32Traits.self) + } else if elfClass == .ELFCLASS64 { + return try getElfImageInfo(at: address, using: reader, + traits: Elf64Traits.self) + } else { + return nil + } + } catch { + return nil + } +} + +@_specialize(kind: full, where R == UnsafeLocalMemoryReader, Traits == Elf32Traits) +@_specialize(kind: full, where R == UnsafeLocalMemoryReader, Traits == Elf64Traits) +@_specialize(kind: full, where R == RemoteMemoryReader, Traits == Elf32Traits) +@_specialize(kind: full, where R == RemoteMemoryReader, Traits == Elf64Traits) +#if os(Linux) +@_specialize(kind: full, where R == MemserverMemoryReader, Traits == Elf32Traits) +@_specialize(kind: full, where R == MemserverMemoryReader, Traits == Elf64Traits) +#endif +func getElfImageInfo( + at address: R.Address, + using reader: R, + traits: Traits.Type +) throws -> (endOfText: R.Address, uuid: [UInt8]?)? { + // Grab the whole 32-bit header + let unswappedHeader = try reader.fetch(from: address, as: Traits.Ehdr.self) + + let header: Traits.Ehdr + if unswappedHeader.shouldByteSwap { + header = unswappedHeader.byteSwapped + } else { + header = unswappedHeader + } + + let byteSwap = header.shouldByteSwap + func maybeSwap(_ x: T) -> T { + if byteSwap { + return x.byteSwapped + } + return x + } + + var endOfText = address + var uuid: [UInt8]? = nil + + // Find the last loadable executable segment, and scan for PT_NOTE + // segments that contain the UUID + var phAddr = ImageSource.Address(address) + ImageSource.Size(header.e_phoff) + for _ in 0...size) + + if noteEnd - noteAddr < nhdr.n_namesz { + // This segment is probably corrupted, so skip it + noteAddr = noteEnd + continue + } + + var isBuildId = false + let nameLen = nhdr.n_namesz > 0 ? nhdr.n_namesz - 1 : 0 + + // Test if this is a "GNU" NT_GNU_BUILD_ID note + if nameLen == 3 { + let byte0 = try reader.fetch(from: noteAddr, as: UInt8.self) + let byte1 = try reader.fetch(from: noteAddr + 1, as: UInt8.self) + let byte2 = try reader.fetch(from: noteAddr + 2, as: UInt8.self) + + if byte0 == 0x47 && byte1 == 0x4e && byte2 == 0x55 && + UInt32(nhdr.n_type) == NT_GNU_BUILD_ID { + isBuildId = true + } + } + + noteAddr += ImageSource.Size(nhdr.n_namesz) + if (noteAddr & 3) != 0 { + noteAddr += 4 - (noteAddr & 3) + } + + if noteEnd - noteAddr < nhdr.n_descsz { + // Corrupted segment, skip + noteAddr = noteEnd + continue + } + + if isBuildId { + uuid = try reader.fetch(from: noteAddr, + count: Int(nhdr.n_descsz), + as: UInt8.self) + } + + noteAddr += ImageSource.Size(nhdr.n_descsz) + if (noteAddr & 3) != 0 { + noteAddr += 4 - (noteAddr & 3) + } + } + } + + phAddr += ImageSource.Address(header.e_phentsize) + } + + return (endOfText: endOfText, uuid: uuid) +} // .. Testing .................................................................. @_spi(ElfTest) public func testElfImageAt(path: String) -> Bool { - guard let source = try? FileImageSource(path: path) else { + guard let source = try? ImageSource(path: path) else { print("\(path) was not accessible") return false } @@ -1978,5 +2091,3 @@ public func testElfImageAt(path: String) -> Bool { return false } } - -#endif // os(Linux) diff --git a/stdlib/public/RuntimeModule/ElfImageCache.swift b/stdlib/public/RuntimeModule/ElfImageCache.swift new file mode 100644 index 00000000000..fb34adbd05c --- /dev/null +++ b/stdlib/public/RuntimeModule/ElfImageCache.swift @@ -0,0 +1,95 @@ +//===--- ElfImageCache.swift - ELF support for Swift ----------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 a per-thread Elf image cache that improves efficiency when +// taking multiple backtraces by avoiding loading ELF images multiple times. +// +//===----------------------------------------------------------------------===// + +import Swift + +#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) +internal import Darwin +#elseif os(Windows) +internal import ucrt +#elseif canImport(Glibc) +internal import Glibc +#elseif canImport(Musl) +internal import Musl +#endif + +/// Provides a per-thread image cache for ELF image processing. This means +/// if you take multiple backtraces from a thread, you won't load the same +/// image multiple times. +final class ElfImageCache { + var elf32: [String: Elf32Image] = [:] + var elf64: [String: Elf64Image] = [:] + + func purge() { + elf32 = [:] + elf64 = [:] + } + + enum Result { + case elf32Image(Elf32Image) + case elf64Image(Elf64Image) + } + func lookup(path: String?) -> Result? { + guard let path = path else { + return nil + } + if let image = elf32[path] { + return .elf32Image(image) + } + if let image = elf64[path] { + return .elf64Image(image) + } + if let source = try? ImageSource(path: path) { + if let elfImage = try? Elf32Image(source: source) { + elf32[path] = elfImage + return .elf32Image(elfImage) + } + if let elfImage = try? Elf64Image(source: source) { + elf64[path] = elfImage + return .elf64Image(elfImage) + } + } + return nil + } + + private static var key: pthread_key_t = { + var theKey = pthread_key_t() + let err = pthread_key_create( + &theKey, + { rawPtr in + let ptr = Unmanaged.fromOpaque( + notMutable(notOptional(rawPtr)) + ) + ptr.release() + } + ) + if err != 0 { + fatalError("Unable to create TSD key for ElfImageCache") + } + return theKey + }() + + static var threadLocal: ElfImageCache { + guard let rawPtr = pthread_getspecific(key) else { + let cache = Unmanaged.passRetained(ElfImageCache()) + pthread_setspecific(key, cache.toOpaque()) + return cache.takeUnretainedValue() + } + let cache = Unmanaged.fromOpaque(rawPtr) + return cache.takeUnretainedValue() + } +} diff --git a/stdlib/public/Backtracing/FramePointerUnwinder.swift b/stdlib/public/RuntimeModule/FramePointerUnwinder.swift similarity index 51% rename from stdlib/public/Backtracing/FramePointerUnwinder.swift rename to stdlib/public/RuntimeModule/FramePointerUnwinder.swift index 9d5b502f35c..3b874bb9425 100644 --- a/stdlib/public/Backtracing/FramePointerUnwinder.swift +++ b/stdlib/public/RuntimeModule/FramePointerUnwinder.swift @@ -20,7 +20,7 @@ import Swift public struct FramePointerUnwinder: Sequence, IteratorProtocol { public typealias Context = C public typealias MemoryReader = M - public typealias Address = MemoryReader.Address + public typealias Address = Context.Address var pc: Address var fp: Address @@ -30,15 +30,18 @@ public struct FramePointerUnwinder: Sequence, Itera var done: Bool #if os(Linux) - var elf32Cache: [Int:Elf32Image] = [:] - var elf64Cache: [Int:Elf64Image] = [:] - var images: [Backtrace.Image]? + var images: ImageMap? #endif var reader: MemoryReader + @_specialize(exported: true, kind: full, where C == HostContext, M == UnsafeLocalMemoryReader) + @_specialize(exported: true, kind: full, where C == HostContext, M == RemoteMemoryReader) + #if os(Linux) + @_specialize(exported: true, kind: full, where C == HostContext, M == MemserverMemoryReader) + #endif public init(context: Context, - images: [Backtrace.Image]?, + images: ImageMap?, memoryReader: MemoryReader) { pc = Address(context.programCounter) @@ -73,42 +76,48 @@ public struct FramePointerUnwinder: Sequence, Itera return false } + @_specialize(exported: true, kind: full, where C == HostContext, M == UnsafeLocalMemoryReader) + @_specialize(exported: true, kind: full, where C == HostContext, M == RemoteMemoryReader) + #if os(Linux) + @_specialize(exported: true, kind: full, where C == HostContext, M == MemserverMemoryReader) + #endif private mutating func isAsyncPC(_ pc: Address) -> Bool { // On Linux, we need to examine the PC to see if this is an async frame #if os(Linux) - let address = FileImageSource.Address(pc) + let address = MemoryReader.Address(pc) if let images = images, - let imageNdx = images.firstIndex( - where: { address >= $0.baseAddress && address < $0.endOfText } - ) { - let relativeAddress = address - FileImageSource.Address(images[imageNdx].baseAddress) - var elf32Image = elf32Cache[imageNdx] - var elf64Image = elf64Cache[imageNdx] + let imageNdx = images.indexOfImage(at: Backtrace.Address(address)) { + let base = MemoryReader.Address(images[imageNdx].baseAddress)! + let relativeAddress = address - base + let cache = ElfImageCache.threadLocal - if elf32Image == nil && elf64Image == nil { - if let source = try? FileImageSource(path: images[imageNdx].path) { - if let elfImage = try? Elf32Image(source: source) { - elf32Image = elfImage - elf32Cache[imageNdx] = elfImage - } else if let elfImage = try? Elf64Image(source: source) { - elf64Image = elfImage - elf64Cache[imageNdx] = elfImage - } + if let hit = cache.lookup(path: images[imageNdx].path) { + switch hit { + case let .elf32Image(image): + if let theSymbol = image.lookupSymbol( + address: Elf32Image.Traits.Address(relativeAddress) + ) { + return isAsyncSymbol(theSymbol.name) + } + case let .elf64Image(image): + if let theSymbol = image.lookupSymbol( + address: Elf64Image.Traits.Address(relativeAddress)) { + return isAsyncSymbol(theSymbol.name) + } } } - - if let theSymbol = elf32Image?.lookupSymbol(address: relativeAddress) { - return isAsyncSymbol(theSymbol.name) - } else if let theSymbol = elf64Image?.lookupSymbol(address: relativeAddress) { - return isAsyncSymbol(theSymbol.name) - } } #endif return false } + @_specialize(exported: true, kind: full, where C == HostContext, M == UnsafeLocalMemoryReader) + @_specialize(exported: true, kind: full, where C == HostContext, M == RemoteMemoryReader) + #if os(Linux) + @_specialize(exported: true, kind: full, where C == HostContext, M == MemserverMemoryReader) + #endif 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 @@ -119,15 +128,25 @@ public struct FramePointerUnwinder: Sequence, Itera #endif } + @_specialize(exported: true, kind: full, where C == HostContext, M == UnsafeLocalMemoryReader) + @_specialize(exported: true, kind: full, where C == HostContext, M == RemoteMemoryReader) + #if os(Linux) + @_specialize(exported: true, kind: full, where C == HostContext, M == MemserverMemoryReader) + #endif private func stripPtrAuth(_ address: Address) -> Address { - return Address(Context.stripPtrAuth(address: Context.Address(address))) + return Context.stripPtrAuth(address: address) } + @_specialize(exported: true, kind: full, where C == HostContext, M == UnsafeLocalMemoryReader) + @_specialize(exported: true, kind: full, where C == HostContext, M == RemoteMemoryReader) + #if os(Linux) + @_specialize(exported: true, kind: full, where C == HostContext, M == MemserverMemoryReader) + #endif private mutating func fetchAsyncContext() -> Bool { let strippedFp = stripPtrAuth(fp) do { - asyncContext = try reader.fetch(from: Address(strippedFp - 8), + asyncContext = try reader.fetch(from: MemoryReader.Address(strippedFp - 8), as: Address.self) return true } catch { @@ -135,7 +154,12 @@ public struct FramePointerUnwinder: Sequence, Itera } } - public mutating func next() -> Backtrace.Frame? { + @_specialize(exported: true, kind: full, where C == HostContext, M == UnsafeLocalMemoryReader) + @_specialize(exported: true, kind: full, where C == HostContext, M == RemoteMemoryReader) + #if os(Linux) + @_specialize(exported: true, kind: full, where C == HostContext, M == MemserverMemoryReader) + #endif + public mutating func next() -> RichFrame
? { if done { return nil } @@ -143,7 +167,7 @@ public struct FramePointerUnwinder: Sequence, Itera if first { first = false pc = stripPtrAuth(pc) - return .programCounter(Backtrace.Address(pc)) + return .programCounter(pc) } if !isAsync { @@ -153,17 +177,20 @@ public struct FramePointerUnwinder: Sequence, Itera let strippedFp = stripPtrAuth(fp) if strippedFp == 0 - || !Context.isAlignedForStack(framePointer: - Context.Address(strippedFp)) { + || !Context.isAlignedForStack(framePointer:strippedFp) { done = true 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) + pc = stripPtrAuth(try reader.fetch( + from:MemoryReader.Address( + strippedFp + + Address(MemoryLayout
.size) + ), + as: Address.self)) + next = try reader.fetch(from: MemoryReader.Address(strippedFp), + as: Address.self) } catch { done = true return nil @@ -176,7 +203,7 @@ public struct FramePointerUnwinder: Sequence, Itera if !isAsyncFrame(next) { fp = next - return .returnAddress(Backtrace.Address(pc)) + return .returnAddress(pc) } } @@ -202,8 +229,10 @@ public struct FramePointerUnwinder: Sequence, Itera // 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) + var next32 = try reader.fetch(from: MemoryReader.Address(strippedCtx), + as: UInt32.self) + var pc32 = try reader.fetch(from: MemoryReader.Address(strippedCtx + 4), + as: UInt32.self) next = Address(next32) pc = stripPtrAuth(Address(pc32)) @@ -215,8 +244,10 @@ public struct FramePointerUnwinder: Sequence, Itera // 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)) + next = try reader.fetch(from: MemoryReader.Address(strippedCtx), + as: Address.self) + pc = stripPtrAuth(try reader.fetch(from: MemoryReader.Address(strippedCtx + 8), + as: Address.self)) } catch { done = true return nil @@ -225,7 +256,6 @@ public struct FramePointerUnwinder: Sequence, Itera #endif asyncContext = next - - return .asyncResumePoint(Backtrace.Address(pc)) + return .asyncResumePoint(pc) } } diff --git a/stdlib/public/Backtracing/Image.swift b/stdlib/public/RuntimeModule/Image.swift similarity index 96% rename from stdlib/public/Backtracing/Image.swift rename to stdlib/public/RuntimeModule/Image.swift index 7f3d1ef6eba..e43b8f16263 100644 --- a/stdlib/public/Backtracing/Image.swift +++ b/stdlib/public/RuntimeModule/Image.swift @@ -23,17 +23,15 @@ struct ImageSymbol { } internal protocol Image { - associatedtype Source: ImageSource - typealias UUID = [UInt8] - typealias Address = Source.Address + typealias Address = ImageSource.Address - init(source: Source, baseAddress: Address, endAddress: Address) throws + init(source: ImageSource, baseAddress: Address, endAddress: Address) throws var baseAddress: Address { get set } var endAddress: Address { get set } - var source: Source { get } + var source: ImageSource { get } var uuid: UUID? { get } var shouldByteSwap: Bool { get } diff --git a/stdlib/public/RuntimeModule/ImageMap+Darwin.swift b/stdlib/public/RuntimeModule/ImageMap+Darwin.swift new file mode 100644 index 00000000000..ee0415f7378 --- /dev/null +++ b/stdlib/public/RuntimeModule/ImageMap+Darwin.swift @@ -0,0 +1,131 @@ +//===--- ImageMap+Darwin.swift --------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// +// +// Darwin specifics for ImageMap capture. +// +//===----------------------------------------------------------------------===// + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + +import Swift + +internal import Darwin +internal import BacktracingImpl.OS.Darwin + +fileprivate func getSysCtlString(_ name: String) -> String? { + return withUnsafeTemporaryAllocation(byteCount: 256, alignment: 16) { + (buffer: UnsafeMutableRawBufferPointer) -> String? in + + var len = buffer.count + let ret = sysctlbyname(name, + buffer.baseAddress, &len, + nil, 0) + if ret != 0 { + return nil + } + + return String(validatingUTF8: + buffer.baseAddress!.assumingMemoryBound(to: CChar.self)) + } +} + +extension ImageMap { + + private static let platform = { + #if os(macOS) + var platform = "macOS" + #elseif os(iOS) + var platform = "iOS" + #elseif os(watchOS) + var platform = "watchOS" + #elseif os(tvOS) + var platform = "tvOS" + #elseif os(visionOS) + var platform = "visionOS" + #endif + + let osVersion = getSysCtlString("kern.osversion") ?? "" + let osProductVersion = getSysCtlString("kern.osproductversion") ?? "" + + return "\(platform) \(osProductVersion) (\(osVersion))" + }() + + 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) + } + + @_spi(Internal) + public static func capture(for process: Any) -> ImageMap { + var images: [Image] = [] + 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(validatingCString: name!), name == "__TEXT" { + endOfText = address + size + } + } + + images.append(Image(name: name, + path: pathString, + uniqueID: theUUID, + baseAddress: machHeaderAddress, + endOfText: endOfText)) + } + } + } + + images.sort(by: { $0.baseAddress < $1.baseAddress }) + + return ImageMap( + platform: ImageMap.platform, + images: images, + wordSize: .sixtyFourBit + ) + } + +} + +#endif // os(macOS) || os(iOS) || os(watchOS) || os(tvOS) diff --git a/stdlib/public/RuntimeModule/ImageMap+Linux.swift b/stdlib/public/RuntimeModule/ImageMap+Linux.swift new file mode 100644 index 00000000000..6a565ddad95 --- /dev/null +++ b/stdlib/public/RuntimeModule/ImageMap+Linux.swift @@ -0,0 +1,169 @@ +//===--- ImageMap+Linux.swift --------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// +// +// Linux specifics for ImageMap capture. +// +//===----------------------------------------------------------------------===// + +#if os(Linux) + +import Swift + +#if canImport(Glibc) +internal import Glibc +#elseif canImport(Musl) +internal import Musl +#endif + +internal import BacktracingImpl.ImageFormats.Elf + +fileprivate func readOSRelease(fd: CInt) -> [String:String]? { + let len = lseek(fd, 0, SEEK_END) + guard len >= 0 else { + return nil + } + return withUnsafeTemporaryAllocation(byteCount: len, alignment: 16) { + (buffer: UnsafeMutableRawBufferPointer) -> [String:String]? in + + _ = lseek(fd, 0, SEEK_SET) + let bytesRead = read(fd, buffer.baseAddress, buffer.count) + guard bytesRead == buffer.count else { + return nil + } + + let asString = String(decoding: buffer, as: UTF8.self) + return Dictionary(OSReleaseScanner(asString), + uniquingKeysWith: { $1 }) + } +} + +fileprivate func readOSRelease() -> [String:String]? { + var fd = open("/etc/os-release", O_RDONLY) + if fd == -1 { + fd = open("/usr/lib/os-release", O_RDONLY) + } + if fd == -1 { + return nil + } + defer { + close(fd) + } + + return readOSRelease(fd: fd) +} + +extension ImageMap { + + private static var platform = { + guard let info = readOSRelease(), + let pretty = info["PRETTY_NAME"] else { + return "Linux (unknown)" + } + + return "Linux (\(pretty))" + }() + + private struct AddressRange { + var low: Address = 0 + var high: Address = 0 + } + + @_specialize(exported: true, kind: full, where M == UnsafeLocalMemoryReader) + @_specialize(exported: true, kind: full, where M == RemoteMemoryReader) + @_specialize(exported: true, kind: full, where M == LocalMemoryReader) + @_spi(Internal) + public static func capture( + using reader: M, + forProcess pid: Int? = nil + ) -> ImageMap { + var images: [Image] = [] + + let wordSize: WordSize + + #if arch(x86_64) || arch(arm64) || arch(arm64_32) + wordSize = .sixtyFourBit + #elseif arch(i386) || arch(arm) + wordSize = .thirtyTwoBit + #endif + + let path: String + if let pid = pid { + path = "/proc/\(pid)/maps" + } else { + path = "/proc/self/maps" + } + + guard let procMaps = readString(from: path) else { + return ImageMap(platform: ImageMap.platform, images: [], wordSize: wordSize) + } + + // Find all the mapped files and get high/low ranges + var mappedFiles: [Substring:AddressRange] = [:] + for match in ProcMapsScanner(procMaps) { + let path = stripWhitespace(match.pathname) + if match.inode == "0" || path == "" { + continue + } + guard let start = Address(match.start, radix: 16), + let end = Address(match.end, radix: 16) else { + continue + } + + if let range = mappedFiles[path] { + mappedFiles[path] = AddressRange(low: Swift.min(start, range.low), + high: Swift.max(end, range.high)) + } else { + mappedFiles[path] = AddressRange(low: start, + high: end) + } + } + + // Look at each mapped file to see if it's an ELF image + for (path, range) in mappedFiles { + // Extract the filename from path + let name: Substring + if let slashIndex = path.lastIndex(of: "/") { + name = path.suffix(from: path.index(after: slashIndex)) + } else { + name = path + } + + // Inspect the image and extract the UUID and end of text + guard let (endOfText, uuid) = getElfImageInfo( + at: M.Address(exactly: range.low)!, + using: reader + ) else { + // Not an ELF image + continue + } + + let image = Image(name: String(name), + path: String(path), + uniqueID: uuid, + baseAddress: range.low, + endOfText: Address(endOfText)) + + images.append(image) + } + + images.sort(by: { $0.baseAddress < $1.baseAddress }) + + return ImageMap( + platform: ImageMap.platform, + images: images, + wordSize: wordSize + ) + } + +} + +#endif // os(Linux) diff --git a/stdlib/public/RuntimeModule/ImageMap.swift b/stdlib/public/RuntimeModule/ImageMap.swift new file mode 100644 index 00000000000..662267280ad --- /dev/null +++ b/stdlib/public/RuntimeModule/ImageMap.swift @@ -0,0 +1,211 @@ +//===--- ImageMap.swift ----------------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 `ImageMap` struct that represents a captured list of loaded +// images. +// +//===----------------------------------------------------------------------===// + +import Swift + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) +internal import Darwin +internal import BacktracingImpl.OS.Darwin +#endif + +/// Holds a map of the process's address space. +public struct ImageMap: Collection, Sendable, Hashable { + + /// A type representing the sequence's elements. + public typealias Element = Backtrace.Image + + /// A type that represents a position in the collection. + public typealias Index = Int + + /// Tells us what size of machine words were used when capturing the + /// image map. + enum WordSize: Sendable { + case sixteenBit + case thirtyTwoBit + case sixtyFourBit + } + + /// We use UInt64s for addresses here. + typealias Address = UInt64 + + /// The internal representation of an image. + struct Image: Sendable, Hashable { + var name: String? + var path: String? + var uniqueID: [UInt8]? + var baseAddress: Address + var endOfText: Address + } + + /// The name of the platform that captured this image map. + public private(set) var platform: String + + /// The actual image storage. + var images: [Image] + + /// The size of words used when capturing. + var wordSize: WordSize + + /// Construct an ImageMap. + init(platform: String, images: [Image], wordSize: WordSize) { + self.platform = platform + self.images = images + self.wordSize = wordSize + } + + /// Construct an ImageMap from CompactImageMap data { + @_spi(Internal) + public init?(compactImageMapData: some Sequence) { + var decoder = CompactImageMapFormat.Decoder(compactImageMapData) + guard let (platform, images, wordSize) = decoder.decode() else { + return nil + } + self.init(platform: platform, images: images, wordSize: wordSize) + } + + /// The position of the first element in a non-empty collection. + public var startIndex: Self.Index { + return 0 + } + + /// The collection's "past the end" position---that is, the position one + /// greater than the last valid subscript argument. + public var endIndex: Self.Index { + return images.count + } + + /// Accesses the element at the specified position. + public subscript(_ ndx: Self.Index) -> Self.Element { + return Backtrace.Image(images[ndx], wordSize: wordSize) + } + + /// Look-up an image by address. + public func indexOfImage(at address: Backtrace.Address) -> Int? { + let addr = UInt64(address)! + var lo = 0, hi = images.count + while lo < hi { + let mid = (lo + hi) / 2 + if images[mid].baseAddress > addr { + hi = mid + } else if images[mid].endOfText <= addr { + lo = mid + 1 + } else { + return mid + } + } + + return nil + } + + /// Returns the position immediately after the given index. + public func index(after ndx: Self.Index) -> Self.Index { + return ndx + 1 + } + + /// Capture the image map for the current process. + public static func capture() -> ImageMap { + #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) + return capture(for: mach_task_self()) + #else + return capture(using: UnsafeLocalMemoryReader()) + #endif + } +} + +extension ImageMap: CustomStringConvertible { + /// Generate a description of an ImageMap + public var description: String { + var lines: [String] = ["Platform: \(platform)", ""] + let addressWidth: Int + switch wordSize { + case .sixteenBit: addressWidth = 4 + case .thirtyTwoBit: addressWidth = 8 + case .sixtyFourBit: addressWidth = 16 + } + + for image in images { + let hexBase = hex(image.baseAddress, width: addressWidth) + let hexEnd = hex(image.endOfText, width: addressWidth) + let buildId: String + if let bytes = image.uniqueID { + buildId = hex(bytes) + } else { + buildId = "" + } + let path = image.path ?? "" + let name = image.name ?? "" + + lines.append("\(hexBase)-\(hexEnd) \(buildId) \(name) \(path)") + } + + return lines.joined(separator: "\n") + } +} + +extension Backtrace.Image { + /// Convert an ImageMap.Image to a Backtrace.Image. + /// + /// Backtrace.Image is the public, user-visible type; ImageMap.Image + /// is an in-memory representation. + init(_ image: ImageMap.Image, wordSize: ImageMap.WordSize) { + let baseAddress: Backtrace.Address + let endOfText: Backtrace.Address + + switch wordSize { + case .sixteenBit: + baseAddress = Backtrace.Address( + UInt16(truncatingIfNeeded: image.baseAddress) + ) + endOfText = Backtrace.Address( + UInt16(truncatingIfNeeded: image.endOfText) + ) + case .thirtyTwoBit: + baseAddress = Backtrace.Address( + UInt32(truncatingIfNeeded: image.baseAddress) + ) + endOfText = Backtrace.Address( + UInt32(truncatingIfNeeded: image.endOfText) + ) + case .sixtyFourBit: + baseAddress = Backtrace.Address(image.baseAddress) + endOfText = Backtrace.Address(image.endOfText) + } + + self.init(name: image.name, + path: image.path, + uniqueID: image.uniqueID, + baseAddress: baseAddress, + endOfText: endOfText) + } +} + +extension ImageMap: Codable { + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + let cimfEncoder = CompactImageMapFormat.Encoder(self) + let base64 = stringFrom(sequence: Base64Encoder(source: cimfEncoder)) + try container.encode(base64) + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + let base64 = try container.decode(String.self) + self.init(compactImageMapData: Base64Decoder(source: base64.utf8))! + } + +} diff --git a/stdlib/public/RuntimeModule/ImageSource.swift b/stdlib/public/RuntimeModule/ImageSource.swift new file mode 100644 index 00000000000..9cd4da43496 --- /dev/null +++ b/stdlib/public/RuntimeModule/ImageSource.swift @@ -0,0 +1,436 @@ +//===--- ImageSource.swift - A place from which to read image data --------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 ImageSource, which tells us where to look for image data. +// +//===----------------------------------------------------------------------===// + +import Swift + +#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) +internal import Darwin +#elseif os(Windows) +internal import ucrt +#elseif canImport(Glibc) +internal import Glibc +#elseif canImport(Musl) +internal import Musl +#endif + +enum ImageSourceError: Error { + case outOfBoundsRead + case posixError(Int32) +} + +struct ImageSource { + + private class Storage { + /// Says how we allocated the buffer. + private enum MemoryBufferKind { + /// Currently empty + case empty + + /// Allocated with UnsafeRawBufferPointer.allocate() + case allocated(Int) + + /// Allocated by mapping memory with mmap() or similar + case mapped + + /// A reference to a subordinate storage + case substorage(Storage) + + /// Not allocated (probably points to a loaded image) + case unowned + } + + private var kind: MemoryBufferKind + + /// The pointer to the actual memory + private(set) var bytes: UnsafeRawBufferPointer! + + /// Gets a mutable pointer to the actual memory + var mutableBytes: UnsafeMutableRawBufferPointer { + guard case let .allocated(count) = kind else { + fatalError("attempted to get mutable reference to immutable ImageSource") + } + return UnsafeMutableRawBufferPointer( + mutating: UnsafeRawBufferPointer(rebasing: bytes[0..= count { + fatalError("ImageSource access out of range") + } + } + + init() { + self.kind = .empty + self.bytes = nil + } + + init(unowned buffer: UnsafeRawBufferPointer) { + self.kind = .unowned + self.bytes = buffer + } + + init(mapped buffer: UnsafeRawBufferPointer) { + self.kind = .mapped + self.bytes = buffer + } + + init(allocated buffer: UnsafeMutableRawBufferPointer, count: Int? = nil) { + self.kind = .allocated(count ?? buffer.count) + self.bytes = UnsafeRawBufferPointer(buffer) + } + + convenience init(capacity: Int, alignment: Int = 0x4000) { + self.init(allocated: UnsafeMutableRawBufferPointer.allocate( + byteCount: capacity, + alignment: 0x1000 + ), + count: 0) + } + + init(parent: Storage, range: Range) { + let chunk = UnsafeRawBufferPointer(rebasing: parent.bytes[range]) + + self.kind = .substorage(parent) + self.bytes = chunk + } + + convenience init(path: String) throws { + let fd = open(path, O_RDONLY, 0) + if fd < 0 { + throw ImageSourceError.posixError(errno) + } + defer { close(fd) } + let size = lseek(fd, 0, SEEK_END) + if size < 0 { + throw ImageSourceError.posixError(errno) + } + let base = mmap(nil, Int(size), PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0) + if base == nil || base! == UnsafeRawPointer(bitPattern: -1)! { + throw ImageSourceError.posixError(errno) + } + + self.init(mapped: UnsafeRawBufferPointer( + start: base, count: Int(size))) + } + + deinit { + switch kind { + case .allocated: + mutableBytes.deallocate() + case .mapped: + munmap(UnsafeMutableRawPointer(mutating: bytes.baseAddress), + bytes.count) + case .substorage, .unowned, .empty: + break + } + } + + /// Subscripting (read-only, for subranges) + subscript(range: Range) -> Storage { + return Storage(parent: self, range: range) + } + + /// Resize the buffer; only supported for allocated or empty storage + func resize(newSize: Int) -> UnsafeMutableRawBufferPointer { + let newBuffer = UnsafeMutableRawBufferPointer.allocate( + byteCount: newSize, + alignment: 0x1000 + ) + switch kind { + case .empty: + kind = .allocated(0) + case let .allocated(count): + assert(newSize >= count) + + let oldPart = UnsafeMutableRawBufferPointer( + rebasing: newBuffer[0.. UnsafeMutableRawBufferPointer { + let capacity: Int + switch kind { + case .empty: + capacity = 0 + case .allocated: + capacity = bytes.count + default: + fatalError("Cannot resize immutable image source storage") + } + + if capacity >= byteCount { + return mutableBytes + } + + let extra = byteCount - capacity + + let increment: Int + if capacity < 1048576 { + let roundedExtra = (extra + 0xffff) & ~0xffff + increment = max(roundedExtra, capacity) + } else { + let roundedExtra = (extra + 0xfffff) & ~0xfffff + let topBit = capacity.bitWidth - capacity.leadingZeroBitCount + increment = max(roundedExtra, 1048576 * (topBit - 20)) + } + + return resize(newSize: capacity + increment) + } + + /// Mark a number of bytes in the mutable buffer as in use. This is + /// used when passing `unusedBytes` to some other code that fills in + /// part of the buffer. + func used(bytes: Int) { + guard bytes >= 0 else { + fatalError("Bytes should not be less than zero") + } + guard case let .allocated(count) = kind else { + fatalError("Cannot append to immutable image source storage") + } + guard mutableBytes.count - count <= bytes else { + fatalError("Buffer overrun detected") + } + kind = .allocated(count + bytes) + } + + /// Append bytes to the mutable buffer; this is only supported for + /// allocated or empty storage. + func append(bytes toAppend: UnsafeRawBufferPointer) { + // Short circuit, otherwise we get in a muddle in requireAtLeast() + if toAppend.count == 0 { + return + } + + let newCount = count + toAppend.count + + let mutableBytes = requireAtLeast(byteCount: newCount) + + guard case let .allocated(count) = kind else { + fatalError("Cannot append to immutable image source storage") + } + + let dest = UnsafeMutableRawBufferPointer( + rebasing: mutableBytes[count..) -> ImageSource { + let intRange = Int(range.lowerBound)..= buffer.count && + offset <= bytes.count - buffer.count else { + throw ImageSourceError.outOfBoundsRead + } + buffer.copyMemory(from: UnsafeRawBufferPointer( + rebasing: bytes[offset..(from address: Address, as type: T.Type) throws -> T { + let size = MemoryLayout.size + let offset = Int(address) + guard offset <= bytes.count - size else { + throw ImageSourceError.outOfBoundsRead + } + return bytes.loadUnaligned(fromByteOffset: offset, as: type) + } + + public func fetchString(from address: Address) throws -> String? { + let offset = Int(address) + let len = strnlen(bytes.baseAddress! + offset, bytes.count - offset) + let stringBytes = bytes[offset.. String? { + let offset = Int(address) + let stringBytes = bytes[offset..(into buffer: UnsafeMutableBufferPointer) throws { + try source.fetch(from: pos, into: buffer) + pos += Size(MemoryLayout.stride * buffer.count) + } + + mutating func read(into pointer: UnsafeMutablePointer) throws { + try source.fetch(from: pos, into: pointer) + pos += Size(MemoryLayout.stride) + } + + mutating func read(as type: T.Type) throws -> T { + let result = try source.fetch(from: pos, as: type) + pos += Size(MemoryLayout.stride) + return result + } + + mutating func read(count: Int, as type: T.Type) throws -> [T] { + let result = try source.fetch(from: pos, count: count, as: type) + pos += Size(MemoryLayout.stride * count) + return result + } + + mutating func readString() throws -> String? { + guard let result = try source.fetchString(from: pos) else { + return nil + } + pos += Size(result.utf8.count + 1) // +1 for the NUL + return result + } + + mutating func readString(length: Int) throws -> String? { + guard let result = try source.fetchString(from: pos, length: length) else { + return nil + } + pos += Size(length) + return result + } +} diff --git a/stdlib/public/Backtracing/Libc.swift b/stdlib/public/RuntimeModule/Libc.swift similarity index 100% rename from stdlib/public/Backtracing/Libc.swift rename to stdlib/public/RuntimeModule/Libc.swift diff --git a/stdlib/public/RuntimeModule/LimitSequence.swift b/stdlib/public/RuntimeModule/LimitSequence.swift new file mode 100644 index 00000000000..0b608dc320f --- /dev/null +++ b/stdlib/public/RuntimeModule/LimitSequence.swift @@ -0,0 +1,237 @@ +//===--- LimitSequence.swift ----------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 a sequence adapter that implements the ability to limit the +// number of items in its output in various ways. +// +//===----------------------------------------------------------------------===// + +import Swift + +/// Sequences you wish to use with `LimitSequence` must use an element type +/// that implements this protocol, so that `LimitSequence` can indicate when +/// it omits or truncates the sequence. +@usableFromInline +protocol LimitableElement { + static func omitted(_: Int) -> Self + static var truncated: Self { get } +} + +/// A `Sequence` that adds the ability to limit the output of another sequence. +@usableFromInline +struct LimitSequence: Sequence + where S.Element == T +{ + /// The element type, which must conform to `LimitableElement` + @usableFromInline + typealias Element = T + + /// The source sequence + @usableFromInline + typealias Source = S + + var source: Source + + /// The maximum number of items that we want in the output of this sequence. + /// This includes `.omitted()` and `.truncated` items. + var limit: Int + + /// The number of items to drop from the head of the sequence. + var offset: Int + + /// The minimum number of items to capture at the tail end of the input + /// sequence. This can be _at most_ `limit - 1`. + var top: Int + + /// Initialise the `LimitSequence` + /// + /// - source: The sequence to draw items from. + /// - limit: The maximum number of items of output we desire. + /// - offset: The number of items to drop from the head of the input sequence. + /// - top: The minimum number of items to capture at the tail end of the + /// input sequence. + /// + /// A `LimitSequence` will read from `source` and emit at most `limit` items, + /// after discarding the first `offset` items from `source`, including a + /// minimum of `top` items. + /// + /// When `LimitSequence` omits items or truncates the sequence, it will + /// insert `.omitted(count)` or `.truncated` items into its output. + @usableFromInline + init(_ source: Source, limit: Int, offset: Int = 0, top: Int = 0) { + self.source = source + self.limit = limit + self.offset = offset + self.top = top + } + + /// Create an iterator for this sequence. + public func makeIterator() -> Iterator { + return Iterator(source.makeIterator(), limit: limit, offset: offset, top: top) + } + + /// The `LimitSequence` Iterator implementation. + /// + /// This works by buffering an element ahead of where we are in the input + /// sequence, so that it can tell whether or not there is more input to + /// follow at any given point. + @usableFromInline + struct Iterator: IteratorProtocol { + /// The iterator for the input sequence. + var iterator: Source.Iterator + + /// We read one element ahead in the input sequence; that element is + /// stored here. + var readAhead: Element? + + /// Tracks the number of items emitted before getting to `top`. + var count = 0 + + /// The maximum number of items to emit, including the `.truncated` + /// or `.omitted()` markers. + var limit: Int + + /// The minimum number of items to capture from the tail of the input + /// sequence. Must be strictly less than `limit`. + var top: Int + + /// A ring buffer that we use to capture the tail. + var topBuffer: [Element] + + /// Points at the first item in `topBuffer`. + var topBase: Int + + /// The index in `topBuffer` that we should output from the next + /// call to `next()`. + var topNdx: Int + + /// Tracks the iterator state. + var state: State + + enum State { + case normal + case outputTop + case done + } + + /// Fill `readAhead` with the next element from the input sequence. + private mutating func readNext() { + if let elt = self.iterator.next() { + readAhead = elt + } else { + readAhead = nil + } + } + + /// Initialise the iterator, and fill in the first read ahead element. + init(_ iterator: Source.Iterator, limit: Int, offset: Int, top: Int) { + self.iterator = iterator + + for _ in 0.. Element? { + switch state { + case .done: + return nil + case .outputTop: + let result = topBuffer[topNdx] + topNdx += 1 + if topNdx == top { + topNdx = 0 + } + if topNdx == topBase { + state = .done + } + return result + case .normal: + break + } + + guard let element = readAhead else { + state = .done + return nil + } + + readNext() + + // Capture the easy part + if count < limit - top - 1 { + count += 1 + return element + } + + if top == 0 && readAhead != nil { + state = .done + return .truncated + } + + let beforeTop = element + + // Fill the top buffer + while let elt = readAhead, topBuffer.count < top{ + topBuffer.append(elt) + + readNext() + } + + if readAhead == nil { + // No elements means we just output beforeTop and we're done + if topBuffer.count == 0 { + state = .done + return beforeTop + } + + // Otherwise, output beforeTop and then the top buffer + topNdx = 0 + if topBuffer.count < top { + topBase = topBuffer.count + } + state = .outputTop + return beforeTop + } + + // Use the top buffer as a circular buffer + var omitted = 1 + while let elt = readAhead { + topBuffer[topBase] = elt + topBase += 1 + omitted += 1 + if topBase == top { + topBase = 0 + } + + readNext() + } + + topNdx = topBase + state = .outputTop + return .omitted(omitted) + } + } +} diff --git a/stdlib/public/Backtracing/MemoryReader.swift b/stdlib/public/RuntimeModule/MemoryReader.swift similarity index 87% rename from stdlib/public/Backtracing/MemoryReader.swift rename to stdlib/public/RuntimeModule/MemoryReader.swift index 6df5073fa93..3dcbec58e50 100644 --- a/stdlib/public/Backtracing/MemoryReader.swift +++ b/stdlib/public/RuntimeModule/MemoryReader.swift @@ -57,6 +57,9 @@ internal import BacktracingImpl.OS.Darwin /// Fetch a NUL terminated string from the specified location in the source func fetchString(from addr: Address) throws -> String? + + /// Fetch a fixed-length string from the specified location in the source + func fetchString(from addr: Address, length: Int) throws -> String? } extension MemoryReader { @@ -106,6 +109,10 @@ extension MemoryReader { return String(decoding: bytes, as: UTF8.self) } + public func fetchString(from addr: Address, length: Int) throws -> String? { + let bytes = try fetch(from: addr, count: length, as: UInt8.self) + return String(decoding: bytes, as: UTF8.self) + } } @_spi(MemoryReaders) public struct UnsafeLocalMemoryReader: MemoryReader { @@ -118,6 +125,16 @@ extension MemoryReader { byteCount: buffer.count ) } + + public func fetch(from address: Address, as type: T.Type) throws -> T { + let ptr = UnsafeRawPointer(bitPattern: UInt(address))! + return ptr.loadUnaligned(fromByteOffset: 0, as: type) + } + + public func fetchString(from address: Address) throws -> String? { + let ptr = UnsafeRawPointer(bitPattern: UInt(address))! + return String(validatingUTF8: ptr.assumingMemoryBound(to: CChar.self)) + } } #if os(macOS) @@ -125,7 +142,8 @@ extension MemoryReader { var result: kern_return_t } -@_spi(MemoryReaders) public struct RemoteMemoryReader: MemoryReader { +@_spi(MemoryReaders) +public struct UncachedRemoteMemoryReader: MemoryReader { private var task: task_t // Sadly we can't expose the type of this argument @@ -151,13 +169,14 @@ extension MemoryReader { } } -@_spi(MemoryReaders) public struct LocalMemoryReader: MemoryReader { +@_spi(MemoryReaders) +public struct UncachedLocalMemoryReader: MemoryReader { public typealias Address = UInt64 public typealias Size = UInt64 public func fetch(from address: Address, into buffer: UnsafeMutableRawBufferPointer) throws { - let reader = RemoteMemoryReader(task: mach_task_self()) + let reader = UncachedRemoteMemoryReader(task: mach_task_self()) return try reader.fetch(from: address, into: buffer) } } @@ -172,7 +191,8 @@ extension MemoryReader { var message: String } -@_spi(MemoryReaders) public struct MemserverMemoryReader: MemoryReader { +@_spi(MemoryReaders) +public struct UncachedMemserverMemoryReader: MemoryReader { private var fd: CInt public init(fd: CInt) { @@ -267,7 +287,8 @@ extension MemoryReader { } } -@_spi(MemoryReaders) public struct RemoteMemoryReader: MemoryReader { +@_spi(MemoryReaders) +public struct UncachedRemoteMemoryReader: MemoryReader { private var pid: pid_t public init(pid: Any) { @@ -288,7 +309,8 @@ extension MemoryReader { } } -@_spi(MemoryReaders) public struct LocalMemoryReader: MemoryReader { +@_spi(MemoryReaders) +public struct UncachedLocalMemoryReader: MemoryReader { private var reader: RemoteMemoryReader init() { diff --git a/stdlib/public/RuntimeModule/OSReleaseScanner.swift b/stdlib/public/RuntimeModule/OSReleaseScanner.swift new file mode 100644 index 00000000000..e5d1d1a76f3 --- /dev/null +++ b/stdlib/public/RuntimeModule/OSReleaseScanner.swift @@ -0,0 +1,186 @@ +//===--- OSReleaseScanner.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 OSReleaseScanner, which is for scanning the /etc/os-release +// file on Linux. +// +//===----------------------------------------------------------------------===// + +#if os(Linux) + +import Swift + +// Lines in /etc/os-release consist of KEY=VALUE pairs. +// +// The VALUE may be quoted with single quotes, in which case its contents +// are left alone. +// +// It may also be quoted with double quotes, in which case slash escapes +// are processed. +// +// If it is unquoted, whitespace will be stripped. + +struct OSReleaseScanner: Sequence, IteratorProtocol { + typealias SS = S.SubSequence + + private enum State { + case normal + case badLine + case comment + case key + case beforeEquals + case beforeValue + case value + case valueWhitespace + case singleQuote + case doubleQuote + case escape + case awaitingNewline + } + + private var asString: S + private var asUTF8: S.UTF8View + private var pos: S.UTF8View.Index + private var state: State + + init(_ string: S) { + asString = string + asUTF8 = string.utf8 + pos = asUTF8.startIndex + state = .normal + } + + mutating func next() -> (String, String)? { + var chunkStart = pos + var whitespaceStart = pos + var key: String = "" + var quotedValue: String = "" + + while pos < asUTF8.endIndex { + let ch = asUTF8[pos] + switch state { + case .normal: + if ch == 32 || ch == 9 || ch == 13 || ch == 10 { + break + } + if ch == UInt8(ascii: "#") { + state = .comment + break + } + chunkStart = pos + state = .key + case .badLine, .comment, .awaitingNewline: + if ch == 13 || ch == 10 { + state = .normal + } + case .key: + if ch == 32 || ch == 9 { + key = String(asString[chunkStart..: CustomStringConvertible, Equatable { + public typealias Address = T + + /// 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. + /// + /// This can only be present at the end of a backtrace; in other cases + /// we will know how many frames we have omitted. For instance, + /// + /// 0: frame 100 <----- bottom of call stack + /// 1: frame 99 + /// 2: frame 98 + /// 3: frame 97 + /// 4: frame 96 + /// 5: ... <----- truncated + 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 "..." + } + } +} + +extension RichFrame: LimitableElement { + // LimitableElement wants to call this "omitted" + public static func omitted(_ count: Int) -> Self { + return .omittedFrames(count) + } +} + +extension Backtrace.Frame { + init(_ frame: RichFrame) { + switch frame { + case let .returnAddress(addr): + self = .returnAddress(Backtrace.Address(addr)!) + case let .programCounter(addr): + self = .programCounter(Backtrace.Address(addr)!) + case let .asyncResumePoint(addr): + self = .asyncResumePoint(Backtrace.Address(addr)!) + case let .omittedFrames(count): + self = .omittedFrames(count) + case .truncated: + self = .truncated + } + } +} diff --git a/stdlib/public/Backtracing/Runtime.swift b/stdlib/public/RuntimeModule/Runtime.swift similarity index 100% rename from stdlib/public/Backtracing/Runtime.swift rename to stdlib/public/RuntimeModule/Runtime.swift diff --git a/stdlib/public/Backtracing/SymbolicatedBacktrace.swift b/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift similarity index 71% rename from stdlib/public/Backtracing/SymbolicatedBacktrace.swift rename to stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift index 0e194bc9498..214231cdc28 100644 --- a/stdlib/public/Backtracing/SymbolicatedBacktrace.swift +++ b/stdlib/public/RuntimeModule/SymbolicatedBacktrace.swift @@ -92,20 +92,15 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { } /// A textual description of this frame. - public func description(width: Int) -> String { + public var description: String { if let symbol = symbol { let isInlined = inlined ? " [inlined]" : "" let isThunk = isSwiftThunk ? " [thunk]" : "" - return "\(captured.description(width: width))\(isInlined)\(isThunk) \(symbol)" + return "\(captured.description)\(isInlined)\(isThunk) \(symbol)" } else { - return captured.description(width: width) + return captured.description } } - - /// A textual description of this frame. - public var description: String { - return description(width: MemoryLayout.size * 2) - } } /// Represents a symbol we've located @@ -218,8 +213,8 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { if stringLen > 0 { return demangled.withMemoryRebound(to: UInt8.self, capacity: stringLen) { - let demangledBytes = UnsafeBufferPointer(start: $0, - count: stringLen) + let demangledBytes = UnsafeBufferPointer(start: $0, + count: stringLen) return String(decoding: demangledBytes, as: UTF8.self) } } @@ -250,19 +245,14 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { } } - /// The width, in bits, of an address in this backtrace. - public var addressWidth: Int { - return backtrace.addressWidth - } + /// The architecture on which this backtrace was captured. + public var architecture: String { return backtrace.architecture } /// A list of captured frame information. - public var frames: [Frame] + public private(set) var frames: [Frame] /// A list of images found in the process. - public var images: [Backtrace.Image] - - /// Shared cache information. - public var sharedCacheInfo: Backtrace.SharedCacheInfo? + public private(set) var images: ImageMap /// True if this backtrace is a Swift runtime failure. public var isSwiftRuntimeFailure: Bool { @@ -283,12 +273,9 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { } /// Construct a SymbolicatedBacktrace from a backtrace and a list of images. - private init(backtrace: Backtrace, images: [Backtrace.Image], - sharedCacheInfo: Backtrace.SharedCacheInfo?, - frames: [Frame]) { + private init(backtrace: Backtrace, images: ImageMap, frames: [Frame]) { self.backtrace = backtrace self.images = images - self.sharedCacheInfo = sharedCacheInfo self.frames = frames } @@ -306,21 +293,20 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { } /// Create a symbolicator. - private static func withSymbolicator(images: [Backtrace.Image], - sharedCacheInfo: Backtrace.SharedCacheInfo?, + private static func withSymbolicator(images: ImageMap, useSymbolCache: Bool, fn: (CSSymbolicatorRef) throws -> T) rethrows -> T { let binaryImageList = images.map{ image in BinaryImageInformation( - base: vm_address_t(image.baseAddress), - extent: vm_address_t(image.endOfText), - uuid: uuidBytesFromBuildID(image.buildID!), + base: vm_address_t(image.baseAddress)!, + extent: vm_address_t(image.endOfText)!, + uuid: uuidBytesFromBuildID(image.uniqueID!), arch: HostContext.coreSymbolicationArchitecture, - path: image.path, + path: image.path ?? "", relocations: [ BinaryRelocationInformation( - base: vm_address_t(image.baseAddress), - extent: vm_address_t(image.endOfText), + base: vm_address_t(image.baseAddress)!, + extent: vm_address_t(image.endOfText)!, name: "__TEXT" ) ], @@ -345,7 +331,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { isInline: Bool, symbol: CSSymbolRef, sourceInfo: CSSourceInfoRef?, - images: [Backtrace.Image]) -> Frame { + images: ImageMap) -> Frame { if CSIsNull(symbol) { return Frame(captured: capturedFrame, symbol: nil) } @@ -375,9 +361,9 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { var imageIndex = -1 var imageName = "" for (ndx, image) in images.enumerated() { - if image.baseAddress == imageBase { + if vm_address_t(image.baseAddress) == imageBase { imageIndex = ndx - imageName = image.name + imageName = image.name ?? "" break } } @@ -385,7 +371,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { let theSymbol = Symbol(imageIndex: imageIndex, imageName: imageName, rawName: rawName, - offset: Int(address - UInt64(range.location)), + offset: Int(UInt64(address)! - UInt64(range.location)), sourceLocation: location) theSymbol.name = name @@ -395,43 +381,31 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { /// Actually symbolicate. internal static func symbolicate(backtrace: Backtrace, - images: [Backtrace.Image]?, - sharedCacheInfo: Backtrace.SharedCacheInfo?, - showInlineFrames: Bool, - showSourceLocations: Bool, - useSymbolCache: Bool) + images: ImageMap?, + options: Backtrace.SymbolicationOptions) -> SymbolicatedBacktrace? { - let theImages: [Backtrace.Image] + let theImages: ImageMap 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() + theImages = ImageMap.capture() } var frames: [Frame] = [] #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) withSymbolicator(images: theImages, - sharedCacheInfo: theCacheInfo, - useSymbolCache: useSymbolCache) { symbolicator in + useSymbolCache: options.contains(.useSymbolCache)) { + symbolicator in for frame in backtrace.frames { switch frame { case .omittedFrames(_), .truncated: frames.append(Frame(captured: frame, symbol: nil)) default: - let address = vm_address_t(frame.adjustedProgramCounter) + let address = vm_address_t(frame.adjustedProgramCounter)! let owner = CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator, address, @@ -439,7 +413,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { if CSIsNull(owner) { frames.append(Frame(captured: frame, symbol: nil)) - } else if showInlineFrames { + } else if options.contains(.showInlineFrames) { // These present in *reverse* order (i.e. the real one first, // then the inlined frames from callee to caller). let pos = frames.count @@ -458,7 +432,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { first = false } - } else if showSourceLocations { + } else if options.contains(.showSourceLocations) { let symbol = CSSymbolOwnerGetSymbolWithAddress(owner, address) let sourceInfo = CSSymbolOwnerGetSourceInfoWithAddress(owner, address) @@ -483,108 +457,90 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { } } #elseif os(Linux) - var elf32Cache: [Int:Elf32Image] = [:] - var elf64Cache: [Int:Elf64Image] = [:] + let cache = ElfImageCache.threadLocal // This could be more efficient; at the moment we execute the line // number programs once per frame, whereas we could just run them once // for all the addresses we're interested in. for frame in backtrace.frames { - let address = FileImageSource.Address(frame.adjustedProgramCounter) - if let imageNdx = theImages.firstIndex( - where: { address >= $0.baseAddress - && address < $0.endOfText } - ) { - let relativeAddress = address - FileImageSource.Address(theImages[imageNdx].baseAddress) + let address = frame.adjustedProgramCounter + if let imageNdx = theImages.indexOfImage(at: address) { + let relativeAddress = ImageSource.Address( + address - theImages[imageNdx].baseAddress + ) + let name = theImages[imageNdx].name ?? "" var symbol: Symbol = Symbol(imageIndex: imageNdx, - imageName: theImages[imageNdx].name, + imageName: name, rawName: "", offset: 0, sourceLocation: nil) - var elf32Image = elf32Cache[imageNdx] - var elf64Image = elf64Cache[imageNdx] - if elf32Image == nil && elf64Image == nil { - if let source = try? FileImageSource(path: theImages[imageNdx].path) { - if let elfImage = try? Elf32Image(source: source) { - elf32Image = elfImage - elf32Cache[imageNdx] = elfImage - } else if let elfImage = try? Elf64Image(source: source) { - elf64Image = elfImage - elf64Cache[imageNdx] = elfImage + func lookupSymbol( + image: ElfImage?, + at imageNdx: Int, + named name: String, + address imageAddr: ImageSource.Address + ) -> Symbol? { + let address = ElfImage.Traits.Address(imageAddr) + + guard let image = image else { + return nil + } + guard let theSymbol = image.lookupSymbol(address: address) else { + return nil + } + + var location: SourceLocation? + + if options.contains(.showSourceLocations) + || options.contains(.showInlineFrames) { + location = try? image.sourceLocation(for: address) + } else { + location = nil + } + + if options.contains(.showInlineFrames) { + for inline in image.inlineCallSites(at: address) { + let fakeSymbol = Symbol(imageIndex: imageNdx, + imageName: name, + rawName: inline.rawName ?? "", + offset: 0, + sourceLocation: location) + frames.append(Frame(captured: frame, + symbol: fakeSymbol, + inlined: true)) + + location = SourceLocation(path: inline.filename, + line: inline.line, + column: inline.column) } } + + return Symbol(imageIndex: imageNdx, + imageName: name, + rawName: theSymbol.name, + offset: theSymbol.offset, + sourceLocation: location) } - if let theSymbol = elf32Image?.lookupSymbol(address: relativeAddress) { - var location: SourceLocation? - - if showSourceLocations || showInlineFrames { - location = try? elf32Image!.sourceLocation(for: relativeAddress) - } else { - location = nil + if let hit = cache.lookup(path: theImages[imageNdx].path) { + switch hit { + case let .elf32Image(image): + if let theSymbol = lookupSymbol(image: image, + at: imageNdx, + named: name, + address: relativeAddress) { + symbol = theSymbol + } + case let .elf64Image(image): + if let theSymbol = lookupSymbol(image: image, + at: imageNdx, + named: name, + address: relativeAddress) { + symbol = theSymbol + } } - - if showInlineFrames { - for inline in elf32Image!.inlineCallSites(at: relativeAddress) { - let fakeSymbol = Symbol(imageIndex: imageNdx, - imageName: theImages[imageNdx].name, - rawName: inline.rawName ?? "", - offset: 0, - sourceLocation: location) - frames.append(Frame(captured: frame, - symbol: fakeSymbol, - inlined: true)) - - location = SourceLocation(path: inline.filename, - line: inline.line, - column: inline.column) - } - } - - symbol = Symbol(imageIndex: imageNdx, - imageName: theImages[imageNdx].name, - rawName: theSymbol.name, - offset: theSymbol.offset, - sourceLocation: location) - } else if let theSymbol = elf64Image?.lookupSymbol(address: relativeAddress) { - var location: SourceLocation? - - if showSourceLocations || showInlineFrames { - location = try? elf64Image!.sourceLocation(for: relativeAddress) - } else { - location = nil - } - - if showInlineFrames { - for inline in elf64Image!.inlineCallSites(at: relativeAddress) { - let fakeSymbol = Symbol(imageIndex: imageNdx, - imageName: theImages[imageNdx].name, - rawName: inline.rawName ?? "", - offset: 0, - sourceLocation: location) - frames.append(Frame(captured: frame, - symbol: fakeSymbol, - inlined: true)) - - location = SourceLocation(path: inline.filename, - line: inline.line, - column: inline.column) - } - } - - symbol = Symbol(imageIndex: imageNdx, - imageName: theImages[imageNdx].name, - rawName: theSymbol.name, - offset: theSymbol.offset, - sourceLocation: location) - } else { - symbol = Symbol(imageIndex: imageNdx, - imageName: theImages[imageNdx].name, - rawName: "", - offset: 0, - sourceLocation: nil) } frames.append(Frame(captured: frame, symbol: symbol)) @@ -599,18 +555,16 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { return SymbolicatedBacktrace(backtrace: backtrace, images: theImages, - sharedCacheInfo: theCacheInfo, frames: frames) } /// Provide a textual version of the backtrace. public var description: String { var lines: [String] = [] - let addressChars = (backtrace.addressWidth + 3) / 4 var n = 0 for frame in frames { - lines.append("\(n)\t\(frame.description(width: addressChars))") + lines.append("\(n)\t\(frame.description)") switch frame.captured { case let .omittedFrames(count): n += count @@ -623,16 +577,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible { lines.append("Images:") lines.append("") for (n, image) in images.enumerated() { - lines.append("\(n)\t\(image.description(width: addressChars))") - } - - if let sharedCacheInfo = sharedCacheInfo { - lines.append("") - lines.append("Shared Cache:") - lines.append("") - lines.append(" UUID: \(hex(sharedCacheInfo.uuid))") - lines.append(" Base: \(hex(sharedCacheInfo.baseAddress, width: addressChars))") - lines.append(" Active: \(!sharedCacheInfo.noCache)") + lines.append("\(n)\t\(image.description)") } return lines.joined(separator: "\n") diff --git a/stdlib/public/Backtracing/Utils.swift b/stdlib/public/RuntimeModule/Utils.swift similarity index 76% rename from stdlib/public/Backtracing/Utils.swift rename to stdlib/public/RuntimeModule/Utils.swift index 50a2a571d9c..e986f2a3882 100644 --- a/stdlib/public/Backtracing/Utils.swift +++ b/stdlib/public/RuntimeModule/Utils.swift @@ -95,3 +95,32 @@ public func stripWhitespace(_ s: S) let lastNonWhitespace = s.lastIndex(where: { !$0.isWhitespace })! return s[firstNonWhitespace...lastNonWhitespace] } + +/// Strip any Optional from a value. +/// +/// This is useful when interfacing with the system C library, because some +/// C libraries have nullability annotations while others do not. +func notOptional(_ optional: T?) -> T { + return optional! +} + +func notOptional(_ value: T) -> T { + return value +} + +/// Convert mutable pointers to non-mutable +/// +/// This is useful when interfacing with the system C library, because some +/// C libraries have const annotations in places others do not. +func notMutable(_ mutable: UnsafeMutablePointer) -> UnsafePointer { + return UnsafePointer(mutable) +} +func notMutable(_ immutable: UnsafePointer) -> UnsafePointer { + return immutable +} +func notMutable(_ mutable: UnsafeMutableRawPointer) -> UnsafeRawPointer { + return UnsafeRawPointer(mutable) +} +func notMutable(_ immutable: UnsafeRawPointer) -> UnsafeRawPointer { + return immutable +} diff --git a/stdlib/public/Backtracing/Win32Extras.cpp b/stdlib/public/RuntimeModule/Win32Extras.cpp similarity index 100% rename from stdlib/public/Backtracing/Win32Extras.cpp rename to stdlib/public/RuntimeModule/Win32Extras.cpp diff --git a/stdlib/public/Backtracing/get-cpu-context.S b/stdlib/public/RuntimeModule/get-cpu-context.S similarity index 100% rename from stdlib/public/Backtracing/get-cpu-context.S rename to stdlib/public/RuntimeModule/get-cpu-context.S diff --git a/stdlib/public/Backtracing/get-cpu-context.asm b/stdlib/public/RuntimeModule/get-cpu-context.asm similarity index 100% rename from stdlib/public/Backtracing/get-cpu-context.asm rename to stdlib/public/RuntimeModule/get-cpu-context.asm diff --git a/stdlib/public/Backtracing/modules/Compression.h b/stdlib/public/RuntimeModule/modules/Compression.h similarity index 100% rename from stdlib/public/Backtracing/modules/Compression.h rename to stdlib/public/RuntimeModule/modules/Compression.h diff --git a/stdlib/public/Backtracing/modules/FixedLayout.h b/stdlib/public/RuntimeModule/modules/FixedLayout.h similarity index 100% rename from stdlib/public/Backtracing/modules/FixedLayout.h rename to stdlib/public/RuntimeModule/modules/FixedLayout.h diff --git a/stdlib/public/Backtracing/modules/ImageFormats/Dwarf/dwarf.h b/stdlib/public/RuntimeModule/modules/ImageFormats/Dwarf/dwarf.h similarity index 100% rename from stdlib/public/Backtracing/modules/ImageFormats/Dwarf/dwarf.h rename to stdlib/public/RuntimeModule/modules/ImageFormats/Dwarf/dwarf.h diff --git a/stdlib/public/Backtracing/modules/ImageFormats/Dwarf/eh_frame_hdr.h b/stdlib/public/RuntimeModule/modules/ImageFormats/Dwarf/eh_frame_hdr.h similarity index 100% rename from stdlib/public/Backtracing/modules/ImageFormats/Dwarf/eh_frame_hdr.h rename to stdlib/public/RuntimeModule/modules/ImageFormats/Dwarf/eh_frame_hdr.h diff --git a/stdlib/public/Backtracing/modules/ImageFormats/Elf/elf.h b/stdlib/public/RuntimeModule/modules/ImageFormats/Elf/elf.h similarity index 100% rename from stdlib/public/Backtracing/modules/ImageFormats/Elf/elf.h rename to stdlib/public/RuntimeModule/modules/ImageFormats/Elf/elf.h diff --git a/stdlib/public/Backtracing/modules/ImageFormats/ImageFormats.modulemap b/stdlib/public/RuntimeModule/modules/ImageFormats/ImageFormats.modulemap similarity index 100% rename from stdlib/public/Backtracing/modules/ImageFormats/ImageFormats.modulemap rename to stdlib/public/RuntimeModule/modules/ImageFormats/ImageFormats.modulemap diff --git a/stdlib/public/Backtracing/modules/OS/Darwin.h b/stdlib/public/RuntimeModule/modules/OS/Darwin.h similarity index 100% rename from stdlib/public/Backtracing/modules/OS/Darwin.h rename to stdlib/public/RuntimeModule/modules/OS/Darwin.h diff --git a/stdlib/public/Backtracing/modules/OS/Libc.h b/stdlib/public/RuntimeModule/modules/OS/Libc.h similarity index 100% rename from stdlib/public/Backtracing/modules/OS/Libc.h rename to stdlib/public/RuntimeModule/modules/OS/Libc.h diff --git a/stdlib/public/Backtracing/modules/OS/Linux.h b/stdlib/public/RuntimeModule/modules/OS/Linux.h similarity index 100% rename from stdlib/public/Backtracing/modules/OS/Linux.h rename to stdlib/public/RuntimeModule/modules/OS/Linux.h diff --git a/stdlib/public/Backtracing/modules/OS/OS.modulemap b/stdlib/public/RuntimeModule/modules/OS/OS.modulemap similarity index 100% rename from stdlib/public/Backtracing/modules/OS/OS.modulemap rename to stdlib/public/RuntimeModule/modules/OS/OS.modulemap diff --git a/stdlib/public/Backtracing/modules/OS/Windows.h b/stdlib/public/RuntimeModule/modules/OS/Windows.h similarity index 100% rename from stdlib/public/Backtracing/modules/OS/Windows.h rename to stdlib/public/RuntimeModule/modules/OS/Windows.h diff --git a/stdlib/public/Backtracing/modules/Runtime/Runtime.h b/stdlib/public/RuntimeModule/modules/Runtime/Runtime.h similarity index 70% rename from stdlib/public/Backtracing/modules/Runtime/Runtime.h rename to stdlib/public/RuntimeModule/modules/Runtime/Runtime.h index c6e830d7ee7..0cda2acc684 100644 --- a/stdlib/public/Backtracing/modules/Runtime/Runtime.h +++ b/stdlib/public/RuntimeModule/modules/Runtime/Runtime.h @@ -23,21 +23,23 @@ #include "swift/Runtime/CrashInfo.h" #ifdef __cplusplus -#define EXTERN_C extern "C" -#else -#define EXTERN_C +extern "C" { #endif // Can't import swift/Runtime/Debug.h because it assumes C++ -EXTERN_C void swift_reportWarning(uint32_t flags, const char *message); +void swift_reportWarning(uint32_t flags, const char *message); // Returns true if the given function is a thunk function -EXTERN_C bool _swift_backtrace_isThunkFunction(const char *rawName); +bool _swift_backtrace_isThunkFunction(const char *rawName); // Demangle the given raw name (supports Swift and C++) -EXTERN_C char *_swift_backtrace_demangle(const char *rawName, - size_t rawNameLength, - char *outputBuffer, - size_t *outputBufferSize); +char *_swift_backtrace_demangle(const char *rawName, + size_t rawNameLength, + char *outputBuffer, + size_t *outputBufferSize); + +#ifdef __cplusplus +} +#endif #endif // SWIFT_BACKTRACING_RUNTIME_H diff --git a/stdlib/public/Backtracing/modules/module.modulemap b/stdlib/public/RuntimeModule/modules/module.modulemap similarity index 100% rename from stdlib/public/Backtracing/modules/module.modulemap rename to stdlib/public/RuntimeModule/modules/module.modulemap diff --git a/stdlib/public/core/CMakeLists.txt b/stdlib/public/core/CMakeLists.txt index eb9547e7491..c706fe53f6a 100644 --- a/stdlib/public/core/CMakeLists.txt +++ b/stdlib/public/core/CMakeLists.txt @@ -335,7 +335,7 @@ endif() list(APPEND swift_stdlib_compile_flags "-plugin-path" "${swift_lib_dir}/swift/host/plugins") set(swift_core_incorporate_object_libraries) -list(APPEND swift_core_incorporate_object_libraries swiftRuntime) +list(APPEND swift_core_incorporate_object_libraries swiftRuntimeCore) list(APPEND swift_core_incorporate_object_libraries swiftLLVMSupport) list(APPEND swift_core_incorporate_object_libraries swiftDemangling) list(APPEND swift_core_incorporate_object_libraries swiftStdlibStubs) diff --git a/stdlib/public/libexec/swift-backtrace/CMakeLists.txt b/stdlib/public/libexec/swift-backtrace/CMakeLists.txt index 7a354cabe3c..ed4bb13dc0f 100644 --- a/stdlib/public/libexec/swift-backtrace/CMakeLists.txt +++ b/stdlib/public/libexec/swift-backtrace/CMakeLists.txt @@ -12,11 +12,11 @@ if(SWIFT_BUILD_SDK_OVERLAY) set(musl Musl) endif() -# Similarly, we only want the _Backtracing dependency if we're building +# Similarly, we only want the Runtime dependency if we're building # with the stdlib. -set(backtracing) -if(SWIFT_BUILD_STDLIB AND SWIFT_ENABLE_BACKTRACING) - set(backtracing _Backtracing) +set(runtime) +if(SWIFT_BUILD_STDLIB AND SWIFT_ENABLE_RUNTIME_MODULE) + set(runtime Runtime) endif() if(NOT SWIFT_BUILD_STDLIB) @@ -25,7 +25,7 @@ endif() set(BACKTRACING_COMPILE_FLAGS "-cxx-interoperability-mode=default" - "-I${SWIFT_STDLIB_SOURCE_DIR}/public/Backtracing/modules" + "-I${SWIFT_STDLIB_SOURCE_DIR}/public/RuntimeModule/modules" "-Xcc;-I${SWIFT_SOURCE_DIR}/include" "-Xcc;-I${CMAKE_BINARY_DIR}/include" "-disable-upcoming-feature;MemberImportVisibility") @@ -51,7 +51,7 @@ endif() add_swift_target_executable(swift-backtrace BUILD_WITH_LIBEXEC ${BACKTRACING_SOURCES} - SWIFT_MODULE_DEPENDS ${backtracing} + SWIFT_MODULE_DEPENDS ${runtime} SWIFT_MODULE_DEPENDS_OSX ${darwin} SWIFT_MODULE_DEPENDS_WINDOWS ${wincrt_sdk} @@ -80,7 +80,7 @@ if(static_target_sdks) ${BACKTRACING_SOURCES} - SWIFT_MODULE_DEPENDS ${backtracing} + SWIFT_MODULE_DEPENDS ${runtime} SWIFT_MODULE_DEPENDS_OSX ${darwin} SWIFT_MODULE_DEPENDS_WINDOWS ${wincrt_sdk} diff --git a/stdlib/public/libexec/swift-backtrace/TargetLinux.swift b/stdlib/public/libexec/swift-backtrace/TargetLinux.swift index d681b724de8..5ef68a49484 100644 --- a/stdlib/public/libexec/swift-backtrace/TargetLinux.swift +++ b/stdlib/public/libexec/swift-backtrace/TargetLinux.swift @@ -23,11 +23,11 @@ import Glibc import Musl #endif -import _Backtracing -@_spi(Internal) import _Backtracing -@_spi(Contexts) import _Backtracing -@_spi(MemoryReaders) import _Backtracing -@_spi(Utils) import _Backtracing +import Runtime +@_spi(Internal) import Runtime +@_spi(Contexts) import Runtime +@_spi(MemoryReaders) import Runtime +@_spi(Utils) import Runtime enum SomeBacktrace { case raw(Backtrace) @@ -52,7 +52,7 @@ class Target { var faultAddress: Address var crashingThread: TargetThread.ThreadID - var images: [Backtrace.Image] = [] + var images: ImageMap var threads: [TargetThread] = [] var crashingThreadNdx: Int = -1 @@ -84,7 +84,7 @@ class Target { } } - var reader: CachingMemoryReader + var reader: MemserverMemoryReader // Get the name of a process private static func getProcessName(pid: pid_t) -> String { @@ -118,7 +118,7 @@ class Target { let memserverFd: CInt = 4 pid = getppid() - reader = CachingMemoryReader(for: MemserverMemoryReader(fd: memserverFd)) + reader = MemserverMemoryReader(fd: memserverFd) name = Self.getProcessName(pid: pid) let crashInfo: CrashInfo @@ -133,8 +133,7 @@ class Target { signal = crashInfo.signal faultAddress = crashInfo.fault_address - images = Backtrace.captureImages(using: reader, - forProcess: Int(pid)) + images = ImageMap.capture(using: reader, forProcess: Int(pid)) do { try fetchThreads(threadListHead: Address(crashInfo.thread_list), @@ -172,34 +171,34 @@ class Target { let backtrace = try Backtrace.capture(from: context, using: reader, images: images, + algorithm: .auto, limit: limit, + offset: 0, top: top) let shouldSymbolicate: Bool - let showInlineFrames: Bool - let showSourceLocations: Bool + var options: Backtrace.SymbolicationOptions switch symbolicate { case .off: shouldSymbolicate = false - showInlineFrames = false - showSourceLocations = false + options = [] case .fast: shouldSymbolicate = true - showInlineFrames = false - showSourceLocations = false + options = [ .showSourceLocations ] case .full: shouldSymbolicate = true - showInlineFrames = true - showSourceLocations = true + options = [ .showInlineFrames, .showSourceLocations ] + } + + if cache { + options.insert(.useSymbolCache) } if shouldSymbolicate { - guard let symbolicated - = backtrace.symbolicated(with: images, - sharedCacheInfo: nil, - showInlineFrames: showInlineFrames, - showSourceLocations: showSourceLocations, - useSymbolCache: cache) else { + guard let symbolicated = backtrace.symbolicated( + with: images, + options: options + ) else { print("unable to symbolicate backtrace for thread \(t.tid)") exit(1) } @@ -243,37 +242,37 @@ class Target { guard let backtrace = try? Backtrace.capture(from: context, using: reader, images: images, + algorithm: .auto, limit: limit, + offset: 0, top: top) else { print("unable to capture backtrace from context for thread \(ndx)") continue } let shouldSymbolicate: Bool - let showInlineFrames: Bool - let showSourceLocations: Bool + var options: Backtrace.SymbolicationOptions switch symbolicate { case .off: shouldSymbolicate = false - showInlineFrames = false - showSourceLocations = false + options = [] case .fast: shouldSymbolicate = true - showInlineFrames = false - showSourceLocations = false + options = [ .showSourceLocations ] case .full: shouldSymbolicate = true - showInlineFrames = true - showSourceLocations = true + options = [ .showInlineFrames, .showSourceLocations ] + } + + if cache { + options.insert(.useSymbolCache) } if shouldSymbolicate { guard let symbolicated = backtrace.symbolicated( with: images, - sharedCacheInfo: nil, - showInlineFrames: showInlineFrames, - showSourceLocations: showSourceLocations, - useSymbolCache: cache) else { + options: options + ) else { print("unable to symbolicate backtrace from context for thread \(ndx)") continue } diff --git a/stdlib/public/libexec/swift-backtrace/TargetMacOS.swift b/stdlib/public/libexec/swift-backtrace/TargetMacOS.swift index fba4f76300c..fe7f445a0d8 100644 --- a/stdlib/public/libexec/swift-backtrace/TargetMacOS.swift +++ b/stdlib/public/libexec/swift-backtrace/TargetMacOS.swift @@ -20,12 +20,13 @@ import Darwin import Darwin.Mach -import _Backtracing -@_spi(Internal) import _Backtracing -@_spi(Contexts) import _Backtracing -@_spi(MemoryReaders) import _Backtracing +import Runtime +@_spi(Internal) import Runtime +@_spi(Contexts) import Runtime +@_spi(MemoryReaders) import Runtime internal import BacktracingImpl.OS.Darwin +internal import BacktracingImpl.Runtime #if arch(x86_64) typealias MContext = darwin_x86_64_mcontext @@ -69,8 +70,7 @@ class Target { var crashingThread: TargetThread.ThreadID var task: task_t - var images: [Backtrace.Image] = [] - var sharedCacheInfo: Backtrace.SharedCacheInfo? + var images: ImageMap var threads: [TargetThread] = [] var crashingThreadNdx: Int = -1 @@ -102,7 +102,7 @@ class Target { } } - var reader: CachingMemoryReader + var reader: RemoteMemoryReader var mcontext: MContext @@ -170,7 +170,7 @@ class Target { task = parentTask - reader = CachingMemoryReader(for: RemoteMemoryReader(task: task_t(task))) + reader = RemoteMemoryReader(task: task_t(task)) name = Self.getProcessName(pid: pid) @@ -194,8 +194,7 @@ class Target { mcontext = mctx - images = Backtrace.captureImages(for: task) - sharedCacheInfo = Backtrace.captureSharedCacheInfo(for: task) + images = ImageMap.capture(for: task) fetchThreads(limit: limit, top: top, cache: cache, symbolicate: symbolicate) } @@ -269,7 +268,9 @@ class Target { guard let backtrace = try? Backtrace.capture(from: ctx, using: reader, images: nil, + algorithm: .auto, limit: limit, + offset: 0, top: top) else { print("swift-backtrace: unable to capture backtrace from context for thread \(ndx)", to: &standardError) @@ -277,30 +278,28 @@ class Target { } let shouldSymbolicate: Bool - let showInlineFrames: Bool - let showSourceLocations: Bool + var options: Backtrace.SymbolicationOptions switch symbolicate { case .off: shouldSymbolicate = false - showInlineFrames = false - showSourceLocations = false + options = [] case .fast: shouldSymbolicate = true - showInlineFrames = false - showSourceLocations = false + options = [ .showSourceLocations ] case .full: shouldSymbolicate = true - showInlineFrames = true - showSourceLocations = true + options = [ .showInlineFrames, .showSourceLocations ] + } + + if cache { + options.insert(.useSymbolCache) } if shouldSymbolicate { guard let symbolicated = backtrace.symbolicated( with: images, - sharedCacheInfo: sharedCacheInfo, - showInlineFrames: showInlineFrames, - showSourceLocations: showSourceLocations, - useSymbolCache: cache) else { + options: options + ) else { print("unable to symbolicate backtrace from context for thread \(ndx)", to: &standardError) exit(1) @@ -334,7 +333,9 @@ class Target { guard let backtrace = try? Backtrace.capture(from: context, using: reader, images: nil, + algorithm: .auto, limit: limit, + offset: 0, top: top) else { print("swift-backtrace: unable to capture backtrace from context for thread \(ndx)", to: &standardError) @@ -342,30 +343,28 @@ class Target { } let shouldSymbolicate: Bool - let showInlineFrames: Bool - let showSourceLocations: Bool + var options: Backtrace.SymbolicationOptions switch symbolicate { case .off: shouldSymbolicate = false - showInlineFrames = false - showSourceLocations = false + options = [] case .fast: shouldSymbolicate = true - showInlineFrames = false - showSourceLocations = false + options = [ .showSourceLocations ] case .full: shouldSymbolicate = true - showInlineFrames = true - showSourceLocations = true + options = [ .showInlineFrames, .showSourceLocations ] + } + + if cache { + options.insert(.useSymbolCache) } if shouldSymbolicate { guard let symbolicated = backtrace.symbolicated( with: images, - sharedCacheInfo: sharedCacheInfo, - showInlineFrames: showInlineFrames, - showSourceLocations: showSourceLocations, - useSymbolCache: cache) else { + options: options + ) else { print("swift-backtrace: unable to symbolicate backtrace from context for thread \(ndx)", to: &standardError) continue diff --git a/stdlib/public/libexec/swift-backtrace/Themes.swift b/stdlib/public/libexec/swift-backtrace/Themes.swift index f9addbc9028..d26f67ee070 100644 --- a/stdlib/public/libexec/swift-backtrace/Themes.swift +++ b/stdlib/public/libexec/swift-backtrace/Themes.swift @@ -14,7 +14,7 @@ // //===----------------------------------------------------------------------===// -@_spi(Formatting) import _Backtracing +@_spi(Formatting) import Runtime protocol ErrorAndWarningTheme { func crashReason(_ s: String) -> String @@ -38,6 +38,16 @@ extension PromptTheme { public func prompt(_ s: String) -> String { return s } } +protocol PlatformArchTheme { + func platform(_ s: String) -> String + func architecture(_ s: String) -> String +} + +extension PlatformArchTheme { + public func platform(_ s: String) -> String { return s } + public func architecture(_ s: String) -> String { return s } +} + protocol MemoryDumpTheme { func address(_ s: String) -> String func data(_ s: String) -> String @@ -67,7 +77,7 @@ extension RegisterDumpTheme { } typealias Theme = BacktraceFormattingTheme & ErrorAndWarningTheme & - PromptTheme & MemoryDumpTheme & RegisterDumpTheme + PromptTheme & MemoryDumpTheme & RegisterDumpTheme & PlatformArchTheme enum Themes { @@ -164,6 +174,13 @@ enum Themes { public func info(_ s: String) -> String { return "ℹ️ \(s)" } + + public func platform(_ s: String) -> String { + return "\(fg: .white)\(s)\(fg: .normal)" + } + public func architecture(_ s: String) -> String { + return "\(fg: .white)\(s)\(fg: .normal)" + } } static let plain = Plain() diff --git a/stdlib/public/libexec/swift-backtrace/Utils.swift b/stdlib/public/libexec/swift-backtrace/Utils.swift index 2fa492bbb09..20e164d5b08 100644 --- a/stdlib/public/libexec/swift-backtrace/Utils.swift +++ b/stdlib/public/libexec/swift-backtrace/Utils.swift @@ -26,7 +26,7 @@ import CRT import Swift -import BacktracingImpl.Runtime +internal import BacktracingImpl.Runtime typealias CrashInfo = swift.runtime.backtrace.CrashInfo @@ -151,6 +151,18 @@ internal func spawn(_ path: String, args: [String]) throws { #endif // os(macOS) +extension Sequence { + /// Return the first element in a Sequence. + /// + /// This is not, in general, a safe thing to do, because the sequence might + /// not be restartable. For the cases where we're using it here, it's OK + /// though. + public var unsafeFirst: Element? { + var iterator = makeIterator() + return iterator.next() + } +} + struct CFileStream: TextOutputStream { var fp: UnsafeMutablePointer diff --git a/stdlib/public/libexec/swift-backtrace/main.swift b/stdlib/public/libexec/swift-backtrace/main.swift index 5c5db180060..e8c8000f61e 100644 --- a/stdlib/public/libexec/swift-backtrace/main.swift +++ b/stdlib/public/libexec/swift-backtrace/main.swift @@ -22,10 +22,10 @@ import Musl import CRT #endif -@_spi(Formatting) import _Backtracing -@_spi(Contexts) import _Backtracing -@_spi(Registers) import _Backtracing -@_spi(MemoryReaders) import _Backtracing +@_spi(Formatting) import Runtime +@_spi(Contexts) import Runtime +@_spi(Registers) import Runtime +@_spi(MemoryReaders) import Runtime @main internal struct SwiftBacktrace { @@ -144,8 +144,8 @@ internal struct SwiftBacktrace { } static func measureDuration(_ body: () -> ()) -> timespec { - var startTime = timespec() - var endTime = timespec() + var startTime = timespec(tv_sec: 0, tv_nsec: 0) + var endTime = timespec(tv_sec: 0, tv_nsec: 0) clock_gettime(CLOCK_MONOTONIC, &startTime) body() @@ -719,6 +719,17 @@ Generate a backtrace for the parent process. var mentionedImages = Set() let formatter = backtraceFormatter() + let platform = target.images.platform + let architecture: String + switch crashingThread.backtrace { + case let .raw(backtrace): + architecture = backtrace.architecture + case let .symbolicated(backtrace): + architecture = backtrace.architecture + } + + writeln("\nPlatform: \(theme.architecture(architecture)) \(theme.platform(target.images.platform))") + func dump(ndx: Int, thread: TargetThread) { let crashed = thread.id == target.crashingThread ? " crashed" : "" let name = !thread.name.isEmpty ? " \"\(thread.name)\"" : "" @@ -786,13 +797,6 @@ Generate a backtrace for the parent process. } } - let addressWidthInChars: Int - switch crashingThread.backtrace { - case let .raw(backtrace): - addressWidthInChars = (backtrace.addressWidth + 3) / 4 - case let .symbolicated(backtrace): - addressWidthInChars = (backtrace.addressWidth + 3) / 4 - } switch args.showImages! { case .none: break @@ -804,12 +808,10 @@ Generate a backtrace for the parent process. } else { writeln("\n\nImages:\n") } - writeln(formatter.format(images: images, - addressWidth: addressWidthInChars)) + writeln(formatter.format(images: images)) case .all: writeln("\n\nImages:\n") - writeln(formatter.format(images: target.images, - addressWidth: addressWidthInChars)) + writeln(formatter.format(images: target.images)) } } @@ -891,20 +893,15 @@ Generate a backtrace for the parent process. let formatter = backtraceFormatter() switch thread.backtrace { case let .raw(backtrace): - let addressWidthInChars = (backtrace.addressWidth + 3) / 4 - if let frame = backtrace.frames.first { - let formatted = formatter.format(frame: frame, - addressWidth: addressWidthInChars) + if let frame = backtrace.frames.unsafeFirst { + let formatted = formatter.format(frame: frame) writeln("\(formatted)") } case let .symbolicated(backtrace): - let addressWidthInChars = (backtrace.addressWidth + 3) / 4 - if let frame = backtrace.frames.drop(while: { $0.isSwiftRuntimeFailure - }).first { - let formatted = formatter.format(frame: frame, - addressWidth: addressWidthInChars) + }).unsafeFirst { + let formatted = formatter.format(frame: frame) writeln("\(formatted)") } } @@ -975,12 +972,10 @@ Generate a backtrace for the parent process. switch thread.backtrace { case let .raw(backtrace): - let addressWidthInChars = (backtrace.addressWidth + 3) / 4 - - if let frame = backtrace.frames.first { + if let frame = backtrace.frames.unsafeFirst { rows += formatter.formatRows( - frame: frame, - addressWidth: addressWidthInChars).map{ row in + frame: frame + ).map{ row in switch row { case let .columns(columns): @@ -991,14 +986,12 @@ Generate a backtrace for the parent process. } } case let .symbolicated(backtrace): - let addressWidthInChars = (backtrace.addressWidth + 3) / 4 - if let frame = backtrace.frames.drop(while: { $0.isSwiftRuntimeFailure - }).first { + }).unsafeFirst { rows += formatter.formatRows( - frame: frame, - addressWidth: addressWidthInChars).map{ row in + frame: frame + ).map{ row in switch row { case let .columns(columns): @@ -1020,15 +1013,7 @@ Generate a backtrace for the parent process. case "images": let formatter = backtraceFormatter() let images = target.images - let addressWidthInChars: Int - switch target.threads[currentThread].backtrace { - case let .raw(backtrace): - addressWidthInChars = (backtrace.addressWidth + 3) / 4 - case let .symbolicated(backtrace): - addressWidthInChars = (backtrace.addressWidth + 3) / 4 - } - let output = formatter.format(images: images, - addressWidth: addressWidthInChars) + let output = formatter.format(images: images) writeln(output) case "set": diff --git a/stdlib/public/runtime/CMakeLists.txt b/stdlib/public/runtime/CMakeLists.txt index be5bb9e352b..407382ff6b3 100644 --- a/stdlib/public/runtime/CMakeLists.txt +++ b/stdlib/public/runtime/CMakeLists.txt @@ -144,7 +144,11 @@ foreach(sdk ${SWIFT_SDKS}) endif() endforeach() -add_swift_target_library(swiftRuntime OBJECT_LIBRARY +# In modern CMake, we would be able to use the previous name `swiftRuntime` +# without clashing with the `Runtime` module, but the current build system +# is architected in such a way that we had to rename this to `swiftRuntimeCore` +# in order to avoid a clash with the new Swift module. +add_swift_target_library(swiftRuntimeCore OBJECT_LIBRARY ${swift_runtime_sources} ${swift_runtime_objc_sources} ${swift_runtime_leaks_sources} @@ -323,4 +327,3 @@ if(static_binary_lnk_file_list) add_dependencies(stdlib ${static_binary_lnk_file_list}) add_custom_target(static_binary_magic ALL DEPENDS ${static_binary_lnk_file_list}) endif() - diff --git a/test/Backtracing/BacktraceWithLimit.swift b/test/Backtracing/BacktraceWithLimit.swift index c91cd29d60c..8c4963eb013 100644 --- a/test/Backtracing/BacktraceWithLimit.swift +++ b/test/Backtracing/BacktraceWithLimit.swift @@ -9,7 +9,7 @@ // REQUIRES: backtracing // REQUIRES: OS=macosx || OS=linux-gnu -import _Backtracing +import Runtime func doFrames(_ count: Int) { if count <= 0 { diff --git a/test/Backtracing/BacktraceWithLimitAndTop.swift b/test/Backtracing/BacktraceWithLimitAndTop.swift index ccd067d1346..618e24606e3 100644 --- a/test/Backtracing/BacktraceWithLimitAndTop.swift +++ b/test/Backtracing/BacktraceWithLimitAndTop.swift @@ -9,7 +9,7 @@ // REQUIRES: backtracing // REQUIRES: OS=macosx || OS=linux-gnu -import _Backtracing +import Runtime func doFrames(_ count: Int, limit: Int, top: Int) { if count <= 0 { diff --git a/test/Backtracing/CodableBacktrace.swift b/test/Backtracing/CodableBacktrace.swift new file mode 100644 index 00000000000..47e4a4ba790 --- /dev/null +++ b/test/Backtracing/CodableBacktrace.swift @@ -0,0 +1,67 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -target %target-cpu-macos15.0 -Xfrontend -parse-as-library -Onone -o %t/CodableBacktrace +// RUN: %target-codesign %t/CodableBacktrace +// RUN: %target-run %t/CodableBacktrace | %FileCheck %s + +// REQUIRES: executable_test +// REQUIRES: backtracing +// REQUIRES: OS=macosx + +// UNSUPPORTED: use_os_stdlib +// UNSUPPORTED: back_deployment_runtime + +import Runtime +import Foundation + +func level1() { + level2() +} + +func level2() { + level3() +} + +func level3() { + level4() +} + +func level4() { + level5() +} + +func level5() { + let backtrace = try! Backtrace.capture() + + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted,.sortedKeys,.withoutEscapingSlashes] + + let data = try! encoder.encode(backtrace) + let json = String(data: data, encoding: .utf8)! + + print(json) + + // CHECK: { + // CHECK: "architecture" : "{{.*}}", + // CHECK: "backtrace" : "{{[A-Za-z0-9+/]*}}" + // CHECK: } + + let decoder = JSONDecoder() + + let bt2 = try! decoder.decode(Backtrace.self, from: data) + + print(bt2) + + // 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] +} + +@main +struct CodableBacktrace { + static func main() { + level1() + } +} diff --git a/test/Backtracing/CompactImageMap.swift b/test/Backtracing/CompactImageMap.swift new file mode 100644 index 00000000000..226a1c48d19 --- /dev/null +++ b/test/Backtracing/CompactImageMap.swift @@ -0,0 +1,64 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -parse-as-library -Onone -o %t/ImageMap +// RUN: %target-codesign %t/ImageMap +// RUN: %target-run %t/ImageMap | tee %t/ImageMap.out +// RUN: cat %t/ImageMap.out | %FileCheck %s + +// UNSUPPORTED: use_os_stdlib +// UNSUPPORTED: back_deployment_runtime +// REQUIRES: executable_test +// REQUIRES: backtracing +// REQUIRES: OS=macosx || OS=linux-gnu + +import Runtime +@_spi(Internal) import Runtime + +@main +struct ImageMapTest { + static func main() { + let map = ImageMap.capture() + let encoder = CompactImageMapFormat.Encoder(map) + let encoded = Array(encoder) + + print(map) + + print("Encoded \(map.count) images in \(encoded.count) bytes") + + for (ndx, byte) in encoded.enumerated() { + let separator: String + if ((ndx + 1) & 0xf) == 0 { + separator = "\n" + } else { + separator = " " + } + + var hex = String(byte, radix: 16) + if hex.count < 2 { + hex = "0" + hex + } + print(hex, terminator: separator) + } + print("") + + guard let decodedMap = ImageMap(compactImageMapData: encoded) else { + print("Unable to decode") + return + } + + print("Decoded \(decodedMap.count) images") + + print(decodedMap) + + if map.description != decodedMap.description { + print("Maps do not match") + } else { + print("Maps match") + } + + // CHECK: Encoded [[COUNT:[0-9]+]] images in [[BYTES:[0-9]+]] bytes + // CHECK-NOT: Unable to decode + // CHECK: Decoded [[COUNT]] images + // CHECK-NOT: Maps do not match + // CHECK: Maps match + } +} diff --git a/test/Backtracing/DwarfReader.swift b/test/Backtracing/DwarfReader.swift index 53ac43b45d8..6c1d71fc3f0 100644 --- a/test/Backtracing/DwarfReader.swift +++ b/test/Backtracing/DwarfReader.swift @@ -6,7 +6,7 @@ // REQUIRES: OS=linux-gnu // REQUIRES: backtracing -@_spi(DwarfTest) import _Backtracing +@_spi(DwarfTest) import Runtime #if canImport(Darwin) import Darwin #elseif canImport(SwiftWASILibc) diff --git a/test/Backtracing/ElfReader.swift b/test/Backtracing/ElfReader.swift index dbd8b5b68c6..0d011617bd9 100644 --- a/test/Backtracing/ElfReader.swift +++ b/test/Backtracing/ElfReader.swift @@ -15,7 +15,7 @@ // REQUIRES: OS=linux-gnu // REQUIRES: backtracing -@_spi(ElfTest) import _Backtracing +@_spi(ElfTest) import Runtime #if canImport(Darwin) import Darwin #elseif canImport(SwiftWASILibc) diff --git a/test/Backtracing/ImageMap.swift b/test/Backtracing/ImageMap.swift new file mode 100644 index 00000000000..d8e0c9e98c4 --- /dev/null +++ b/test/Backtracing/ImageMap.swift @@ -0,0 +1,27 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -parse-as-library -Onone -o %t/ImageMap +// RUN: %target-codesign %t/ImageMap +// RUN: %target-run %t/ImageMap | %FileCheck %s + +// UNSUPPORTED: use_os_stdlib +// UNSUPPORTED: back_deployment_runtime +// REQUIRES: executable_test +// REQUIRES: backtracing +// REQUIRES: OS=macosx || OS=linux-gnu + +import Runtime + +@main +struct ImageMapTest { + static func main() { + let map = ImageMap.capture() + + // We expect ImageMap, followed by one or more additional lines + + // CHECK: {{0x[0-9a-f]*-0x[0-9a-f]*}} {{(|[0-9a-f]*)}} ImageMap {{.*}}/ImageMap + // CHECK-NEXT: {{0x[0-9a-f]*-0x[0-9a-f]*}} {{(|[0-9a-f]*)}} [[NAME:[^ ]*]] {{.*}}/[[NAME]] + print(map) + } +} + + diff --git a/test/Backtracing/SimpleAsyncBacktrace.swift b/test/Backtracing/SimpleAsyncBacktrace.swift index 592a080fba9..941466aae17 100644 --- a/test/Backtracing/SimpleAsyncBacktrace.swift +++ b/test/Backtracing/SimpleAsyncBacktrace.swift @@ -12,7 +12,7 @@ // UNSUPPORTED: use_os_stdlib // UNSUPPORTED: back_deployment_runtime -import _Backtracing +import Runtime @available(SwiftStdlib 5.1, *) func level1() async { diff --git a/test/Backtracing/SimpleBacktrace.swift b/test/Backtracing/SimpleBacktrace.swift index 1695ff896b7..acccd40c75a 100644 --- a/test/Backtracing/SimpleBacktrace.swift +++ b/test/Backtracing/SimpleBacktrace.swift @@ -1,5 +1,5 @@ // RUN: %empty-directory(%t) -// RUN: %target-build-swift %s -Xfrontend -enable-implicit-backtracing-module-import -parse-as-library -Onone -o %t/SimpleBacktrace +// RUN: %target-build-swift %s -Xfrontend -parse-as-library -Onone -o %t/SimpleBacktrace // RUN: %target-codesign %t/SimpleBacktrace // RUN: %target-run %t/SimpleBacktrace | %FileCheck %s @@ -10,6 +10,8 @@ // UNSUPPORTED: use_os_stdlib // UNSUPPORTED: back_deployment_runtime +import Runtime + func level1() { level2() } diff --git a/test/Backtracing/SymbolicatedBacktrace.swift b/test/Backtracing/SymbolicatedBacktrace.swift index f1251b82df6..0d11b30ff5d 100644 --- a/test/Backtracing/SymbolicatedBacktrace.swift +++ b/test/Backtracing/SymbolicatedBacktrace.swift @@ -9,7 +9,7 @@ // REQUIRES: backtracing // REQUIRES: OS=macosx || OS=linux-gnu -import _Backtracing +import Runtime func kablam() { kerpow() @@ -32,7 +32,9 @@ func splat() { } func pow() { - let backtrace = try! Backtrace.capture().symbolicated(useSymbolCache: false)! + let backtrace = try! Backtrace.capture().symbolicated( + options: [ .showInlineFrames, .showSourceLocations ] + )! // CHECK: 0{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [0] SymbolicatedBacktrace pow() // CHECK: 1{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [0] SymbolicatedBacktrace splat() diff --git a/test/Backtracing/SymbolicatedBacktraceInline.swift b/test/Backtracing/SymbolicatedBacktraceInline.swift index 3f7065dd0e7..2990bb6f029 100644 --- a/test/Backtracing/SymbolicatedBacktraceInline.swift +++ b/test/Backtracing/SymbolicatedBacktraceInline.swift @@ -13,7 +13,7 @@ // which presumably doesn't have a frame pointer. When we add the Dwarf EH // unwinder, we should be able to turn this test on. -import _Backtracing +import Runtime func kablam() { kerpow() @@ -36,7 +36,9 @@ func splat() { } func pow() { - let backtrace = try! Backtrace.capture().symbolicated(useSymbolCache: false)! + let backtrace = try! Backtrace.capture().symbolicated( + options: [ .showInlineFrames, .showSourceLocations ] + )! // CHECK: 0{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [0] SymbolicatedBacktraceInline pow() // CHECK: 1{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [inlined] [0] SymbolicatedBacktraceInline splat() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b8ca503f601..503a8748ae6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -82,6 +82,14 @@ function(get_test_dependencies SDK result_var_name) set(deps_binaries) + if (SWIFT_BUILD_LIBEXEC AND SWIFT_ENABLE_BACKTRACING) + # Only add if `swift-backtrace` builds for this SDK + if (TARGET "swift-backtrace-${SDK}") + list(APPEND deps_binaries + "swift-backtrace-${SDK}") + endif() + endif() + if (SWIFT_INCLUDE_TOOLS) list(APPEND deps_binaries libMockPlugin @@ -251,6 +259,7 @@ normalize_boolean_spelling(SWIFT_STDLIB_ENABLE_UNICODE_DATA) normalize_boolean_spelling(SWIFT_ENABLE_DISPATCH) normalize_boolean_spelling(SWIFT_STDLIB_ENABLE_OBJC_INTEROP) normalize_boolean_spelling(SWIFT_ENABLE_BACKTRACING) +normalize_boolean_spelling(SWIFT_ENABLE_RUNTIME_MODULE) normalize_boolean_spelling(SWIFT_BUILD_SWIFT_SYNTAX) normalize_boolean_spelling(SWIFT_ENABLE_SYNCHRONIZATION) normalize_boolean_spelling(SWIFT_ENABLE_VOLATILE) @@ -473,6 +482,10 @@ foreach(SDK ${SWIFT_SDKS}) list(APPEND LIT_ARGS "--param" "volatile") endif() + if(SWIFT_ENABLE_RUNTIME_MODULE) + list(APPEND LIT_ARGS "--param" "runtime_module") + endif() + if(SWIFT_BUILD_REMOTE_MIRROR) list(APPEND LIT_ARGS "--param" "remote_mirror") endif() diff --git a/test/lit.cfg b/test/lit.cfg index dcb96fded8c..cd6931963bc 100644 --- a/test/lit.cfg +++ b/test/lit.cfg @@ -1326,7 +1326,7 @@ if run_vendor == 'apple': "swiftDarwin", "swiftSwiftPrivateThreadExtras", "swiftSwiftOnoneSupport", "swift_Concurrency"] if backtracing is not None: - libraries.append('swift_Backtracing') + libraries.append('swiftRuntime') for library in libraries: swift_execution_tests_extra_flags += ' -Xlinker -l%s'% library @@ -2208,12 +2208,12 @@ 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", +# Add 'runtime_module' as the path to the Runtime .swiftmodule file +runtime_module = os.path.join(stdlib_dir, "Runtime.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)))) +config.substitutions.append(('%runtime_module', runtime_module)) +config.substitutions.append(('%/runtime_module', + '/'.join(os.path.normpath(runtime_module).split(os.sep)))) # Add 'distributed_module' as the path to the Distributed .swiftmodule file distributed_module = os.path.join(stdlib_dir, "Distributed.swiftmodule", diff --git a/tools/SourceKit/tools/sourcekitd-test/Options.td b/tools/SourceKit/tools/sourcekitd-test/Options.td index ef2f8c6926e..774bfa04c6b 100644 --- a/tools/SourceKit/tools/sourcekitd-test/Options.td +++ b/tools/SourceKit/tools/sourcekitd-test/Options.td @@ -116,14 +116,6 @@ 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 2fc43eade84..14345dfbb40 100644 --- a/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp +++ b/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp @@ -464,10 +464,6 @@ 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 9f85fab2b76..a962e312825 100644 --- a/tools/SourceKit/tools/sourcekitd-test/TestOptions.h +++ b/tools/SourceKit/tools/sourcekitd-test/TestOptions.h @@ -135,8 +135,6 @@ struct TestOptions { bool measureInstructions = false; bool DisableImplicitConcurrencyModuleImport = false; bool DisableImplicitStringProcessingModuleImport = false; - bool EnableImplicitBacktracingModuleImport = false; - bool DisableImplicitBacktracingModuleImport = false; std::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 b2468cc2323..c79a8f297c0 100644 --- a/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp +++ b/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp @@ -1222,20 +1222,6 @@ 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 2ff77fb1193..c8ce3eb92ce 100644 --- a/tools/swift-ide-test/swift-ide-test.cpp +++ b/tools/swift-ide-test/swift-ide-test.cpp @@ -833,16 +833,6 @@ 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"), @@ -4470,13 +4460,6 @@ 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().enableFeature(Feature::NamedOpaqueTypes); diff --git a/unittests/runtime/CMakeLists.txt b/unittests/runtime/CMakeLists.txt index 4a9d32e5e24..920452c17fe 100644 --- a/unittests/runtime/CMakeLists.txt +++ b/unittests/runtime/CMakeLists.txt @@ -116,7 +116,7 @@ if(("${SWIFT_HOST_VARIANT_SDK}" STREQUAL "${SWIFT_PRIMARY_VARIANT_SDK}") AND # The runtime tests link to internal runtime symbols, which aren't exported # from the swiftCore dylib, so we need to link to both the runtime archive # and the stdlib. - $ + $ $ $ ) diff --git a/unittests/runtime/LongTests/CMakeLists.txt b/unittests/runtime/LongTests/CMakeLists.txt index e16bc0d540b..74709e914cd 100644 --- a/unittests/runtime/LongTests/CMakeLists.txt +++ b/unittests/runtime/LongTests/CMakeLists.txt @@ -42,7 +42,7 @@ if(("${SWIFT_HOST_VARIANT_SDK}" STREQUAL "${SWIFT_PRIMARY_VARIANT_SDK}") AND # The runtime tests link to internal runtime symbols, which aren't exported # from the swiftCore dylib, so we need to link to both the runtime archive # and the stdlib. - $ + $ $ $ ) diff --git a/utils/build_swift/build_swift/driver_arguments.py b/utils/build_swift/build_swift/driver_arguments.py index e53dccb61bb..71042e53dc1 100644 --- a/utils/build_swift/build_swift/driver_arguments.py +++ b/utils/build_swift/build_swift/driver_arguments.py @@ -1493,6 +1493,10 @@ def create_argument_parser(): default=True, help='Enable Volatile module.') + option('--enable-runtime-module', toggle_true, + default=True, + help='Enable Runtime module.') + option('--enable-experimental-parser-validation', toggle_true, default=True, help='Enable experimental Swift Parser validation by default.') diff --git a/utils/build_swift/tests/expected_options.py b/utils/build_swift/tests/expected_options.py index 5c37377c30b..1b906115dc0 100644 --- a/utils/build_swift/tests/expected_options.py +++ b/utils/build_swift/tests/expected_options.py @@ -188,6 +188,7 @@ EXPECTED_DEFAULTS = { 'swift_enable_backtracing': True, 'enable_synchronization': True, 'enable_volatile': True, + 'enable_runtime_module': True, 'enable_lsan': False, 'enable_sanitize_coverage': False, 'disable_guaranteed_normal_arguments': False, @@ -629,6 +630,7 @@ EXPECTED_OPTIONS = [ EnableOption('--enable-experimental-observation'), EnableOption('--enable-experimental-parser-validation'), EnableOption('--enable-lsan'), + EnableOption('--enable-runtime-module'), EnableOption('--enable-sanitize-coverage'), EnableOption('--enable-tsan'), EnableOption('--enable-tsan-runtime'), diff --git a/utils/swift_build_support/swift_build_support/products/swift.py b/utils/swift_build_support/swift_build_support/products/swift.py index ba36cf4edaa..d649c8d6bf4 100644 --- a/utils/swift_build_support/swift_build_support/products/swift.py +++ b/utils/swift_build_support/swift_build_support/products/swift.py @@ -74,6 +74,9 @@ class Swift(product.Product): # Add volatile flag. self.cmake_options.extend(self._enable_volatile) + # Add runtime module flag. + self.cmake_options.extend(self._enable_runtime_module) + # Add static vprintf flag self.cmake_options.extend(self._enable_stdlib_static_vprintf) @@ -235,6 +238,11 @@ updated without updating swift.py?") return [('SWIFT_ENABLE_VOLATILE:BOOL', self.args.enable_volatile)] + @property + def _enable_runtime_module(self): + return [('SWIFT_ENABLE_RUNTIME_MODULE:BOOL', + self.args.enable_runtime_module)] + @property def _enable_stdlib_static_vprintf(self): return [('SWIFT_STDLIB_STATIC_PRINT', diff --git a/utils/swift_build_support/tests/products/test_swift.py b/utils/swift_build_support/tests/products/test_swift.py index 2d690aaa631..6a7ea32f241 100644 --- a/utils/swift_build_support/tests/products/test_swift.py +++ b/utils/swift_build_support/tests/products/test_swift.py @@ -63,6 +63,7 @@ class SwiftTestCase(unittest.TestCase): swift_enable_backtracing=False, enable_synchronization=False, enable_volatile=False, + enable_runtime_module=False, build_early_swiftsyntax=False, build_swift_stdlib_static_print=False, build_swift_stdlib_unicode_data=True, @@ -112,6 +113,7 @@ class SwiftTestCase(unittest.TestCase): '-DSWIFT_ENABLE_BACKTRACING:BOOL=FALSE', '-DSWIFT_ENABLE_SYNCHRONIZATION:BOOL=FALSE', '-DSWIFT_ENABLE_VOLATILE:BOOL=FALSE', + '-DSWIFT_ENABLE_RUNTIME_MODULE:BOOL=FALSE', '-DSWIFT_STDLIB_STATIC_PRINT=FALSE', '-DSWIFT_FREESTANDING_IS_DARWIN:BOOL=FALSE', '-DSWIFT_STDLIB_BUILD_PRIVATE:BOOL=TRUE', @@ -146,6 +148,7 @@ class SwiftTestCase(unittest.TestCase): '-DSWIFT_ENABLE_BACKTRACING:BOOL=FALSE', '-DSWIFT_ENABLE_SYNCHRONIZATION:BOOL=FALSE', '-DSWIFT_ENABLE_VOLATILE:BOOL=FALSE', + '-DSWIFT_ENABLE_RUNTIME_MODULE:BOOL=FALSE', '-DSWIFT_STDLIB_STATIC_PRINT=FALSE', '-DSWIFT_FREESTANDING_IS_DARWIN:BOOL=FALSE', '-DSWIFT_STDLIB_BUILD_PRIVATE:BOOL=TRUE', @@ -470,6 +473,19 @@ class SwiftTestCase(unittest.TestCase): [x for x in swift.cmake_options if 'DSWIFT_ENABLE_VOLATILE' in x]) + def test_runtime_module_flags(self): + self.args.enable_runtime_module = True + swift = Swift( + args=self.args, + toolchain=self.toolchain, + source_dir='/path/to/src', + build_dir='/path/to/build') + self.assertEqual( + ['-DSWIFT_ENABLE_RUNTIME_MODULE:BOOL=' + 'TRUE'], + [x for x in swift.cmake_options + if 'DSWIFT_ENABLE_RUNTIME_MODULE' in x]) + def test_freestanding_is_darwin_flags(self): self.args.swift_freestanding_is_darwin = True swift = Swift( diff --git a/validation-test/SIL/verify_all_overlays.py b/validation-test/SIL/verify_all_overlays.py index 5f9a244f780..c2b3441f250 100755 --- a/validation-test/SIL/verify_all_overlays.py +++ b/validation-test/SIL/verify_all_overlays.py @@ -39,9 +39,9 @@ for module_file in os.listdir(sdk_overlay_dir): if module_name == "DifferentiationUnittest": continue # Backtracing needs its own additional modules in the module path - if module_name == "_Backtracing": + if module_name == "Runtime": extra_args = ["-I", os.path.join(source_dir, "stdlib", - "public", "Backtracing", "modules"), + "public", "RuntimeModule", "modules"), "-I", os.path.join(source_dir, "include")] # _Concurrency needs its own additional modules in the module path if module_name == "_Concurrency":