mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Merge pull request #78516 from al45tair/eng/PR-124913332
[Backtracing] Implement API per SE-0419
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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>"
|
||||
|
||||
@@ -28,12 +28,6 @@ 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)
|
||||
set(_cov_flags $<$<COMPILE_LANGUAGE:Swift>:-profile-generate -profile-coverage-mapping>)
|
||||
target_compile_options(${name} PRIVATE ${_cov_flags})
|
||||
|
||||
@@ -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.
|
||||
|
||||
167
docs/CompactBacktraceFormat.md
Normal file
167
docs/CompactBacktraceFormat.md
Normal 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.
|
||||
230
docs/CompactImageMapFormat.md
Normal file
230
docs/CompactImageMapFormat.md
Normal 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%.
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.">;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
258
stdlib/public/RuntimeModule/Address.swift
Normal file
258
stdlib/public/RuntimeModule/Address.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
69
stdlib/public/RuntimeModule/Backtrace+Codable.swift
Normal file
69
stdlib/public/RuntimeModule/Backtrace+Codable.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
433
stdlib/public/RuntimeModule/Backtrace.swift
Normal file
433
stdlib/public/RuntimeModule/Backtrace.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 let path = image.path {
|
||||
if options._sanitizePaths {
|
||||
imagePath = sanitizePath(image.path)
|
||||
imagePath = sanitizePath(path)
|
||||
} else {
|
||||
imagePath = image.path
|
||||
imagePath = path
|
||||
}
|
||||
} else {
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
333
stdlib/public/RuntimeModule/Base64.swift
Normal file
333
stdlib/public/RuntimeModule/Base64.swift
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
660
stdlib/public/RuntimeModule/CompactBacktrace.swift
Normal file
660
stdlib/public/RuntimeModule/CompactBacktrace.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
804
stdlib/public/RuntimeModule/CompactImageMap.swift
Normal file
804
stdlib/public/RuntimeModule/CompactImageMap.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,86 +318,56 @@ 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
|
||||
|
||||
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)
|
||||
return UnsafeRawBufferPointer(rebasing: source.bytes[offset...])
|
||||
},
|
||||
output: {
|
||||
(used: UInt, done: Bool) throws -> UnsafeMutableBufferPointer<UInt8>? in
|
||||
(used: UInt, done: Bool) throws -> UnsafeMutableRawBufferPointer? in
|
||||
|
||||
if used == 0 {
|
||||
return outputBuffer
|
||||
return output.unusedBytes
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
))
|
||||
)
|
||||
output.used(bytes: Int(totalBytes))
|
||||
}
|
||||
} else {
|
||||
// Otherwise, we decompress in chunks and append them to the array.
|
||||
|
||||
let buffer
|
||||
= UnsafeMutableBufferPointer<UInt8>.allocate(capacity: 2 * bufSize)
|
||||
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 {
|
||||
buffer.deallocate()
|
||||
outputBuffer.deallocate()
|
||||
}
|
||||
|
||||
let inputBuffer = UnsafeMutableBufferPointer(rebasing: buffer[0..<bufSize])
|
||||
let outputBuffer = UnsafeMutableBufferPointer(rebasing: buffer[bufSize...])
|
||||
let _ = try stream.decompress(
|
||||
input: {
|
||||
() throws -> UnsafeRawBufferPointer in
|
||||
|
||||
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)
|
||||
return UnsafeRawBufferPointer(rebasing: source.bytes[offset...])
|
||||
},
|
||||
output: {
|
||||
(used: UInt, done: Bool) throws -> UnsafeMutableBufferPointer<UInt8>? in
|
||||
(used: UInt, done: Bool) throws -> UnsafeMutableRawBufferPointer? in
|
||||
|
||||
data.append(contentsOf: outputBuffer[..<Int(used)])
|
||||
output.append(
|
||||
bytes: UnsafeRawBufferPointer(rebasing: outputBuffer[..<Int(used)])
|
||||
)
|
||||
if !done {
|
||||
return outputBuffer
|
||||
} else {
|
||||
@@ -397,143 +375,73 @@ fileprivate func decompress<S: CompressedStream, I: ImageSource>(
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
internal struct ElfCompressedImageSource<Traits: ElfTraits>: 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 < 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 {
|
||||
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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
@@ -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)
|
||||
60
stdlib/public/RuntimeModule/EightByteBuffer.swift
Normal file
60
stdlib/public/RuntimeModule/EightByteBuffer.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,6 @@
|
||||
|
||||
// ###FIXME: We shouldn't really use String for paths.
|
||||
|
||||
#if os(Linux)
|
||||
|
||||
import Swift
|
||||
|
||||
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
|
||||
@@ -75,7 +73,6 @@ let EI_VERSION = swift.runtime.EI_VERSION
|
||||
let EI_OSABI = swift.runtime.EI_OSABI
|
||||
let EI_ABIVERSION = swift.runtime.EI_ABIVERSION
|
||||
let EI_PAD = swift.runtime.EI_PAD
|
||||
let EI_NIDENT = BacktracingImpl.EI_NIDENT
|
||||
|
||||
let ELFMAG0 = swift.runtime.ELFMAG0
|
||||
let ELFMAG1 = swift.runtime.ELFMAG1
|
||||
@@ -164,15 +161,11 @@ let DT_INIT_ARRAYSZ = swift.runtime.DT_INIT_ARRAYSZ
|
||||
let DT_FINI_ARRAYSZ = swift.runtime.DT_FINI_ARRAYSZ
|
||||
let DT_RUNPATH = swift.runtime.DT_RUNPATH
|
||||
let DT_FLAGS = swift.runtime.DT_FLAGS
|
||||
|
||||
let DT_ENCODING = swift.runtime.DT_ENCODING
|
||||
|
||||
let DT_PREINIT_ARRAY = swift.runtime.DT_PREINIT_ARRAY
|
||||
let DT_PREINIT_ARRAYSZ = swift.runtime.DT_PREINIT_ARRAYSZ
|
||||
|
||||
let DT_LOOS = swift.runtime.DT_LOOS
|
||||
let DT_HIOS = swift.runtime.DT_HIOS
|
||||
|
||||
let DT_LOPROC = swift.runtime.DT_LOPROC
|
||||
let DT_HIPROC = swift.runtime.DT_HIPROC
|
||||
|
||||
@@ -226,6 +219,9 @@ let ELF64_R_INFO = swift.runtime.ELF64_R_INFO
|
||||
typealias Elf32_Phdr = swift.runtime.Elf32_Phdr
|
||||
typealias Elf64_Phdr = swift.runtime.Elf64_Phdr
|
||||
|
||||
typealias Elf32_Nhdr = swift.runtime.Elf32_Nhdr
|
||||
typealias Elf64_Nhdr = swift.runtime.Elf64_Nhdr
|
||||
|
||||
typealias Elf32_Dyn = swift.runtime.Elf32_Dyn
|
||||
typealias Elf64_Dyn = swift.runtime.Elf64_Dyn
|
||||
|
||||
@@ -302,7 +298,7 @@ private let crc32Table: [UInt32] = [
|
||||
]
|
||||
|
||||
private func updateCrc(_ crc: UInt32,
|
||||
_ bytes: UnsafeBufferPointer<UInt8>) -> UInt32 {
|
||||
_ bytes: UnsafeRawBufferPointer) -> UInt32 {
|
||||
var theCrc = ~crc
|
||||
for byte in bytes {
|
||||
theCrc = crc32Table[Int(UInt8(truncatingIfNeeded: theCrc)
|
||||
@@ -420,6 +416,12 @@ extension Elf64_Chdr: Elf_Chdr {
|
||||
}
|
||||
}
|
||||
|
||||
extension Elf_Chdr_Type: ByteSwappable {
|
||||
var byteSwapped: Self {
|
||||
return Elf_Chdr_Type(rawValue: rawValue.byteSwapped)!
|
||||
}
|
||||
}
|
||||
|
||||
extension Elf32_Sym: ByteSwappable {
|
||||
var byteSwapped: Self {
|
||||
return Elf32_Sym(
|
||||
@@ -970,19 +972,17 @@ struct Elf64Traits: ElfTraits {
|
||||
// .. ElfStringSection .........................................................
|
||||
|
||||
struct ElfStringSection {
|
||||
let bytes: [UInt8]
|
||||
let source: ImageSource
|
||||
|
||||
func getStringAt(index: Int) -> String? {
|
||||
if index < 0 || index >= bytes.count {
|
||||
if index < 0 || index >= source.bytes.count {
|
||||
return nil
|
||||
}
|
||||
|
||||
let slice = bytes[index...]
|
||||
let slice = UnsafeRawBufferPointer(rebasing: source.bytes[index...])
|
||||
var len: Int = 0
|
||||
slice.withUnsafeBufferPointer{ ptr in
|
||||
len = strnlen(ptr.baseAddress!, ptr.count)
|
||||
}
|
||||
return String(decoding: bytes[index..<index+len], as: UTF8.self)
|
||||
len = strnlen(slice.baseAddress!, slice.count)
|
||||
return String(decoding: source.bytes[index..<index+len], as: UTF8.self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -995,17 +995,6 @@ enum ElfImageError: Error {
|
||||
case badStringTableSectionIndex
|
||||
}
|
||||
|
||||
protocol ElfGetSectionProtocol {
|
||||
func getSection(_ name: String, debug: Bool) -> (any ImageSource)?
|
||||
func getSection(_ name: String) -> (any ImageSource)?
|
||||
}
|
||||
|
||||
extension ElfGetSectionProtocol {
|
||||
func getSection(_ name: String) -> (any ImageSource)? {
|
||||
return getSection(name, debug: false)
|
||||
}
|
||||
}
|
||||
|
||||
protocol ElfSymbolProtocol: Equatable {
|
||||
associatedtype Address: FixedWidthInteger
|
||||
associatedtype Size: FixedWidthInteger
|
||||
@@ -1027,22 +1016,14 @@ protocol ElfSymbolTableProtocol {
|
||||
func lookupSymbol(address: Traits.Address) -> Symbol?
|
||||
}
|
||||
|
||||
protocol ElfImageProtocol: Image, ElfGetSectionProtocol, DwarfSource {
|
||||
protocol ElfSymbolLookupProtocol {
|
||||
associatedtype Traits: ElfTraits
|
||||
associatedtype SymbolTable: ElfSymbolTableProtocol
|
||||
where SymbolTable.Traits == Traits
|
||||
typealias CallSiteInfo = DwarfReader<ElfImage<Traits>>.CallSiteInfo
|
||||
typealias SourceLocation = SymbolicatedBacktrace.SourceLocation
|
||||
|
||||
var header: Traits.Ehdr { get }
|
||||
var programHeaders: [Traits.Phdr] { get }
|
||||
var sectionHeaders: [Traits.Shdr]? { get }
|
||||
|
||||
var imageName: String { get }
|
||||
var debugImage: (any ElfImageProtocol)? { get }
|
||||
var debugLinkCRC: UInt32? { get }
|
||||
|
||||
var symbolTable: SymbolTable { get }
|
||||
|
||||
func _getSymbolTable(debug: Bool) -> SymbolTable
|
||||
func lookupSymbol(address: Traits.Address) -> ImageSymbol?
|
||||
func inlineCallSites(at address: Traits.Address) -> ArraySlice<CallSiteInfo>
|
||||
func sourceLocation(for address: Traits.Address) throws -> SourceLocation?
|
||||
}
|
||||
|
||||
struct ElfSymbolTable<SomeElfTraits: ElfTraits>: ElfSymbolTableProtocol {
|
||||
@@ -1065,19 +1046,18 @@ struct ElfSymbolTable<SomeElfTraits: ElfTraits>: ElfSymbolTableProtocol {
|
||||
|
||||
init() {}
|
||||
|
||||
init?<ElfImage: ElfImageProtocol>(image: ElfImage) {
|
||||
@_specialize(kind: full, where SomeElfTraits == Elf32Traits)
|
||||
@_specialize(kind: full, where SomeElfTraits == Elf64Traits)
|
||||
init?(image: ElfImage<Traits>) {
|
||||
guard let strtab = image.getSection(".strtab", debug: false),
|
||||
let symtab = image.getSection(".symtab", debug: false),
|
||||
let strings = strtab.fetchAllBytes(),
|
||||
let symdata = symtab.fetchAllBytes() else {
|
||||
let symtab = image.getSection(".symtab", debug: false) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let stringSect = ElfStringSection(bytes: strings)
|
||||
let stringSect = ElfStringSection(source: strtab)
|
||||
|
||||
// Extract all the data
|
||||
symdata.withUnsafeBufferPointer{
|
||||
$0.withMemoryRebound(to: Traits.Sym.self) { symbols in
|
||||
symtab.bytes.withMemoryRebound(to: Traits.Sym.self) { symbols in
|
||||
for symbol in symbols {
|
||||
// Ignore things that are not functions
|
||||
if symbol.st_type != .STT_FUNC {
|
||||
@@ -1103,7 +1083,6 @@ struct ElfSymbolTable<SomeElfTraits: ElfTraits>: ElfSymbolTableProtocol {
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now sort by address
|
||||
_symbols.sort(by: {
|
||||
@@ -1117,6 +1096,8 @@ struct ElfSymbolTable<SomeElfTraits: ElfTraits>: ElfSymbolTableProtocol {
|
||||
_symbols = sortedSymbols
|
||||
}
|
||||
|
||||
@_specialize(kind: full, where SomeElfTraits == Elf32Traits)
|
||||
@_specialize(kind: full, where SomeElfTraits == Elf64Traits)
|
||||
public func merged(with other: ElfSymbolTable<Traits>) -> ElfSymbolTable<Traits> {
|
||||
var merged: [Symbol] = []
|
||||
|
||||
@@ -1159,6 +1140,8 @@ struct ElfSymbolTable<SomeElfTraits: ElfTraits>: ElfSymbolTableProtocol {
|
||||
return ElfSymbolTable(sortedSymbols: merged)
|
||||
}
|
||||
|
||||
@_specialize(kind: full, where SomeElfTraits == Elf32Traits)
|
||||
@_specialize(kind: full, where SomeElfTraits == Elf64Traits)
|
||||
public func lookupSymbol(address: Traits.Address) -> Symbol? {
|
||||
var min = 0
|
||||
var max = _symbols.count
|
||||
@@ -1179,7 +1162,7 @@ struct ElfSymbolTable<SomeElfTraits: ElfTraits>: ElfSymbolTableProtocol {
|
||||
ndx -= 1
|
||||
}
|
||||
return _symbols[ndx]
|
||||
} else if symbol.value < address {
|
||||
} else if symbol.value <= address {
|
||||
min = mid + 1
|
||||
} else if symbol.value > address {
|
||||
max = mid
|
||||
@@ -1190,27 +1173,28 @@ struct ElfSymbolTable<SomeElfTraits: ElfTraits>: ElfSymbolTableProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
class ElfImage<SomeImageSource: ImageSource,
|
||||
SomeElfTraits: ElfTraits>: ElfImageProtocol {
|
||||
final class ElfImage<SomeElfTraits: ElfTraits>
|
||||
: DwarfSource, ElfSymbolLookupProtocol {
|
||||
typealias Traits = SomeElfTraits
|
||||
typealias Source = SomeImageSource
|
||||
typealias SymbolTable = ElfSymbolTable<SomeElfTraits>
|
||||
|
||||
// This is arbitrary and it isn't in the spec
|
||||
let maxNoteNameLength = 256
|
||||
|
||||
var baseAddress: Source.Address
|
||||
var endAddress: Source.Address
|
||||
var baseAddress: ImageSource.Address
|
||||
var endAddress: ImageSource.Address
|
||||
|
||||
var source: SomeImageSource
|
||||
var source: ImageSource
|
||||
var header: Traits.Ehdr
|
||||
var programHeaders: [Traits.Phdr]
|
||||
var sectionHeaders: [Traits.Shdr]?
|
||||
var shouldByteSwap: Bool { return header.shouldByteSwap }
|
||||
|
||||
required init(source: SomeImageSource,
|
||||
baseAddress: Source.Address = 0,
|
||||
endAddress: Source.Address = 0) throws {
|
||||
@_specialize(kind: full, where SomeElfTraits == Elf32Traits)
|
||||
@_specialize(kind: full, where SomeElfTraits == Elf64Traits)
|
||||
required init(source: ImageSource,
|
||||
baseAddress: ImageSource.Address = 0,
|
||||
endAddress: ImageSource.Address = 0) throws {
|
||||
self.source = source
|
||||
self.baseAddress = baseAddress
|
||||
self.endAddress = endAddress
|
||||
@@ -1237,11 +1221,11 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
}
|
||||
|
||||
var phdrs: [Traits.Phdr] = []
|
||||
var phAddr = Source.Address(header.e_phoff)
|
||||
var phAddr = ImageSource.Address(header.e_phoff)
|
||||
for _ in 0..<header.e_phnum {
|
||||
let phdr = maybeSwap(try source.fetch(from: phAddr, as: Traits.Phdr.self))
|
||||
phdrs.append(phdr)
|
||||
phAddr += Source.Address(header.e_phentsize)
|
||||
phAddr += ImageSource.Address(header.e_phentsize)
|
||||
}
|
||||
programHeaders = phdrs
|
||||
|
||||
@@ -1249,11 +1233,11 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
sectionHeaders = nil
|
||||
} else {
|
||||
var shdrs: [Traits.Shdr] = []
|
||||
var shAddr = Source.Address(header.e_shoff)
|
||||
var shAddr = ImageSource.Address(header.e_shoff)
|
||||
for _ in 0..<header.e_shnum {
|
||||
let shdr = maybeSwap(try source.fetch(from: shAddr, as: Traits.Shdr.self))
|
||||
shdrs.append(shdr)
|
||||
shAddr += Source.Address(header.e_shentsize)
|
||||
shAddr += ImageSource.Address(header.e_shentsize)
|
||||
}
|
||||
sectionHeaders = shdrs
|
||||
}
|
||||
@@ -1270,16 +1254,18 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
}
|
||||
|
||||
struct Notes: Sequence {
|
||||
var image: ElfImage<Source, Traits>
|
||||
var image: ElfImage<Traits>
|
||||
|
||||
struct NoteIterator: IteratorProtocol {
|
||||
var image: ElfImage<Source, Traits>
|
||||
var image: ElfImage<Traits>
|
||||
|
||||
var hdrNdx = -1
|
||||
var noteAddr = Source.Address()
|
||||
var noteEnd = Source.Address()
|
||||
var noteAddr = ImageSource.Address()
|
||||
var noteEnd = ImageSource.Address()
|
||||
|
||||
init(image: ElfImage<Source, Traits>) {
|
||||
@_specialize(kind: full, where SomeElfTraits == Elf32Traits)
|
||||
@_specialize(kind: full, where SomeElfTraits == Elf64Traits)
|
||||
init(image: ElfImage<Traits>) {
|
||||
self.image = image
|
||||
}
|
||||
|
||||
@@ -1287,15 +1273,25 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
let ph = image.programHeaders[hdrNdx]
|
||||
|
||||
if image.source.isMappedImage {
|
||||
noteAddr = Source.Address(ph.p_vaddr)
|
||||
noteEnd = noteAddr + Source.Address(ph.p_memsz)
|
||||
noteAddr = ImageSource.Address(ph.p_vaddr)
|
||||
noteEnd = noteAddr + ImageSource.Address(ph.p_memsz)
|
||||
} else {
|
||||
noteAddr = Source.Address(ph.p_offset)
|
||||
noteEnd = noteAddr + Source.Address(ph.p_filesz)
|
||||
noteAddr = ImageSource.Address(ph.p_offset)
|
||||
noteEnd = noteAddr + ImageSource.Address(ph.p_filesz)
|
||||
}
|
||||
}
|
||||
|
||||
@_specialize(kind: full, where SomeElfTraits == Elf32Traits)
|
||||
@_specialize(kind: full, where SomeElfTraits == Elf64Traits)
|
||||
mutating func next() -> Note? {
|
||||
let byteSwap = image.shouldByteSwap
|
||||
func maybeSwap<T: ByteSwappable>(_ x: T) -> T {
|
||||
if byteSwap {
|
||||
return x.byteSwapped
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
if hdrNdx >= image.programHeaders.count {
|
||||
return nil
|
||||
}
|
||||
@@ -1311,9 +1307,10 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
}
|
||||
|
||||
do {
|
||||
let nhdr = try image.fetch(from: noteAddr, as: Traits.Nhdr.self)
|
||||
let nhdr = maybeSwap(try image.source.fetch(from: noteAddr,
|
||||
as: Traits.Nhdr.self))
|
||||
|
||||
noteAddr += Source.Address(MemoryLayout<Traits.Nhdr>.size)
|
||||
noteAddr += ImageSource.Address(MemoryLayout<Traits.Nhdr>.size)
|
||||
|
||||
if noteEnd - noteAddr < nhdr.n_namesz {
|
||||
// The segment is probably corrupted
|
||||
@@ -1322,12 +1319,15 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
}
|
||||
|
||||
let nameLen = nhdr.n_namesz > 0 ? nhdr.n_namesz - 1 : 0
|
||||
let nameBytes = try image.fetch(from: noteAddr,
|
||||
count: Int(nameLen),
|
||||
as: UInt8.self)
|
||||
let name = String(decoding: nameBytes, as: UTF8.self)
|
||||
guard let name = try image.source.fetchString(from: noteAddr,
|
||||
length: Int(nameLen))
|
||||
else {
|
||||
// Bad note name
|
||||
noteAddr = noteEnd
|
||||
continue
|
||||
}
|
||||
|
||||
noteAddr += Source.Address(nhdr.n_namesz)
|
||||
noteAddr += ImageSource.Address(nhdr.n_namesz)
|
||||
if (noteAddr & 3) != 0 {
|
||||
noteAddr += 4 - (noteAddr & 3)
|
||||
}
|
||||
@@ -1338,11 +1338,11 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
continue
|
||||
}
|
||||
|
||||
let desc = try image.fetch(from: noteAddr,
|
||||
let desc = try image.source.fetch(from: noteAddr,
|
||||
count: Int(nhdr.n_descsz),
|
||||
as: UInt8.self)
|
||||
|
||||
noteAddr += Source.Address(nhdr.n_descsz)
|
||||
noteAddr += ImageSource.Address(nhdr.n_descsz)
|
||||
if (noteAddr & 3) != 0 {
|
||||
noteAddr += 4 - (noteAddr & 3)
|
||||
}
|
||||
@@ -1382,47 +1382,19 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
}
|
||||
|
||||
private var _debugLinkCRC: UInt32?
|
||||
var debugLinkCRC: UInt32? {
|
||||
guard let bounds = source.bounds else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var debugLinkCRC: UInt32 {
|
||||
if let crc = _debugLinkCRC {
|
||||
return crc
|
||||
}
|
||||
|
||||
let bufSize = 65536
|
||||
let buffer = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: bufSize)
|
||||
defer {
|
||||
buffer.deallocate()
|
||||
}
|
||||
|
||||
var pos = bounds.base
|
||||
var remaining = bounds.size
|
||||
var crc: UInt32 = 0
|
||||
do {
|
||||
while remaining > 0 {
|
||||
let todo = min(bufSize, Int(remaining))
|
||||
let slice = buffer[..<todo]
|
||||
let chunk = UnsafeMutableBufferPointer<UInt8>(rebasing: slice)
|
||||
|
||||
try fetch(from: pos, into: chunk)
|
||||
|
||||
crc = updateCrc(crc, UnsafeBufferPointer(chunk))
|
||||
|
||||
remaining -= Source.Size(todo)
|
||||
pos += Source.Address(todo)
|
||||
}
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
|
||||
let crc = updateCrc(0, source.bytes)
|
||||
_debugLinkCRC = crc
|
||||
return crc
|
||||
}
|
||||
|
||||
struct Range {
|
||||
var base: Source.Address
|
||||
var size: Source.Size
|
||||
var base: ImageSource.Address
|
||||
var size: ImageSource.Size
|
||||
}
|
||||
|
||||
struct EHFrameInfo {
|
||||
@@ -1442,19 +1414,21 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
if phdr.p_type == .PT_GNU_EH_FRAME {
|
||||
var ehFrameHdrRange: Range
|
||||
if source.isMappedImage {
|
||||
ehFrameHdrRange = Range(base: Source.Address(phdr.p_vaddr),
|
||||
size: Source.Size(phdr.p_memsz))
|
||||
ehFrameHdrRange = Range(base: ImageSource.Address(phdr.p_vaddr),
|
||||
size: ImageSource.Size(phdr.p_memsz))
|
||||
} else {
|
||||
ehFrameHdrRange = Range(base: Source.Address(phdr.p_offset),
|
||||
size: Source.Size(phdr.p_filesz))
|
||||
ehFrameHdrRange = Range(base: ImageSource.Address(phdr.p_offset),
|
||||
size: ImageSource.Size(phdr.p_filesz))
|
||||
}
|
||||
|
||||
if (ehFrameHdrRange.size < MemoryLayout<EHFrameHdr>.size) {
|
||||
continue
|
||||
}
|
||||
|
||||
guard let ehdr = try? fetch(from: Source.Address(ehFrameHdrRange.base),
|
||||
as: EHFrameHdr.self) else {
|
||||
guard let ehdr = try? source.fetch(
|
||||
from: ImageSource.Address(ehFrameHdrRange.base),
|
||||
as: EHFrameHdr.self
|
||||
) else {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1462,11 +1436,11 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
continue
|
||||
}
|
||||
|
||||
let pc = ehFrameHdrRange.base + Source.Address(MemoryLayout<EHFrameHdr>.size)
|
||||
let pc = ehFrameHdrRange.base + ImageSource.Address(MemoryLayout<EHFrameHdr>.size)
|
||||
guard let (_, eh_frame_ptr) =
|
||||
try? source.fetchEHValue(from: Source.Address(pc),
|
||||
try? source.fetchEHValue(from: ImageSource.Address(pc),
|
||||
with: ehdr.eh_frame_ptr_enc,
|
||||
pc: Source.Address(pc)) else {
|
||||
pc: ImageSource.Address(pc)) else {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1476,18 +1450,17 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
// .eh_frame section, so we just rely on it being properly
|
||||
// terminated. This does mean that bulk fetching the entire
|
||||
// thing isn't a good idea.
|
||||
ehFrameInfo.ehFrameSection = Range(base: Source.Address(eh_frame_ptr),
|
||||
size: ~Source.Size(0))
|
||||
ehFrameInfo.ehFrameSection = Range(base: ImageSource.Address(eh_frame_ptr),
|
||||
size: ~ImageSource.Size(0))
|
||||
}
|
||||
}
|
||||
|
||||
if let sectionHeaders = sectionHeaders {
|
||||
let stringShdr = sectionHeaders[Int(header.e_shstrndx)]
|
||||
do {
|
||||
let bytes = try source.fetch(from: Source.Address(stringShdr.sh_offset),
|
||||
count: Int(stringShdr.sh_size),
|
||||
as: UInt8.self)
|
||||
let stringSect = ElfStringSection(bytes: bytes)
|
||||
let base = ImageSource.Address(stringShdr.sh_offset)
|
||||
let end = base + ImageSource.Size(stringShdr.sh_size)
|
||||
let stringSource = source[base..<end]
|
||||
let stringSect = ElfStringSection(source: stringSource)
|
||||
|
||||
for shdr in sectionHeaders {
|
||||
guard let name = stringSect.getStringAt(index: Int(shdr.sh_name)) else {
|
||||
@@ -1495,12 +1468,10 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
}
|
||||
|
||||
if name == ".eh_frame" {
|
||||
ehFrameInfo.ehFrameSection = Range(base: Source.Address(shdr.sh_offset),
|
||||
size: Source.Size(shdr.sh_size))
|
||||
ehFrameInfo.ehFrameSection = Range(base: ImageSource.Address(shdr.sh_offset),
|
||||
size: ImageSource.Size(shdr.sh_size))
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
return ehFrameInfo
|
||||
@@ -1528,16 +1499,16 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
|
||||
// If we have external debug information, this points at it
|
||||
private var _checkedDebugImage: Bool?
|
||||
private var _debugImage: (any ElfImageProtocol)?
|
||||
var debugImage: (any ElfImageProtocol)? {
|
||||
private var _debugImage: ElfImage<Traits>?
|
||||
var debugImage: ElfImage<Traits>? {
|
||||
if let checked = _checkedDebugImage, checked {
|
||||
return _debugImage
|
||||
}
|
||||
|
||||
let tryPath = { [self] (_ path: String) -> (any ElfImageProtocol)? in
|
||||
let tryPath = { [self] (_ path: String) -> ElfImage<Traits>? in
|
||||
do {
|
||||
let fileSource = try FileImageSource(path: path)
|
||||
let image = try ElfImage<FileImageSource, Traits>(source: fileSource)
|
||||
let fileSource = try ImageSource(path: path)
|
||||
let image = try ElfImage<Traits>(source: fileSource)
|
||||
_debugImage = image
|
||||
return image
|
||||
} catch {
|
||||
@@ -1562,7 +1533,7 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
let debugLink = getDebugLink()
|
||||
let debugAltLink = getDebugAltLink()
|
||||
|
||||
let tryLink = { (_ link: String) -> (any ElfImageProtocol)? in
|
||||
let tryLink = { (_ link: String) -> ElfImage<Traits>? in
|
||||
if let image = tryPath("\(imageDir)/\(link)") {
|
||||
return image
|
||||
}
|
||||
@@ -1592,9 +1563,8 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
|
||||
if let debugData = getSection(".gnu_debugdata") {
|
||||
do {
|
||||
let source = try LZMACompressedImageSource(source: debugData)
|
||||
_debugImage = try ElfImage<LZMACompressedImageSource,
|
||||
Traits>(source: source)
|
||||
let source = try ImageSource(lzmaCompressedImageSource: debugData)
|
||||
_debugImage = try ElfImage<Traits>(source: source)
|
||||
_checkedDebugImage = true
|
||||
return _debugImage
|
||||
} catch let CompressedImageSourceError.libraryNotFound(library) {
|
||||
@@ -1617,15 +1587,17 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
/// In general, the section may be compressed or even in a different image;
|
||||
/// this is particularly the case for debug sections. We will only attempt
|
||||
/// to look for other images if `debug` is `true`.
|
||||
func getSection(_ name: String, debug: Bool) -> (any ImageSource)? {
|
||||
@_specialize(kind: full, where SomeElfTraits == Elf32Traits)
|
||||
@_specialize(kind: full, where SomeElfTraits == Elf64Traits)
|
||||
func getSection(_ name: String, debug: Bool = false) -> ImageSource? {
|
||||
if let sectionHeaders = sectionHeaders {
|
||||
let zname = ".z" + name.dropFirst()
|
||||
let stringShdr = sectionHeaders[Int(header.e_shstrndx)]
|
||||
do {
|
||||
let bytes = try source.fetch(from: Source.Address(stringShdr.sh_offset),
|
||||
count: Int(stringShdr.sh_size),
|
||||
as: UInt8.self)
|
||||
let stringSect = ElfStringSection(bytes: bytes)
|
||||
let base = ImageSource.Address(stringShdr.sh_offset)
|
||||
let end = base + ImageSource.Size(stringShdr.sh_size)
|
||||
let stringSource = source[base..<end]
|
||||
let stringSect = ElfStringSection(source: stringSource)
|
||||
|
||||
for shdr in sectionHeaders {
|
||||
guard let sname
|
||||
@@ -1634,21 +1606,24 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
}
|
||||
|
||||
if name == sname {
|
||||
let subSource = SubImageSource(parent: source,
|
||||
baseAddress: Source.Address(shdr.sh_offset),
|
||||
length: Source.Size(shdr.sh_size))
|
||||
let base = ImageSource.Address(shdr.sh_offset)
|
||||
let end = base + ImageSource.Size(shdr.sh_size)
|
||||
let subSource = source[base..<end]
|
||||
|
||||
if (shdr.sh_flags & Traits.Shdr.Flags(SHF_COMPRESSED)) != 0 {
|
||||
return try ElfCompressedImageSource<Traits>(source: subSource)
|
||||
return try ImageSource(elfCompressedImageSource: subSource,
|
||||
traits: Traits.self)
|
||||
} else {
|
||||
return subSource
|
||||
}
|
||||
}
|
||||
|
||||
if zname == sname {
|
||||
let subSource = SubImageSource(parent: source,
|
||||
baseAddress: Source.Address(shdr.sh_offset),
|
||||
length: Source.Size(shdr.sh_size))
|
||||
return try ElfGNUCompressedImageSource(source: subSource)
|
||||
let base = ImageSource.Address(shdr.sh_offset)
|
||||
let end = base + ImageSource.Size(shdr.sh_size)
|
||||
let subSource = source[base..<end]
|
||||
|
||||
return try ImageSource(gnuCompressedImageSource: subSource)
|
||||
}
|
||||
}
|
||||
} catch let CompressedImageSourceError.libraryNotFound(library) {
|
||||
@@ -1685,26 +1660,24 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let bytes = section.fetchAllBytes() else {
|
||||
guard let link = try? section.fetchString(from: 0) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let nullIndex = bytes.firstIndex(of: UInt8(0)) else {
|
||||
let nullIndex = ImageSource.Address(link.utf8.count)
|
||||
let crcIndex = (nullIndex + 4) & ~3
|
||||
|
||||
guard let unswappedCrc = try? section.fetch(
|
||||
from: crcIndex, as: UInt32.self
|
||||
) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let link = String(decoding: bytes[0..<nullIndex], as: UTF8.self)
|
||||
let crcIndex = (nullIndex + 3) & ~3
|
||||
let crcBytes = bytes[crcIndex..<crcIndex+4]
|
||||
|
||||
let crc = crcBytes.withUnsafeBufferPointer {
|
||||
$0.withMemoryRebound(to: UInt32.self) { buf in
|
||||
let crc: UInt32
|
||||
if shouldByteSwap {
|
||||
return buf[0].byteSwapped
|
||||
crc = unswappedCrc.byteSwapped
|
||||
} else {
|
||||
return buf[0]
|
||||
}
|
||||
}
|
||||
crc = unswappedCrc
|
||||
}
|
||||
|
||||
return DebugLinkInfo(link: link, crc: crc)
|
||||
@@ -1716,16 +1689,13 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let bytes = section.fetchAllBytes() else {
|
||||
guard let link = try? section.fetchString(from: 0) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let nullIndex = bytes.firstIndex(of: UInt8(0)) else {
|
||||
return nil
|
||||
}
|
||||
let nullIndex = link.utf8.count
|
||||
|
||||
let link = String(decoding: bytes[0..<nullIndex], as: UTF8.self)
|
||||
let uuid = [UInt8](bytes[(nullIndex+1)...])
|
||||
let uuid = [UInt8](section.bytes[(nullIndex + 1)...])
|
||||
|
||||
return DebugAltLinkInfo(link: link, uuid: uuid)
|
||||
}
|
||||
@@ -1736,11 +1706,7 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
return nil
|
||||
}
|
||||
|
||||
if let data = sectionSource.fetchAllBytes() {
|
||||
return String(decoding: data, as: UTF8.self)
|
||||
}
|
||||
|
||||
return nil
|
||||
return String(decoding: sectionSource.bytes, as: UTF8.self)
|
||||
}
|
||||
|
||||
struct ElfSymbol {
|
||||
@@ -1790,8 +1756,8 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
return localTable
|
||||
}
|
||||
|
||||
public func lookupSymbol(address: Source.Address) -> ImageSymbol? {
|
||||
let relativeAddress = Traits.Address(address - baseAddress)
|
||||
public func lookupSymbol(address: Traits.Address) -> ImageSymbol? {
|
||||
let relativeAddress = address - Traits.Address(baseAddress)
|
||||
guard let symbol = symbolTable.lookupSymbol(address: relativeAddress) else {
|
||||
return nil
|
||||
}
|
||||
@@ -1800,7 +1766,7 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
offset: Int(relativeAddress - symbol.value))
|
||||
}
|
||||
|
||||
func getDwarfSection(_ section: DwarfSection) -> (any ImageSource)? {
|
||||
func getDwarfSection(_ section: DwarfSection) -> ImageSource? {
|
||||
switch section {
|
||||
case .debugAbbrev: return getSection(".debug_abbrev")
|
||||
case .debugAddr: return getSection(".debug_addr")
|
||||
@@ -1832,7 +1798,9 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
|
||||
typealias CallSiteInfo = DwarfReader<ElfImage>.CallSiteInfo
|
||||
|
||||
func inlineCallSites(at address: Address) -> ArraySlice<CallSiteInfo> {
|
||||
func inlineCallSites(
|
||||
at address: Traits.Address
|
||||
) -> ArraySlice<CallSiteInfo> {
|
||||
guard let callSiteInfo = dwarfReader?.inlineCallSites else {
|
||||
return [][0..<0]
|
||||
}
|
||||
@@ -1870,9 +1838,11 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
|
||||
typealias SourceLocation = SymbolicatedBacktrace.SourceLocation
|
||||
|
||||
func sourceLocation(for address: Address) throws -> SourceLocation? {
|
||||
func sourceLocation(
|
||||
for address: Traits.Address
|
||||
) throws -> SourceLocation? {
|
||||
var result: SourceLocation? = nil
|
||||
var prevState: DwarfReader<ElfImage>.LineNumberState? = nil
|
||||
var prevState: DwarfLineNumberState? = nil
|
||||
guard let dwarfReader = dwarfReader else {
|
||||
return nil
|
||||
}
|
||||
@@ -1901,14 +1871,157 @@ class ElfImage<SomeImageSource: ImageSource,
|
||||
}
|
||||
}
|
||||
|
||||
typealias Elf32Image<S: ImageSource> = ElfImage<S, Elf32Traits>
|
||||
typealias Elf64Image<S: ImageSource> = ElfImage<S, Elf64Traits>
|
||||
typealias Elf32Image = ElfImage<Elf32Traits>
|
||||
typealias Elf64Image = ElfImage<Elf64Traits>
|
||||
|
||||
// .. Checking for ELF images ..................................................
|
||||
|
||||
/// Test if there is a valid ELF image at the specified address; if there is,
|
||||
/// extract the address range for the text segment and the UUID, if any.
|
||||
@_specialize(kind: full, where R == UnsafeLocalMemoryReader)
|
||||
@_specialize(kind: full, where R == RemoteMemoryReader)
|
||||
#if os(Linux)
|
||||
@_specialize(kind: full, where R == MemserverMemoryReader)
|
||||
#endif
|
||||
func getElfImageInfo<R: MemoryReader>(at address: R.Address,
|
||||
using reader: R)
|
||||
-> (endOfText: R.Address, uuid: [UInt8]?)?
|
||||
{
|
||||
do {
|
||||
// Check the magic number first
|
||||
let magic = try reader.fetch(from: address, as: Elf_Magic.self)
|
||||
|
||||
if magic != ElfMagic {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read the class from the next byte
|
||||
let elfClass = Elf_Ehdr_Class(rawValue: try reader.fetch(from: address + 4,
|
||||
as: UInt8.self))
|
||||
|
||||
if elfClass == .ELFCLASS32 {
|
||||
return try getElfImageInfo(at: address, using: reader,
|
||||
traits: Elf32Traits.self)
|
||||
} else if elfClass == .ELFCLASS64 {
|
||||
return try getElfImageInfo(at: address, using: reader,
|
||||
traits: Elf64Traits.self)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@_specialize(kind: full, where R == UnsafeLocalMemoryReader, Traits == Elf32Traits)
|
||||
@_specialize(kind: full, where R == UnsafeLocalMemoryReader, Traits == Elf64Traits)
|
||||
@_specialize(kind: full, where R == RemoteMemoryReader, Traits == Elf32Traits)
|
||||
@_specialize(kind: full, where R == RemoteMemoryReader, Traits == Elf64Traits)
|
||||
#if os(Linux)
|
||||
@_specialize(kind: full, where R == MemserverMemoryReader, Traits == Elf32Traits)
|
||||
@_specialize(kind: full, where R == MemserverMemoryReader, Traits == Elf64Traits)
|
||||
#endif
|
||||
func getElfImageInfo<R: MemoryReader, Traits: ElfTraits>(
|
||||
at address: R.Address,
|
||||
using reader: R,
|
||||
traits: Traits.Type
|
||||
) throws -> (endOfText: R.Address, uuid: [UInt8]?)? {
|
||||
// Grab the whole 32-bit header
|
||||
let unswappedHeader = try reader.fetch(from: address, as: Traits.Ehdr.self)
|
||||
|
||||
let header: Traits.Ehdr
|
||||
if unswappedHeader.shouldByteSwap {
|
||||
header = unswappedHeader.byteSwapped
|
||||
} else {
|
||||
header = unswappedHeader
|
||||
}
|
||||
|
||||
let byteSwap = header.shouldByteSwap
|
||||
func maybeSwap<T: ByteSwappable>(_ x: T) -> T {
|
||||
if byteSwap {
|
||||
return x.byteSwapped
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
var endOfText = address
|
||||
var uuid: [UInt8]? = nil
|
||||
|
||||
// Find the last loadable executable segment, and scan for PT_NOTE
|
||||
// segments that contain the UUID
|
||||
var phAddr = ImageSource.Address(address) + ImageSource.Size(header.e_phoff)
|
||||
for _ in 0..<header.e_phnum {
|
||||
let phdr = maybeSwap(try reader.fetch(from: phAddr, as: Traits.Phdr.self))
|
||||
if phdr.p_type == .PT_LOAD && (phdr.p_flags & PF_X) != 0 {
|
||||
endOfText = max(endOfText, address + ImageSource.Address(phdr.p_vaddr)
|
||||
+ ImageSource.Size(phdr.p_memsz))
|
||||
}
|
||||
if phdr.p_type == .PT_NOTE {
|
||||
var noteAddr = address + ImageSource.Address(phdr.p_vaddr)
|
||||
let noteEnd = noteAddr + ImageSource.Size(phdr.p_memsz)
|
||||
|
||||
while noteAddr < noteEnd {
|
||||
let nhdr = maybeSwap(try reader.fetch(
|
||||
from: noteAddr, as: Traits.Nhdr.self))
|
||||
|
||||
noteAddr += ImageSource.Size(MemoryLayout<Traits.Nhdr>.size)
|
||||
|
||||
if noteEnd - noteAddr < nhdr.n_namesz {
|
||||
// This segment is probably corrupted, so skip it
|
||||
noteAddr = noteEnd
|
||||
continue
|
||||
}
|
||||
|
||||
var isBuildId = false
|
||||
let nameLen = nhdr.n_namesz > 0 ? nhdr.n_namesz - 1 : 0
|
||||
|
||||
// Test if this is a "GNU" NT_GNU_BUILD_ID note
|
||||
if nameLen == 3 {
|
||||
let byte0 = try reader.fetch(from: noteAddr, as: UInt8.self)
|
||||
let byte1 = try reader.fetch(from: noteAddr + 1, as: UInt8.self)
|
||||
let byte2 = try reader.fetch(from: noteAddr + 2, as: UInt8.self)
|
||||
|
||||
if byte0 == 0x47 && byte1 == 0x4e && byte2 == 0x55 &&
|
||||
UInt32(nhdr.n_type) == NT_GNU_BUILD_ID {
|
||||
isBuildId = true
|
||||
}
|
||||
}
|
||||
|
||||
noteAddr += ImageSource.Size(nhdr.n_namesz)
|
||||
if (noteAddr & 3) != 0 {
|
||||
noteAddr += 4 - (noteAddr & 3)
|
||||
}
|
||||
|
||||
if noteEnd - noteAddr < nhdr.n_descsz {
|
||||
// Corrupted segment, skip
|
||||
noteAddr = noteEnd
|
||||
continue
|
||||
}
|
||||
|
||||
if isBuildId {
|
||||
uuid = try reader.fetch(from: noteAddr,
|
||||
count: Int(nhdr.n_descsz),
|
||||
as: UInt8.self)
|
||||
}
|
||||
|
||||
noteAddr += ImageSource.Size(nhdr.n_descsz)
|
||||
if (noteAddr & 3) != 0 {
|
||||
noteAddr += 4 - (noteAddr & 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
phAddr += ImageSource.Address(header.e_phentsize)
|
||||
}
|
||||
|
||||
return (endOfText: endOfText, uuid: uuid)
|
||||
}
|
||||
|
||||
// .. Testing ..................................................................
|
||||
|
||||
@_spi(ElfTest)
|
||||
public func testElfImageAt(path: String) -> Bool {
|
||||
guard let source = try? FileImageSource(path: path) else {
|
||||
guard let source = try? ImageSource(path: path) else {
|
||||
print("\(path) was not accessible")
|
||||
return false
|
||||
}
|
||||
@@ -1978,5 +2091,3 @@ public func testElfImageAt(path: String) -> Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
#endif // os(Linux)
|
||||
95
stdlib/public/RuntimeModule/ElfImageCache.swift
Normal file
95
stdlib/public/RuntimeModule/ElfImageCache.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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 imageNdx = images.indexOfImage(at: Backtrace.Address(address)) {
|
||||
let base = MemoryReader.Address(images[imageNdx].baseAddress)!
|
||||
let relativeAddress = address - base
|
||||
let cache = ElfImageCache.threadLocal
|
||||
|
||||
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)
|
||||
) {
|
||||
let relativeAddress = address - FileImageSource.Address(images[imageNdx].baseAddress)
|
||||
var elf32Image = elf32Cache[imageNdx]
|
||||
var elf64Image = elf64Cache[imageNdx]
|
||||
|
||||
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 theSymbol = elf32Image?.lookupSymbol(address: relativeAddress) {
|
||||
return isAsyncSymbol(theSymbol.name)
|
||||
} else if let theSymbol = elf64Image?.lookupSymbol(address: relativeAddress) {
|
||||
}
|
||||
case let .elf64Image(image):
|
||||
if let theSymbol = image.lookupSymbol(
|
||||
address: Elf64Image.Traits.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),
|
||||
pc = stripPtrAuth(try reader.fetch(
|
||||
from:MemoryReader.Address(
|
||||
strippedFp
|
||||
+ Address(MemoryLayout<Address>.size)
|
||||
),
|
||||
as: Address.self))
|
||||
next = try reader.fetch(from: Address(strippedFp), 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)
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
131
stdlib/public/RuntimeModule/ImageMap+Darwin.swift
Normal file
131
stdlib/public/RuntimeModule/ImageMap+Darwin.swift
Normal 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)
|
||||
169
stdlib/public/RuntimeModule/ImageMap+Linux.swift
Normal file
169
stdlib/public/RuntimeModule/ImageMap+Linux.swift
Normal 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)
|
||||
211
stdlib/public/RuntimeModule/ImageMap.swift
Normal file
211
stdlib/public/RuntimeModule/ImageMap.swift
Normal 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))!
|
||||
}
|
||||
|
||||
}
|
||||
436
stdlib/public/RuntimeModule/ImageSource.swift
Normal file
436
stdlib/public/RuntimeModule/ImageSource.swift
Normal 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
|
||||
}
|
||||
}
|
||||
237
stdlib/public/RuntimeModule/LimitSequence.swift
Normal file
237
stdlib/public/RuntimeModule/LimitSequence.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
186
stdlib/public/RuntimeModule/OSReleaseScanner.swift
Normal file
186
stdlib/public/RuntimeModule/OSReleaseScanner.swift
Normal 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)
|
||||
146
stdlib/public/RuntimeModule/RichFrame.swift
Normal file
146
stdlib/public/RuntimeModule/RichFrame.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,7 +213,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
|
||||
if stringLen > 0 {
|
||||
return demangled.withMemoryRebound(to: UInt8.self,
|
||||
capacity: stringLen) {
|
||||
let demangledBytes = UnsafeBufferPointer(start: $0,
|
||||
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,53 +457,53 @@ 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
|
||||
}
|
||||
|
||||
if let theSymbol = elf32Image?.lookupSymbol(address: relativeAddress) {
|
||||
var location: SourceLocation?
|
||||
|
||||
if showSourceLocations || showInlineFrames {
|
||||
location = try? elf32Image!.sourceLocation(for: relativeAddress)
|
||||
if options.contains(.showSourceLocations)
|
||||
|| options.contains(.showInlineFrames) {
|
||||
location = try? image.sourceLocation(for: address)
|
||||
} else {
|
||||
location = nil
|
||||
}
|
||||
|
||||
if showInlineFrames {
|
||||
for inline in elf32Image!.inlineCallSites(at: relativeAddress) {
|
||||
if options.contains(.showInlineFrames) {
|
||||
for inline in image.inlineCallSites(at: address) {
|
||||
let fakeSymbol = Symbol(imageIndex: imageNdx,
|
||||
imageName: theImages[imageNdx].name,
|
||||
imageName: name,
|
||||
rawName: inline.rawName ?? "<unknown>",
|
||||
offset: 0,
|
||||
sourceLocation: location)
|
||||
@@ -543,48 +517,30 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
symbol = Symbol(imageIndex: imageNdx,
|
||||
imageName: theImages[imageNdx].name,
|
||||
return Symbol(imageIndex: imageNdx,
|
||||
imageName: 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)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
char *_swift_backtrace_demangle(const char *rawName,
|
||||
size_t rawNameLength,
|
||||
char *outputBuffer,
|
||||
size_t *outputBufferSize);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // SWIFT_BACKTRACING_RUNTIME_H
|
||||
@@ -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)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// REQUIRES: backtracing
|
||||
// REQUIRES: OS=macosx || OS=linux-gnu
|
||||
|
||||
import _Backtracing
|
||||
import Runtime
|
||||
|
||||
func doFrames(_ count: Int) {
|
||||
if count <= 0 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
67
test/Backtracing/CodableBacktrace.swift
Normal file
67
test/Backtracing/CodableBacktrace.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
64
test/Backtracing/CompactImageMap.swift
Normal file
64
test/Backtracing/CompactImageMap.swift
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
27
test/Backtracing/ImageMap.swift
Normal file
27
test/Backtracing/ImageMap.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// UNSUPPORTED: use_os_stdlib
|
||||
// UNSUPPORTED: back_deployment_runtime
|
||||
|
||||
import _Backtracing
|
||||
import Runtime
|
||||
|
||||
@available(SwiftStdlib 5.1, *)
|
||||
func level1() async {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
12
test/lit.cfg
12
test/lit.cfg
@@ -1326,7 +1326,7 @@ if run_vendor == 'apple':
|
||||
"swiftDarwin", "swiftSwiftPrivateThreadExtras",
|
||||
"swiftSwiftOnoneSupport", "swift_Concurrency"]
|
||||
if backtracing is not None:
|
||||
libraries.append('swift_Backtracing')
|
||||
libraries.append('swiftRuntime')
|
||||
for library in libraries:
|
||||
swift_execution_tests_extra_flags += ' -Xlinker -l%s'% library
|
||||
|
||||
@@ -2208,12 +2208,12 @@ config.substitutions.append(('%concurrency_module', concurrency_module))
|
||||
config.substitutions.append(('%/concurrency_module',
|
||||
'/'.join(os.path.normpath(concurrency_module).split(os.sep))))
|
||||
|
||||
# Add 'backtracing_module' as the path to the _Backtracing .swiftmodule file
|
||||
backtracing_module = os.path.join(stdlib_dir, "_Backtracing.swiftmodule",
|
||||
# Add 'runtime_module' as the path to the Runtime .swiftmodule file
|
||||
runtime_module = os.path.join(stdlib_dir, "Runtime.swiftmodule",
|
||||
target_specific_module_triple + ".swiftmodule")
|
||||
config.substitutions.append(('%backtracing_module', backtracing_module))
|
||||
config.substitutions.append(('%/backtracing_module',
|
||||
'/'.join(os.path.normpath(backtracing_module).split(os.sep))))
|
||||
config.substitutions.append(('%runtime_module', runtime_module))
|
||||
config.substitutions.append(('%/runtime_module',
|
||||
'/'.join(os.path.normpath(runtime_module).split(os.sep))))
|
||||
|
||||
# Add 'distributed_module' as the path to the Distributed .swiftmodule file
|
||||
distributed_module = os.path.join(stdlib_dir, "Distributed.swiftmodule",
|
||||
|
||||
@@ -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>;
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user