Merge pull request #78516 from al45tair/eng/PR-124913332

[Backtracing] Implement API per SE-0419
This commit is contained in:
Alastair Houghton
2025-01-28 10:48:33 +00:00
committed by GitHub
107 changed files with 6195 additions and 2422 deletions

View File

@@ -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")

View File

@@ -173,7 +173,6 @@ add_compile_options(
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xfrontend -enable-experimental-concise-pound-file>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xfrontend -enable-lexical-lifetimes=false>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xfrontend -disable-implicit-concurrency-module-import>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xfrontend -disable-implicit-backtracing-module-import>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xfrontend -disable-implicit-string-processing-module-import>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xfrontend -enforce-exclusivity=unchecked>"
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xfrontend -enable-ossa-modules>"

View File

@@ -28,13 +28,7 @@ function(_add_host_swift_compile_options name)
"$<$<COMPILE_LANGUAGE:Swift>: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
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xfrontend -disable-implicit-backtracing-module-import>")
endif()
if(SWIFT_ANALYZE_CODE_COVERAGE)
if(SWIFT_ANALYZE_CODE_COVERAGE)
set(_cov_flags $<$<COMPILE_LANGUAGE:Swift>:-profile-generate -profile-coverage-mapping>)
target_compile_options(${name} PRIVATE ${_cov_flags})
target_link_options(${name} PRIVATE ${_cov_flags})

View File

@@ -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 <CompactBacktraceFormat.md>`. 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 <CompactImageMapFormat.md>` to minimise
storage requirements.

View File

@@ -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.

View File

@@ -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
`/<name>.framework/Versions/<version>/<name>`. 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%.

View File

@@ -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"

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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.">;

View File

@@ -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.

View File

@@ -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)) {

View File

@@ -230,9 +230,9 @@ int autolink_extract_main(ArrayRef<const char *> Args, const char *Argv0,
"-lswift_StringProcessing",
"-lswiftRegexBuilder",
"-lswift_RegexParser",
"-lswift_Backtracing",
"-lswift_Builtin_float",
"-lswift_math",
"-lswiftRuntime",
"-lswiftSynchronization",
"-lswiftGlibc",
"-lswiftAndroid",

View File

@@ -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");

View File

@@ -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);

View File

@@ -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)

View File

@@ -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)

View File

@@ -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<T>: ImageSource {
private var array: Array<T>
public init(array: Array<T>) {
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<T>.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)..<Int(addr+requested)])
}
}
}

View File

@@ -1,685 +0,0 @@
//===--- 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
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
internal import BacktracingImpl.OS.Darwin
#endif
#if os(Linux)
internal import BacktracingImpl.ImageFormats.Elf
#endif
/// Holds a backtrace.
public struct Backtrace: CustomStringConvertible, Sendable {
/// The type of an address.
///
/// This is intentionally _not_ a pointer, because you shouldn't be
/// dereferencing them; they may refer to some other process, for
/// example.
public typealias Address = UInt64
/// The unwind algorithm to use.
public enum UnwindAlgorithm {
/// Choose the most appropriate for the platform.
case auto
/// Use the fastest viable method.
///
/// Typically this means walking the frame pointers.
case fast
/// Use the most precise available method.
///
/// On Darwin and on ELF platforms, this will use EH unwind
/// information. On Windows, it will use Win32 API functions.
case precise
}
/// Represents an individual frame in a backtrace.
public enum Frame: CustomStringConvertible, Sendable {
/// A program counter value.
///
/// This might come from a signal handler, or an exception or some
/// other situation in which we have captured the actual program counter.
///
/// These can be directly symbolicated, as-is, with no adjustment.
case programCounter(Address)
/// A return address.
///
/// Corresponds to a normal function call.
///
/// Requires adjustment when symbolicating for a backtrace, because it
/// points at the address after the one that triggered the child frame.
case returnAddress(Address)
/// An async resume point.
///
/// Corresponds to an `await` in an async task.
///
/// Can be directly symbolicated, as-is.
case asyncResumePoint(Address)
/// Indicates a discontinuity in the backtrace.
///
/// This occurs when you set a limit and a minimum number of frames at
/// the top. For example, if you set a limit of 10 frames and a minimum
/// of 4 top frames, but the backtrace generated 100 frames, you will see
///
/// 0: frame 100 <----- bottom of call stack
/// 1: frame 99
/// 2: frame 98
/// 3: frame 97
/// 4: frame 96
/// 5: ... <----- omittedFrames(92)
/// 6: frame 3
/// 7: frame 2
/// 8: frame 1
/// 9: frame 0 <----- top of call stack
///
/// Note that the limit *includes* the discontinuity.
///
/// This is good for handling cases involving deep recursion.
case omittedFrames(Int)
/// Indicates a discontinuity of unknown length.
case truncated
/// The program counter, without any adjustment.
public var originalProgramCounter: Address {
switch self {
case let .returnAddress(addr):
return addr
case let .programCounter(addr):
return addr
case let .asyncResumePoint(addr):
return addr
case .omittedFrames(_), .truncated:
return 0
}
}
/// The adjusted program counter to use for symbolication.
public var adjustedProgramCounter: Address {
switch self {
case let .returnAddress(addr):
return addr - 1
case let .programCounter(addr):
return addr
case let .asyncResumePoint(addr):
return addr
case .omittedFrames(_), .truncated:
return 0
}
}
/// A textual description of this frame.
public func description(width: Int) -> 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<Address>.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)) <no build ID> \(name) \(path)"
}
}
/// A textual description of an Image.
public var description: String {
return description(width: MemoryLayout<Address>.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<Ctx: Context, Rdr: MemoryReader>(
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<Ctx.Address>.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)..<limit,
with: topFrames.prefix(secondPart))
}
return Backtrace(architecture: context.architecture,
addressWidth: addressWidth,
frames: frames,
images: images)
}
} else {
return Backtrace(architecture: context.architecture,
addressWidth: addressWidth,
frames: Array(unwinder),
images: images)
}
}
}
/// Capture a list of the images currently mapped into the calling
/// process.
///
/// @returns A list of `Image`s.
public static func captureImages() -> [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<T>(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<uuid_t>.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<M: MemoryReader>(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<M>
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<uuid_t>.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")
}
}

View File

@@ -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..<start+buffer.count])
}
}

View File

@@ -1,158 +0,0 @@
//===--- ImageSource.swift - A place from which to read image data --------===//
//
// 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 ImageSource, which is a protocol that can be implemented to
// provide an image reader with a way to read data from a file, a buffer
// in memory, or wherever we might wish to read an image from.
//
//===----------------------------------------------------------------------===//
import Swift
struct ImageBounds<Address: FixedWidthInteger,
Size: FixedWidthInteger> {
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<Address, Size>
/// 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<Address, Size>
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<T>(into buffer: UnsafeMutableBufferPointer<T>) throws {
try source.fetch(from: pos, into: buffer)
pos += UInt64(MemoryLayout<T>.stride * buffer.count)
}
public mutating func read<T>(into pointer: UnsafeMutablePointer<T>) throws {
try source.fetch(from: pos, into: pointer)
pos += UInt64(MemoryLayout<T>.stride)
}
public mutating func read<T>(as type: T.Type) throws -> T {
let stride = MemoryLayout<T>.stride
let result = try source.fetch(from: pos, as: type)
pos += UInt64(stride)
return result
}
public mutating func read<T>(count: Int, as type: T.Type) throws -> [T] {
let stride = MemoryLayout<T>.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<S: ImageSource>: 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)
}
}

View File

@@ -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<M: MemoryReader>: 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)
}
}

View File

@@ -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)

View File

@@ -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<major><minor>_hooks'
#define COMPATIBILITY_OVERRIDE_SECTION_NAME_swiftRuntime "__swift" \
#define COMPATIBILITY_OVERRIDE_SECTION_NAME_swiftRuntimeCore "__swift" \
SWIFT_VERSION_MAJOR \
SWIFT_VERSION_MINOR \
"_hooks"

View File

@@ -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<T: FixedWidthInteger>(
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?<T: FixedWidthInteger>(_ 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)
}
}
}

View File

@@ -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<UTF8.CodeUnit>) -> 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)
}
}
}

View File

@@ -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 ?? "<unknown>") \(path ?? "<unknown>")"
} else {
return "\(baseAddress)-\(endOfText) <no build ID> \(name ?? "<unknown>") \(path ?? "<unknown>")"
}
}
}
/// 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<Frame> {
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<Address: FixedWidthInteger>(architecture: String,
frames: some Sequence<RichFrame<Address>>,
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<Ctx: Context, Rdr: MemoryReader>(
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)
}
}
}

View File

@@ -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<Backtrace.Frame>,
addressWidth: Int) -> String {
public func format(frames: some Sequence<Backtrace.Frame>) -> 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<SymbolicatedBacktrace.Frame>,
addressWidth: Int) -> String {
public func format(
frames: some Sequence<SymbolicatedBacktrace.Frame>
) -> String {
var rows: [TableRow] = []
var sourceLocationsShown = Set<SymbolicatedBacktrace.SourceLocation>()
@@ -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<Int>()
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 = "<no build ID>"
}
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 = "<unknown>"
}
let imageName = image.name ?? "<unknown>"
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<Backtrace.Image>,
addressWidth: Int) -> String {
public func format(images: some Sequence<Backtrace.Image>) -> String {
let rows = images.map{
TableRow.columns(
formatColumns(image: $0,
addressWidth: addressWidth)
formatColumns(image: $0)
)
}

View File

@@ -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<Int8>) -> UInt8? in
let value = table[Int(char)]
guard value >= 0 else {
return nil
}
return UInt8(truncatingIfNeeded: value)
}
}
}
@_spi(Base64)
public struct Base64Encoder<S: Sequence>: 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<S: Sequence>: 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")
}
}
}
}

View File

@@ -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

View File

@@ -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<T: MemoryReader>: MemoryReader {
private var reader: T
public class CachingMemoryReader<Reader: MemoryReader>: 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<T: MemoryReader>: 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<T: MemoryReader>: MemoryReader {
}
}
}
#if os(Linux)
@_spi(MemoryReaders)
public typealias MemserverMemoryReader
= CachingMemoryReader<UncachedMemserverMemoryReader>
extension CachingMemoryReader where Reader == UncachedMemserverMemoryReader {
convenience public init(fd: CInt) {
self.init(for: UncachedMemserverMemoryReader(fd: fd))
}
}
#endif
@_spi(MemoryReaders)
public typealias RemoteMemoryReader = CachingMemoryReader<UncachedRemoteMemoryReader>
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<UncachedLocalMemoryReader>
extension CachingMemoryReader where Reader == UncachedLocalMemoryReader {
convenience public init() {
self.init(for: UncachedLocalMemoryReader())
}
}

View File

@@ -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<S: Sequence<UInt8>>: 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<S: Sequence<UInt8>>: 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..<count {
guard let byte = iterator!.next() else {
return nil
}
word = (word << 8) | UInt64(byte)
}
if absolute {
lastAddress = word
} else {
lastAddress = lastAddress &+ word
}
switch wordSize {
case .sixteenBit:
return Address(UInt16(truncatingIfNeeded: lastAddress))
case .thirtyTwoBit:
return Address(UInt32(truncatingIfNeeded: lastAddress))
case .sixtyFourBit:
return Address(UInt64(truncatingIfNeeded: lastAddress))
}
}
private mutating func decodeWord(
_ count: Int
) -> Int? {
var word: Int = 0
for _ in 0..<count {
guard let byte = iterator!.next() else {
return nil
}
word = (word << 8) | Int(byte)
}
return word
}
private mutating func finished() {
iterator = nil
}
private mutating func fail() {
iterator = nil
}
// Note: If we hit an error while decoding, we will return .truncated.
public mutating func next() -> 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<A: FixedWidthInteger, S: Sequence<RichFrame<A>>>: Sequence {
typealias Element = UInt8
typealias Frame = Backtrace.Frame
typealias SourceFrame = RichFrame<A>
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
}
}
}

View File

@@ -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..<pos
let prefix = String(str[range])!
body(prefix)
}
pos = str.index(after: pos)
}
}
/// Decodes a Sequence containing Compact ImageMap Format data into am
/// ImageMap.
@_spi(Internal)
public struct Decoder<S: Sequence<UInt8>> {
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..<utf8Length {
guard let byte = iterator.next() else {
return nil
}
bytes.append(byte)
}
return String(decoding: bytes, as: UTF8.self)
}
mutating func decodeAddress(_ count: Int) -> 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..<count {
guard let byte = iterator.next() else {
return nil
}
word = (word << 8) | UInt64(byte)
}
return word
}
mutating func decodePath() -> 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..<count {
guard let char = iterator.next() else {
return nil
}
if base + n > stringBase! && (char == slash
|| char == backslash) {
let prefix = String(decoding: resultBytes[stringBase!..<base+n],
as: UTF8.self)
#if DEBUG_COMPACT_IMAGE_MAP
print("define \(nextCode) = \(prefix)")
#endif
pathPrefixes[nextCode] = prefix
nextCode += 1
}
resultBytes.append(char)
#if DEBUG_COMPACT_IMAGE_MAP
var hex = String(char, radix: 16)
if hex.count == 1 {
hex = "0" + hex
}
#endif
}
#if DEBUG_COMPACT_IMAGE_MAP
let theString = String(decoding: resultBytes[base...], as: UTF8.self)
print("str '\(theString)'")
#endif
} else if byte < 0x80 {
// `framewk`
let count = Int((byte & 0x3f) + 1)
guard let version = iterator.next() else {
return nil
}
var nameBytes: [UInt8] = []
nameBytes.reserveCapacity(count)
for _ in 0..<count {
guard let char = iterator.next() else {
return nil
}
nameBytes.append(char)
}
#if DEBUG_COMPACT_IMAGE_MAP
let name = String(decoding: nameBytes, as: UTF8.self)
let versionChar = String(Unicode.Scalar(version))
print("framewk version='\(versionChar)' name='\(name)'")
#endif
resultBytes.append(slash)
resultBytes.append(contentsOf: nameBytes)
resultBytes.append(contentsOf: ".framework/Versions/".utf8)
resultBytes.append(version)
resultBytes.append(slash)
resultBytes.append(contentsOf: nameBytes)
return String(decoding: resultBytes, as: UTF8.self)
} else {
// `expand`
var code: Int
if (byte & 0x40) == 0 {
code = Int(byte & 0x3f)
} else {
let byteCount = Int(byte & 0x3f)
code = 0
for _ in 0..<byteCount {
guard let byte = iterator.next() else {
return nil
}
code = (code << 8) | Int(byte)
}
}
#if DEBUG_COMPACT_IMAGE_MAP
print("expand \(code) = \(String(describing: pathPrefixes[code]))")
#endif
guard let prefix = pathPrefixes[code] else {
return nil
}
resultBytes.append(contentsOf: prefix.utf8)
}
guard let b = iterator.next() else {
return nil
}
byte = b
}
}
mutating func decode() -> (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..<count {
// Decode the header byte
guard let header = iterator.next() else {
return nil
}
let relative = (header & 0x80) != 0
let acount = Int(((header >> 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..<buildIdBytes {
guard let byte = iterator.next() else {
return nil
}
buildId!.append(byte)
}
}
#if DEBUG_COMPACT_IMAGE_MAP
print("buildId = \(buildId)")
#endif
// Decode the path
let path = decodePath()
let name: String?
// Extract the name from the path
if let path = path {
if let lastSlashNdx = path.utf8.lastIndex(
where: { $0 == slash || $0 == backslash }
) {
let nameNdx = path.index(after: lastSlashNdx)
name = String(path[nameNdx...])
} else {
name = path
}
} else {
name = nil
}
let image = ImageMap.Image(
name: name,
path: path,
uniqueID: buildId,
baseAddress: baseAddress,
endOfText: endOfText
)
images.append(image)
}
let wsMap: ImageMap.WordSize
switch wordSize {
case .sixteenBit:
wsMap = .sixteenBit
case .thirtyTwoBit:
wsMap = .thirtyTwoBit
case .sixtyFourBit:
wsMap = .sixtyFourBit
}
return (platform, images, wsMap)
}
}
/// Encodes an ImageMap as a Sequence<UInt8>
@_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 /<name>.framework/Versions/<version>/<name>
if let name = source.images[ndx].name, !name.isEmpty {
let nameCount = name.utf8.count
let expectedLen = 1 // '/'
+ nameCount // <name>
+ 20 // .framework/Versions/
+ 1 // <version>
+ 1 // '/'
+ nameCount // <name>
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
}
}
}
}
}

View File

@@ -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<UInt8>
typealias InputSource = () throws -> UnsafeRawBufferPointer
typealias OutputSink = (_ used: UInt, _ done: Bool) throws
-> UnsafeMutableBufferPointer<UInt8>?
-> 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<T>(_ 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<S: CompressedStream, I: ImageSource>(
stream: S, source: I, dataBounds: I.Bounds, uncompressedSize: UInt? = nil)
throws -> [UInt8] {
fileprivate func decompress<S: CompressedStream>(
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<UInt8>.allocate(capacity: bufSize)
defer {
inputBuffer.deallocate()
}
return try [UInt8].init(unsafeUninitializedCapacity: Int(uncompressedSize)) {
(outputBuffer: inout UnsafeMutableBufferPointer<UInt8>,
count: inout Int) in
count = Int(try stream.decompress(
input: { () throws -> UnsafeBufferPointer<UInt8> in
let chunkSize = min(Int(remaining), inputBuffer.count)
let slice = inputBuffer[0..<chunkSize]
let buffer = UnsafeMutableBufferPointer(rebasing: slice)
try source.fetch(from: pos, into: buffer)
pos += I.Address(chunkSize)
remaining -= I.Size(chunkSize)
return UnsafeBufferPointer(buffer)
},
output: {
(used: UInt, done: Bool) throws -> UnsafeMutableBufferPointer<UInt8>? in
if used == 0 {
return outputBuffer
} else {
return nil
}
}
))
}
} else {
// Otherwise, we decompress in chunks and append them to the array.
let buffer
= UnsafeMutableBufferPointer<UInt8>.allocate(capacity: 2 * bufSize)
defer {
buffer.deallocate()
}
let inputBuffer = UnsafeMutableBufferPointer(rebasing: buffer[0..<bufSize])
let outputBuffer = UnsafeMutableBufferPointer(rebasing: buffer[bufSize...])
var data = [UInt8]()
_ = try stream.decompress(
input: { () throws -> UnsafeBufferPointer<UInt8> in
let chunkSize = min(Int(remaining), inputBuffer.count)
let slice = inputBuffer[0..<chunkSize]
let buffer = UnsafeMutableBufferPointer(rebasing: slice)
try source.fetch(from: pos, into: buffer)
pos += I.Address(chunkSize)
remaining -= I.Size(chunkSize)
return UnsafeBufferPointer(buffer)
},
output: {
(used: UInt, done: Bool) throws -> UnsafeMutableBufferPointer<UInt8>? in
data.append(contentsOf: outputBuffer[..<Int(used)])
if !done {
return outputBuffer
} else {
return nil
}
if used == 0 {
return output.unusedBytes
} else {
return nil
}
)
return data
}
}
)
output.used(bytes: Int(totalBytes))
}
internal struct ElfCompressedImageSource<Traits: ElfTraits>: ImageSource {
fileprivate func decompressChunked<S: CompressedStream>(
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[..<Int(used)])
)
if !done {
return outputBuffer
} else {
return nil
}
}
)
}
if bounds.size < MemoryLayout<Traits.Chdr>.size {
extension ImageSource {
@_specialize(kind: full, where Traits == Elf32Traits)
@_specialize(kind: full, where Traits == Elf64Traits)
init<Traits: ElfTraits>(elfCompressedImageSource source: ImageSource,
traits: Traits.Type) throws {
if source.bytes.count < MemoryLayout<Traits.Chdr>.size {
throw CompressedImageSourceError.badCompressedData
}
let chdr = try source.fetch(from: bounds.base,
as: Traits.Chdr.self)
let dataBounds = bounds.adjusted(by: MemoryLayout<Traits.Chdr>.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<Traits.Chdr>.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<Traits.Chdr>.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..<toFetch {
outBuf[n] = data[Int(addr) + n]
}
}
}
}
internal struct ElfGNUCompressedImageSource: ImageSource {
private var data: [UInt8]
var isMappedImage: Bool { return false }
var path: String? { return nil }
var bounds: Bounds? { return Bounds(base: Address(0), size: Size(data.count)) }
init(source: some ImageSource) throws {
guard let bounds = source.bounds else {
throw CompressedImageSourceError.unboundedImageSource
}
if bounds.size < 12 {
init(gnuCompressedImageSource source: ImageSource) throws {
if source.bytes.count < 12 {
throw CompressedImageSourceError.badCompressedData
}
let magic = try source.fetch(from: bounds.base, as: UInt32.self)
if magic != 0x42494c5a {
throw CompressedImageSourceError.badCompressedData
let magic = try source.fetch(from: 0, as: UInt32.self)
let rawUncompressedSize = try source.fetch(from: 4, as: UInt64.self)
let uncompressedSize: UInt64
switch magic {
case 0x42494c5a: // BILZ
uncompressedSize = rawUncompressedSize.byteSwapped
case 0x5a4c4942: // ZLIB
uncompressedSize = rawUncompressedSize
default:
throw CompressedImageSourceError.badCompressedData
}
let uncompressedSize
= UInt(try source.fetch(from: bounds.base + 4, as: UInt64.self).byteSwapped)
self.init(capacity: Int(uncompressedSize), isMappedImage: false, path: nil)
data = try decompress(stream: ZLibStream(),
source: source,
dataBounds: bounds.adjusted(by: 12),
uncompressedSize: uncompressedSize)
try decompress(stream: ZLibStream(),
source: source, offset: 12,
output: &self)
}
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)
}
init(lzmaCompressedImageSource source: ImageSource) throws {
self.init(isMappedImage: false, path: nil)
buffer.withMemoryRebound(to: UInt8.self) { outBuf in
for n in 0..<toFetch {
outBuf[n] = data[Int(addr) + n]
}
}
try decompressChunked(stream: LZMAStream(),
source: source, offset: 0,
output: &self)
}
}
internal struct LZMACompressedImageSource: ImageSource {
private var data: [UInt8]
var isMappedImage: Bool { return false }
var path: String? { return nil }
var bounds: Bounds? { return Bounds(base: Address(0), size: Size(data.count)) }
init(source: some ImageSource) throws {
// Only supported for bounded image sources
guard let bounds = source.bounds else {
throw CompressedImageSourceError.unboundedImageSource
}
data = try decompress(stream: LZMAStream(),
source: source,
dataBounds: bounds)
}
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..<toFetch {
outBuf[n] = data[Int(addr) + n]
}
}
}
}
#endif // os(Linux)

View File

@@ -46,6 +46,9 @@ private func symbol<T>(_ 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>?) -> CFIndex =
symbol(coreFoundationHandle, "CFStringGetBytes")
static let CFStringGetCharactersPtr:
@convention(c) (CFString) -> UnsafePointer<UniChar>? =
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<UniChar>? {
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)

View File

@@ -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<S: DwarfSource> {
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<S: DwarfSource> {
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 "<unknown>"
}
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 = "<unknown>"
}
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<T: FixedWidthInteger>(_ 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..<length {
_ = try cursor.readULEB128()
}
}
}
}
}
}
var units: [Unit] = []
var lineNumberInfo: [LineNumberInfo] = []
var lineNumberInfo: [DwarfLineNumberInfo] = []
struct RangeListInfo {
var length: UInt64
@@ -827,6 +571,8 @@ struct DwarfReader<S: DwarfSource> {
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<S: DwarfSource> {
}
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<S: DwarfSource> {
}
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<S: DwarfSource> {
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<S: DwarfSource> {
}
var dirNames: [String] = []
var fileInfo: [FileInfo] = []
var fileInfo: [DwarfFileInfo] = []
if version == 3 || version == 4 {
// .11 include_directories
@@ -1152,7 +895,7 @@ struct DwarfReader<S: DwarfSource> {
// 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: "<unknown>",
directoryIndex: 0,
timestamp: nil,
@@ -1172,7 +915,7 @@ struct DwarfReader<S: DwarfSource> {
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<S: DwarfSource> {
md5sum = nil
}
fileInfo.append(FileInfo(
fileInfo.append(DwarfFileInfo(
path: path,
directoryIndex: dirIndex,
timestamp: timestamp,
@@ -1285,12 +1028,10 @@ struct DwarfReader<S: DwarfSource> {
}
// The actual program comes next
let program = try cursor.read(count: Int(nextOffset - cursor.pos),
as: UInt8.self)
let program = cursor.source[cursor.pos..<nextOffset]
cursor.pos = nextOffset
result.append(LineNumberInfo(
result.append(DwarfLineNumberInfo(
baseOffset: baseOffset,
version: version,
addressSize: addressSize,
@@ -1829,7 +1570,7 @@ struct DwarfReader<S: DwarfSource> {
if tag != .DW_TAG_subprogram {
return
}
refAttrs = try readDieAttributes(
at: &cursor,
unit: unit,
@@ -2005,11 +1746,268 @@ struct DwarfReader<S: DwarfSource> {
}
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 "<unknown>"
}
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 = "<unknown>"
}
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<T: FixedWidthInteger>(_ 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..<length {
_ = try cursor.readULEB128()
}
}
}
}
}
}
// .. Testing ..................................................................
@_spi(DwarfTest)
public func testDwarfReaderFor(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
}
@@ -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<Elf32Image<FileImageSource>>
var reader: DwarfReader<Elf32Image>
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<Elf64Image<FileImageSource>>
var reader: DwarfReader<Elf64Image>
do {
reader = try DwarfReader(source: elfImage)
} catch {
@@ -2060,5 +2058,3 @@ public func testDwarfReaderFor(path: String) -> Bool {
return false
}
}
#endif // os(Linux)

View File

@@ -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<T: FixedWidthInteger>(_ value: T) where T: SignedInteger {
self.init(Int64(value))
}
init<T: FixedWidthInteger>(_ 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
}
}
}
}

View File

@@ -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<ElfImageCache>.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<ElfImageCache>.passRetained(ElfImageCache())
pthread_setspecific(key, cache.toOpaque())
return cache.takeUnretainedValue()
}
let cache = Unmanaged<ElfImageCache>.fromOpaque(rawPtr)
return cache.takeUnretainedValue()
}
}

View File

@@ -20,7 +20,7 @@ import Swift
public struct FramePointerUnwinder<C: Context, M: MemoryReader>: 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<C: Context, M: MemoryReader>: Sequence, Itera
var done: Bool
#if os(Linux)
var elf32Cache: [Int:Elf32Image<FileImageSource>] = [:]
var elf64Cache: [Int:Elf64Image<FileImageSource>] = [:]
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<C: Context, M: MemoryReader>: 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<C: Context, M: MemoryReader>: 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<C: Context, M: MemoryReader>: 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<Address>? {
if done {
return nil
}
@@ -143,7 +167,7 @@ public struct FramePointerUnwinder<C: Context, M: MemoryReader>: 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<C: Context, M: MemoryReader>: 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<Address>.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<Address>.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<C: Context, M: MemoryReader>: Sequence, Itera
if !isAsyncFrame(next) {
fp = next
return .returnAddress(Backtrace.Address(pc))
return .returnAddress(pc)
}
}
@@ -202,8 +229,10 @@ public struct FramePointerUnwinder<C: Context, M: MemoryReader>: 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<C: Context, M: MemoryReader>: 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<C: Context, M: MemoryReader>: Sequence, Itera
#endif
asyncContext = next
return .asyncResumePoint(Backtrace.Address(pc))
return .asyncResumePoint(pc)
}
}

View File

@@ -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 }

View File

@@ -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") ?? "<unknown>"
let osProductVersion = getSysCtlString("kern.osproductversion") ?? "<unknown>"
return "\(platform) \(osProductVersion) (\(osVersion))"
}()
private static func withDyldProcessInfo<T>(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<uuid_t>.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)

View File

@@ -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<M: MemoryReader>(
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)

View File

@@ -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<UInt8>) {
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 = "<no build ID>"
}
let path = image.path ?? "<unknown>"
let name = image.name ?? "<unknown>"
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))!
}
}

View File

@@ -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])
)
}
/// Gets a mutable pointer to the unused space
var unusedBytes: UnsafeMutableRawBufferPointer {
guard case let .allocated(count) = kind else {
fatalError("attempted to get mutable reference to immutable ImageSource")
}
return UnsafeMutableRawBufferPointer(
mutating: UnsafeRawBufferPointer(rebasing: bytes[count...])
)
}
/// Return the number of bytes in this ImageSource
var count: Int {
switch kind {
case .empty:
return 0
case let .allocated(count):
return count
case .mapped, .substorage, .unowned:
return bytes.count
}
}
@inline(__always)
private func _rangeCheck(_ ndx: Int) {
if ndx < 0 || ndx >= 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<Int>) {
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<Int>) -> 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..<count]
)
oldPart.copyMemory(from: bytes)
mutableBytes.deallocate()
kind = .allocated(count)
default:
fatalError("Cannot resize immutable image source storage")
}
bytes = UnsafeRawBufferPointer(newBuffer)
return newBuffer
}
/// Make sure the buffer has at least a certain number of bytes;
/// only supported for allocated or empty storage.
func requireAtLeast(byteCount: Int) -> 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..<newCount]
)
dest.copyMemory(from: toAppend)
kind = .allocated(newCount)
}
}
/// The storage holding the image data.
private var storage: Storage
/// The number of bytes of data this ImageSource holds.
var count: Int { return storage.count }
/// The memory holding the image data.
var bytes: UnsafeRawBufferPointer { return storage.bytes }
/// A mutable refernece to the image data (only for allocated storage)
var mutableBytes: UnsafeMutableRawBufferPointer { return storage.mutableBytes }
/// A mutable reference to unused bytes in the storage
var unusedBytes: UnsafeMutableRawBufferPointer { return storage.unusedBytes }
/// Says whether we are looking at a loaded (i.e. with ld.so or dyld) image.
private(set) var isMappedImage: Bool
/// If this ImageSource knows its path, this will be non-nil.
private(set) var path: String?
/// Private initialiser, not for general use
private init(storage: Storage, isMappedImage: Bool, path: String?) {
self.storage = storage
self.isMappedImage = isMappedImage
self.path = path
}
/// Initialise an empty storage
init(isMappedImage: Bool, path: String? = nil) {
self.init(storage: Storage(), isMappedImage: isMappedImage, path: path)
}
/// Initialise from unowned storage
init(unowned: UnsafeRawBufferPointer, isMappedImage: Bool, path: String? = nil) {
self.init(storage: Storage(unowned: unowned),
isMappedImage: isMappedImage, path: path)
}
/// Initialise from mapped storage
init(mapped: UnsafeRawBufferPointer, isMappedImage: Bool, path: String? = nil) {
self.init(storage: Storage(mapped: mapped),
isMappedImage: isMappedImage, path: path)
}
/// Initialise with a specified capacity
init(capacity: Int, isMappedImage: Bool, path: String? = nil) {
self.init(storage: Storage(capacity: capacity),
isMappedImage: isMappedImage, path: path)
}
/// Initialise with a mapped file
init(path: String) throws {
self.init(storage: try Storage(path: path),
isMappedImage: false, path: path)
}
/// Get a sub-range of this ImageSource as an ImageSource
subscript(range: Range<Address>) -> ImageSource {
let intRange = Int(range.lowerBound)..<Int(range.upperBound)
return ImageSource(storage: storage[intRange],
isMappedImage: isMappedImage,
path: path)
}
/// Mark unused bytes in the storage as used
func used(bytes: Int) {
storage.used(bytes: bytes)
}
/// Append bytes to an empty or allocated storage
func append(bytes toAppend: UnsafeRawBufferPointer) {
storage.append(bytes: toAppend)
}
}
// MemoryReader support
extension ImageSource: MemoryReader {
public func fetch(from address: Address,
into buffer: UnsafeMutableRawBufferPointer) throws {
let offset = Int(address)
guard bytes.count >= buffer.count &&
offset <= bytes.count - buffer.count else {
throw ImageSourceError.outOfBoundsRead
}
buffer.copyMemory(from: UnsafeRawBufferPointer(
rebasing: bytes[offset..<offset + buffer.count]))
}
public func fetch<T>(from address: Address, as type: T.Type) throws -> T {
let size = MemoryLayout<T>.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..<offset+len]
return String(decoding: stringBytes, as: UTF8.self)
}
public func fetchString(from address: Address, length: Int) throws -> String? {
let offset = Int(address)
let stringBytes = bytes[offset..<offset+length]
return String(decoding: stringBytes, as: UTF8.self)
}
}
/// Used as a cursor by the DWARF code
struct ImageSourceCursor {
typealias Address = ImageSource.Address
typealias Size = ImageSource.Size
var source: ImageSource
var pos: Address
init(source: ImageSource, offset: Address = 0) {
self.source = source
self.pos = offset
}
mutating func read(into buffer: UnsafeMutableRawBufferPointer) throws {
try source.fetch(from: pos, into: buffer)
pos += Size(buffer.count)
}
mutating func read<T>(into buffer: UnsafeMutableBufferPointer<T>) throws {
try source.fetch(from: pos, into: buffer)
pos += Size(MemoryLayout<T>.stride * buffer.count)
}
mutating func read<T>(into pointer: UnsafeMutablePointer<T>) throws {
try source.fetch(from: pos, into: pointer)
pos += Size(MemoryLayout<T>.stride)
}
mutating func read<T>(as type: T.Type) throws -> T {
let result = try source.fetch(from: pos, as: type)
pos += Size(MemoryLayout<T>.stride)
return result
}
mutating func read<T>(count: Int, as type: T.Type) throws -> [T] {
let result = try source.fetch(from: pos, count: count, as: type)
pos += Size(MemoryLayout<T>.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
}
}

View File

@@ -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<T: LimitableElement, S: Sequence>: 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..<offset {
if self.iterator.next() == nil {
break
}
}
self.readAhead = nil
self.limit = limit
self.top = Swift.min(top, limit - 1)
self.state = .normal
self.topBuffer = []
self.topBuffer.reserveCapacity(top)
self.topBase = 0
self.topNdx = 0
readNext()
}
/// Retrieve the next element in the output sequence.
public mutating func next() -> 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)
}
}
}

View File

@@ -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<T>(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() {

View File

@@ -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<S: StringProtocol>: 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..<pos])
state = .beforeEquals
break
}
if ch == 13 || ch == 10 {
state = .normal
break
}
if ch == UInt8(ascii: "=") {
key = String(asString[chunkStart..<pos])
state = .beforeValue
break
}
case .beforeEquals:
if ch == UInt8(ascii: "=") {
state = .beforeValue
break
}
if ch == 32 || ch == 9 {
break
}
state = .badLine
case .beforeValue:
if ch == 32 || ch == 9 {
break
}
if ch == UInt8(ascii: "\"") {
state = .doubleQuote
chunkStart = asUTF8.index(after: pos)
quotedValue = ""
break
}
if ch == UInt8(ascii: "'") {
state = .singleQuote
chunkStart = asUTF8.index(after: pos)
break
}
chunkStart = pos
state = .value
case .value:
if ch == 13 || ch == 10 {
let value = String(asString[chunkStart..<pos])
state = .normal
return (key, value)
}
if ch == 32 || ch == 9 {
state = .valueWhitespace
whitespaceStart = pos
}
case .valueWhitespace:
if ch == 13 || ch == 10 {
let value = String(asString[chunkStart..<whitespaceStart])
state = .normal
return (key, value)
}
if ch != 32 && ch != 9 {
state = .value
}
case .singleQuote:
if ch == UInt8(ascii: "'") {
let value = String(asString[chunkStart..<pos])
state = .awaitingNewline
return (key, value)
}
case .doubleQuote:
if ch == UInt8(ascii: "\\") {
let chunk = String(asString[chunkStart..<pos])
quotedValue += chunk
chunkStart = asUTF8.index(after: pos)
state = .escape
break
}
if ch == UInt8(ascii: "\"") {
let chunk = String(asString[chunkStart..<pos])
quotedValue += chunk
state = .awaitingNewline
return (key, quotedValue)
}
case .escape:
let toEscape = asString[chunkStart...pos]
switch toEscape {
case "n":
quotedValue += "\n"
case "t":
quotedValue += "\t"
default:
quotedValue += toEscape
}
chunkStart = asUTF8.index(after: pos)
state = .doubleQuote
}
pos = asUTF8.index(after: pos)
}
return nil
}
}
#endif // os(Linux)

View File

@@ -0,0 +1,146 @@
//===--- RichFrame.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 default rich frame storage type used by `Backtrace`
//
//===----------------------------------------------------------------------===//
import Swift
@_spi(Internal)
public enum RichFrame<T: FixedWidthInteger>: 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<T>(_ frame: RichFrame<T>) {
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
}
}
}

View File

@@ -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<Backtrace.Address>.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<UInt8>(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<T>(images: [Backtrace.Image],
sharedCacheInfo: Backtrace.SharedCacheInfo?,
private static func withSymbolicator<T>(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 ?? "<unknown>"
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<FileImageSource>] = [:]
var elf64Cache: [Int:Elf64Image<FileImageSource>] = [:]
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 ?? "<unknown>"
var symbol: Symbol = Symbol(imageIndex: imageNdx,
imageName: theImages[imageNdx].name,
imageName: name,
rawName: "<unknown>",
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<ElfImage: ElfSymbolLookupProtocol>(
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 ?? "<unknown>",
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 ?? "<unknown>",
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 ?? "<unknown>",
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: "<unknown>",
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")

View File

@@ -95,3 +95,32 @@ public func stripWhitespace<S: StringProtocol>(_ 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<T>(_ optional: T?) -> T {
return optional!
}
func notOptional<T>(_ 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<T>(_ mutable: UnsafeMutablePointer<T>) -> UnsafePointer<T> {
return UnsafePointer<T>(mutable)
}
func notMutable<T>(_ immutable: UnsafePointer<T>) -> UnsafePointer<T> {
return immutable
}
func notMutable(_ mutable: UnsafeMutableRawPointer) -> UnsafeRawPointer {
return UnsafeRawPointer(mutable)
}
func notMutable(_ immutable: UnsafeRawPointer) -> UnsafeRawPointer {
return immutable
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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}

View File

@@ -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<MemserverMemoryReader>
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
}

View File

@@ -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<RemoteMemoryReader>
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

View File

@@ -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()

View File

@@ -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<FILE>

View File

@@ -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<Int>()
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":

View File

@@ -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()

View File

@@ -9,7 +9,7 @@
// REQUIRES: backtracing
// REQUIRES: OS=macosx || OS=linux-gnu
import _Backtracing
import Runtime
func doFrames(_ count: Int) {
if count <= 0 {

View File

@@ -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 {

View File

@@ -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()
}
}

View File

@@ -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
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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]*}} {{(<no build ID>|[0-9a-f]*)}} ImageMap {{.*}}/ImageMap
// CHECK-NEXT: {{0x[0-9a-f]*-0x[0-9a-f]*}} {{(<no build ID>|[0-9a-f]*)}} [[NAME:[^ ]*]] {{.*}}/[[NAME]]
print(map)
}
}

View File

@@ -12,7 +12,7 @@
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: back_deployment_runtime
import _Backtracing
import Runtime
@available(SwiftStdlib 5.1, *)
func level1() async {

View File

@@ -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()
}

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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",

View File

@@ -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<end_pos>;

View File

@@ -464,10 +464,6 @@ bool TestOptions::parseArgs(llvm::ArrayRef<const char *> 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'

View File

@@ -135,8 +135,6 @@ struct TestOptions {
bool measureInstructions = false;
bool DisableImplicitConcurrencyModuleImport = false;
bool DisableImplicitStringProcessingModuleImport = false;
bool EnableImplicitBacktracingModuleImport = false;
bool DisableImplicitBacktracingModuleImport = false;
std::optional<unsigned> CompletionCheckDependencyInterval;
unsigned repeatRequest = 1;
struct VFSFile {

View File

@@ -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);

View File

@@ -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<bool>
EnableImplicitBacktracingImport("enable-implicit-backtracing-module-import",
llvm::cl::desc("Enable implicit import of _Backtracing module"),
llvm::cl::init(false));
static llvm::cl::opt<bool>
DisableImplicitBacktracingImport("disable-implicit-backtracing-module-import",
llvm::cl::desc("Disable implicit import of _Backtracing module"),
llvm::cl::init(false));
static llvm::cl::opt<bool> 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);

Some files were not shown because too many files have changed in this diff Show More