mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[Backtracing] Rename _Backtracing to Runtime.
Move the backtracing code into a new Runtime module. This means renaming the Swift Runtime's CMake target because otherwise there will be a name clash. rdar://124913332
This commit is contained in:
@@ -727,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)
|
||||
@@ -1384,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")
|
||||
|
||||
@@ -319,3 +319,11 @@ 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.
|
||||
|
||||
|
||||
141
docs/CompactBacktraceFormat.md
Normal file
141
docs/CompactBacktraceFormat.md
Normal file
@@ -0,0 +1,141 @@
|
||||
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 encoded 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 |
|
||||
| `1xxxxxxx` | reserved | Reserved for future expansion |
|
||||
|
||||
### `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.
|
||||
@@ -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",
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
263
stdlib/public/RuntimeModule/Address.swift
Normal file
263
stdlib/public/RuntimeModule/Address.swift
Normal file
@@ -0,0 +1,263 @@
|
||||
//===--- 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(addr)
|
||||
case let .thirtyTwoBit(addr):
|
||||
guard T.bitWidth >= 32 else { return nil }
|
||||
return T(addr)
|
||||
case let .sixtyFourBit(addr):
|
||||
guard T.bitWidth >= 64 else { return nil }
|
||||
return T(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.
|
||||
init?(_ address: Backtrace.Address) {
|
||||
guard let result = address.toFixedWidth(type: Self.self) else {
|
||||
return nil
|
||||
}
|
||||
self = result
|
||||
}
|
||||
}
|
||||
|
||||
extension Backtrace.Address {
|
||||
/// Convert from a UInt16.
|
||||
init(_ value: UInt16) {
|
||||
if value == 0 {
|
||||
self.representation = .null
|
||||
return
|
||||
}
|
||||
self.representation = .sixteenBit(value)
|
||||
}
|
||||
|
||||
/// Convert from a UInt32.
|
||||
init(_ value: UInt32) {
|
||||
if value == 0 {
|
||||
self.representation = .null
|
||||
return
|
||||
}
|
||||
self.representation = .thirtyTwoBit(value)
|
||||
}
|
||||
|
||||
/// Convert from a UInt64.
|
||||
init(_ value: UInt64) {
|
||||
if value == 0 {
|
||||
self.representation = .null
|
||||
return
|
||||
}
|
||||
self.representation = .sixtyFourBit(value)
|
||||
}
|
||||
|
||||
/// Convert from a FixedWidthInteger
|
||||
init<T: FixedWidthInteger>(_ value: T) {
|
||||
if value == 0 {
|
||||
self.representation = .null
|
||||
return
|
||||
}
|
||||
|
||||
switch T.bitWidth {
|
||||
case 16:
|
||||
self.representation = .sixteenBit(UInt16(value))
|
||||
case 32:
|
||||
self.representation = .thirtyTwoBit(UInt32(value))
|
||||
case 64:
|
||||
self.representation = .sixtyFourBit(UInt64(value))
|
||||
default:
|
||||
fatalError("Unsupported address width")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,10 +38,51 @@ internal import BacktracingImpl.ImageFormats.Elf
|
||||
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 typealias Address = UInt64
|
||||
public struct Address: Hashable, Codable, Sendable {
|
||||
enum Representation: Hashable, Codable, 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 {
|
||||
@@ -108,6 +149,16 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
||||
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.
|
||||
@@ -139,66 +190,63 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
||||
}
|
||||
|
||||
/// A textual description of this frame.
|
||||
public func description(width: Int) -> String {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case let .programCounter(addr):
|
||||
return "\(hex(addr, width: width))"
|
||||
return "\(addr)"
|
||||
case let .returnAddress(addr):
|
||||
return "\(hex(addr, width: width)) [ra]"
|
||||
return "\(addr) [ra]"
|
||||
case let .asyncResumePoint(addr):
|
||||
return "\(hex(addr, width: width)) [async]"
|
||||
return "\(addr) [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
|
||||
private(set) public var name: String?
|
||||
|
||||
/// The full path to the image (e.g. /usr/lib/swift/libswiftCore.dylib).
|
||||
public var path: String
|
||||
private(set) 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 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.
|
||||
public var baseAddress: Backtrace.Address
|
||||
private(set) public var baseAddress: Backtrace.Address
|
||||
|
||||
/// The end of the text segment in this image.
|
||||
public var endOfText: Backtrace.Address
|
||||
private(set) 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)
|
||||
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 var architecture: String
|
||||
|
||||
/// The width of an address in this backtrace, in bits.
|
||||
public var addressWidth: Int
|
||||
/// The actual backtrace data, stored in Compact Backtrace Format.
|
||||
private var representation: [UInt8]
|
||||
|
||||
/// A list of captured frame information.
|
||||
public var frames: [Frame]
|
||||
@available(macOS 10.15, *)
|
||||
public var frames: some Sequence<Frame> {
|
||||
return CompactBacktraceFormat.Decoder(representation)
|
||||
}
|
||||
|
||||
/// A list of captured images.
|
||||
///
|
||||
@@ -208,36 +256,6 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
||||
/// 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;
|
||||
@@ -245,28 +263,33 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
||||
/// 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.
|
||||
/// Parameters:
|
||||
///
|
||||
/// @returns A new `Backtrace` struct.
|
||||
/// - 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) throws -> Backtrace {
|
||||
top: Int = 16,
|
||||
images: [Image]? = nil) throws -> Backtrace {
|
||||
#if os(Linux)
|
||||
let images = captureImages()
|
||||
// On Linux, we need the captured images to resolve async functions
|
||||
let theImages = images ?? captureImages()
|
||||
#else
|
||||
let images: [Image]? = nil
|
||||
let theImages = images
|
||||
#endif
|
||||
|
||||
// N.B. We use offset+1 here to skip this frame, rather than inlining
|
||||
@@ -274,7 +297,7 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
||||
return try HostContext.withCurrentContext { ctx in
|
||||
try capture(from: ctx,
|
||||
using: UnsafeLocalMemoryReader(),
|
||||
images: images,
|
||||
images: theImages,
|
||||
algorithm: algorithm,
|
||||
limit: limit,
|
||||
offset: offset + 1,
|
||||
@@ -282,108 +305,6 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
@_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.
|
||||
///
|
||||
@@ -396,7 +317,139 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
|
||||
/// 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: [Image]? = 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] = []
|
||||
|
||||
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
|
||||
init<Address: FixedWidthInteger>(architecture: String,
|
||||
frames: some Sequence<RichFrame<Address>>,
|
||||
images: [Image]?) {
|
||||
self.architecture = architecture
|
||||
self.representation = Array(CompactBacktraceFormat.Encoder(frames))
|
||||
self.images = images
|
||||
}
|
||||
}
|
||||
|
||||
// -- Capture Implementation -------------------------------------------------
|
||||
|
||||
extension 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
|
||||
public static func capture<Ctx: Context, Rdr: MemoryReader>(
|
||||
from context: Ctx,
|
||||
using memoryReader: Rdr,
|
||||
images: [Image]?,
|
||||
algorithm: UnwindAlgorithm,
|
||||
limit: Int?,
|
||||
offset: Int,
|
||||
top: Int
|
||||
) 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,
|
||||
images: images)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- Darwin -----------------------------------------------------------------
|
||||
|
||||
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
|
||||
extension Backtrace {
|
||||
|
||||
private static func withDyldProcessInfo<T>(for task: task_t,
|
||||
fn: (OpaquePointer?) throws -> T)
|
||||
rethrows -> T {
|
||||
@@ -413,9 +466,7 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
||||
|
||||
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] = []
|
||||
@@ -450,7 +501,7 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
||||
|
||||
images.append(Image(name: name,
|
||||
path: pathString,
|
||||
buildID: theUUID,
|
||||
uniqueID: theUUID,
|
||||
baseAddress: Address(machHeaderAddress),
|
||||
endOfText: Address(endOfText)))
|
||||
}
|
||||
@@ -459,18 +510,28 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
||||
|
||||
return images.sorted(by: { $0.baseAddress < $1.baseAddress })
|
||||
}
|
||||
#else // !(os(macOS) || os(iOS) || os(tvOS) || os(watchOS))
|
||||
}
|
||||
#endif // os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
|
||||
|
||||
// -- Linux ------------------------------------------------------------------
|
||||
|
||||
#if os(Linux)
|
||||
extension Backtrace {
|
||||
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] {
|
||||
@_specialize(exported: true, kind: full, where M == UnsafeLocalMemoryReader)
|
||||
@_specialize(exported: true, kind: full, where M == RemoteMemoryReader)
|
||||
@_specialize(exported: true, kind: full, where M == LocalMemoryReader)
|
||||
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"
|
||||
@@ -489,8 +550,8 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
||||
if match.inode == "0" || path == "" {
|
||||
continue
|
||||
}
|
||||
guard let start = Address(match.start, radix: 16),
|
||||
let end = Address(match.end, radix: 16) else {
|
||||
guard let start = Address(match.start),
|
||||
let end = Address(match.end) else {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -503,25 +564,8 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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: "/") {
|
||||
@@ -531,155 +575,24 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
||||
}
|
||||
|
||||
// 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
|
||||
guard let (endOfText, uuid) = getElfImageInfo(at: M.Address(range.low)!,
|
||||
using: reader) else {
|
||||
// Not an ELF iamge
|
||||
continue
|
||||
}
|
||||
|
||||
let image = Image(name: String(name),
|
||||
path: String(path),
|
||||
buildID: theUUID,
|
||||
uniqueID: uuid,
|
||||
baseAddress: range.low,
|
||||
endOfText: endOfText)
|
||||
endOfText: Address(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")
|
||||
}
|
||||
}
|
||||
#endif // os(Linux)
|
||||
|
||||
|
||||
@@ -527,24 +527,22 @@ public struct BacktraceFormatter {
|
||||
/// Format an individual frame into a list of columns.
|
||||
///
|
||||
/// @param frame The frame to format.
|
||||
/// @param addressWidth The width, in characters, of an address.
|
||||
/// @param index The frame index, if required.
|
||||
///
|
||||
/// @result An array of strings, one per column.
|
||||
public func formatColumns(frame: Backtrace.Frame,
|
||||
addressWidth: Int,
|
||||
index: Int? = nil) -> [String] {
|
||||
let pc: String
|
||||
var attrs: [String] = []
|
||||
|
||||
switch frame {
|
||||
case let .programCounter(address):
|
||||
pc = "\(hex(address, width: addressWidth))"
|
||||
pc = "\(address)"
|
||||
case let .returnAddress(address):
|
||||
pc = "\(hex(address, width: addressWidth))"
|
||||
pc = "\(address)"
|
||||
attrs.append("ra")
|
||||
case let .asyncResumePoint(address):
|
||||
pc = "\(hex(address, width: addressWidth))"
|
||||
pc = "\(address)"
|
||||
attrs.append("async")
|
||||
case .omittedFrames(_), .truncated:
|
||||
pc = "..."
|
||||
@@ -567,30 +565,24 @@ public struct BacktraceFormatter {
|
||||
/// Format a frame into a list of rows.
|
||||
///
|
||||
/// @param frame The frame to format.
|
||||
/// @param addressWidth The width, in characters, of an address.
|
||||
/// @param index The frame index, if required.
|
||||
///
|
||||
/// @result An array of table rows.
|
||||
public func formatRows(frame: Backtrace.Frame,
|
||||
addressWidth: Int,
|
||||
index: Int? = nil) -> [TableRow] {
|
||||
return [.columns(formatColumns(frame: frame,
|
||||
addressWidth: addressWidth,
|
||||
index: index))]
|
||||
}
|
||||
|
||||
/// Format just one frame.
|
||||
///
|
||||
/// @param frame The frame to format.
|
||||
/// @param addressWidth The width, in characters, of an address.
|
||||
/// @param index The frame index, if required.
|
||||
///
|
||||
/// @result A `String` containing the formatted data.
|
||||
public func format(frame: Backtrace.Frame,
|
||||
addressWidth: Int,
|
||||
index: Int? = nil) -> String {
|
||||
let rows = formatRows(frame: frame,
|
||||
addressWidth: addressWidth,
|
||||
index: index)
|
||||
return BacktraceFormatter.formatTable(rows, alignments: [.right])
|
||||
}
|
||||
@@ -598,16 +590,14 @@ public struct BacktraceFormatter {
|
||||
/// Format the frame list from a backtrace.
|
||||
///
|
||||
/// @param frames The frames to format.
|
||||
/// @param addressWidth The width, in characters, of an address.
|
||||
///
|
||||
/// @result A `String` containing the formatted data.
|
||||
public func format(frames: some Sequence<Backtrace.Frame>,
|
||||
addressWidth: Int) -> String {
|
||||
public func format(frames: some Sequence<Backtrace.Frame>) -> String {
|
||||
var rows: [TableRow] = []
|
||||
|
||||
var n = 0
|
||||
for frame in frames {
|
||||
rows += formatRows(frame: frame, addressWidth: addressWidth, index: n)
|
||||
rows += formatRows(frame: frame, index: n)
|
||||
|
||||
if case let .omittedFrames(count) = frame {
|
||||
n += count
|
||||
@@ -625,8 +615,7 @@ public struct BacktraceFormatter {
|
||||
///
|
||||
/// @result A `String` containing the formatted data.
|
||||
public func format(backtrace: Backtrace) -> String {
|
||||
return format(frames: backtrace.frames,
|
||||
addressWidth: (backtrace.addressWidth + 3) / 4)
|
||||
return format(frames: backtrace.frames)
|
||||
}
|
||||
|
||||
/// Grab source lines for a symbolicated backtrace.
|
||||
@@ -743,19 +732,18 @@ public struct BacktraceFormatter {
|
||||
///
|
||||
/// @result An array of strings, one per column.
|
||||
public func formatColumns(frame: SymbolicatedBacktrace.Frame,
|
||||
addressWidth: Int,
|
||||
index: Int? = nil) -> [String] {
|
||||
let pc: String
|
||||
var attrs: [String] = []
|
||||
|
||||
switch frame.captured {
|
||||
case let .programCounter(address):
|
||||
pc = "\(hex(address, width: addressWidth))"
|
||||
pc = "\(address)"
|
||||
case let .returnAddress(address):
|
||||
pc = "\(hex(address, width: addressWidth))"
|
||||
pc = "\(address)"
|
||||
attrs.append("ra")
|
||||
case let .asyncResumePoint(address):
|
||||
pc = "\(hex(address, width: addressWidth))"
|
||||
pc = "\(address)"
|
||||
attrs.append("async")
|
||||
case .omittedFrames(_), .truncated:
|
||||
pc = ""
|
||||
@@ -855,16 +843,13 @@ public struct BacktraceFormatter {
|
||||
/// Format a frame into a list of rows.
|
||||
///
|
||||
/// @param frame The frame to format.
|
||||
/// @param addressWidth The width, in characters, of an address.
|
||||
/// @param index The frame index, if required.
|
||||
///
|
||||
/// @result An array of table rows.
|
||||
public func formatRows(frame: SymbolicatedBacktrace.Frame,
|
||||
addressWidth: Int,
|
||||
index: Int? = nil,
|
||||
showSource: Bool = true) -> [TableRow] {
|
||||
let columns = formatColumns(frame: frame,
|
||||
addressWidth: addressWidth,
|
||||
index: index)
|
||||
var rows: [TableRow] = [.columns(columns)]
|
||||
|
||||
@@ -884,16 +869,13 @@ public struct BacktraceFormatter {
|
||||
/// Format just one frame.
|
||||
///
|
||||
/// @param frame The frame to format.
|
||||
/// @param addressWidth The width, in characters, of an address.
|
||||
/// @param index The frame index, if required.
|
||||
///
|
||||
/// @result A `String` containing the formatted data.
|
||||
public func format(frame: SymbolicatedBacktrace.Frame,
|
||||
addressWidth: Int,
|
||||
index: Int? = nil,
|
||||
showSource: Bool = true) -> String {
|
||||
let rows = formatRows(frame: frame, addressWidth: addressWidth,
|
||||
index: index, showSource: showSource)
|
||||
let rows = formatRows(frame: frame, index: index, showSource: showSource)
|
||||
return BacktraceFormatter.formatTable(rows, alignments: [.right])
|
||||
}
|
||||
|
||||
@@ -907,11 +889,11 @@ public struct BacktraceFormatter {
|
||||
/// Format the frame list from a symbolicated backtrace.
|
||||
///
|
||||
/// @param frames The frames to format.
|
||||
/// @param addressWidth The width, in characters, of an address.
|
||||
///
|
||||
/// @result A `String` containing the formatted data.
|
||||
public func format(frames: some Sequence<SymbolicatedBacktrace.Frame>,
|
||||
addressWidth: Int) -> String {
|
||||
public func format(
|
||||
frames: some Sequence<SymbolicatedBacktrace.Frame>
|
||||
) -> String {
|
||||
var rows: [TableRow] = []
|
||||
var sourceLocationsShown = Set<SymbolicatedBacktrace.SourceLocation>()
|
||||
|
||||
@@ -931,8 +913,7 @@ public struct BacktraceFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
rows += formatRows(frame: frame, addressWidth: addressWidth,
|
||||
index: n, showSource: showSource)
|
||||
rows += formatRows(frame: frame, index: n, showSource: showSource)
|
||||
|
||||
if case let .omittedFrames(count) = frame.captured {
|
||||
n += count
|
||||
@@ -950,16 +931,14 @@ public struct BacktraceFormatter {
|
||||
///
|
||||
/// @result A `String` containing the formatted data.
|
||||
public func format(backtrace: SymbolicatedBacktrace) -> String {
|
||||
let addressChars = (backtrace.addressWidth + 3) / 4
|
||||
var result = format(frames: backtrace.frames, addressWidth: addressChars)
|
||||
var result = format(frames: backtrace.frames)
|
||||
|
||||
switch options._showImages {
|
||||
case .none:
|
||||
break
|
||||
case .all:
|
||||
result += "\n\nImages:\n"
|
||||
result += format(images: backtrace.images,
|
||||
addressWidth: addressChars)
|
||||
result += format(images: backtrace.images)
|
||||
case .mentioned:
|
||||
var mentionedImages = Set<Int>()
|
||||
for frame in backtrace.frames {
|
||||
@@ -978,7 +957,7 @@ public struct BacktraceFormatter {
|
||||
} else {
|
||||
result += "\n\nImages (only mentioned):\n"
|
||||
}
|
||||
result += format(images: images, addressWidth: addressChars)
|
||||
result += format(images: images)
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -987,28 +966,31 @@ public struct BacktraceFormatter {
|
||||
/// Format a `Backtrace.Image` into a list of columns.
|
||||
///
|
||||
/// @param image The `Image` object to format.
|
||||
/// @param addressWidth The width of an address, in characters.
|
||||
///
|
||||
/// @result An array of strings, one per column.
|
||||
public func formatColumns(image: Backtrace.Image,
|
||||
addressWidth: Int) -> [String] {
|
||||
let addressRange = "\(hex(image.baseAddress, width: addressWidth))–\(hex(image.endOfText, width: addressWidth))"
|
||||
public func formatColumns(image: Backtrace.Image) -> [String] {
|
||||
let addressRange = "\(image.baseAddress)–\(image.endOfText)"
|
||||
let buildID: String
|
||||
if let bytes = image.buildID {
|
||||
if let bytes = image.uniqueID {
|
||||
buildID = hex(bytes)
|
||||
} else {
|
||||
buildID = "<no build ID>"
|
||||
}
|
||||
let imagePath: String
|
||||
if options._sanitizePaths {
|
||||
imagePath = sanitizePath(image.path)
|
||||
if let path = image.path {
|
||||
if options._sanitizePaths {
|
||||
imagePath = sanitizePath(path)
|
||||
} else {
|
||||
imagePath = path
|
||||
}
|
||||
} else {
|
||||
imagePath = image.path
|
||||
imagePath = "<unknown>"
|
||||
}
|
||||
let imageName = image.name ?? "<unknown>"
|
||||
return [
|
||||
options._theme.imageAddressRange(addressRange),
|
||||
options._theme.imageBuildID(buildID),
|
||||
options._theme.imageName(image.name),
|
||||
options._theme.imageName(imageName),
|
||||
options._theme.imagePath(imagePath)
|
||||
]
|
||||
}
|
||||
@@ -1016,15 +998,12 @@ public struct BacktraceFormatter {
|
||||
/// Format an array of `Backtrace.Image`s.
|
||||
///
|
||||
/// @param images The array of `Image` objects to format.
|
||||
/// @param addressWidth The width of an address, in characters.
|
||||
///
|
||||
/// @result A string containing the formatted data.
|
||||
public func format(images: some Sequence<Backtrace.Image>,
|
||||
addressWidth: Int) -> String {
|
||||
public func format(images: some Sequence<Backtrace.Image>) -> String {
|
||||
let rows = images.map{
|
||||
TableRow.columns(
|
||||
formatColumns(image: $0,
|
||||
addressWidth: addressWidth)
|
||||
formatColumns(image: $0)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,30 @@ 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
|
||||
BacktraceFormatter.swift
|
||||
ByteSwapping.swift
|
||||
CachingMemoryReader.swift
|
||||
Context.swift
|
||||
CompactBacktrace.swift
|
||||
Compression.swift
|
||||
Context.swift
|
||||
CoreSymbolication.swift
|
||||
Dwarf.swift
|
||||
EightByteBuffer.swift
|
||||
Elf.swift
|
||||
FileImageSource.swift
|
||||
ElfImageCache.swift
|
||||
FramePointerUnwinder.swift
|
||||
Image.swift
|
||||
ImageSource.swift
|
||||
MemoryImageSource.swift
|
||||
Libc.swift
|
||||
LimitSequence.swift
|
||||
MemoryReader.swift
|
||||
ProcMapsScanner.swift
|
||||
Registers.swift
|
||||
Runtime.swift
|
||||
RichFrame.swift
|
||||
SymbolicatedBacktrace.swift
|
||||
Utils.swift
|
||||
Win32Extras.cpp
|
||||
@@ -48,24 +53,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 +80,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 +94,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())
|
||||
}
|
||||
}
|
||||
439
stdlib/public/RuntimeModule/CompactBacktrace.swift
Normal file
439
stdlib/public/RuntimeModule/CompactBacktrace.swift
Normal file
@@ -0,0 +1,439 @@
|
||||
//===--- 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)!
|
||||
|
||||
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))!
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
||||
/// 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
|
||||
|
||||
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 finished() {
|
||||
iterator = nil
|
||||
}
|
||||
|
||||
private mutating func fail() {
|
||||
iterator = nil
|
||||
}
|
||||
|
||||
// Note: If we hit an error while decoding, we will return .trucnated.
|
||||
|
||||
public mutating func next() -> Frame? {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return .programCounter(addr)
|
||||
case let .ra(absolute, count):
|
||||
guard let addr = decodeAddress(absolute, count) else {
|
||||
finished()
|
||||
return .truncated
|
||||
}
|
||||
return .returnAddress(addr)
|
||||
case let .async(absolute, count):
|
||||
guard let addr = decodeAddress(absolute, count) else {
|
||||
finished()
|
||||
return .truncated
|
||||
}
|
||||
return .asyncResumePoint(addr)
|
||||
case let .omit(external, count):
|
||||
if !external {
|
||||
return .omittedFrames(count)
|
||||
} else {
|
||||
var word: Int = 0
|
||||
for _ in 0..<count {
|
||||
guard let byte = iterator!.next() else {
|
||||
finished()
|
||||
return .truncated
|
||||
}
|
||||
word = (word << 8) | Int(byte)
|
||||
}
|
||||
return .omittedFrames(word)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// 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 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)
|
||||
case done
|
||||
}
|
||||
var bytes = EightByteBuffer()
|
||||
var state: State = .start
|
||||
|
||||
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 emitAddressNext(
|
||||
_ 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)
|
||||
return (absolute: true, count: absCount)
|
||||
} else {
|
||||
bytes = EightByteBuffer(delta)
|
||||
state = .emittingBytes(8 - deltaCount)
|
||||
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 emitExternalCountNext(
|
||||
_ count: Int
|
||||
) -> Int {
|
||||
let ucount = UInt64(count)
|
||||
let zeroes = ucount.leadingZeroBitCount >> 3
|
||||
let byteCount = 8 - zeroes
|
||||
bytes = EightByteBuffer(ucount)
|
||||
state = .emittingBytes(zeroes)
|
||||
return byteCount
|
||||
}
|
||||
|
||||
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):
|
||||
|
||||
let byte = bytes[ndx]
|
||||
if ndx + 1 == 8 {
|
||||
state = .ready
|
||||
} else {
|
||||
state = .emittingBytes(ndx + 1)
|
||||
}
|
||||
return byte
|
||||
|
||||
case .ready:
|
||||
|
||||
// Grab a rich frame and encode it
|
||||
guard let frame = iterator.next() else {
|
||||
state = .done
|
||||
return nil
|
||||
}
|
||||
|
||||
switch frame {
|
||||
case let .programCounter(addr):
|
||||
let (absolute, count) = emitAddressNext(addr)
|
||||
return Instruction.pc(absolute: absolute,
|
||||
count: count).rawValue
|
||||
case let .returnAddress(addr):
|
||||
let (absolute, count) = emitAddressNext(addr)
|
||||
return Instruction.ra(absolute: absolute,
|
||||
count: count).rawValue
|
||||
case let .asyncResumePoint(addr):
|
||||
let (absolute, count) = emitAddressNext(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 = emitExternalCountNext(count)
|
||||
return Instruction.omit(external: true,
|
||||
count: countCount).rawValue
|
||||
case .truncated:
|
||||
self.state = .done
|
||||
return Instruction.trunc.rawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,6 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#if os(Linux)
|
||||
|
||||
import Swift
|
||||
|
||||
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
|
||||
@@ -56,18 +54,26 @@ let lzma_stream_init = swift.runtime.lzma_stream_init
|
||||
// .. CompressedStream .........................................................
|
||||
|
||||
protocol CompressedStream {
|
||||
typealias InputSource = () throws -> UnsafeBufferPointer<UInt8>
|
||||
typealias InputSource = () throws -> UnsafeRawBufferPointer
|
||||
typealias OutputSink = (_ used: UInt, _ done: Bool) throws
|
||||
-> UnsafeMutableBufferPointer<UInt8>?
|
||||
-> UnsafeMutableRawBufferPointer?
|
||||
|
||||
func decompress(input: InputSource, output: OutputSink) throws -> UInt
|
||||
}
|
||||
|
||||
// .. Compression library bindings .............................................
|
||||
|
||||
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
|
||||
private var lzmaHandle = dlopen("liblzma.dylib", RTLD_LAZY)
|
||||
private var zlibHandle = dlopen("libz.dylib", RTLD_LAZY)
|
||||
private var zstdHandle = dlopen("libzstd.dylib", RTLD_LAZY)
|
||||
#elseif os(Linux)
|
||||
private var lzmaHandle = dlopen("liblzma.so.5", RTLD_LAZY)
|
||||
private var zlibHandle = dlopen("libz.so.1", RTLD_LAZY)
|
||||
private var zstdHandle = dlopen("libzstd.so.1", RTLD_LAZY)
|
||||
#elseif os(Windows)
|
||||
// ###TODO
|
||||
#endif
|
||||
|
||||
private func symbol<T>(_ handle: UnsafeMutableRawPointer?, _ name: String) -> T? {
|
||||
guard let handle = handle, let result = dlsym(handle, name) else {
|
||||
@@ -152,7 +158,9 @@ struct ZLibStream: CompressedStream {
|
||||
let buffer = try input()
|
||||
|
||||
// Not really mutable; this is just an issue with z_const
|
||||
stream.next_in = UnsafeMutablePointer(mutating: buffer.baseAddress)
|
||||
stream.next_in = UnsafeMutablePointer(
|
||||
mutating: buffer.baseAddress?.assumingMemoryBound(to: UInt8.self)
|
||||
)
|
||||
stream.avail_in = CUnsignedInt(buffer.count)
|
||||
}
|
||||
|
||||
@@ -161,7 +169,7 @@ struct ZLibStream: CompressedStream {
|
||||
throw CompressedImageSourceError.outputOverrun
|
||||
}
|
||||
|
||||
stream.next_out = buffer.baseAddress
|
||||
stream.next_out = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self)
|
||||
stream.avail_out = CUnsignedInt(buffer.count)
|
||||
outputBufferSize = UInt(buffer.count)
|
||||
}
|
||||
@@ -211,7 +219,7 @@ struct ZStdStream: CompressedStream {
|
||||
if inBuffer.size == inBuffer.pos {
|
||||
let buffer = try input()
|
||||
|
||||
inBuffer.src = UnsafeRawPointer(buffer.baseAddress)
|
||||
inBuffer.src = buffer.baseAddress
|
||||
inBuffer.size = buffer.count
|
||||
inBuffer.pos = 0
|
||||
}
|
||||
@@ -225,7 +233,7 @@ struct ZStdStream: CompressedStream {
|
||||
throw CompressedImageSourceError.outputOverrun
|
||||
}
|
||||
|
||||
outBuffer.dst = UnsafeMutableRawPointer(buffer.baseAddress)
|
||||
outBuffer.dst = buffer.baseAddress
|
||||
outBuffer.size = buffer.count
|
||||
outBuffer.pos = 0
|
||||
}
|
||||
@@ -280,7 +288,7 @@ struct LZMAStream: CompressedStream {
|
||||
while true {
|
||||
if stream.avail_in == 0 {
|
||||
let buffer = try input()
|
||||
stream.next_in = buffer.baseAddress
|
||||
stream.next_in = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self)
|
||||
stream.avail_in = buffer.count
|
||||
}
|
||||
|
||||
@@ -289,7 +297,7 @@ struct LZMAStream: CompressedStream {
|
||||
throw CompressedImageSourceError.outputOverrun
|
||||
}
|
||||
|
||||
stream.next_out = buffer.baseAddress
|
||||
stream.next_out = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self)
|
||||
stream.avail_out = buffer.count
|
||||
outputBufferSize = UInt(buffer.count)
|
||||
}
|
||||
@@ -310,230 +318,130 @@ struct LZMAStream: CompressedStream {
|
||||
|
||||
// .. Image Sources ............................................................
|
||||
|
||||
fileprivate func decompress<S: CompressedStream, I: ImageSource>(
|
||||
stream: S, source: I, dataBounds: I.Bounds, uncompressedSize: UInt? = nil)
|
||||
throws -> [UInt8] {
|
||||
fileprivate func decompress<S: CompressedStream>(
|
||||
stream: S,
|
||||
source: ImageSource,
|
||||
offset: Int,
|
||||
output: inout ImageSource
|
||||
) throws {
|
||||
let totalBytes = try stream.decompress(
|
||||
input: {
|
||||
() throws -> UnsafeRawBufferPointer in
|
||||
|
||||
var pos = dataBounds.base
|
||||
var remaining = dataBounds.size
|
||||
return UnsafeRawBufferPointer(rebasing: source.bytes[offset...])
|
||||
},
|
||||
output: {
|
||||
(used: UInt, done: Bool) throws -> UnsafeMutableRawBufferPointer? in
|
||||
|
||||
let bufSize = 65536
|
||||
|
||||
if let uncompressedSize = uncompressedSize {
|
||||
// If we know the uncompressed size, we can decompress directly into the
|
||||
// array.
|
||||
|
||||
let inputBuffer
|
||||
= UnsafeMutableBufferPointer<UInt8>.allocate(capacity: bufSize)
|
||||
defer {
|
||||
inputBuffer.deallocate()
|
||||
}
|
||||
|
||||
return try [UInt8].init(unsafeUninitializedCapacity: Int(uncompressedSize)) {
|
||||
(outputBuffer: inout UnsafeMutableBufferPointer<UInt8>,
|
||||
count: inout Int) in
|
||||
|
||||
count = Int(try stream.decompress(
|
||||
input: { () throws -> UnsafeBufferPointer<UInt8> in
|
||||
|
||||
let chunkSize = min(Int(remaining), inputBuffer.count)
|
||||
let slice = inputBuffer[0..<chunkSize]
|
||||
let buffer = UnsafeMutableBufferPointer(rebasing: slice)
|
||||
|
||||
try source.fetch(from: pos, into: buffer)
|
||||
|
||||
pos += I.Address(chunkSize)
|
||||
remaining -= I.Size(chunkSize)
|
||||
|
||||
return UnsafeBufferPointer(buffer)
|
||||
},
|
||||
output: {
|
||||
(used: UInt, done: Bool) throws -> UnsafeMutableBufferPointer<UInt8>? in
|
||||
|
||||
if used == 0 {
|
||||
return outputBuffer
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
} else {
|
||||
// Otherwise, we decompress in chunks and append them to the array.
|
||||
|
||||
let buffer
|
||||
= UnsafeMutableBufferPointer<UInt8>.allocate(capacity: 2 * bufSize)
|
||||
defer {
|
||||
buffer.deallocate()
|
||||
}
|
||||
|
||||
let inputBuffer = UnsafeMutableBufferPointer(rebasing: buffer[0..<bufSize])
|
||||
let outputBuffer = UnsafeMutableBufferPointer(rebasing: buffer[bufSize...])
|
||||
|
||||
var data = [UInt8]()
|
||||
|
||||
_ = try stream.decompress(
|
||||
input: { () throws -> UnsafeBufferPointer<UInt8> in
|
||||
|
||||
let chunkSize = min(Int(remaining), inputBuffer.count)
|
||||
let slice = inputBuffer[0..<chunkSize]
|
||||
let buffer = UnsafeMutableBufferPointer(rebasing: slice)
|
||||
|
||||
try source.fetch(from: pos, into: buffer)
|
||||
|
||||
pos += I.Address(chunkSize)
|
||||
remaining -= I.Size(chunkSize)
|
||||
|
||||
return UnsafeBufferPointer(buffer)
|
||||
},
|
||||
output: {
|
||||
(used: UInt, done: Bool) throws -> UnsafeMutableBufferPointer<UInt8>? in
|
||||
|
||||
data.append(contentsOf: outputBuffer[..<Int(used)])
|
||||
if !done {
|
||||
return outputBuffer
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
if used == 0 {
|
||||
return output.unusedBytes
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
)
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
||||
)
|
||||
output.used(bytes: Int(totalBytes))
|
||||
}
|
||||
|
||||
internal struct ElfCompressedImageSource<Traits: ElfTraits>: ImageSource {
|
||||
fileprivate func decompressChunked<S: CompressedStream>(
|
||||
stream: S,
|
||||
source: ImageSource,
|
||||
offset: Int,
|
||||
output: inout ImageSource
|
||||
) throws {
|
||||
let bufSize = 65536
|
||||
let outputBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: bufSize,
|
||||
alignment: 16)
|
||||
defer {
|
||||
outputBuffer.deallocate()
|
||||
}
|
||||
|
||||
private var data: [UInt8]
|
||||
let _ = try stream.decompress(
|
||||
input: {
|
||||
() throws -> UnsafeRawBufferPointer in
|
||||
|
||||
var isMappedImage: Bool { return false }
|
||||
var path: String? { return nil }
|
||||
var bounds: Bounds? { return Bounds(base: Address(0), size: Size(data.count)) }
|
||||
return UnsafeRawBufferPointer(rebasing: source.bytes[offset...])
|
||||
},
|
||||
output: {
|
||||
(used: UInt, done: Bool) throws -> UnsafeMutableRawBufferPointer? in
|
||||
|
||||
init(source: some ImageSource) throws {
|
||||
guard let bounds = source.bounds else {
|
||||
throw CompressedImageSourceError.unboundedImageSource
|
||||
output.append(
|
||||
bytes: UnsafeRawBufferPointer(rebasing: outputBuffer[..<Int(used)])
|
||||
)
|
||||
if !done {
|
||||
return outputBuffer
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if bounds.size < MemoryLayout<Traits.Chdr>.size {
|
||||
extension ImageSource {
|
||||
@_specialize(kind: full, where Traits == Elf32Traits)
|
||||
@_specialize(kind: full, where Traits == Elf64Traits)
|
||||
init<Traits: ElfTraits>(elfCompressedImageSource source: ImageSource,
|
||||
traits: Traits.Type) throws {
|
||||
if source.bytes.count < MemoryLayout<Traits.Chdr>.size {
|
||||
throw CompressedImageSourceError.badCompressedData
|
||||
}
|
||||
|
||||
let chdr = try source.fetch(from: bounds.base,
|
||||
as: Traits.Chdr.self)
|
||||
let dataBounds = bounds.adjusted(by: MemoryLayout<Traits.Chdr>.stride)
|
||||
let rawChdr = try source.fetch(from: 0, as: Traits.Chdr.self)
|
||||
let chdr: Traits.Chdr
|
||||
switch rawChdr.ch_type {
|
||||
case .ELFCOMPRESS_ZLIB.byteSwapped, .ELFCOMPRESS_ZSTD.byteSwapped:
|
||||
chdr = rawChdr.byteSwapped
|
||||
default:
|
||||
chdr = rawChdr
|
||||
}
|
||||
|
||||
let uncompressedSize = UInt(chdr.ch_size)
|
||||
|
||||
self.init(capacity: Int(uncompressedSize), isMappedImage: false, path: nil)
|
||||
|
||||
switch chdr.ch_type {
|
||||
case .ELFCOMPRESS_ZLIB:
|
||||
data = try decompress(stream: ZLibStream(),
|
||||
source: source, dataBounds: dataBounds,
|
||||
uncompressedSize: uncompressedSize)
|
||||
try decompress(stream: ZLibStream(),
|
||||
source: source, offset: MemoryLayout<Traits.Chdr>.stride,
|
||||
output: &self)
|
||||
case .ELFCOMPRESS_ZSTD:
|
||||
data = try decompress(stream: ZStdStream(),
|
||||
source: source, dataBounds: dataBounds,
|
||||
uncompressedSize: uncompressedSize)
|
||||
try decompress(stream: ZStdStream(),
|
||||
source: source, offset: MemoryLayout<Traits.Chdr>.stride,
|
||||
output: &self)
|
||||
default:
|
||||
throw CompressedImageSourceError.unsupportedFormat
|
||||
}
|
||||
}
|
||||
|
||||
public func fetch(from addr: Address,
|
||||
into buffer: UnsafeMutableRawBufferPointer) throws {
|
||||
let toFetch = buffer.count
|
||||
if addr < 0 || addr > data.count || data.count - Int(addr) < toFetch {
|
||||
throw CompressedImageSourceError.outOfRangeFetch(addr, toFetch)
|
||||
}
|
||||
|
||||
buffer.withMemoryRebound(to: UInt8.self) { outBuf in
|
||||
for n in 0..<toFetch {
|
||||
outBuf[n] = data[Int(addr) + n]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal struct ElfGNUCompressedImageSource: ImageSource {
|
||||
|
||||
private var data: [UInt8]
|
||||
|
||||
var isMappedImage: Bool { return false }
|
||||
var path: String? { return nil }
|
||||
var bounds: Bounds? { return Bounds(base: Address(0), size: Size(data.count)) }
|
||||
|
||||
init(source: some ImageSource) throws {
|
||||
guard let bounds = source.bounds else {
|
||||
throw CompressedImageSourceError.unboundedImageSource
|
||||
}
|
||||
|
||||
if bounds.size < 12 {
|
||||
init(gnuCompressedImageSource source: ImageSource) throws {
|
||||
if source.bytes.count < 12 {
|
||||
throw CompressedImageSourceError.badCompressedData
|
||||
}
|
||||
|
||||
let magic = try source.fetch(from: bounds.base, as: UInt32.self)
|
||||
if magic != 0x42494c5a {
|
||||
throw CompressedImageSourceError.badCompressedData
|
||||
let magic = try source.fetch(from: 0, as: UInt32.self)
|
||||
let rawUncompressedSize = try source.fetch(from: 4, as: UInt64.self)
|
||||
let uncompressedSize: UInt64
|
||||
switch magic {
|
||||
case 0x42494c5a: // BILZ
|
||||
uncompressedSize = rawUncompressedSize.byteSwapped
|
||||
case 0x5a4c4942: // ZLIB
|
||||
uncompressedSize = rawUncompressedSize
|
||||
default:
|
||||
throw CompressedImageSourceError.badCompressedData
|
||||
}
|
||||
|
||||
let uncompressedSize
|
||||
= UInt(try source.fetch(from: bounds.base + 4, as: UInt64.self).byteSwapped)
|
||||
self.init(capacity: Int(uncompressedSize), isMappedImage: false, path: nil)
|
||||
|
||||
data = try decompress(stream: ZLibStream(),
|
||||
source: source,
|
||||
dataBounds: bounds.adjusted(by: 12),
|
||||
uncompressedSize: uncompressedSize)
|
||||
try decompress(stream: ZLibStream(),
|
||||
source: source, offset: 12,
|
||||
output: &self)
|
||||
}
|
||||
|
||||
public func fetch(from addr: Address,
|
||||
into buffer: UnsafeMutableRawBufferPointer) throws {
|
||||
let toFetch = buffer.count
|
||||
if addr < 0 || addr > data.count || data.count - Int(addr) < toFetch {
|
||||
throw CompressedImageSourceError.outOfRangeFetch(addr, toFetch)
|
||||
}
|
||||
init(lzmaCompressedImageSource source: ImageSource) throws {
|
||||
self.init(isMappedImage: false, path: nil)
|
||||
|
||||
buffer.withMemoryRebound(to: UInt8.self) { outBuf in
|
||||
for n in 0..<toFetch {
|
||||
outBuf[n] = data[Int(addr) + n]
|
||||
}
|
||||
}
|
||||
try decompressChunked(stream: LZMAStream(),
|
||||
source: source, offset: 0,
|
||||
output: &self)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal struct LZMACompressedImageSource: ImageSource {
|
||||
|
||||
private var data: [UInt8]
|
||||
|
||||
var isMappedImage: Bool { return false }
|
||||
var path: String? { return nil }
|
||||
var bounds: Bounds? { return Bounds(base: Address(0), size: Size(data.count)) }
|
||||
|
||||
init(source: some ImageSource) throws {
|
||||
// Only supported for bounded image sources
|
||||
guard let bounds = source.bounds else {
|
||||
throw CompressedImageSourceError.unboundedImageSource
|
||||
}
|
||||
|
||||
data = try decompress(stream: LZMAStream(),
|
||||
source: source,
|
||||
dataBounds: bounds)
|
||||
}
|
||||
|
||||
public func fetch(from addr: Address,
|
||||
into buffer: UnsafeMutableRawBufferPointer) throws {
|
||||
let toFetch = buffer.count
|
||||
if addr < 0 || addr > data.count || data.count - Int(addr) < toFetch {
|
||||
throw CompressedImageSourceError.outOfRangeFetch(addr, toFetch)
|
||||
}
|
||||
|
||||
buffer.withMemoryRebound(to: UInt8.self) { outBuf in
|
||||
for n in 0..<toFetch {
|
||||
outBuf[n] = data[Int(addr) + n]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // os(Linux)
|
||||
@@ -15,8 +15,6 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#if os(Linux)
|
||||
|
||||
import Swift
|
||||
|
||||
internal import BacktracingImpl.ImageFormats.Dwarf
|
||||
@@ -500,7 +498,7 @@ enum DwarfSection {
|
||||
|
||||
protocol DwarfSource {
|
||||
|
||||
func getDwarfSection(_ section: DwarfSection) -> (any ImageSource)?
|
||||
func getDwarfSection(_ section: DwarfSection) -> ImageSource?
|
||||
|
||||
}
|
||||
|
||||
@@ -523,14 +521,14 @@ struct DwarfReader<S: DwarfSource> {
|
||||
var attributes: [(Dwarf_Attribute, Dwarf_Form, Int64?)]
|
||||
}
|
||||
|
||||
var infoSection: any ImageSource
|
||||
var abbrevSection: any ImageSource
|
||||
var lineSection: (any ImageSource)?
|
||||
var addrSection: (any ImageSource)?
|
||||
var strSection: (any ImageSource)?
|
||||
var lineStrSection: (any ImageSource)?
|
||||
var strOffsetsSection: (any ImageSource)?
|
||||
var rangesSection: (any ImageSource)?
|
||||
var infoSection: ImageSource
|
||||
var abbrevSection: ImageSource
|
||||
var lineSection: ImageSource?
|
||||
var addrSection: ImageSource?
|
||||
var strSection: ImageSource?
|
||||
var lineStrSection: ImageSource?
|
||||
var strOffsetsSection: ImageSource?
|
||||
var rangesSection: ImageSource?
|
||||
var shouldSwap: Bool
|
||||
|
||||
typealias DwarfAbbrev = UInt64
|
||||
@@ -557,263 +555,9 @@ struct DwarfReader<S: DwarfSource> {
|
||||
var attributes: [Dwarf_Attribute:DwarfValue] = [:]
|
||||
}
|
||||
|
||||
struct FileInfo {
|
||||
var path: String
|
||||
var directoryIndex: Int?
|
||||
var timestamp: Int?
|
||||
var size: UInt64?
|
||||
var md5sum: [UInt8]?
|
||||
}
|
||||
|
||||
struct LineNumberState: CustomStringConvertible {
|
||||
var address: Address
|
||||
var opIndex: UInt
|
||||
var file: Int
|
||||
var path: String
|
||||
var line: Int
|
||||
var column: Int
|
||||
var isStmt: Bool
|
||||
var basicBlock: Bool
|
||||
var endSequence: Bool
|
||||
var prologueEnd: Bool
|
||||
var epilogueBegin: Bool
|
||||
var isa: UInt
|
||||
var discriminator: UInt
|
||||
|
||||
var description: String {
|
||||
var flags: [String] = []
|
||||
if isStmt {
|
||||
flags.append("is_stmt")
|
||||
}
|
||||
if basicBlock {
|
||||
flags.append("basic_block")
|
||||
}
|
||||
if endSequence {
|
||||
flags.append("end_sequence")
|
||||
}
|
||||
if prologueEnd {
|
||||
flags.append("prologue_end")
|
||||
}
|
||||
if epilogueBegin {
|
||||
flags.append("epilogue_begin")
|
||||
}
|
||||
|
||||
let flagsString = flags.joined(separator:" ")
|
||||
|
||||
return """
|
||||
\(hex(address)) \(pad(line, 6)) \(pad(column, 6)) \(pad(file, 6)) \
|
||||
\(pad(isa, 3)) \(pad(discriminator, 13)) \(flagsString)
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
struct LineNumberInfo {
|
||||
var baseOffset: Address
|
||||
var version: Int
|
||||
var addressSize: Int?
|
||||
var selectorSize: Int?
|
||||
var headerLength: UInt64
|
||||
var minimumInstructionLength: UInt
|
||||
var maximumOpsPerInstruction: UInt
|
||||
var defaultIsStmt: Bool
|
||||
var lineBase: Int8
|
||||
var lineRange: UInt8
|
||||
var opcodeBase: UInt8
|
||||
var standardOpcodeLengths: [UInt64]
|
||||
var directories: [String] = []
|
||||
var files: [FileInfo] = []
|
||||
var program: [UInt8] = []
|
||||
var shouldSwap: Bool
|
||||
|
||||
/// Compute the full path for a file, given its index in the file table.
|
||||
func fullPathForFile(index: Int) -> String {
|
||||
if index >= files.count {
|
||||
return "<unknown>"
|
||||
}
|
||||
|
||||
let info = files[index]
|
||||
if info.path.hasPrefix("/") {
|
||||
return info.path
|
||||
}
|
||||
|
||||
let dirName: String
|
||||
if let dirIndex = info.directoryIndex,
|
||||
dirIndex < directories.count {
|
||||
dirName = directories[dirIndex]
|
||||
} else {
|
||||
dirName = "<unknown>"
|
||||
}
|
||||
|
||||
return "\(dirName)/\(info.path)"
|
||||
}
|
||||
|
||||
/// Execute the line number program, calling a closure for every line
|
||||
/// table entry.
|
||||
mutating func executeProgram(
|
||||
line: (LineNumberState, inout Bool) -> ()
|
||||
) throws {
|
||||
let source = ArrayImageSource(array: program)
|
||||
let bounds = source.bounds!
|
||||
var cursor = ImageSourceCursor(source: source)
|
||||
|
||||
func maybeSwap<T: FixedWidthInteger>(_ x: T) -> T {
|
||||
if shouldSwap {
|
||||
return x.byteSwapped
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Table 6.4: Line number program initial state
|
||||
let initialState = LineNumberState(
|
||||
address: 0,
|
||||
opIndex: 0,
|
||||
file: 1,
|
||||
path: fullPathForFile(index: 1),
|
||||
line: 1,
|
||||
column: 0,
|
||||
isStmt: defaultIsStmt,
|
||||
basicBlock: false,
|
||||
endSequence: false,
|
||||
prologueEnd: false,
|
||||
epilogueBegin: false,
|
||||
isa: 0,
|
||||
discriminator: 0
|
||||
)
|
||||
|
||||
var state = initialState
|
||||
|
||||
// Flag to allow fast exit
|
||||
var done = false
|
||||
|
||||
while !done && cursor.pos < bounds.end {
|
||||
let opcode = try cursor.read(as: Dwarf_LNS_Opcode.self)
|
||||
|
||||
if opcode.rawValue >= opcodeBase {
|
||||
// Special opcode
|
||||
let adjustedOpcode = UInt(opcode.rawValue - opcodeBase)
|
||||
let advance = adjustedOpcode / UInt(lineRange)
|
||||
let lineAdvance = adjustedOpcode % UInt(lineRange)
|
||||
let instrAdvance
|
||||
= (state.opIndex + advance) / maximumOpsPerInstruction
|
||||
let newOp = (state.opIndex + advance) % maximumOpsPerInstruction
|
||||
state.address += Address(instrAdvance)
|
||||
state.opIndex = newOp
|
||||
state.line += Int(lineBase) + Int(lineAdvance)
|
||||
|
||||
line(state, &done)
|
||||
|
||||
state.discriminator = 0
|
||||
state.basicBlock = false
|
||||
state.prologueEnd = false
|
||||
state.epilogueBegin = false
|
||||
} else if opcode == .DW_LNS_extended {
|
||||
// Extended opcode
|
||||
let length = try cursor.readULEB128()
|
||||
let opcode = try cursor.read(as: Dwarf_LNE_Opcode.self)
|
||||
|
||||
switch opcode {
|
||||
case .DW_LNE_end_sequence:
|
||||
state.endSequence = true
|
||||
line(state, &done)
|
||||
state = initialState
|
||||
case .DW_LNE_set_address:
|
||||
let address: UInt64
|
||||
guard let addressSize = addressSize else {
|
||||
throw DwarfError.unspecifiedAddressSize
|
||||
}
|
||||
switch addressSize {
|
||||
case 4:
|
||||
address = UInt64(maybeSwap(try cursor.read(as: UInt32.self)))
|
||||
case 8:
|
||||
address = maybeSwap(try cursor.read(as: UInt64.self))
|
||||
default:
|
||||
throw DwarfError.badAddressSize(addressSize)
|
||||
}
|
||||
state.address = Address(address)
|
||||
case .DW_LNE_define_file:
|
||||
guard let path = try cursor.readString() else {
|
||||
throw DwarfError.badString
|
||||
}
|
||||
let directoryIndex = try cursor.readULEB128()
|
||||
let timestamp = try cursor.readULEB128()
|
||||
let size = try cursor.readULEB128()
|
||||
files.append(FileInfo(
|
||||
path: path,
|
||||
directoryIndex: Int(directoryIndex),
|
||||
timestamp: timestamp != 0 ? Int(timestamp) : nil,
|
||||
size: size != 0 ? size : nil,
|
||||
md5sum: nil
|
||||
))
|
||||
case .DW_LNE_set_discriminator:
|
||||
let discriminator = try cursor.readULEB128()
|
||||
state.discriminator = UInt(discriminator)
|
||||
default:
|
||||
cursor.pos += length - 1
|
||||
}
|
||||
} else {
|
||||
// Standard opcode
|
||||
switch opcode {
|
||||
case .DW_LNS_copy:
|
||||
line(state, &done)
|
||||
state.discriminator = 0
|
||||
state.basicBlock = false
|
||||
state.prologueEnd = false
|
||||
state.epilogueBegin = false
|
||||
case .DW_LNS_advance_pc:
|
||||
let advance = UInt(try cursor.readULEB128())
|
||||
let instrAdvance
|
||||
= (state.opIndex + advance) / maximumOpsPerInstruction
|
||||
let newOp = (state.opIndex + advance) % maximumOpsPerInstruction
|
||||
state.address += Address(instrAdvance)
|
||||
state.opIndex = newOp
|
||||
case .DW_LNS_advance_line:
|
||||
let advance = try cursor.readSLEB128()
|
||||
state.line += Int(advance)
|
||||
case .DW_LNS_set_file:
|
||||
let file = Int(try cursor.readULEB128())
|
||||
state.file = file
|
||||
state.path = fullPathForFile(index: state.file)
|
||||
case .DW_LNS_set_column:
|
||||
let column = Int(try cursor.readULEB128())
|
||||
state.column = column
|
||||
case .DW_LNS_negate_stmt:
|
||||
state.isStmt = !state.isStmt
|
||||
case .DW_LNS_set_basic_block:
|
||||
state.basicBlock = true
|
||||
case .DW_LNS_const_add_pc:
|
||||
let adjustedOpcode = UInt(255 - opcodeBase)
|
||||
let advance = adjustedOpcode / UInt(lineRange)
|
||||
let instrAdvance
|
||||
= (state.opIndex + advance) / maximumOpsPerInstruction
|
||||
let newOp = (state.opIndex + advance) % maximumOpsPerInstruction
|
||||
state.address += Address(instrAdvance)
|
||||
state.opIndex = newOp
|
||||
case .DW_LNS_fixed_advance_pc:
|
||||
let advance = try cursor.read(as: Dwarf_Half.self)
|
||||
state.address += Address(advance)
|
||||
state.opIndex = 0
|
||||
case .DW_LNS_set_prologue_end:
|
||||
state.prologueEnd = true
|
||||
case .DW_LNS_set_epilogue_begin:
|
||||
state.epilogueBegin = true
|
||||
case .DW_LNS_set_isa:
|
||||
let isa = UInt(try cursor.readULEB128())
|
||||
state.isa = isa
|
||||
default:
|
||||
// Skip this unknown opcode
|
||||
let length = standardOpcodeLengths[Int(opcode.rawValue)]
|
||||
for _ in 0..<length {
|
||||
_ = try cursor.readULEB128()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var units: [Unit] = []
|
||||
|
||||
var lineNumberInfo: [LineNumberInfo] = []
|
||||
var lineNumberInfo: [DwarfLineNumberInfo] = []
|
||||
|
||||
struct RangeListInfo {
|
||||
var length: UInt64
|
||||
@@ -827,6 +571,8 @@ struct DwarfReader<S: DwarfSource> {
|
||||
|
||||
var rangeListInfo: RangeListInfo?
|
||||
|
||||
@_specialize(kind: full, where S == Elf32Image)
|
||||
@_specialize(kind: full, where S == Elf64Image)
|
||||
init(source: Source, shouldSwap: Bool = false) throws {
|
||||
// ###TODO: This should be optional, because we can have just line number
|
||||
// information. We should test that, too.
|
||||
@@ -873,7 +619,7 @@ struct DwarfReader<S: DwarfSource> {
|
||||
}
|
||||
|
||||
lineNumberInfo[n].directories[0] = dirname
|
||||
lineNumberInfo[n].files[0] = FileInfo(
|
||||
lineNumberInfo[n].files[0] = DwarfFileInfo(
|
||||
path: filename,
|
||||
directoryIndex: 0,
|
||||
timestamp: nil,
|
||||
@@ -896,14 +642,11 @@ struct DwarfReader<S: DwarfSource> {
|
||||
}
|
||||
|
||||
private func readUnits() throws -> [Unit] {
|
||||
guard let bounds = infoSection.bounds else {
|
||||
return []
|
||||
}
|
||||
|
||||
let end = infoSection.bytes.count
|
||||
var units: [Unit] = []
|
||||
var cursor = ImageSourceCursor(source: infoSection)
|
||||
|
||||
while cursor.pos < bounds.end {
|
||||
while cursor.pos < end {
|
||||
// See 7.5.1.1 Full and Partial Compilation Unit Headers
|
||||
let base = cursor.pos
|
||||
|
||||
@@ -1053,16 +796,16 @@ struct DwarfReader<S: DwarfSource> {
|
||||
return units
|
||||
}
|
||||
|
||||
private func readLineNumberInfo() throws -> [LineNumberInfo] {
|
||||
guard let lineSection = lineSection,
|
||||
let bounds = lineSection.bounds else {
|
||||
private func readLineNumberInfo() throws -> [DwarfLineNumberInfo] {
|
||||
guard let lineSection = lineSection else {
|
||||
return []
|
||||
}
|
||||
|
||||
var result: [LineNumberInfo] = []
|
||||
let end = lineSection.bytes.count
|
||||
var result: [DwarfLineNumberInfo] = []
|
||||
var cursor = ImageSourceCursor(source: lineSection, offset: 0)
|
||||
|
||||
while cursor.pos < bounds.end {
|
||||
while cursor.pos < end {
|
||||
// 6.2.4 The Line Number Program Header
|
||||
|
||||
// .1 unit_length
|
||||
@@ -1127,7 +870,7 @@ struct DwarfReader<S: DwarfSource> {
|
||||
}
|
||||
|
||||
var dirNames: [String] = []
|
||||
var fileInfo: [FileInfo] = []
|
||||
var fileInfo: [DwarfFileInfo] = []
|
||||
|
||||
if version == 3 || version == 4 {
|
||||
// .11 include_directories
|
||||
@@ -1152,7 +895,7 @@ struct DwarfReader<S: DwarfSource> {
|
||||
|
||||
// Prior to version 5, the compilation unit's filename is not included;
|
||||
// put a placeholder here for now, which we'll fix up later.
|
||||
fileInfo.append(FileInfo(
|
||||
fileInfo.append(DwarfFileInfo(
|
||||
path: "<unknown>",
|
||||
directoryIndex: 0,
|
||||
timestamp: nil,
|
||||
@@ -1172,7 +915,7 @@ struct DwarfReader<S: DwarfSource> {
|
||||
let timestamp = try cursor.readULEB128()
|
||||
let size = try cursor.readULEB128()
|
||||
|
||||
fileInfo.append(FileInfo(
|
||||
fileInfo.append(DwarfFileInfo(
|
||||
path: path,
|
||||
directoryIndex: Int(dirIndex),
|
||||
timestamp: timestamp != 0 ? Int(timestamp) : nil,
|
||||
@@ -1275,7 +1018,7 @@ struct DwarfReader<S: DwarfSource> {
|
||||
md5sum = nil
|
||||
}
|
||||
|
||||
fileInfo.append(FileInfo(
|
||||
fileInfo.append(DwarfFileInfo(
|
||||
path: path,
|
||||
directoryIndex: dirIndex,
|
||||
timestamp: timestamp,
|
||||
@@ -1285,12 +1028,10 @@ struct DwarfReader<S: DwarfSource> {
|
||||
}
|
||||
|
||||
// The actual program comes next
|
||||
let program = try cursor.read(count: Int(nextOffset - cursor.pos),
|
||||
as: UInt8.self)
|
||||
|
||||
let program = cursor.source[cursor.pos..<nextOffset]
|
||||
cursor.pos = nextOffset
|
||||
|
||||
result.append(LineNumberInfo(
|
||||
result.append(DwarfLineNumberInfo(
|
||||
baseOffset: baseOffset,
|
||||
version: version,
|
||||
addressSize: addressSize,
|
||||
@@ -1829,7 +1570,7 @@ struct DwarfReader<S: DwarfSource> {
|
||||
if tag != .DW_TAG_subprogram {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
refAttrs = try readDieAttributes(
|
||||
at: &cursor,
|
||||
unit: unit,
|
||||
@@ -2005,11 +1746,268 @@ struct DwarfReader<S: DwarfSource> {
|
||||
|
||||
}
|
||||
|
||||
struct DwarfFileInfo {
|
||||
var path: String
|
||||
var directoryIndex: Int?
|
||||
var timestamp: Int?
|
||||
var size: UInt64?
|
||||
var md5sum: [UInt8]?
|
||||
}
|
||||
|
||||
struct DwarfLineNumberState: CustomStringConvertible {
|
||||
typealias Address = UInt64
|
||||
|
||||
var address: Address
|
||||
var opIndex: UInt
|
||||
var file: Int
|
||||
var path: String
|
||||
var line: Int
|
||||
var column: Int
|
||||
var isStmt: Bool
|
||||
var basicBlock: Bool
|
||||
var endSequence: Bool
|
||||
var prologueEnd: Bool
|
||||
var epilogueBegin: Bool
|
||||
var isa: UInt
|
||||
var discriminator: UInt
|
||||
|
||||
var description: String {
|
||||
var flags: [String] = []
|
||||
if isStmt {
|
||||
flags.append("is_stmt")
|
||||
}
|
||||
if basicBlock {
|
||||
flags.append("basic_block")
|
||||
}
|
||||
if endSequence {
|
||||
flags.append("end_sequence")
|
||||
}
|
||||
if prologueEnd {
|
||||
flags.append("prologue_end")
|
||||
}
|
||||
if epilogueBegin {
|
||||
flags.append("epilogue_begin")
|
||||
}
|
||||
|
||||
let flagsString = flags.joined(separator:" ")
|
||||
|
||||
return """
|
||||
\(hex(address)) \(pad(line, 6)) \(pad(column, 6)) \(pad(file, 6)) \
|
||||
\(pad(isa, 3)) \(pad(discriminator, 13)) \(flagsString)
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
struct DwarfLineNumberInfo {
|
||||
typealias Address = UInt64
|
||||
|
||||
var baseOffset: Address
|
||||
var version: Int
|
||||
var addressSize: Int?
|
||||
var selectorSize: Int?
|
||||
var headerLength: UInt64
|
||||
var minimumInstructionLength: UInt
|
||||
var maximumOpsPerInstruction: UInt
|
||||
var defaultIsStmt: Bool
|
||||
var lineBase: Int8
|
||||
var lineRange: UInt8
|
||||
var opcodeBase: UInt8
|
||||
var standardOpcodeLengths: [UInt64]
|
||||
var directories: [String] = []
|
||||
var files: [DwarfFileInfo] = []
|
||||
var program: ImageSource
|
||||
var shouldSwap: Bool
|
||||
|
||||
/// Compute the full path for a file, given its index in the file table.
|
||||
func fullPathForFile(index: Int) -> String {
|
||||
if index >= files.count {
|
||||
return "<unknown>"
|
||||
}
|
||||
|
||||
let info = files[index]
|
||||
if info.path.hasPrefix("/") {
|
||||
return info.path
|
||||
}
|
||||
|
||||
let dirName: String
|
||||
if let dirIndex = info.directoryIndex,
|
||||
dirIndex < directories.count {
|
||||
dirName = directories[dirIndex]
|
||||
} else {
|
||||
dirName = "<unknown>"
|
||||
}
|
||||
|
||||
return "\(dirName)/\(info.path)"
|
||||
}
|
||||
|
||||
/// Execute the line number program, calling a closure for every line
|
||||
/// table entry.
|
||||
mutating func executeProgram(
|
||||
line: (DwarfLineNumberState, inout Bool) -> ()
|
||||
) throws {
|
||||
let end = program.bytes.count
|
||||
var cursor = ImageSourceCursor(source: program)
|
||||
|
||||
func maybeSwap<T: FixedWidthInteger>(_ x: T) -> T {
|
||||
if shouldSwap {
|
||||
return x.byteSwapped
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Table 6.4: Line number program initial state
|
||||
let initialState = DwarfLineNumberState(
|
||||
address: 0,
|
||||
opIndex: 0,
|
||||
file: 1,
|
||||
path: fullPathForFile(index: 1),
|
||||
line: 1,
|
||||
column: 0,
|
||||
isStmt: defaultIsStmt,
|
||||
basicBlock: false,
|
||||
endSequence: false,
|
||||
prologueEnd: false,
|
||||
epilogueBegin: false,
|
||||
isa: 0,
|
||||
discriminator: 0
|
||||
)
|
||||
|
||||
var state = initialState
|
||||
|
||||
// Flag to allow fast exit
|
||||
var done = false
|
||||
|
||||
while !done && cursor.pos < end {
|
||||
let opcode = try cursor.read(as: Dwarf_LNS_Opcode.self)
|
||||
|
||||
if opcode.rawValue >= opcodeBase {
|
||||
// Special opcode
|
||||
let adjustedOpcode = UInt(opcode.rawValue - opcodeBase)
|
||||
let advance = adjustedOpcode / UInt(lineRange)
|
||||
let lineAdvance = adjustedOpcode % UInt(lineRange)
|
||||
let instrAdvance
|
||||
= (state.opIndex + advance) / maximumOpsPerInstruction
|
||||
let newOp = (state.opIndex + advance) % maximumOpsPerInstruction
|
||||
state.address += Address(instrAdvance)
|
||||
state.opIndex = newOp
|
||||
state.line += Int(lineBase) + Int(lineAdvance)
|
||||
|
||||
line(state, &done)
|
||||
|
||||
state.discriminator = 0
|
||||
state.basicBlock = false
|
||||
state.prologueEnd = false
|
||||
state.epilogueBegin = false
|
||||
} else if opcode == .DW_LNS_extended {
|
||||
// Extended opcode
|
||||
let length = try cursor.readULEB128()
|
||||
let opcode = try cursor.read(as: Dwarf_LNE_Opcode.self)
|
||||
|
||||
switch opcode {
|
||||
case .DW_LNE_end_sequence:
|
||||
state.endSequence = true
|
||||
line(state, &done)
|
||||
state = initialState
|
||||
case .DW_LNE_set_address:
|
||||
let address: UInt64
|
||||
guard let addressSize = addressSize else {
|
||||
throw DwarfError.unspecifiedAddressSize
|
||||
}
|
||||
switch addressSize {
|
||||
case 4:
|
||||
address = UInt64(maybeSwap(try cursor.read(as: UInt32.self)))
|
||||
case 8:
|
||||
address = maybeSwap(try cursor.read(as: UInt64.self))
|
||||
default:
|
||||
throw DwarfError.badAddressSize(addressSize)
|
||||
}
|
||||
state.address = Address(address)
|
||||
case .DW_LNE_define_file:
|
||||
guard let path = try cursor.readString() else {
|
||||
throw DwarfError.badString
|
||||
}
|
||||
let directoryIndex = try cursor.readULEB128()
|
||||
let timestamp = try cursor.readULEB128()
|
||||
let size = try cursor.readULEB128()
|
||||
files.append(DwarfFileInfo(
|
||||
path: path,
|
||||
directoryIndex: Int(directoryIndex),
|
||||
timestamp: timestamp != 0 ? Int(timestamp) : nil,
|
||||
size: size != 0 ? size : nil,
|
||||
md5sum: nil
|
||||
))
|
||||
case .DW_LNE_set_discriminator:
|
||||
let discriminator = try cursor.readULEB128()
|
||||
state.discriminator = UInt(discriminator)
|
||||
default:
|
||||
cursor.pos += length - 1
|
||||
}
|
||||
} else {
|
||||
// Standard opcode
|
||||
switch opcode {
|
||||
case .DW_LNS_copy:
|
||||
line(state, &done)
|
||||
state.discriminator = 0
|
||||
state.basicBlock = false
|
||||
state.prologueEnd = false
|
||||
state.epilogueBegin = false
|
||||
case .DW_LNS_advance_pc:
|
||||
let advance = UInt(try cursor.readULEB128())
|
||||
let instrAdvance
|
||||
= (state.opIndex + advance) / maximumOpsPerInstruction
|
||||
let newOp = (state.opIndex + advance) % maximumOpsPerInstruction
|
||||
state.address += Address(instrAdvance)
|
||||
state.opIndex = newOp
|
||||
case .DW_LNS_advance_line:
|
||||
let advance = try cursor.readSLEB128()
|
||||
state.line += Int(advance)
|
||||
case .DW_LNS_set_file:
|
||||
let file = Int(try cursor.readULEB128())
|
||||
state.file = file
|
||||
state.path = fullPathForFile(index: state.file)
|
||||
case .DW_LNS_set_column:
|
||||
let column = Int(try cursor.readULEB128())
|
||||
state.column = column
|
||||
case .DW_LNS_negate_stmt:
|
||||
state.isStmt = !state.isStmt
|
||||
case .DW_LNS_set_basic_block:
|
||||
state.basicBlock = true
|
||||
case .DW_LNS_const_add_pc:
|
||||
let adjustedOpcode = UInt(255 - opcodeBase)
|
||||
let advance = adjustedOpcode / UInt(lineRange)
|
||||
let instrAdvance
|
||||
= (state.opIndex + advance) / maximumOpsPerInstruction
|
||||
let newOp = (state.opIndex + advance) % maximumOpsPerInstruction
|
||||
state.address += Address(instrAdvance)
|
||||
state.opIndex = newOp
|
||||
case .DW_LNS_fixed_advance_pc:
|
||||
let advance = try cursor.read(as: Dwarf_Half.self)
|
||||
state.address += Address(advance)
|
||||
state.opIndex = 0
|
||||
case .DW_LNS_set_prologue_end:
|
||||
state.prologueEnd = true
|
||||
case .DW_LNS_set_epilogue_begin:
|
||||
state.epilogueBegin = true
|
||||
case .DW_LNS_set_isa:
|
||||
let isa = UInt(try cursor.readULEB128())
|
||||
state.isa = isa
|
||||
default:
|
||||
// Skip this unknown opcode
|
||||
let length = standardOpcodeLengths[Int(opcode.rawValue)]
|
||||
for _ in 0..<length {
|
||||
_ = try cursor.readULEB128()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .. Testing ..................................................................
|
||||
|
||||
@_spi(DwarfTest)
|
||||
public func testDwarfReaderFor(path: String) -> Bool {
|
||||
guard let source = try? FileImageSource(path: path) else {
|
||||
guard let source = try? ImageSource(path: path) else {
|
||||
print("\(path) was not accessible")
|
||||
return false
|
||||
}
|
||||
@@ -2017,7 +2015,7 @@ public func testDwarfReaderFor(path: String) -> Bool {
|
||||
if let elfImage = try? Elf32Image(source: source) {
|
||||
print("\(path) is a 32-bit ELF image")
|
||||
|
||||
var reader: DwarfReader<Elf32Image<FileImageSource>>
|
||||
var reader: DwarfReader<Elf32Image>
|
||||
do {
|
||||
reader = try DwarfReader(source: elfImage)
|
||||
} catch {
|
||||
@@ -2034,7 +2032,7 @@ public func testDwarfReaderFor(path: String) -> Bool {
|
||||
} else if let elfImage = try? Elf64Image(source: source) {
|
||||
print("\(path) is a 64-bit ELF image")
|
||||
|
||||
var reader: DwarfReader<Elf64Image<FileImageSource>>
|
||||
var reader: DwarfReader<Elf64Image>
|
||||
do {
|
||||
reader = try DwarfReader(source: elfImage)
|
||||
} catch {
|
||||
@@ -2060,5 +2058,3 @@ public func testDwarfReaderFor(path: String) -> Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
#endif // os(Linux)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
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 {
|
||||
return nil
|
||||
}
|
||||
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,13 +30,16 @@ 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]?
|
||||
#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]?,
|
||||
memoryReader: MemoryReader) {
|
||||
@@ -73,42 +76,51 @@ 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 }
|
||||
where: { address >= MemoryReader.Address($0.baseAddress)!
|
||||
&& address < MemoryReader.Address($0.endOfText)! }
|
||||
) {
|
||||
let relativeAddress = address - FileImageSource.Address(images[imageNdx].baseAddress)
|
||||
var elf32Image = elf32Cache[imageNdx]
|
||||
var elf64Image = elf64Cache[imageNdx]
|
||||
let base = MemoryReader.Address(images[imageNdx].baseAddress)!
|
||||
let relativeAddress = address - base
|
||||
let cache = ElfImageCache.threadLocal
|
||||
|
||||
if elf32Image == nil && elf64Image == nil {
|
||||
if let source = try? FileImageSource(path: images[imageNdx].path) {
|
||||
if let elfImage = try? Elf32Image(source: source) {
|
||||
elf32Image = elfImage
|
||||
elf32Cache[imageNdx] = elfImage
|
||||
} else if let elfImage = try? Elf64Image(source: source) {
|
||||
elf64Image = elfImage
|
||||
elf64Cache[imageNdx] = elfImage
|
||||
}
|
||||
if let hit = cache.lookup(path: images[imageNdx].path) {
|
||||
switch hit {
|
||||
case let .elf32Image(image):
|
||||
if let theSymbol = image.lookupSymbol(
|
||||
address: Elf32Image.Traits.Address(relativeAddress)
|
||||
) {
|
||||
return isAsyncSymbol(theSymbol.name)
|
||||
}
|
||||
case let .elf64Image(image):
|
||||
if let theSymbol = image.lookupSymbol(
|
||||
address: Elf64Image.Traits.Address(relativeAddress)) {
|
||||
return isAsyncSymbol(theSymbol.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let theSymbol = elf32Image?.lookupSymbol(address: relativeAddress) {
|
||||
return isAsyncSymbol(theSymbol.name)
|
||||
} else if let theSymbol = elf64Image?.lookupSymbol(address: relativeAddress) {
|
||||
return isAsyncSymbol(theSymbol.name)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@_specialize(exported: true, kind: full, where C == HostContext, M == UnsafeLocalMemoryReader)
|
||||
@_specialize(exported: true, kind: full, where C == HostContext, M == RemoteMemoryReader)
|
||||
#if os(Linux)
|
||||
@_specialize(exported: true, kind: full, where C == HostContext, M == MemserverMemoryReader)
|
||||
#endif
|
||||
private func isAsyncFrame(_ storedFp: Address) -> Bool {
|
||||
#if (os(macOS) || os(iOS) || os(watchOS)) && (arch(arm64) || arch(arm64_32) || arch(x86_64))
|
||||
// On Darwin, we borrow a bit of the frame pointer to indicate async
|
||||
@@ -119,15 +131,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 +157,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 +170,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 +180,20 @@ public struct FramePointerUnwinder<C: Context, M: MemoryReader>: Sequence, Itera
|
||||
let strippedFp = stripPtrAuth(fp)
|
||||
|
||||
if strippedFp == 0
|
||||
|| !Context.isAlignedForStack(framePointer:
|
||||
Context.Address(strippedFp)) {
|
||||
|| !Context.isAlignedForStack(framePointer:strippedFp) {
|
||||
done = true
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
pc = stripPtrAuth(try reader.fetch(from:
|
||||
strippedFp + Address(MemoryLayout<Address>.size),
|
||||
as: Address.self))
|
||||
next = try reader.fetch(from: Address(strippedFp), as: Address.self)
|
||||
pc = stripPtrAuth(try reader.fetch(
|
||||
from:MemoryReader.Address(
|
||||
strippedFp
|
||||
+ Address(MemoryLayout<Address>.size)
|
||||
),
|
||||
as: Address.self))
|
||||
next = try reader.fetch(from: MemoryReader.Address(strippedFp),
|
||||
as: Address.self)
|
||||
} catch {
|
||||
done = true
|
||||
return nil
|
||||
@@ -176,7 +206,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 +232,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 +247,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
|
||||
@@ -226,6 +260,6 @@ public struct FramePointerUnwinder<C: Context, M: MemoryReader>: Sequence, Itera
|
||||
|
||||
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 }
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
231
stdlib/public/RuntimeModule/LimitSequence.swift
Normal file
231
stdlib/public/RuntimeModule/LimitSequence.swift
Normal file
@@ -0,0 +1,231 @@
|
||||
//===--- 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.
|
||||
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.
|
||||
struct LimitSequence<T: LimitableElement, S: Sequence>: Sequence
|
||||
where S.Element == T
|
||||
{
|
||||
/// The element type, which must conform to `LimitableElement`
|
||||
typealias Element = T
|
||||
|
||||
/// The source sequence
|
||||
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.
|
||||
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.
|
||||
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() {
|
||||
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 {
|
||||
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
|
||||
@@ -250,20 +245,12 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
/// The width, in bits, of an address in this backtrace.
|
||||
public var addressWidth: Int {
|
||||
return backtrace.addressWidth
|
||||
}
|
||||
|
||||
/// A list of captured frame information.
|
||||
public var frames: [Frame]
|
||||
|
||||
/// A list of images found in the process.
|
||||
public var images: [Backtrace.Image]
|
||||
|
||||
/// Shared cache information.
|
||||
public var sharedCacheInfo: Backtrace.SharedCacheInfo?
|
||||
|
||||
/// True if this backtrace is a Swift runtime failure.
|
||||
public var isSwiftRuntimeFailure: Bool {
|
||||
guard let frame = frames.first else { return false }
|
||||
@@ -284,11 +271,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]) {
|
||||
self.backtrace = backtrace
|
||||
self.images = images
|
||||
self.sharedCacheInfo = sharedCacheInfo
|
||||
self.frames = frames
|
||||
}
|
||||
|
||||
@@ -307,20 +292,19 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
|
||||
|
||||
/// Create a symbolicator.
|
||||
private static func withSymbolicator<T>(images: [Backtrace.Image],
|
||||
sharedCacheInfo: Backtrace.SharedCacheInfo?,
|
||||
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"
|
||||
)
|
||||
],
|
||||
@@ -375,9 +359,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 +369,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
|
||||
|
||||
@@ -396,10 +380,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
|
||||
/// Actually symbolicate.
|
||||
internal static func symbolicate(backtrace: Backtrace,
|
||||
images: [Backtrace.Image]?,
|
||||
sharedCacheInfo: Backtrace.SharedCacheInfo?,
|
||||
showInlineFrames: Bool,
|
||||
showSourceLocations: Bool,
|
||||
useSymbolCache: Bool)
|
||||
options: Backtrace.SymbolicationOptions)
|
||||
-> SymbolicatedBacktrace? {
|
||||
|
||||
let theImages: [Backtrace.Image]
|
||||
@@ -411,27 +392,18 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
|
||||
theImages = Backtrace.captureImages()
|
||||
}
|
||||
|
||||
let theCacheInfo: Backtrace.SharedCacheInfo?
|
||||
if let sharedCacheInfo = sharedCacheInfo {
|
||||
theCacheInfo = sharedCacheInfo
|
||||
} else if let sharedCacheInfo = backtrace.sharedCacheInfo {
|
||||
theCacheInfo = sharedCacheInfo
|
||||
} else {
|
||||
theCacheInfo = Backtrace.captureSharedCacheInfo()
|
||||
}
|
||||
|
||||
var frames: [Frame] = []
|
||||
|
||||
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
|
||||
withSymbolicator(images: theImages,
|
||||
sharedCacheInfo: theCacheInfo,
|
||||
useSymbolCache: useSymbolCache) { symbolicator in
|
||||
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 +411,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 +430,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
|
||||
|
||||
first = false
|
||||
}
|
||||
} else if showSourceLocations {
|
||||
} else if options.contains(.showSourceLocations) {
|
||||
let symbol = CSSymbolOwnerGetSymbolWithAddress(owner, address)
|
||||
let sourceInfo = CSSymbolOwnerGetSourceInfoWithAddress(owner,
|
||||
address)
|
||||
@@ -483,108 +455,92 @@ 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)
|
||||
let address = frame.adjustedProgramCounter
|
||||
if let imageNdx = theImages.firstIndex(
|
||||
where: { address >= $0.baseAddress
|
||||
&& address < $0.endOfText }
|
||||
where: { address >= $0.baseAddress && address < $0.endOfText }
|
||||
) {
|
||||
let relativeAddress = address - FileImageSource.Address(theImages[imageNdx].baseAddress)
|
||||
let relativeAddress = ImageSource.Address(
|
||||
address - theImages[imageNdx].baseAddress
|
||||
)
|
||||
let name = theImages[imageNdx].name ?? "<unknown>"
|
||||
var symbol: Symbol = Symbol(imageIndex: imageNdx,
|
||||
imageName: theImages[imageNdx].name,
|
||||
imageName: name,
|
||||
rawName: "<unknown>",
|
||||
offset: 0,
|
||||
sourceLocation: nil)
|
||||
var elf32Image = elf32Cache[imageNdx]
|
||||
var elf64Image = elf64Cache[imageNdx]
|
||||
|
||||
if elf32Image == nil && elf64Image == nil {
|
||||
if let source = try? FileImageSource(path: theImages[imageNdx].path) {
|
||||
if let elfImage = try? Elf32Image(source: source) {
|
||||
elf32Image = elfImage
|
||||
elf32Cache[imageNdx] = elfImage
|
||||
} else if let elfImage = try? Elf64Image(source: source) {
|
||||
elf64Image = elfImage
|
||||
elf64Cache[imageNdx] = elfImage
|
||||
func lookupSymbol<ElfImage: ElfSymbolLookupProtocol>(
|
||||
image: ElfImage?,
|
||||
at imageNdx: Int,
|
||||
named name: String,
|
||||
address imageAddr: ImageSource.Address
|
||||
) -> Symbol? {
|
||||
let address = ElfImage.Traits.Address(imageAddr)
|
||||
|
||||
guard let image = image else {
|
||||
return nil
|
||||
}
|
||||
guard let theSymbol = image.lookupSymbol(address: address) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var location: SourceLocation?
|
||||
|
||||
if options.contains(.showSourceLocations)
|
||||
|| options.contains(.showInlineFrames) {
|
||||
location = try? image.sourceLocation(for: address)
|
||||
} else {
|
||||
location = nil
|
||||
}
|
||||
|
||||
if options.contains(.showInlineFrames) {
|
||||
for inline in image.inlineCallSites(at: address) {
|
||||
let fakeSymbol = Symbol(imageIndex: imageNdx,
|
||||
imageName: name,
|
||||
rawName: inline.rawName ?? "<unknown>",
|
||||
offset: 0,
|
||||
sourceLocation: location)
|
||||
frames.append(Frame(captured: frame,
|
||||
symbol: fakeSymbol,
|
||||
inlined: true))
|
||||
|
||||
location = SourceLocation(path: inline.filename,
|
||||
line: inline.line,
|
||||
column: inline.column)
|
||||
}
|
||||
}
|
||||
|
||||
return Symbol(imageIndex: imageNdx,
|
||||
imageName: name,
|
||||
rawName: theSymbol.name,
|
||||
offset: theSymbol.offset,
|
||||
sourceLocation: location)
|
||||
}
|
||||
|
||||
if let theSymbol = elf32Image?.lookupSymbol(address: relativeAddress) {
|
||||
var location: SourceLocation?
|
||||
|
||||
if showSourceLocations || showInlineFrames {
|
||||
location = try? elf32Image!.sourceLocation(for: relativeAddress)
|
||||
} else {
|
||||
location = nil
|
||||
if let hit = cache.lookup(path: theImages[imageNdx].path) {
|
||||
switch hit {
|
||||
case let .elf32Image(image):
|
||||
if let theSymbol = lookupSymbol(image: image,
|
||||
at: imageNdx,
|
||||
named: name,
|
||||
address: relativeAddress) {
|
||||
symbol = theSymbol
|
||||
}
|
||||
case let .elf64Image(image):
|
||||
if let theSymbol = lookupSymbol(image: image,
|
||||
at: imageNdx,
|
||||
named: name,
|
||||
address: relativeAddress) {
|
||||
symbol = theSymbol
|
||||
}
|
||||
}
|
||||
|
||||
if showInlineFrames {
|
||||
for inline in elf32Image!.inlineCallSites(at: relativeAddress) {
|
||||
let fakeSymbol = Symbol(imageIndex: imageNdx,
|
||||
imageName: theImages[imageNdx].name,
|
||||
rawName: inline.rawName ?? "<unknown>",
|
||||
offset: 0,
|
||||
sourceLocation: location)
|
||||
frames.append(Frame(captured: frame,
|
||||
symbol: fakeSymbol,
|
||||
inlined: true))
|
||||
|
||||
location = SourceLocation(path: inline.filename,
|
||||
line: inline.line,
|
||||
column: inline.column)
|
||||
}
|
||||
}
|
||||
|
||||
symbol = Symbol(imageIndex: imageNdx,
|
||||
imageName: theImages[imageNdx].name,
|
||||
rawName: theSymbol.name,
|
||||
offset: theSymbol.offset,
|
||||
sourceLocation: location)
|
||||
} else if let theSymbol = elf64Image?.lookupSymbol(address: relativeAddress) {
|
||||
var location: SourceLocation?
|
||||
|
||||
if showSourceLocations || showInlineFrames {
|
||||
location = try? elf64Image!.sourceLocation(for: relativeAddress)
|
||||
} else {
|
||||
location = nil
|
||||
}
|
||||
|
||||
if showInlineFrames {
|
||||
for inline in elf64Image!.inlineCallSites(at: relativeAddress) {
|
||||
let fakeSymbol = Symbol(imageIndex: imageNdx,
|
||||
imageName: theImages[imageNdx].name,
|
||||
rawName: inline.rawName ?? "<unknown>",
|
||||
offset: 0,
|
||||
sourceLocation: location)
|
||||
frames.append(Frame(captured: frame,
|
||||
symbol: fakeSymbol,
|
||||
inlined: true))
|
||||
|
||||
location = SourceLocation(path: inline.filename,
|
||||
line: inline.line,
|
||||
column: inline.column)
|
||||
}
|
||||
}
|
||||
|
||||
symbol = Symbol(imageIndex: imageNdx,
|
||||
imageName: theImages[imageNdx].name,
|
||||
rawName: theSymbol.name,
|
||||
offset: theSymbol.offset,
|
||||
sourceLocation: location)
|
||||
} else {
|
||||
symbol = Symbol(imageIndex: imageNdx,
|
||||
imageName: theImages[imageNdx].name,
|
||||
rawName: "<unknown>",
|
||||
offset: 0,
|
||||
sourceLocation: nil)
|
||||
}
|
||||
|
||||
frames.append(Frame(captured: frame, symbol: symbol))
|
||||
@@ -599,18 +555,16 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
|
||||
|
||||
return SymbolicatedBacktrace(backtrace: backtrace,
|
||||
images: theImages,
|
||||
sharedCacheInfo: theCacheInfo,
|
||||
frames: frames)
|
||||
}
|
||||
|
||||
/// Provide a textual version of the backtrace.
|
||||
public var description: String {
|
||||
var lines: [String] = []
|
||||
let addressChars = (backtrace.addressWidth + 3) / 4
|
||||
|
||||
var n = 0
|
||||
for frame in frames {
|
||||
lines.append("\(n)\t\(frame.description(width: addressChars))")
|
||||
lines.append("\(n)\t\(frame.description)")
|
||||
switch frame.captured {
|
||||
case let .omittedFrames(count):
|
||||
n += count
|
||||
@@ -623,16 +577,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
|
||||
lines.append("Images:")
|
||||
lines.append("")
|
||||
for (n, image) in images.enumerated() {
|
||||
lines.append("\(n)\t\(image.description(width: addressChars))")
|
||||
}
|
||||
|
||||
if let sharedCacheInfo = sharedCacheInfo {
|
||||
lines.append("")
|
||||
lines.append("Shared Cache:")
|
||||
lines.append("")
|
||||
lines.append(" UUID: \(hex(sharedCacheInfo.uuid))")
|
||||
lines.append(" Base: \(hex(sharedCacheInfo.baseAddress, width: addressChars))")
|
||||
lines.append(" Active: \(!sharedCacheInfo.noCache)")
|
||||
lines.append("\(n)\t\(image.description)")
|
||||
}
|
||||
|
||||
return lines.joined(separator: "\n")
|
||||
@@ -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,
|
||||
size_t rawNameLength,
|
||||
char *outputBuffer,
|
||||
size_t *outputBufferSize);
|
||||
char *_swift_backtrace_demangle(const char *rawName,
|
||||
size_t rawNameLength,
|
||||
char *outputBuffer,
|
||||
size_t *outputBufferSize);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // SWIFT_BACKTRACING_RUNTIME_H
|
||||
@@ -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}
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -172,34 +172,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 +243,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,10 +20,10 @@
|
||||
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
|
||||
|
||||
@@ -70,7 +70,6 @@ class Target {
|
||||
|
||||
var task: task_t
|
||||
var images: [Backtrace.Image] = []
|
||||
var sharedCacheInfo: Backtrace.SharedCacheInfo?
|
||||
|
||||
var threads: [TargetThread] = []
|
||||
var crashingThreadNdx: Int = -1
|
||||
@@ -102,7 +101,7 @@ class Target {
|
||||
}
|
||||
}
|
||||
|
||||
var reader: CachingMemoryReader<RemoteMemoryReader>
|
||||
var reader: RemoteMemoryReader
|
||||
|
||||
var mcontext: MContext
|
||||
|
||||
@@ -170,7 +169,7 @@ class Target {
|
||||
|
||||
task = parentTask
|
||||
|
||||
reader = CachingMemoryReader(for: RemoteMemoryReader(task: task_t(task)))
|
||||
reader = RemoteMemoryReader(task: task_t(task))
|
||||
|
||||
name = Self.getProcessName(pid: pid)
|
||||
|
||||
@@ -195,7 +194,6 @@ class Target {
|
||||
mcontext = mctx
|
||||
|
||||
images = Backtrace.captureImages(for: task)
|
||||
sharedCacheInfo = Backtrace.captureSharedCacheInfo(for: task)
|
||||
|
||||
fetchThreads(limit: limit, top: top, cache: cache, symbolicate: symbolicate)
|
||||
}
|
||||
@@ -269,7 +267,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 +277,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 +332,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 +342,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
|
||||
|
||||
@@ -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()
|
||||
@@ -786,13 +786,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 +797,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 +882,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 +961,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 +975,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 +1002,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,7 @@ foreach(sdk ${SWIFT_SDKS})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
add_swift_target_library(swiftRuntime OBJECT_LIBRARY
|
||||
add_swift_target_library(swiftRuntimeCore OBJECT_LIBRARY
|
||||
${swift_runtime_sources}
|
||||
${swift_runtime_objc_sources}
|
||||
${swift_runtime_leaks_sources}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// UNSUPPORTED: use_os_stdlib
|
||||
// UNSUPPORTED: back_deployment_runtime
|
||||
|
||||
import _Backtracing
|
||||
import Runtime
|
||||
|
||||
@available(SwiftStdlib 5.1, *)
|
||||
func level1() async {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
// UNSUPPORTED: use_os_stdlib
|
||||
// UNSUPPORTED: back_deployment_runtime
|
||||
|
||||
import _Backtracing
|
||||
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,11 @@ function(get_test_dependencies SDK result_var_name)
|
||||
|
||||
set(deps_binaries)
|
||||
|
||||
if (SWIFT_ENABLE_BACKTRACING)
|
||||
list(APPEND deps_binaries
|
||||
swift-backtrace)
|
||||
endif()
|
||||
|
||||
if (SWIFT_INCLUDE_TOOLS)
|
||||
list(APPEND deps_binaries
|
||||
libMockPlugin
|
||||
@@ -251,6 +256,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 +479,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,7 +116,7 @@ if(("${SWIFT_HOST_VARIANT_SDK}" STREQUAL "${SWIFT_PRIMARY_VARIANT_SDK}") AND
|
||||
# The runtime tests link to internal runtime symbols, which aren't exported
|
||||
# from the swiftCore dylib, so we need to link to both the runtime archive
|
||||
# and the stdlib.
|
||||
$<TARGET_OBJECTS:swiftRuntime${SWIFT_PRIMARY_VARIANT_SUFFIX}>
|
||||
$<TARGET_OBJECTS:swiftRuntimeCore${SWIFT_PRIMARY_VARIANT_SUFFIX}>
|
||||
$<TARGET_OBJECTS:swiftLLVMSupport${SWIFT_PRIMARY_VARIANT_SUFFIX}>
|
||||
$<TARGET_OBJECTS:swiftDemangling${SWIFT_PRIMARY_VARIANT_SUFFIX}>
|
||||
)
|
||||
|
||||
@@ -42,7 +42,7 @@ if(("${SWIFT_HOST_VARIANT_SDK}" STREQUAL "${SWIFT_PRIMARY_VARIANT_SDK}") AND
|
||||
# The runtime tests link to internal runtime symbols, which aren't exported
|
||||
# from the swiftCore dylib, so we need to link to both the runtime archive
|
||||
# and the stdlib.
|
||||
$<TARGET_OBJECTS:swiftRuntime${SWIFT_PRIMARY_VARIANT_SUFFIX}>
|
||||
$<TARGET_OBJECTS:swiftRuntimeCore${SWIFT_PRIMARY_VARIANT_SUFFIX}>
|
||||
$<TARGET_OBJECTS:swiftLLVMSupport${SWIFT_PRIMARY_VARIANT_SUFFIX}>
|
||||
$<TARGET_OBJECTS:swiftDemangling${SWIFT_PRIMARY_VARIANT_SUFFIX}>
|
||||
)
|
||||
|
||||
@@ -1493,6 +1493,10 @@ def create_argument_parser():
|
||||
default=True,
|
||||
help='Enable Volatile module.')
|
||||
|
||||
option('--enable-runtime-module', toggle_true,
|
||||
default=True,
|
||||
help='Enable Runtime module.')
|
||||
|
||||
option('--enable-experimental-parser-validation', toggle_true,
|
||||
default=True,
|
||||
help='Enable experimental Swift Parser validation by default.')
|
||||
|
||||
@@ -188,6 +188,7 @@ EXPECTED_DEFAULTS = {
|
||||
'swift_enable_backtracing': True,
|
||||
'enable_synchronization': True,
|
||||
'enable_volatile': True,
|
||||
'enable_runtime_module': True,
|
||||
'enable_lsan': False,
|
||||
'enable_sanitize_coverage': False,
|
||||
'disable_guaranteed_normal_arguments': False,
|
||||
|
||||
@@ -74,6 +74,9 @@ class Swift(product.Product):
|
||||
# Add volatile flag.
|
||||
self.cmake_options.extend(self._enable_volatile)
|
||||
|
||||
# Add runtime module flag.
|
||||
self.cmake_options.extend(self._enable_runtime_module)
|
||||
|
||||
# Add static vprintf flag
|
||||
self.cmake_options.extend(self._enable_stdlib_static_vprintf)
|
||||
|
||||
@@ -235,6 +238,11 @@ updated without updating swift.py?")
|
||||
return [('SWIFT_ENABLE_VOLATILE:BOOL',
|
||||
self.args.enable_volatile)]
|
||||
|
||||
@property
|
||||
def _enable_runtime_module(self):
|
||||
return [('SWIFT_ENABLE_RUNTIME_MODULE:BOOL',
|
||||
self.args.enable_runtime_module)]
|
||||
|
||||
@property
|
||||
def _enable_stdlib_static_vprintf(self):
|
||||
return [('SWIFT_STDLIB_STATIC_PRINT',
|
||||
|
||||
@@ -63,6 +63,7 @@ class SwiftTestCase(unittest.TestCase):
|
||||
swift_enable_backtracing=False,
|
||||
enable_synchronization=False,
|
||||
enable_volatile=False,
|
||||
enable_runtime_module=False,
|
||||
build_early_swiftsyntax=False,
|
||||
build_swift_stdlib_static_print=False,
|
||||
build_swift_stdlib_unicode_data=True,
|
||||
@@ -112,6 +113,7 @@ class SwiftTestCase(unittest.TestCase):
|
||||
'-DSWIFT_ENABLE_BACKTRACING:BOOL=FALSE',
|
||||
'-DSWIFT_ENABLE_SYNCHRONIZATION:BOOL=FALSE',
|
||||
'-DSWIFT_ENABLE_VOLATILE:BOOL=FALSE',
|
||||
'-DSWIFT_ENABLE_RUNTIME_MODULE:BOOL=FALSE',
|
||||
'-DSWIFT_STDLIB_STATIC_PRINT=FALSE',
|
||||
'-DSWIFT_FREESTANDING_IS_DARWIN:BOOL=FALSE',
|
||||
'-DSWIFT_STDLIB_BUILD_PRIVATE:BOOL=TRUE',
|
||||
@@ -146,6 +148,7 @@ class SwiftTestCase(unittest.TestCase):
|
||||
'-DSWIFT_ENABLE_BACKTRACING:BOOL=FALSE',
|
||||
'-DSWIFT_ENABLE_SYNCHRONIZATION:BOOL=FALSE',
|
||||
'-DSWIFT_ENABLE_VOLATILE:BOOL=FALSE',
|
||||
'-DSWIFT_ENABLE_RUNTIME_MODULE:BOOL=FALSE',
|
||||
'-DSWIFT_STDLIB_STATIC_PRINT=FALSE',
|
||||
'-DSWIFT_FREESTANDING_IS_DARWIN:BOOL=FALSE',
|
||||
'-DSWIFT_STDLIB_BUILD_PRIVATE:BOOL=TRUE',
|
||||
@@ -470,6 +473,19 @@ class SwiftTestCase(unittest.TestCase):
|
||||
[x for x in swift.cmake_options
|
||||
if 'DSWIFT_ENABLE_VOLATILE' in x])
|
||||
|
||||
def test_runtime_module_flags(self):
|
||||
self.args.enable_runtime_module = True
|
||||
swift = Swift(
|
||||
args=self.args,
|
||||
toolchain=self.toolchain,
|
||||
source_dir='/path/to/src',
|
||||
build_dir='/path/to/build')
|
||||
self.assertEqual(
|
||||
['-DSWIFT_ENABLE_RUNTIME_MODULE:BOOL='
|
||||
'TRUE'],
|
||||
[x for x in swift.cmake_options
|
||||
if 'DSWIFT_ENABLE_RUNTIME_MODULE in x])
|
||||
|
||||
def test_freestanding_is_darwin_flags(self):
|
||||
self.args.swift_freestanding_is_darwin = True
|
||||
swift = Swift(
|
||||
|
||||
@@ -39,9 +39,9 @@ for module_file in os.listdir(sdk_overlay_dir):
|
||||
if module_name == "DifferentiationUnittest":
|
||||
continue
|
||||
# Backtracing needs its own additional modules in the module path
|
||||
if module_name == "_Backtracing":
|
||||
if module_name == "Runtime":
|
||||
extra_args = ["-I", os.path.join(source_dir, "stdlib",
|
||||
"public", "Backtracing", "modules"),
|
||||
"public", "RuntimeModule", "modules"),
|
||||
"-I", os.path.join(source_dir, "include")]
|
||||
# _Concurrency needs its own additional modules in the module path
|
||||
if module_name == "_Concurrency":
|
||||
|
||||
Reference in New Issue
Block a user