[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:
Alastair Houghton
2024-10-24 11:49:38 +01:00
parent 18496c5626
commit 760cc57bef
78 changed files with 3391 additions and 1900 deletions

View File

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

View File

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

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

View File

@@ -651,22 +651,6 @@ static void recordShadowedDeclsAfterTypeMatch(
}
}
// Next, prefer any other module over the _Backtracing module.
if (auto spModule = ctx.getLoadedModule(ctx.Id_Backtracing)) {
if ((firstModule == spModule) != (secondModule == spModule)) {
// If second module is _StringProcessing, then it is shadowed by
// first.
if (secondModule == spModule) {
shadowed.insert(secondDecl);
continue;
}
// Otherwise, the first declaration is shadowed by the second.
shadowed.insert(firstDecl);
break;
}
}
// Next, prefer any other module over the Observation module.
if (auto obsModule = ctx.getLoadedModule(ctx.Id_Observation)) {
if ((firstModule == obsModule) != (secondModule == obsModule)) {

View File

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

View File

@@ -1,48 +0,0 @@
//===--- ArrayImageSource.swift - An image source backed by an Array -------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Defines ArrayImageSource, an image source that is backed by a Swift Array.
//
//===----------------------------------------------------------------------===//
import Swift
enum ArrayImageSourceError: Error {
case outOfBoundsRead(UInt64, UInt64)
}
struct ArrayImageSource<T>: ImageSource {
private var array: Array<T>
public init(array: Array<T>) {
self.array = array
}
public var isMappedImage: Bool { return false }
public var path: String? { return nil }
public var bounds: Bounds? {
return Bounds(base: 0, size: Size(array.count * MemoryLayout<T>.stride))
}
public func fetch(from addr: Address,
into buffer: UnsafeMutableRawBufferPointer) throws {
try array.withUnsafeBytes{
let size = Size($0.count)
let requested = Size(buffer.count)
if addr > size || requested > size - addr {
throw ArrayImageSourceError.outOfBoundsRead(addr, requested)
}
buffer.copyBytes(from: $0[Int(addr)..<Int(addr+requested)])
}
}
}

View File

@@ -1,81 +0,0 @@
//===--- FileImageSource.swift - An image source that reads from a file ---===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Defines FileImageSource, an image source that reads data from a file.
//
//===----------------------------------------------------------------------===//
import Swift
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
internal import Darwin
#elseif os(Windows)
internal import ucrt
#elseif canImport(Glibc)
internal import Glibc
#elseif canImport(Musl)
internal import Musl
#endif
enum FileImageSourceError: Error {
case posixError(Int32)
case outOfRangeRead
}
class FileImageSource: ImageSource {
private var _mapping: UnsafeRawBufferPointer
public var isMappedImage: Bool { return false }
private var _path: String
public var path: String? { return _path }
public var bounds: Bounds? {
return Bounds(base: 0, size: Size(_mapping.count))
}
public init(path: String) throws {
_path = path
let fd = open(path, O_RDONLY, 0)
if fd < 0 {
throw FileImageSourceError.posixError(errno)
}
defer { close(fd) }
let size = lseek(fd, 0, SEEK_END)
if size < 0 {
throw FileImageSourceError.posixError(errno)
}
let base = mmap(nil, Int(size), PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0)
if base == nil || base! == UnsafeRawPointer(bitPattern: -1)! {
throw FileImageSourceError.posixError(errno)
}
_mapping = UnsafeRawBufferPointer(start: base, count: Int(size))
}
deinit {
munmap(UnsafeMutableRawPointer(mutating: _mapping.baseAddress),
_mapping.count)
}
public func fetch(from addr: Address,
into buffer: UnsafeMutableRawBufferPointer) throws {
let start = Int(addr)
guard _mapping.indices.contains(start) else {
throw FileImageSourceError.outOfRangeRead
}
let slice = _mapping[start...]
guard slice.count >= buffer.count else {
throw FileImageSourceError.outOfRangeRead
}
buffer.copyBytes(from: slice[start..<start+buffer.count])
}
}

View File

@@ -1,158 +0,0 @@
//===--- ImageSource.swift - A place from which to read image data --------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Defines ImageSource, which is a protocol that can be implemented to
// provide an image reader with a way to read data from a file, a buffer
// in memory, or wherever we might wish to read an image from.
//
//===----------------------------------------------------------------------===//
import Swift
struct ImageBounds<Address: FixedWidthInteger,
Size: FixedWidthInteger> {
var base: Address
var size: Size
var end: Address {
return base + Address(size)
}
func adjusted(by offset: some FixedWidthInteger) -> Self {
return Self(base: base + Address(offset), size: size - Size(offset))
}
}
protocol ImageSource: MemoryReader {
typealias Bounds = ImageBounds<Address, Size>
/// Says whether we are looking at a loaded image in memory or not.
/// The layout in memory may differ from the on-disk layout; in particular,
/// some information may not be available when the image is mapped into
/// memory (an example is ELF section headers).
var isMappedImage: Bool { get }
/// If this ImageSource knows its path, this will be non-nil.
var path: String? { get }
/// If this ImageSource knows its bounds, this will be non-nil.
var bounds: Bounds? { get }
}
struct ImageSourceCursor {
typealias Address = UInt64
typealias Size = UInt64
typealias Bounds = ImageBounds<Address, Size>
var source: any ImageSource
var pos: Address
init(source: any ImageSource, offset: Address = 0) {
self.source = source
self.pos = offset
}
public mutating func read(into buffer: UnsafeMutableRawBufferPointer) throws {
try source.fetch(from: pos, into: buffer)
pos += UInt64(buffer.count)
}
public mutating func read<T>(into buffer: UnsafeMutableBufferPointer<T>) throws {
try source.fetch(from: pos, into: buffer)
pos += UInt64(MemoryLayout<T>.stride * buffer.count)
}
public mutating func read<T>(into pointer: UnsafeMutablePointer<T>) throws {
try source.fetch(from: pos, into: pointer)
pos += UInt64(MemoryLayout<T>.stride)
}
public mutating func read<T>(as type: T.Type) throws -> T {
let stride = MemoryLayout<T>.stride
let result = try source.fetch(from: pos, as: type)
pos += UInt64(stride)
return result
}
public mutating func read<T>(count: Int, as type: T.Type) throws -> [T] {
let stride = MemoryLayout<T>.stride
let result = try source.fetch(from: pos, count: count, as: type)
pos += UInt64(stride * count)
return result
}
public mutating func readString() throws -> String? {
var bytes: [UInt8] = []
while true {
let ch = try read(as: UInt8.self)
if ch == 0 {
break
}
bytes.append(ch)
}
return String(decoding: bytes, as: UTF8.self)
}
}
extension ImageSource {
/// Fetch all the data from this image source (which must be bounded)
func fetchAllBytes() -> [UInt8]? {
guard let bounds = self.bounds else {
return nil
}
if let data = try? fetch(from: bounds.base,
count: Int(bounds.size),
as: UInt8.self) {
return data
}
return nil
}
}
enum SubImageSourceError: Error {
case outOfRangeFetch(UInt64, Int)
}
struct SubImageSource<S: ImageSource>: ImageSource {
var parent: S
var baseAddress: Address
var length: Size
var path: String? { return parent.path }
var bounds: Bounds? {
return Bounds(base: 0, size: length)
}
public init(parent: S, baseAddress: Address, length: Size) {
self.parent = parent
self.baseAddress = baseAddress
self.length = length
}
public var isMappedImage: Bool {
return parent.isMappedImage
}
public func fetch(from addr: Address,
into buffer: UnsafeMutableRawBufferPointer) throws {
let toFetch = buffer.count
if addr < 0 || addr > length {
throw SubImageSourceError.outOfRangeFetch(UInt64(addr), toFetch)
}
if Address(length) - addr < toFetch {
throw SubImageSourceError.outOfRangeFetch(UInt64(addr), toFetch)
}
return try parent.fetch(from: baseAddress + addr, into: buffer)
}
}

View File

@@ -1,35 +0,0 @@
//===--- MemoryImageSource.swift - An image source that reads from a file ---===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Defines MemoryImageSource, an image source that reads data using a
// MemoryReader.
//
//===----------------------------------------------------------------------===//
import Swift
class MemoryImageSource<M: MemoryReader>: ImageSource {
private var reader: M
public var isMappedImage: Bool { return true }
public var path: String? { return nil }
public var bounds: Bounds? { return nil }
public init(with reader: M) {
self.reader = reader
}
public func fetch(from addr: Address,
into buffer: UnsafeMutableRawBufferPointer) throws {
try reader.fetch(from: addr, into: buffer)
}
}

View File

@@ -301,8 +301,8 @@ if(SWIFT_BUILD_STDLIB AND NOT SWIFT_STDLIB_BUILD_ONLY_CORE_MODULES)
add_subdirectory(Observation)
endif()
if(SWIFT_ENABLE_BACKTRACING)
add_subdirectory(Backtracing)
if(SWIFT_ENABLE_RUNTIME_MODULE)
add_subdirectory(RuntimeModule)
endif()
if(SWIFT_ENABLE_SYNCHRONIZATION)

View File

@@ -123,7 +123,7 @@ namespace swift {
// Include path computation. Code that includes this file can write `#include
// "..CompatibilityOverride/CompatibilityOverrideIncludePath.h"` to include the
// appropriate .def file for the current library.
#define COMPATIBILITY_OVERRIDE_INCLUDE_PATH_swiftRuntime \
#define COMPATIBILITY_OVERRIDE_INCLUDE_PATH_swiftRuntimeCore \
"../CompatibilityOverride/CompatibilityOverrideRuntime.def"
#define COMPATIBILITY_OVERRIDE_INCLUDE_PATH_swift_Concurrency \
"../CompatibilityOverride/CompatibilityOverrideConcurrency.def"
@@ -155,7 +155,7 @@ namespace swift {
// resolve to string literal containing the appropriate section name for the
// current library.
// Turns into '__swift<major><minor>_hooks'
#define COMPATIBILITY_OVERRIDE_SECTION_NAME_swiftRuntime "__swift" \
#define COMPATIBILITY_OVERRIDE_SECTION_NAME_swiftRuntimeCore "__swift" \
SWIFT_VERSION_MAJOR \
SWIFT_VERSION_MINOR \
"_hooks"

View File

@@ -0,0 +1,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)
}
}
}

View File

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

View File

@@ -527,24 +527,22 @@ public struct BacktraceFormatter {
/// Format an individual frame into a list of columns.
///
/// @param frame The frame to format.
/// @param addressWidth The width, in characters, of an address.
/// @param index The frame index, if required.
///
/// @result An array of strings, one per column.
public func formatColumns(frame: Backtrace.Frame,
addressWidth: Int,
index: Int? = nil) -> [String] {
let pc: String
var attrs: [String] = []
switch frame {
case let .programCounter(address):
pc = "\(hex(address, width: addressWidth))"
pc = "\(address)"
case let .returnAddress(address):
pc = "\(hex(address, width: addressWidth))"
pc = "\(address)"
attrs.append("ra")
case let .asyncResumePoint(address):
pc = "\(hex(address, width: addressWidth))"
pc = "\(address)"
attrs.append("async")
case .omittedFrames(_), .truncated:
pc = "..."
@@ -567,30 +565,24 @@ public struct BacktraceFormatter {
/// Format a frame into a list of rows.
///
/// @param frame The frame to format.
/// @param addressWidth The width, in characters, of an address.
/// @param index The frame index, if required.
///
/// @result An array of table rows.
public func formatRows(frame: Backtrace.Frame,
addressWidth: Int,
index: Int? = nil) -> [TableRow] {
return [.columns(formatColumns(frame: frame,
addressWidth: addressWidth,
index: index))]
}
/// Format just one frame.
///
/// @param frame The frame to format.
/// @param addressWidth The width, in characters, of an address.
/// @param index The frame index, if required.
///
/// @result A `String` containing the formatted data.
public func format(frame: Backtrace.Frame,
addressWidth: Int,
index: Int? = nil) -> String {
let rows = formatRows(frame: frame,
addressWidth: addressWidth,
index: index)
return BacktraceFormatter.formatTable(rows, alignments: [.right])
}
@@ -598,16 +590,14 @@ public struct BacktraceFormatter {
/// Format the frame list from a backtrace.
///
/// @param frames The frames to format.
/// @param addressWidth The width, in characters, of an address.
///
/// @result A `String` containing the formatted data.
public func format(frames: some Sequence<Backtrace.Frame>,
addressWidth: Int) -> String {
public func format(frames: some Sequence<Backtrace.Frame>) -> String {
var rows: [TableRow] = []
var n = 0
for frame in frames {
rows += formatRows(frame: frame, addressWidth: addressWidth, index: n)
rows += formatRows(frame: frame, index: n)
if case let .omittedFrames(count) = frame {
n += count
@@ -625,8 +615,7 @@ public struct BacktraceFormatter {
///
/// @result A `String` containing the formatted data.
public func format(backtrace: Backtrace) -> String {
return format(frames: backtrace.frames,
addressWidth: (backtrace.addressWidth + 3) / 4)
return format(frames: backtrace.frames)
}
/// Grab source lines for a symbolicated backtrace.
@@ -743,19 +732,18 @@ public struct BacktraceFormatter {
///
/// @result An array of strings, one per column.
public func formatColumns(frame: SymbolicatedBacktrace.Frame,
addressWidth: Int,
index: Int? = nil) -> [String] {
let pc: String
var attrs: [String] = []
switch frame.captured {
case let .programCounter(address):
pc = "\(hex(address, width: addressWidth))"
pc = "\(address)"
case let .returnAddress(address):
pc = "\(hex(address, width: addressWidth))"
pc = "\(address)"
attrs.append("ra")
case let .asyncResumePoint(address):
pc = "\(hex(address, width: addressWidth))"
pc = "\(address)"
attrs.append("async")
case .omittedFrames(_), .truncated:
pc = ""
@@ -855,16 +843,13 @@ public struct BacktraceFormatter {
/// Format a frame into a list of rows.
///
/// @param frame The frame to format.
/// @param addressWidth The width, in characters, of an address.
/// @param index The frame index, if required.
///
/// @result An array of table rows.
public func formatRows(frame: SymbolicatedBacktrace.Frame,
addressWidth: Int,
index: Int? = nil,
showSource: Bool = true) -> [TableRow] {
let columns = formatColumns(frame: frame,
addressWidth: addressWidth,
index: index)
var rows: [TableRow] = [.columns(columns)]
@@ -884,16 +869,13 @@ public struct BacktraceFormatter {
/// Format just one frame.
///
/// @param frame The frame to format.
/// @param addressWidth The width, in characters, of an address.
/// @param index The frame index, if required.
///
/// @result A `String` containing the formatted data.
public func format(frame: SymbolicatedBacktrace.Frame,
addressWidth: Int,
index: Int? = nil,
showSource: Bool = true) -> String {
let rows = formatRows(frame: frame, addressWidth: addressWidth,
index: index, showSource: showSource)
let rows = formatRows(frame: frame, index: index, showSource: showSource)
return BacktraceFormatter.formatTable(rows, alignments: [.right])
}
@@ -907,11 +889,11 @@ public struct BacktraceFormatter {
/// Format the frame list from a symbolicated backtrace.
///
/// @param frames The frames to format.
/// @param addressWidth The width, in characters, of an address.
///
/// @result A `String` containing the formatted data.
public func format(frames: some Sequence<SymbolicatedBacktrace.Frame>,
addressWidth: Int) -> String {
public func format(
frames: some Sequence<SymbolicatedBacktrace.Frame>
) -> String {
var rows: [TableRow] = []
var sourceLocationsShown = Set<SymbolicatedBacktrace.SourceLocation>()
@@ -931,8 +913,7 @@ public struct BacktraceFormatter {
}
}
rows += formatRows(frame: frame, addressWidth: addressWidth,
index: n, showSource: showSource)
rows += formatRows(frame: frame, index: n, showSource: showSource)
if case let .omittedFrames(count) = frame.captured {
n += count
@@ -950,16 +931,14 @@ public struct BacktraceFormatter {
///
/// @result A `String` containing the formatted data.
public func format(backtrace: SymbolicatedBacktrace) -> String {
let addressChars = (backtrace.addressWidth + 3) / 4
var result = format(frames: backtrace.frames, addressWidth: addressChars)
var result = format(frames: backtrace.frames)
switch options._showImages {
case .none:
break
case .all:
result += "\n\nImages:\n"
result += format(images: backtrace.images,
addressWidth: addressChars)
result += format(images: backtrace.images)
case .mentioned:
var mentionedImages = Set<Int>()
for frame in backtrace.frames {
@@ -978,7 +957,7 @@ public struct BacktraceFormatter {
} else {
result += "\n\nImages (only mentioned):\n"
}
result += format(images: images, addressWidth: addressChars)
result += format(images: images)
}
return result
@@ -987,28 +966,31 @@ public struct BacktraceFormatter {
/// Format a `Backtrace.Image` into a list of columns.
///
/// @param image The `Image` object to format.
/// @param addressWidth The width of an address, in characters.
///
/// @result An array of strings, one per column.
public func formatColumns(image: Backtrace.Image,
addressWidth: Int) -> [String] {
let addressRange = "\(hex(image.baseAddress, width: addressWidth))\(hex(image.endOfText, width: addressWidth))"
public func formatColumns(image: Backtrace.Image) -> [String] {
let addressRange = "\(image.baseAddress)\(image.endOfText)"
let buildID: String
if let bytes = image.buildID {
if let bytes = image.uniqueID {
buildID = hex(bytes)
} else {
buildID = "<no build ID>"
}
let imagePath: String
if options._sanitizePaths {
imagePath = sanitizePath(image.path)
if let path = image.path {
if options._sanitizePaths {
imagePath = sanitizePath(path)
} else {
imagePath = path
}
} else {
imagePath = image.path
imagePath = "<unknown>"
}
let imageName = image.name ?? "<unknown>"
return [
options._theme.imageAddressRange(addressRange),
options._theme.imageBuildID(buildID),
options._theme.imageName(image.name),
options._theme.imageName(imageName),
options._theme.imagePath(imagePath)
]
}
@@ -1016,15 +998,12 @@ public struct BacktraceFormatter {
/// Format an array of `Backtrace.Image`s.
///
/// @param images The array of `Image` objects to format.
/// @param addressWidth The width of an address, in characters.
///
/// @result A string containing the formatted data.
public func format(images: some Sequence<Backtrace.Image>,
addressWidth: Int) -> String {
public func format(images: some Sequence<Backtrace.Image>) -> String {
let rows = images.map{
TableRow.columns(
formatColumns(image: $0,
addressWidth: addressWidth)
formatColumns(image: $0)
)
}

View File

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

View File

@@ -18,18 +18,17 @@ import Swift
// The size of the pages in the page cache (must be a power of 2)
fileprivate let pageSize = 4096
fileprivate let pageMask = pageSize - 1
// The largest chunk we will try to cache data for
fileprivate let maxCachedSize = pageSize * 8
@_spi(MemoryReaders)
public class CachingMemoryReader<T: MemoryReader>: MemoryReader {
private var reader: T
public class CachingMemoryReader<Reader: MemoryReader>: MemoryReader {
private var reader: Reader
private var cache: [Address:UnsafeRawBufferPointer]
public init(for reader: T) {
public init(for reader: Reader) {
self.reader = reader
self.cache = [:]
}
@@ -40,7 +39,7 @@ public class CachingMemoryReader<T: MemoryReader>: MemoryReader {
}
}
private func getPage(at address: Address) throws -> UnsafeRawBufferPointer {
func getPage(at address: Address) throws -> UnsafeRawBufferPointer {
precondition((address & Address(pageMask)) == 0)
if let page = cache[address] {
@@ -84,3 +83,39 @@ public class CachingMemoryReader<T: MemoryReader>: MemoryReader {
}
}
}
#if os(Linux)
@_spi(MemoryReaders)
public typealias MemserverMemoryReader
= CachingMemoryReader<UncachedMemserverMemoryReader>
extension CachingMemoryReader where Reader == UncachedMemserverMemoryReader {
convenience public init(fd: CInt) {
self.init(for: UncachedMemserverMemoryReader(fd: fd))
}
}
#endif
@_spi(MemoryReaders)
public typealias RemoteMemoryReader = CachingMemoryReader<UncachedRemoteMemoryReader>
extension CachingMemoryReader where Reader == UncachedRemoteMemoryReader {
#if os(macOS)
convenience public init(task: Any) {
self.init(for: UncachedRemoteMemoryReader(task: task))
}
#elseif os(Linux)
convenience public init(pid: Any) {
self.init(for: UncachedRemoteMemoryReader(pid: pid))
}
#endif
}
@_spi(MemoryReaders)
public typealias LocalMemoryReader = CachingMemoryReader<UncachedLocalMemoryReader>
extension CachingMemoryReader where Reader == UncachedLocalMemoryReader {
convenience public init() {
self.init(for: UncachedLocalMemoryReader())
}
}

View File

@@ -0,0 +1,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
}
}
}

View File

@@ -25,8 +25,6 @@
//
//===----------------------------------------------------------------------===//
#if os(Linux)
import Swift
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
@@ -56,18 +54,26 @@ let lzma_stream_init = swift.runtime.lzma_stream_init
// .. CompressedStream .........................................................
protocol CompressedStream {
typealias InputSource = () throws -> UnsafeBufferPointer<UInt8>
typealias InputSource = () throws -> UnsafeRawBufferPointer
typealias OutputSink = (_ used: UInt, _ done: Bool) throws
-> UnsafeMutableBufferPointer<UInt8>?
-> UnsafeMutableRawBufferPointer?
func decompress(input: InputSource, output: OutputSink) throws -> UInt
}
// .. Compression library bindings .............................................
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
private var lzmaHandle = dlopen("liblzma.dylib", RTLD_LAZY)
private var zlibHandle = dlopen("libz.dylib", RTLD_LAZY)
private var zstdHandle = dlopen("libzstd.dylib", RTLD_LAZY)
#elseif os(Linux)
private var lzmaHandle = dlopen("liblzma.so.5", RTLD_LAZY)
private var zlibHandle = dlopen("libz.so.1", RTLD_LAZY)
private var zstdHandle = dlopen("libzstd.so.1", RTLD_LAZY)
#elseif os(Windows)
// ###TODO
#endif
private func symbol<T>(_ handle: UnsafeMutableRawPointer?, _ name: String) -> T? {
guard let handle = handle, let result = dlsym(handle, name) else {
@@ -152,7 +158,9 @@ struct ZLibStream: CompressedStream {
let buffer = try input()
// Not really mutable; this is just an issue with z_const
stream.next_in = UnsafeMutablePointer(mutating: buffer.baseAddress)
stream.next_in = UnsafeMutablePointer(
mutating: buffer.baseAddress?.assumingMemoryBound(to: UInt8.self)
)
stream.avail_in = CUnsignedInt(buffer.count)
}
@@ -161,7 +169,7 @@ struct ZLibStream: CompressedStream {
throw CompressedImageSourceError.outputOverrun
}
stream.next_out = buffer.baseAddress
stream.next_out = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self)
stream.avail_out = CUnsignedInt(buffer.count)
outputBufferSize = UInt(buffer.count)
}
@@ -211,7 +219,7 @@ struct ZStdStream: CompressedStream {
if inBuffer.size == inBuffer.pos {
let buffer = try input()
inBuffer.src = UnsafeRawPointer(buffer.baseAddress)
inBuffer.src = buffer.baseAddress
inBuffer.size = buffer.count
inBuffer.pos = 0
}
@@ -225,7 +233,7 @@ struct ZStdStream: CompressedStream {
throw CompressedImageSourceError.outputOverrun
}
outBuffer.dst = UnsafeMutableRawPointer(buffer.baseAddress)
outBuffer.dst = buffer.baseAddress
outBuffer.size = buffer.count
outBuffer.pos = 0
}
@@ -280,7 +288,7 @@ struct LZMAStream: CompressedStream {
while true {
if stream.avail_in == 0 {
let buffer = try input()
stream.next_in = buffer.baseAddress
stream.next_in = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self)
stream.avail_in = buffer.count
}
@@ -289,7 +297,7 @@ struct LZMAStream: CompressedStream {
throw CompressedImageSourceError.outputOverrun
}
stream.next_out = buffer.baseAddress
stream.next_out = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self)
stream.avail_out = buffer.count
outputBufferSize = UInt(buffer.count)
}
@@ -310,230 +318,130 @@ struct LZMAStream: CompressedStream {
// .. Image Sources ............................................................
fileprivate func decompress<S: CompressedStream, I: ImageSource>(
stream: S, source: I, dataBounds: I.Bounds, uncompressedSize: UInt? = nil)
throws -> [UInt8] {
fileprivate func decompress<S: CompressedStream>(
stream: S,
source: ImageSource,
offset: Int,
output: inout ImageSource
) throws {
let totalBytes = try stream.decompress(
input: {
() throws -> UnsafeRawBufferPointer in
var pos = dataBounds.base
var remaining = dataBounds.size
return UnsafeRawBufferPointer(rebasing: source.bytes[offset...])
},
output: {
(used: UInt, done: Bool) throws -> UnsafeMutableRawBufferPointer? in
let bufSize = 65536
if let uncompressedSize = uncompressedSize {
// If we know the uncompressed size, we can decompress directly into the
// array.
let inputBuffer
= UnsafeMutableBufferPointer<UInt8>.allocate(capacity: bufSize)
defer {
inputBuffer.deallocate()
}
return try [UInt8].init(unsafeUninitializedCapacity: Int(uncompressedSize)) {
(outputBuffer: inout UnsafeMutableBufferPointer<UInt8>,
count: inout Int) in
count = Int(try stream.decompress(
input: { () throws -> UnsafeBufferPointer<UInt8> in
let chunkSize = min(Int(remaining), inputBuffer.count)
let slice = inputBuffer[0..<chunkSize]
let buffer = UnsafeMutableBufferPointer(rebasing: slice)
try source.fetch(from: pos, into: buffer)
pos += I.Address(chunkSize)
remaining -= I.Size(chunkSize)
return UnsafeBufferPointer(buffer)
},
output: {
(used: UInt, done: Bool) throws -> UnsafeMutableBufferPointer<UInt8>? in
if used == 0 {
return outputBuffer
} else {
return nil
}
}
))
}
} else {
// Otherwise, we decompress in chunks and append them to the array.
let buffer
= UnsafeMutableBufferPointer<UInt8>.allocate(capacity: 2 * bufSize)
defer {
buffer.deallocate()
}
let inputBuffer = UnsafeMutableBufferPointer(rebasing: buffer[0..<bufSize])
let outputBuffer = UnsafeMutableBufferPointer(rebasing: buffer[bufSize...])
var data = [UInt8]()
_ = try stream.decompress(
input: { () throws -> UnsafeBufferPointer<UInt8> in
let chunkSize = min(Int(remaining), inputBuffer.count)
let slice = inputBuffer[0..<chunkSize]
let buffer = UnsafeMutableBufferPointer(rebasing: slice)
try source.fetch(from: pos, into: buffer)
pos += I.Address(chunkSize)
remaining -= I.Size(chunkSize)
return UnsafeBufferPointer(buffer)
},
output: {
(used: UInt, done: Bool) throws -> UnsafeMutableBufferPointer<UInt8>? in
data.append(contentsOf: outputBuffer[..<Int(used)])
if !done {
return outputBuffer
} else {
return nil
}
if used == 0 {
return output.unusedBytes
} else {
return nil
}
)
return data
}
}
)
output.used(bytes: Int(totalBytes))
}
internal struct ElfCompressedImageSource<Traits: ElfTraits>: ImageSource {
fileprivate func decompressChunked<S: CompressedStream>(
stream: S,
source: ImageSource,
offset: Int,
output: inout ImageSource
) throws {
let bufSize = 65536
let outputBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: bufSize,
alignment: 16)
defer {
outputBuffer.deallocate()
}
private var data: [UInt8]
let _ = try stream.decompress(
input: {
() throws -> UnsafeRawBufferPointer in
var isMappedImage: Bool { return false }
var path: String? { return nil }
var bounds: Bounds? { return Bounds(base: Address(0), size: Size(data.count)) }
return UnsafeRawBufferPointer(rebasing: source.bytes[offset...])
},
output: {
(used: UInt, done: Bool) throws -> UnsafeMutableRawBufferPointer? in
init(source: some ImageSource) throws {
guard let bounds = source.bounds else {
throw CompressedImageSourceError.unboundedImageSource
output.append(
bytes: UnsafeRawBufferPointer(rebasing: outputBuffer[..<Int(used)])
)
if !done {
return outputBuffer
} else {
return nil
}
}
)
}
if bounds.size < MemoryLayout<Traits.Chdr>.size {
extension ImageSource {
@_specialize(kind: full, where Traits == Elf32Traits)
@_specialize(kind: full, where Traits == Elf64Traits)
init<Traits: ElfTraits>(elfCompressedImageSource source: ImageSource,
traits: Traits.Type) throws {
if source.bytes.count < MemoryLayout<Traits.Chdr>.size {
throw CompressedImageSourceError.badCompressedData
}
let chdr = try source.fetch(from: bounds.base,
as: Traits.Chdr.self)
let dataBounds = bounds.adjusted(by: MemoryLayout<Traits.Chdr>.stride)
let rawChdr = try source.fetch(from: 0, as: Traits.Chdr.self)
let chdr: Traits.Chdr
switch rawChdr.ch_type {
case .ELFCOMPRESS_ZLIB.byteSwapped, .ELFCOMPRESS_ZSTD.byteSwapped:
chdr = rawChdr.byteSwapped
default:
chdr = rawChdr
}
let uncompressedSize = UInt(chdr.ch_size)
self.init(capacity: Int(uncompressedSize), isMappedImage: false, path: nil)
switch chdr.ch_type {
case .ELFCOMPRESS_ZLIB:
data = try decompress(stream: ZLibStream(),
source: source, dataBounds: dataBounds,
uncompressedSize: uncompressedSize)
try decompress(stream: ZLibStream(),
source: source, offset: MemoryLayout<Traits.Chdr>.stride,
output: &self)
case .ELFCOMPRESS_ZSTD:
data = try decompress(stream: ZStdStream(),
source: source, dataBounds: dataBounds,
uncompressedSize: uncompressedSize)
try decompress(stream: ZStdStream(),
source: source, offset: MemoryLayout<Traits.Chdr>.stride,
output: &self)
default:
throw CompressedImageSourceError.unsupportedFormat
}
}
public func fetch(from addr: Address,
into buffer: UnsafeMutableRawBufferPointer) throws {
let toFetch = buffer.count
if addr < 0 || addr > data.count || data.count - Int(addr) < toFetch {
throw CompressedImageSourceError.outOfRangeFetch(addr, toFetch)
}
buffer.withMemoryRebound(to: UInt8.self) { outBuf in
for n in 0..<toFetch {
outBuf[n] = data[Int(addr) + n]
}
}
}
}
internal struct ElfGNUCompressedImageSource: ImageSource {
private var data: [UInt8]
var isMappedImage: Bool { return false }
var path: String? { return nil }
var bounds: Bounds? { return Bounds(base: Address(0), size: Size(data.count)) }
init(source: some ImageSource) throws {
guard let bounds = source.bounds else {
throw CompressedImageSourceError.unboundedImageSource
}
if bounds.size < 12 {
init(gnuCompressedImageSource source: ImageSource) throws {
if source.bytes.count < 12 {
throw CompressedImageSourceError.badCompressedData
}
let magic = try source.fetch(from: bounds.base, as: UInt32.self)
if magic != 0x42494c5a {
throw CompressedImageSourceError.badCompressedData
let magic = try source.fetch(from: 0, as: UInt32.self)
let rawUncompressedSize = try source.fetch(from: 4, as: UInt64.self)
let uncompressedSize: UInt64
switch magic {
case 0x42494c5a: // BILZ
uncompressedSize = rawUncompressedSize.byteSwapped
case 0x5a4c4942: // ZLIB
uncompressedSize = rawUncompressedSize
default:
throw CompressedImageSourceError.badCompressedData
}
let uncompressedSize
= UInt(try source.fetch(from: bounds.base + 4, as: UInt64.self).byteSwapped)
self.init(capacity: Int(uncompressedSize), isMappedImage: false, path: nil)
data = try decompress(stream: ZLibStream(),
source: source,
dataBounds: bounds.adjusted(by: 12),
uncompressedSize: uncompressedSize)
try decompress(stream: ZLibStream(),
source: source, offset: 12,
output: &self)
}
public func fetch(from addr: Address,
into buffer: UnsafeMutableRawBufferPointer) throws {
let toFetch = buffer.count
if addr < 0 || addr > data.count || data.count - Int(addr) < toFetch {
throw CompressedImageSourceError.outOfRangeFetch(addr, toFetch)
}
init(lzmaCompressedImageSource source: ImageSource) throws {
self.init(isMappedImage: false, path: nil)
buffer.withMemoryRebound(to: UInt8.self) { outBuf in
for n in 0..<toFetch {
outBuf[n] = data[Int(addr) + n]
}
}
try decompressChunked(stream: LZMAStream(),
source: source, offset: 0,
output: &self)
}
}
internal struct LZMACompressedImageSource: ImageSource {
private var data: [UInt8]
var isMappedImage: Bool { return false }
var path: String? { return nil }
var bounds: Bounds? { return Bounds(base: Address(0), size: Size(data.count)) }
init(source: some ImageSource) throws {
// Only supported for bounded image sources
guard let bounds = source.bounds else {
throw CompressedImageSourceError.unboundedImageSource
}
data = try decompress(stream: LZMAStream(),
source: source,
dataBounds: bounds)
}
public func fetch(from addr: Address,
into buffer: UnsafeMutableRawBufferPointer) throws {
let toFetch = buffer.count
if addr < 0 || addr > data.count || data.count - Int(addr) < toFetch {
throw CompressedImageSourceError.outOfRangeFetch(addr, toFetch)
}
buffer.withMemoryRebound(to: UInt8.self) { outBuf in
for n in 0..<toFetch {
outBuf[n] = data[Int(addr) + n]
}
}
}
}
#endif // os(Linux)

View File

@@ -15,8 +15,6 @@
//
//===----------------------------------------------------------------------===//
#if os(Linux)
import Swift
internal import BacktracingImpl.ImageFormats.Dwarf
@@ -500,7 +498,7 @@ enum DwarfSection {
protocol DwarfSource {
func getDwarfSection(_ section: DwarfSection) -> (any ImageSource)?
func getDwarfSection(_ section: DwarfSection) -> ImageSource?
}
@@ -523,14 +521,14 @@ struct DwarfReader<S: DwarfSource> {
var attributes: [(Dwarf_Attribute, Dwarf_Form, Int64?)]
}
var infoSection: any ImageSource
var abbrevSection: any ImageSource
var lineSection: (any ImageSource)?
var addrSection: (any ImageSource)?
var strSection: (any ImageSource)?
var lineStrSection: (any ImageSource)?
var strOffsetsSection: (any ImageSource)?
var rangesSection: (any ImageSource)?
var infoSection: ImageSource
var abbrevSection: ImageSource
var lineSection: ImageSource?
var addrSection: ImageSource?
var strSection: ImageSource?
var lineStrSection: ImageSource?
var strOffsetsSection: ImageSource?
var rangesSection: ImageSource?
var shouldSwap: Bool
typealias DwarfAbbrev = UInt64
@@ -557,263 +555,9 @@ struct DwarfReader<S: DwarfSource> {
var attributes: [Dwarf_Attribute:DwarfValue] = [:]
}
struct FileInfo {
var path: String
var directoryIndex: Int?
var timestamp: Int?
var size: UInt64?
var md5sum: [UInt8]?
}
struct LineNumberState: CustomStringConvertible {
var address: Address
var opIndex: UInt
var file: Int
var path: String
var line: Int
var column: Int
var isStmt: Bool
var basicBlock: Bool
var endSequence: Bool
var prologueEnd: Bool
var epilogueBegin: Bool
var isa: UInt
var discriminator: UInt
var description: String {
var flags: [String] = []
if isStmt {
flags.append("is_stmt")
}
if basicBlock {
flags.append("basic_block")
}
if endSequence {
flags.append("end_sequence")
}
if prologueEnd {
flags.append("prologue_end")
}
if epilogueBegin {
flags.append("epilogue_begin")
}
let flagsString = flags.joined(separator:" ")
return """
\(hex(address)) \(pad(line, 6)) \(pad(column, 6)) \(pad(file, 6)) \
\(pad(isa, 3)) \(pad(discriminator, 13)) \(flagsString)
"""
}
}
struct LineNumberInfo {
var baseOffset: Address
var version: Int
var addressSize: Int?
var selectorSize: Int?
var headerLength: UInt64
var minimumInstructionLength: UInt
var maximumOpsPerInstruction: UInt
var defaultIsStmt: Bool
var lineBase: Int8
var lineRange: UInt8
var opcodeBase: UInt8
var standardOpcodeLengths: [UInt64]
var directories: [String] = []
var files: [FileInfo] = []
var program: [UInt8] = []
var shouldSwap: Bool
/// Compute the full path for a file, given its index in the file table.
func fullPathForFile(index: Int) -> String {
if index >= files.count {
return "<unknown>"
}
let info = files[index]
if info.path.hasPrefix("/") {
return info.path
}
let dirName: String
if let dirIndex = info.directoryIndex,
dirIndex < directories.count {
dirName = directories[dirIndex]
} else {
dirName = "<unknown>"
}
return "\(dirName)/\(info.path)"
}
/// Execute the line number program, calling a closure for every line
/// table entry.
mutating func executeProgram(
line: (LineNumberState, inout Bool) -> ()
) throws {
let source = ArrayImageSource(array: program)
let bounds = source.bounds!
var cursor = ImageSourceCursor(source: source)
func maybeSwap<T: FixedWidthInteger>(_ x: T) -> T {
if shouldSwap {
return x.byteSwapped
}
return x
}
// Table 6.4: Line number program initial state
let initialState = LineNumberState(
address: 0,
opIndex: 0,
file: 1,
path: fullPathForFile(index: 1),
line: 1,
column: 0,
isStmt: defaultIsStmt,
basicBlock: false,
endSequence: false,
prologueEnd: false,
epilogueBegin: false,
isa: 0,
discriminator: 0
)
var state = initialState
// Flag to allow fast exit
var done = false
while !done && cursor.pos < bounds.end {
let opcode = try cursor.read(as: Dwarf_LNS_Opcode.self)
if opcode.rawValue >= opcodeBase {
// Special opcode
let adjustedOpcode = UInt(opcode.rawValue - opcodeBase)
let advance = adjustedOpcode / UInt(lineRange)
let lineAdvance = adjustedOpcode % UInt(lineRange)
let instrAdvance
= (state.opIndex + advance) / maximumOpsPerInstruction
let newOp = (state.opIndex + advance) % maximumOpsPerInstruction
state.address += Address(instrAdvance)
state.opIndex = newOp
state.line += Int(lineBase) + Int(lineAdvance)
line(state, &done)
state.discriminator = 0
state.basicBlock = false
state.prologueEnd = false
state.epilogueBegin = false
} else if opcode == .DW_LNS_extended {
// Extended opcode
let length = try cursor.readULEB128()
let opcode = try cursor.read(as: Dwarf_LNE_Opcode.self)
switch opcode {
case .DW_LNE_end_sequence:
state.endSequence = true
line(state, &done)
state = initialState
case .DW_LNE_set_address:
let address: UInt64
guard let addressSize = addressSize else {
throw DwarfError.unspecifiedAddressSize
}
switch addressSize {
case 4:
address = UInt64(maybeSwap(try cursor.read(as: UInt32.self)))
case 8:
address = maybeSwap(try cursor.read(as: UInt64.self))
default:
throw DwarfError.badAddressSize(addressSize)
}
state.address = Address(address)
case .DW_LNE_define_file:
guard let path = try cursor.readString() else {
throw DwarfError.badString
}
let directoryIndex = try cursor.readULEB128()
let timestamp = try cursor.readULEB128()
let size = try cursor.readULEB128()
files.append(FileInfo(
path: path,
directoryIndex: Int(directoryIndex),
timestamp: timestamp != 0 ? Int(timestamp) : nil,
size: size != 0 ? size : nil,
md5sum: nil
))
case .DW_LNE_set_discriminator:
let discriminator = try cursor.readULEB128()
state.discriminator = UInt(discriminator)
default:
cursor.pos += length - 1
}
} else {
// Standard opcode
switch opcode {
case .DW_LNS_copy:
line(state, &done)
state.discriminator = 0
state.basicBlock = false
state.prologueEnd = false
state.epilogueBegin = false
case .DW_LNS_advance_pc:
let advance = UInt(try cursor.readULEB128())
let instrAdvance
= (state.opIndex + advance) / maximumOpsPerInstruction
let newOp = (state.opIndex + advance) % maximumOpsPerInstruction
state.address += Address(instrAdvance)
state.opIndex = newOp
case .DW_LNS_advance_line:
let advance = try cursor.readSLEB128()
state.line += Int(advance)
case .DW_LNS_set_file:
let file = Int(try cursor.readULEB128())
state.file = file
state.path = fullPathForFile(index: state.file)
case .DW_LNS_set_column:
let column = Int(try cursor.readULEB128())
state.column = column
case .DW_LNS_negate_stmt:
state.isStmt = !state.isStmt
case .DW_LNS_set_basic_block:
state.basicBlock = true
case .DW_LNS_const_add_pc:
let adjustedOpcode = UInt(255 - opcodeBase)
let advance = adjustedOpcode / UInt(lineRange)
let instrAdvance
= (state.opIndex + advance) / maximumOpsPerInstruction
let newOp = (state.opIndex + advance) % maximumOpsPerInstruction
state.address += Address(instrAdvance)
state.opIndex = newOp
case .DW_LNS_fixed_advance_pc:
let advance = try cursor.read(as: Dwarf_Half.self)
state.address += Address(advance)
state.opIndex = 0
case .DW_LNS_set_prologue_end:
state.prologueEnd = true
case .DW_LNS_set_epilogue_begin:
state.epilogueBegin = true
case .DW_LNS_set_isa:
let isa = UInt(try cursor.readULEB128())
state.isa = isa
default:
// Skip this unknown opcode
let length = standardOpcodeLengths[Int(opcode.rawValue)]
for _ in 0..<length {
_ = try cursor.readULEB128()
}
}
}
}
}
}
var units: [Unit] = []
var lineNumberInfo: [LineNumberInfo] = []
var lineNumberInfo: [DwarfLineNumberInfo] = []
struct RangeListInfo {
var length: UInt64
@@ -827,6 +571,8 @@ struct DwarfReader<S: DwarfSource> {
var rangeListInfo: RangeListInfo?
@_specialize(kind: full, where S == Elf32Image)
@_specialize(kind: full, where S == Elf64Image)
init(source: Source, shouldSwap: Bool = false) throws {
// ###TODO: This should be optional, because we can have just line number
// information. We should test that, too.
@@ -873,7 +619,7 @@ struct DwarfReader<S: DwarfSource> {
}
lineNumberInfo[n].directories[0] = dirname
lineNumberInfo[n].files[0] = FileInfo(
lineNumberInfo[n].files[0] = DwarfFileInfo(
path: filename,
directoryIndex: 0,
timestamp: nil,
@@ -896,14 +642,11 @@ struct DwarfReader<S: DwarfSource> {
}
private func readUnits() throws -> [Unit] {
guard let bounds = infoSection.bounds else {
return []
}
let end = infoSection.bytes.count
var units: [Unit] = []
var cursor = ImageSourceCursor(source: infoSection)
while cursor.pos < bounds.end {
while cursor.pos < end {
// See 7.5.1.1 Full and Partial Compilation Unit Headers
let base = cursor.pos
@@ -1053,16 +796,16 @@ struct DwarfReader<S: DwarfSource> {
return units
}
private func readLineNumberInfo() throws -> [LineNumberInfo] {
guard let lineSection = lineSection,
let bounds = lineSection.bounds else {
private func readLineNumberInfo() throws -> [DwarfLineNumberInfo] {
guard let lineSection = lineSection else {
return []
}
var result: [LineNumberInfo] = []
let end = lineSection.bytes.count
var result: [DwarfLineNumberInfo] = []
var cursor = ImageSourceCursor(source: lineSection, offset: 0)
while cursor.pos < bounds.end {
while cursor.pos < end {
// 6.2.4 The Line Number Program Header
// .1 unit_length
@@ -1127,7 +870,7 @@ struct DwarfReader<S: DwarfSource> {
}
var dirNames: [String] = []
var fileInfo: [FileInfo] = []
var fileInfo: [DwarfFileInfo] = []
if version == 3 || version == 4 {
// .11 include_directories
@@ -1152,7 +895,7 @@ struct DwarfReader<S: DwarfSource> {
// Prior to version 5, the compilation unit's filename is not included;
// put a placeholder here for now, which we'll fix up later.
fileInfo.append(FileInfo(
fileInfo.append(DwarfFileInfo(
path: "<unknown>",
directoryIndex: 0,
timestamp: nil,
@@ -1172,7 +915,7 @@ struct DwarfReader<S: DwarfSource> {
let timestamp = try cursor.readULEB128()
let size = try cursor.readULEB128()
fileInfo.append(FileInfo(
fileInfo.append(DwarfFileInfo(
path: path,
directoryIndex: Int(dirIndex),
timestamp: timestamp != 0 ? Int(timestamp) : nil,
@@ -1275,7 +1018,7 @@ struct DwarfReader<S: DwarfSource> {
md5sum = nil
}
fileInfo.append(FileInfo(
fileInfo.append(DwarfFileInfo(
path: path,
directoryIndex: dirIndex,
timestamp: timestamp,
@@ -1285,12 +1028,10 @@ struct DwarfReader<S: DwarfSource> {
}
// The actual program comes next
let program = try cursor.read(count: Int(nextOffset - cursor.pos),
as: UInt8.self)
let program = cursor.source[cursor.pos..<nextOffset]
cursor.pos = nextOffset
result.append(LineNumberInfo(
result.append(DwarfLineNumberInfo(
baseOffset: baseOffset,
version: version,
addressSize: addressSize,
@@ -1829,7 +1570,7 @@ struct DwarfReader<S: DwarfSource> {
if tag != .DW_TAG_subprogram {
return
}
refAttrs = try readDieAttributes(
at: &cursor,
unit: unit,
@@ -2005,11 +1746,268 @@ struct DwarfReader<S: DwarfSource> {
}
struct DwarfFileInfo {
var path: String
var directoryIndex: Int?
var timestamp: Int?
var size: UInt64?
var md5sum: [UInt8]?
}
struct DwarfLineNumberState: CustomStringConvertible {
typealias Address = UInt64
var address: Address
var opIndex: UInt
var file: Int
var path: String
var line: Int
var column: Int
var isStmt: Bool
var basicBlock: Bool
var endSequence: Bool
var prologueEnd: Bool
var epilogueBegin: Bool
var isa: UInt
var discriminator: UInt
var description: String {
var flags: [String] = []
if isStmt {
flags.append("is_stmt")
}
if basicBlock {
flags.append("basic_block")
}
if endSequence {
flags.append("end_sequence")
}
if prologueEnd {
flags.append("prologue_end")
}
if epilogueBegin {
flags.append("epilogue_begin")
}
let flagsString = flags.joined(separator:" ")
return """
\(hex(address)) \(pad(line, 6)) \(pad(column, 6)) \(pad(file, 6)) \
\(pad(isa, 3)) \(pad(discriminator, 13)) \(flagsString)
"""
}
}
struct DwarfLineNumberInfo {
typealias Address = UInt64
var baseOffset: Address
var version: Int
var addressSize: Int?
var selectorSize: Int?
var headerLength: UInt64
var minimumInstructionLength: UInt
var maximumOpsPerInstruction: UInt
var defaultIsStmt: Bool
var lineBase: Int8
var lineRange: UInt8
var opcodeBase: UInt8
var standardOpcodeLengths: [UInt64]
var directories: [String] = []
var files: [DwarfFileInfo] = []
var program: ImageSource
var shouldSwap: Bool
/// Compute the full path for a file, given its index in the file table.
func fullPathForFile(index: Int) -> String {
if index >= files.count {
return "<unknown>"
}
let info = files[index]
if info.path.hasPrefix("/") {
return info.path
}
let dirName: String
if let dirIndex = info.directoryIndex,
dirIndex < directories.count {
dirName = directories[dirIndex]
} else {
dirName = "<unknown>"
}
return "\(dirName)/\(info.path)"
}
/// Execute the line number program, calling a closure for every line
/// table entry.
mutating func executeProgram(
line: (DwarfLineNumberState, inout Bool) -> ()
) throws {
let end = program.bytes.count
var cursor = ImageSourceCursor(source: program)
func maybeSwap<T: FixedWidthInteger>(_ x: T) -> T {
if shouldSwap {
return x.byteSwapped
}
return x
}
// Table 6.4: Line number program initial state
let initialState = DwarfLineNumberState(
address: 0,
opIndex: 0,
file: 1,
path: fullPathForFile(index: 1),
line: 1,
column: 0,
isStmt: defaultIsStmt,
basicBlock: false,
endSequence: false,
prologueEnd: false,
epilogueBegin: false,
isa: 0,
discriminator: 0
)
var state = initialState
// Flag to allow fast exit
var done = false
while !done && cursor.pos < end {
let opcode = try cursor.read(as: Dwarf_LNS_Opcode.self)
if opcode.rawValue >= opcodeBase {
// Special opcode
let adjustedOpcode = UInt(opcode.rawValue - opcodeBase)
let advance = adjustedOpcode / UInt(lineRange)
let lineAdvance = adjustedOpcode % UInt(lineRange)
let instrAdvance
= (state.opIndex + advance) / maximumOpsPerInstruction
let newOp = (state.opIndex + advance) % maximumOpsPerInstruction
state.address += Address(instrAdvance)
state.opIndex = newOp
state.line += Int(lineBase) + Int(lineAdvance)
line(state, &done)
state.discriminator = 0
state.basicBlock = false
state.prologueEnd = false
state.epilogueBegin = false
} else if opcode == .DW_LNS_extended {
// Extended opcode
let length = try cursor.readULEB128()
let opcode = try cursor.read(as: Dwarf_LNE_Opcode.self)
switch opcode {
case .DW_LNE_end_sequence:
state.endSequence = true
line(state, &done)
state = initialState
case .DW_LNE_set_address:
let address: UInt64
guard let addressSize = addressSize else {
throw DwarfError.unspecifiedAddressSize
}
switch addressSize {
case 4:
address = UInt64(maybeSwap(try cursor.read(as: UInt32.self)))
case 8:
address = maybeSwap(try cursor.read(as: UInt64.self))
default:
throw DwarfError.badAddressSize(addressSize)
}
state.address = Address(address)
case .DW_LNE_define_file:
guard let path = try cursor.readString() else {
throw DwarfError.badString
}
let directoryIndex = try cursor.readULEB128()
let timestamp = try cursor.readULEB128()
let size = try cursor.readULEB128()
files.append(DwarfFileInfo(
path: path,
directoryIndex: Int(directoryIndex),
timestamp: timestamp != 0 ? Int(timestamp) : nil,
size: size != 0 ? size : nil,
md5sum: nil
))
case .DW_LNE_set_discriminator:
let discriminator = try cursor.readULEB128()
state.discriminator = UInt(discriminator)
default:
cursor.pos += length - 1
}
} else {
// Standard opcode
switch opcode {
case .DW_LNS_copy:
line(state, &done)
state.discriminator = 0
state.basicBlock = false
state.prologueEnd = false
state.epilogueBegin = false
case .DW_LNS_advance_pc:
let advance = UInt(try cursor.readULEB128())
let instrAdvance
= (state.opIndex + advance) / maximumOpsPerInstruction
let newOp = (state.opIndex + advance) % maximumOpsPerInstruction
state.address += Address(instrAdvance)
state.opIndex = newOp
case .DW_LNS_advance_line:
let advance = try cursor.readSLEB128()
state.line += Int(advance)
case .DW_LNS_set_file:
let file = Int(try cursor.readULEB128())
state.file = file
state.path = fullPathForFile(index: state.file)
case .DW_LNS_set_column:
let column = Int(try cursor.readULEB128())
state.column = column
case .DW_LNS_negate_stmt:
state.isStmt = !state.isStmt
case .DW_LNS_set_basic_block:
state.basicBlock = true
case .DW_LNS_const_add_pc:
let adjustedOpcode = UInt(255 - opcodeBase)
let advance = adjustedOpcode / UInt(lineRange)
let instrAdvance
= (state.opIndex + advance) / maximumOpsPerInstruction
let newOp = (state.opIndex + advance) % maximumOpsPerInstruction
state.address += Address(instrAdvance)
state.opIndex = newOp
case .DW_LNS_fixed_advance_pc:
let advance = try cursor.read(as: Dwarf_Half.self)
state.address += Address(advance)
state.opIndex = 0
case .DW_LNS_set_prologue_end:
state.prologueEnd = true
case .DW_LNS_set_epilogue_begin:
state.epilogueBegin = true
case .DW_LNS_set_isa:
let isa = UInt(try cursor.readULEB128())
state.isa = isa
default:
// Skip this unknown opcode
let length = standardOpcodeLengths[Int(opcode.rawValue)]
for _ in 0..<length {
_ = try cursor.readULEB128()
}
}
}
}
}
}
// .. Testing ..................................................................
@_spi(DwarfTest)
public func testDwarfReaderFor(path: String) -> Bool {
guard let source = try? FileImageSource(path: path) else {
guard let source = try? ImageSource(path: path) else {
print("\(path) was not accessible")
return false
}
@@ -2017,7 +2015,7 @@ public func testDwarfReaderFor(path: String) -> Bool {
if let elfImage = try? Elf32Image(source: source) {
print("\(path) is a 32-bit ELF image")
var reader: DwarfReader<Elf32Image<FileImageSource>>
var reader: DwarfReader<Elf32Image>
do {
reader = try DwarfReader(source: elfImage)
} catch {
@@ -2034,7 +2032,7 @@ public func testDwarfReaderFor(path: String) -> Bool {
} else if let elfImage = try? Elf64Image(source: source) {
print("\(path) is a 64-bit ELF image")
var reader: DwarfReader<Elf64Image<FileImageSource>>
var reader: DwarfReader<Elf64Image>
do {
reader = try DwarfReader(source: elfImage)
} catch {
@@ -2060,5 +2058,3 @@ public func testDwarfReaderFor(path: String) -> Bool {
return false
}
}
#endif // os(Linux)

View File

@@ -0,0 +1,60 @@
//===--- EightByteBuffer.swift --------------------------------*- swift -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// A statically allocated buffer for holding a small number of bytes.
//
//===----------------------------------------------------------------------===//
import Swift
struct EightByteBuffer {
var word: UInt64
init() {
word = 0
}
init(_ qword: UInt64) {
word = qword.bigEndian
}
init(_ qword: Int64) {
self.init(UInt64(bitPattern: qword))
}
init<T: FixedWidthInteger>(_ value: T) where T: SignedInteger {
self.init(Int64(value))
}
init<T: FixedWidthInteger>(_ value: T) {
self.init(UInt64(value))
}
subscript(ndx: Int) -> UInt8 {
get {
if ndx < 0 || ndx >= 8 {
fatalError("Index out of range")
}
return withUnsafeBytes(of: word) { buffer in
return buffer[ndx]
}
}
set(newValue) {
if ndx < 0 || ndx >= 8 {
fatalError("Index out of range")
}
withUnsafeMutableBytes(of: &word) { buffer in
buffer[ndx] = newValue
}
}
}
}

View File

@@ -0,0 +1,95 @@
//===--- ElfImageCache.swift - ELF support for Swift ----------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Provides a per-thread Elf image cache that improves efficiency when
// taking multiple backtraces by avoiding loading ELF images multiple times.
//
//===----------------------------------------------------------------------===//
import Swift
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
internal import Darwin
#elseif os(Windows)
internal import ucrt
#elseif canImport(Glibc)
internal import Glibc
#elseif canImport(Musl)
internal import Musl
#endif
/// Provides a per-thread image cache for ELF image processing. This means
/// if you take multiple backtraces from a thread, you won't load the same
/// image multiple times.
final class ElfImageCache {
var elf32: [String: Elf32Image] = [:]
var elf64: [String: Elf64Image] = [:]
func purge() {
elf32 = [:]
elf64 = [:]
}
enum Result {
case elf32Image(Elf32Image)
case elf64Image(Elf64Image)
}
func lookup(path: String?) -> Result? {
guard let path = path else {
return nil
}
if let image = elf32[path] {
return .elf32Image(image)
}
if let image = elf64[path] {
return .elf64Image(image)
}
if let source = try? ImageSource(path: path) {
if let elfImage = try? Elf32Image(source: source) {
elf32[path] = elfImage
return .elf32Image(elfImage)
}
if let elfImage = try? Elf64Image(source: source) {
elf64[path] = elfImage
return .elf64Image(elfImage)
}
}
return nil
}
private static var key: pthread_key_t? = {
var theKey = pthread_key_t()
let err = pthread_key_create(
&theKey,
{ rawPtr in
let ptr = Unmanaged<ElfImageCache>.fromOpaque(
notMutable(notOptional(rawPtr))
)
ptr.release()
}
)
if err != 0 {
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()
}
}

View File

@@ -20,7 +20,7 @@ import Swift
public struct FramePointerUnwinder<C: Context, M: MemoryReader>: Sequence, IteratorProtocol {
public typealias Context = C
public typealias MemoryReader = M
public typealias Address = MemoryReader.Address
public typealias Address = Context.Address
var pc: Address
var fp: Address
@@ -30,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)
}
}

View File

@@ -23,17 +23,15 @@ struct ImageSymbol {
}
internal protocol Image {
associatedtype Source: ImageSource
typealias UUID = [UInt8]
typealias Address = Source.Address
typealias Address = ImageSource.Address
init(source: Source, baseAddress: Address, endAddress: Address) throws
init(source: ImageSource, baseAddress: Address, endAddress: Address) throws
var baseAddress: Address { get set }
var endAddress: Address { get set }
var source: Source { get }
var source: ImageSource { get }
var uuid: UUID? { get }
var shouldByteSwap: Bool { get }

View File

@@ -0,0 +1,436 @@
//===--- ImageSource.swift - A place from which to read image data --------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Defines ImageSource, which tells us where to look for image data.
//
//===----------------------------------------------------------------------===//
import Swift
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
internal import Darwin
#elseif os(Windows)
internal import ucrt
#elseif canImport(Glibc)
internal import Glibc
#elseif canImport(Musl)
internal import Musl
#endif
enum ImageSourceError: Error {
case outOfBoundsRead
case posixError(Int32)
}
struct ImageSource {
private class Storage {
/// Says how we allocated the buffer.
private enum MemoryBufferKind {
/// Currently empty
case empty
/// Allocated with UnsafeRawBufferPointer.allocate()
case allocated(Int)
/// Allocated by mapping memory with mmap() or similar
case mapped
/// A reference to a subordinate storage
case substorage(Storage)
/// Not allocated (probably points to a loaded image)
case unowned
}
private var kind: MemoryBufferKind
/// The pointer to the actual memory
private(set) var bytes: UnsafeRawBufferPointer!
/// Gets a mutable pointer to the actual memory
var mutableBytes: UnsafeMutableRawBufferPointer {
guard case let .allocated(count) = kind else {
fatalError("attempted to get mutable reference to immutable ImageSource")
}
return UnsafeMutableRawBufferPointer(
mutating: UnsafeRawBufferPointer(rebasing: bytes[0..<count])
)
}
/// Gets a mutable pointer to the unused space
var unusedBytes: UnsafeMutableRawBufferPointer {
guard case let .allocated(count) = kind else {
fatalError("attempted to get mutable reference to immutable ImageSource")
}
return UnsafeMutableRawBufferPointer(
mutating: UnsafeRawBufferPointer(rebasing: bytes[count...])
)
}
/// Return the number of bytes in this ImageSource
var count: Int {
switch kind {
case .empty:
return 0
case let .allocated(count):
return count
case .mapped, .substorage, .unowned:
return bytes.count
}
}
@inline(__always)
private func _rangeCheck(_ ndx: Int) {
if ndx < 0 || ndx >= count {
fatalError("ImageSource access out of range")
}
}
init() {
self.kind = .empty
self.bytes = nil
}
init(unowned buffer: UnsafeRawBufferPointer) {
self.kind = .unowned
self.bytes = buffer
}
init(mapped buffer: UnsafeRawBufferPointer) {
self.kind = .mapped
self.bytes = buffer
}
init(allocated buffer: UnsafeMutableRawBufferPointer, count: Int? = nil) {
self.kind = .allocated(count ?? buffer.count)
self.bytes = UnsafeRawBufferPointer(buffer)
}
convenience init(capacity: Int, alignment: Int = 0x4000) {
self.init(allocated: UnsafeMutableRawBufferPointer.allocate(
byteCount: capacity,
alignment: 0x1000
),
count: 0)
}
init(parent: Storage, range: Range<Int>) {
let chunk = UnsafeRawBufferPointer(rebasing: parent.bytes[range])
self.kind = .substorage(parent)
self.bytes = chunk
}
convenience init(path: String) throws {
let fd = open(path, O_RDONLY, 0)
if fd < 0 {
throw ImageSourceError.posixError(errno)
}
defer { close(fd) }
let size = lseek(fd, 0, SEEK_END)
if size < 0 {
throw ImageSourceError.posixError(errno)
}
let base = mmap(nil, Int(size), PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0)
if base == nil || base! == UnsafeRawPointer(bitPattern: -1)! {
throw ImageSourceError.posixError(errno)
}
self.init(mapped: UnsafeRawBufferPointer(
start: base, count: Int(size)))
}
deinit {
switch kind {
case .allocated:
mutableBytes.deallocate()
case .mapped:
munmap(UnsafeMutableRawPointer(mutating: bytes.baseAddress),
bytes.count)
case .substorage, .unowned, .empty:
break
}
}
/// Subscripting (read-only, for subranges)
subscript(range: Range<Int>) -> Storage {
return Storage(parent: self, range: range)
}
/// Resize the buffer; only supported for allocated or empty storage
func resize(newSize: Int) -> UnsafeMutableRawBufferPointer {
let newBuffer = UnsafeMutableRawBufferPointer.allocate(
byteCount: newSize,
alignment: 0x1000
)
switch kind {
case .empty:
kind = .allocated(0)
case let .allocated(count):
assert(newSize >= count)
let oldPart = UnsafeMutableRawBufferPointer(
rebasing: newBuffer[0..<count]
)
oldPart.copyMemory(from: bytes)
mutableBytes.deallocate()
kind = .allocated(count)
default:
fatalError("Cannot resize immutable image source storage")
}
bytes = UnsafeRawBufferPointer(newBuffer)
return newBuffer
}
/// Make sure the buffer has at least a certain number of bytes;
/// only supported for allocated or empty storage.
func requireAtLeast(byteCount: Int) -> UnsafeMutableRawBufferPointer {
let capacity: Int
switch kind {
case .empty:
capacity = 0
case .allocated:
capacity = bytes.count
default:
fatalError("Cannot resize immutable image source storage")
}
if capacity >= byteCount {
return mutableBytes
}
let extra = byteCount - capacity
let increment: Int
if capacity < 1048576 {
let roundedExtra = (extra + 0xffff) & ~0xffff
increment = max(roundedExtra, capacity)
} else {
let roundedExtra = (extra + 0xfffff) & ~0xfffff
let topBit = capacity.bitWidth - capacity.leadingZeroBitCount
increment = max(roundedExtra, 1048576 * (topBit - 20))
}
return resize(newSize: capacity + increment)
}
/// Mark a number of bytes in the mutable buffer as in use. This is
/// used when passing `unusedBytes` to some other code that fills in
/// part of the buffer.
func used(bytes: Int) {
guard bytes >= 0 else {
fatalError("Bytes should not be less than zero")
}
guard case let .allocated(count) = kind else {
fatalError("Cannot append to immutable image source storage")
}
guard mutableBytes.count - count <= bytes else {
fatalError("Buffer overrun detected")
}
kind = .allocated(count + bytes)
}
/// Append bytes to the mutable buffer; this is only supported for
/// allocated or empty storage.
func append(bytes toAppend: UnsafeRawBufferPointer) {
// Short circuit, otherwise we get in a muddle in requireAtLeast()
if toAppend.count == 0 {
return
}
let newCount = count + toAppend.count
let mutableBytes = requireAtLeast(byteCount: newCount)
guard case let .allocated(count) = kind else {
fatalError("Cannot append to immutable image source storage")
}
let dest = UnsafeMutableRawBufferPointer(
rebasing: mutableBytes[count..<newCount]
)
dest.copyMemory(from: toAppend)
kind = .allocated(newCount)
}
}
/// The storage holding the image data.
private var storage: Storage
/// The number of bytes of data this ImageSource holds.
var count: Int { return storage.count }
/// The memory holding the image data.
var bytes: UnsafeRawBufferPointer { return storage.bytes }
/// A mutable refernece to the image data (only for allocated storage)
var mutableBytes: UnsafeMutableRawBufferPointer { return storage.mutableBytes }
/// A mutable reference to unused bytes in the storage
var unusedBytes: UnsafeMutableRawBufferPointer { return storage.unusedBytes }
/// Says whether we are looking at a loaded (i.e. with ld.so or dyld) image.
private(set) var isMappedImage: Bool
/// If this ImageSource knows its path, this will be non-nil.
private(set) var path: String?
/// Private initialiser, not for general use
private init(storage: Storage, isMappedImage: Bool, path: String?) {
self.storage = storage
self.isMappedImage = isMappedImage
self.path = path
}
/// Initialise an empty storage
init(isMappedImage: Bool, path: String? = nil) {
self.init(storage: Storage(), isMappedImage: isMappedImage, path: path)
}
/// Initialise from unowned storage
init(unowned: UnsafeRawBufferPointer, isMappedImage: Bool, path: String? = nil) {
self.init(storage: Storage(unowned: unowned),
isMappedImage: isMappedImage, path: path)
}
/// Initialise from mapped storage
init(mapped: UnsafeRawBufferPointer, isMappedImage: Bool, path: String? = nil) {
self.init(storage: Storage(mapped: mapped),
isMappedImage: isMappedImage, path: path)
}
/// Initialise with a specified capacity
init(capacity: Int, isMappedImage: Bool, path: String? = nil) {
self.init(storage: Storage(capacity: capacity),
isMappedImage: isMappedImage, path: path)
}
/// Initialise with a mapped file
init(path: String) throws {
self.init(storage: try Storage(path: path),
isMappedImage: false, path: path)
}
/// Get a sub-range of this ImageSource as an ImageSource
subscript(range: Range<Address>) -> ImageSource {
let intRange = Int(range.lowerBound)..<Int(range.upperBound)
return ImageSource(storage: storage[intRange],
isMappedImage: isMappedImage,
path: path)
}
/// Mark unused bytes in the storage as used
func used(bytes: Int) {
storage.used(bytes: bytes)
}
/// Append bytes to an empty or allocated storage
func append(bytes toAppend: UnsafeRawBufferPointer) {
storage.append(bytes: toAppend)
}
}
// MemoryReader support
extension ImageSource: MemoryReader {
public func fetch(from address: Address,
into buffer: UnsafeMutableRawBufferPointer) throws {
let offset = Int(address)
guard bytes.count >= buffer.count &&
offset <= bytes.count - buffer.count else {
throw ImageSourceError.outOfBoundsRead
}
buffer.copyMemory(from: UnsafeRawBufferPointer(
rebasing: bytes[offset..<offset + buffer.count]))
}
public func fetch<T>(from address: Address, as type: T.Type) throws -> T {
let size = MemoryLayout<T>.size
let offset = Int(address)
guard offset <= bytes.count - size else {
throw ImageSourceError.outOfBoundsRead
}
return bytes.loadUnaligned(fromByteOffset: offset, as: type)
}
public func fetchString(from address: Address) throws -> String? {
let offset = Int(address)
let len = strnlen(bytes.baseAddress! + offset, bytes.count - offset)
let stringBytes = bytes[offset..<offset+len]
return String(decoding: stringBytes, as: UTF8.self)
}
public func fetchString(from address: Address, length: Int) throws -> String? {
let offset = Int(address)
let stringBytes = bytes[offset..<offset+length]
return String(decoding: stringBytes, as: UTF8.self)
}
}
/// Used as a cursor by the DWARF code
struct ImageSourceCursor {
typealias Address = ImageSource.Address
typealias Size = ImageSource.Size
var source: ImageSource
var pos: Address
init(source: ImageSource, offset: Address = 0) {
self.source = source
self.pos = offset
}
mutating func read(into buffer: UnsafeMutableRawBufferPointer) throws {
try source.fetch(from: pos, into: buffer)
pos += Size(buffer.count)
}
mutating func read<T>(into buffer: UnsafeMutableBufferPointer<T>) throws {
try source.fetch(from: pos, into: buffer)
pos += Size(MemoryLayout<T>.stride * buffer.count)
}
mutating func read<T>(into pointer: UnsafeMutablePointer<T>) throws {
try source.fetch(from: pos, into: pointer)
pos += Size(MemoryLayout<T>.stride)
}
mutating func read<T>(as type: T.Type) throws -> T {
let result = try source.fetch(from: pos, as: type)
pos += Size(MemoryLayout<T>.stride)
return result
}
mutating func read<T>(count: Int, as type: T.Type) throws -> [T] {
let result = try source.fetch(from: pos, count: count, as: type)
pos += Size(MemoryLayout<T>.stride * count)
return result
}
mutating func readString() throws -> String? {
guard let result = try source.fetchString(from: pos) else {
return nil
}
pos += Size(result.utf8.count + 1) // +1 for the NUL
return result
}
mutating func readString(length: Int) throws -> String? {
guard let result = try source.fetchString(from: pos, length: length) else {
return nil
}
pos += Size(length)
return result
}
}

View File

@@ -0,0 +1,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)
}
}
}

View File

@@ -57,6 +57,9 @@ internal import BacktracingImpl.OS.Darwin
/// Fetch a NUL terminated string from the specified location in the source
func fetchString(from addr: Address) throws -> String?
/// Fetch a fixed-length string from the specified location in the source
func fetchString(from addr: Address, length: Int) throws -> String?
}
extension MemoryReader {
@@ -106,6 +109,10 @@ extension MemoryReader {
return String(decoding: bytes, as: UTF8.self)
}
public func fetchString(from addr: Address, length: Int) throws -> String? {
let bytes = try fetch(from: addr, count: length, as: UInt8.self)
return String(decoding: bytes, as: UTF8.self)
}
}
@_spi(MemoryReaders) public struct UnsafeLocalMemoryReader: MemoryReader {
@@ -118,6 +125,16 @@ extension MemoryReader {
byteCount: buffer.count
)
}
public func fetch<T>(from address: Address, as type: T.Type) throws -> T {
let ptr = UnsafeRawPointer(bitPattern: UInt(address))!
return ptr.loadUnaligned(fromByteOffset: 0, as: type)
}
public func fetchString(from address: Address) throws -> String? {
let ptr = UnsafeRawPointer(bitPattern: UInt(address))!
return String(validatingUTF8: ptr.assumingMemoryBound(to: CChar.self))
}
}
#if os(macOS)
@@ -125,7 +142,8 @@ extension MemoryReader {
var result: kern_return_t
}
@_spi(MemoryReaders) public struct RemoteMemoryReader: MemoryReader {
@_spi(MemoryReaders)
public struct UncachedRemoteMemoryReader: MemoryReader {
private var task: task_t
// Sadly we can't expose the type of this argument
@@ -151,13 +169,14 @@ extension MemoryReader {
}
}
@_spi(MemoryReaders) public struct LocalMemoryReader: MemoryReader {
@_spi(MemoryReaders)
public struct UncachedLocalMemoryReader: MemoryReader {
public typealias Address = UInt64
public typealias Size = UInt64
public func fetch(from address: Address,
into buffer: UnsafeMutableRawBufferPointer) throws {
let reader = RemoteMemoryReader(task: mach_task_self())
let reader = UncachedRemoteMemoryReader(task: mach_task_self())
return try reader.fetch(from: address, into: buffer)
}
}
@@ -172,7 +191,8 @@ extension MemoryReader {
var message: String
}
@_spi(MemoryReaders) public struct MemserverMemoryReader: MemoryReader {
@_spi(MemoryReaders)
public struct UncachedMemserverMemoryReader: MemoryReader {
private var fd: CInt
public init(fd: CInt) {
@@ -267,7 +287,8 @@ extension MemoryReader {
}
}
@_spi(MemoryReaders) public struct RemoteMemoryReader: MemoryReader {
@_spi(MemoryReaders)
public struct UncachedRemoteMemoryReader: MemoryReader {
private var pid: pid_t
public init(pid: Any) {
@@ -288,7 +309,8 @@ extension MemoryReader {
}
}
@_spi(MemoryReaders) public struct LocalMemoryReader: MemoryReader {
@_spi(MemoryReaders)
public struct UncachedLocalMemoryReader: MemoryReader {
private var reader: RemoteMemoryReader
init() {

View File

@@ -0,0 +1,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
}
}
}

View File

@@ -92,20 +92,15 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
}
/// A textual description of this frame.
public func description(width: Int) -> String {
public var description: String {
if let symbol = symbol {
let isInlined = inlined ? " [inlined]" : ""
let isThunk = isSwiftThunk ? " [thunk]" : ""
return "\(captured.description(width: width))\(isInlined)\(isThunk) \(symbol)"
return "\(captured.description)\(isInlined)\(isThunk) \(symbol)"
} else {
return captured.description(width: width)
return captured.description
}
}
/// A textual description of this frame.
public var description: String {
return description(width: MemoryLayout<Backtrace.Address>.size * 2)
}
}
/// Represents a symbol we've located
@@ -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")

View File

@@ -95,3 +95,32 @@ public func stripWhitespace<S: StringProtocol>(_ s: S)
let lastNonWhitespace = s.lastIndex(where: { !$0.isWhitespace })!
return s[firstNonWhitespace...lastNonWhitespace]
}
/// Strip any Optional from a value.
///
/// This is useful when interfacing with the system C library, because some
/// C libraries have nullability annotations while others do not.
func notOptional<T>(_ optional: T?) -> T {
return optional!
}
func notOptional<T>(_ value: T) -> T {
return value
}
/// Convert mutable pointers to non-mutable
///
/// This is useful when interfacing with the system C library, because some
/// C libraries have const annotations in places others do not.
func notMutable<T>(_ mutable: UnsafeMutablePointer<T>) -> UnsafePointer<T> {
return UnsafePointer<T>(mutable)
}
func notMutable<T>(_ immutable: UnsafePointer<T>) -> UnsafePointer<T> {
return immutable
}
func notMutable(_ mutable: UnsafeMutableRawPointer) -> UnsafeRawPointer {
return UnsafeRawPointer(mutable)
}
func notMutable(_ immutable: UnsafeRawPointer) -> UnsafeRawPointer {
return immutable
}

View File

@@ -23,21 +23,23 @@
#include "swift/Runtime/CrashInfo.h"
#ifdef __cplusplus
#define EXTERN_C extern "C"
#else
#define EXTERN_C
extern "C" {
#endif
// Can't import swift/Runtime/Debug.h because it assumes C++
EXTERN_C void swift_reportWarning(uint32_t flags, const char *message);
void swift_reportWarning(uint32_t flags, const char *message);
// Returns true if the given function is a thunk function
EXTERN_C bool _swift_backtrace_isThunkFunction(const char *rawName);
bool _swift_backtrace_isThunkFunction(const char *rawName);
// Demangle the given raw name (supports Swift and C++)
EXTERN_C char *_swift_backtrace_demangle(const char *rawName,
size_t rawNameLength,
char *outputBuffer,
size_t *outputBufferSize);
char *_swift_backtrace_demangle(const char *rawName,
size_t rawNameLength,
char *outputBuffer,
size_t *outputBufferSize);
#ifdef __cplusplus
}
#endif
#endif // SWIFT_BACKTRACING_RUNTIME_H

View File

@@ -335,7 +335,7 @@ endif()
list(APPEND swift_stdlib_compile_flags "-plugin-path" "${swift_lib_dir}/swift/host/plugins")
set(swift_core_incorporate_object_libraries)
list(APPEND swift_core_incorporate_object_libraries swiftRuntime)
list(APPEND swift_core_incorporate_object_libraries swiftRuntimeCore)
list(APPEND swift_core_incorporate_object_libraries swiftLLVMSupport)
list(APPEND swift_core_incorporate_object_libraries swiftDemangling)
list(APPEND swift_core_incorporate_object_libraries swiftStdlibStubs)

View File

@@ -12,11 +12,11 @@ if(SWIFT_BUILD_SDK_OVERLAY)
set(musl Musl)
endif()
# Similarly, we only want the _Backtracing dependency if we're building
# Similarly, we only want the Runtime dependency if we're building
# with the stdlib.
set(backtracing)
if(SWIFT_BUILD_STDLIB AND SWIFT_ENABLE_BACKTRACING)
set(backtracing _Backtracing)
set(runtime)
if(SWIFT_BUILD_STDLIB AND SWIFT_ENABLE_RUNTIME_MODULE)
set(runtime Runtime)
endif()
if(NOT SWIFT_BUILD_STDLIB)
@@ -25,7 +25,7 @@ endif()
set(BACKTRACING_COMPILE_FLAGS
"-cxx-interoperability-mode=default"
"-I${SWIFT_STDLIB_SOURCE_DIR}/public/Backtracing/modules"
"-I${SWIFT_STDLIB_SOURCE_DIR}/public/RuntimeModule/modules"
"-Xcc;-I${SWIFT_SOURCE_DIR}/include"
"-Xcc;-I${CMAKE_BINARY_DIR}/include"
"-disable-upcoming-feature;MemberImportVisibility")
@@ -51,7 +51,7 @@ endif()
add_swift_target_executable(swift-backtrace BUILD_WITH_LIBEXEC
${BACKTRACING_SOURCES}
SWIFT_MODULE_DEPENDS ${backtracing}
SWIFT_MODULE_DEPENDS ${runtime}
SWIFT_MODULE_DEPENDS_OSX ${darwin}
SWIFT_MODULE_DEPENDS_WINDOWS ${wincrt_sdk}

View File

@@ -23,11 +23,11 @@ import Glibc
import Musl
#endif
import _Backtracing
@_spi(Internal) import _Backtracing
@_spi(Contexts) import _Backtracing
@_spi(MemoryReaders) import _Backtracing
@_spi(Utils) import _Backtracing
import Runtime
@_spi(Internal) import Runtime
@_spi(Contexts) import Runtime
@_spi(MemoryReaders) import Runtime
@_spi(Utils) import Runtime
enum SomeBacktrace {
case raw(Backtrace)
@@ -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
}

View File

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

View File

@@ -14,7 +14,7 @@
//
//===----------------------------------------------------------------------===//
@_spi(Formatting) import _Backtracing
@_spi(Formatting) import Runtime
protocol ErrorAndWarningTheme {
func crashReason(_ s: String) -> String

View File

@@ -151,6 +151,18 @@ internal func spawn(_ path: String, args: [String]) throws {
#endif // os(macOS)
extension Sequence {
/// Return the first element in a Sequence.
///
/// This is not, in general, a safe thing to do, because the sequence might
/// not be restartable. For the cases where we're using it here, it's OK
/// though.
public var unsafeFirst: Element? {
var iterator = makeIterator()
return iterator.next()
}
}
struct CFileStream: TextOutputStream {
var fp: UnsafeMutablePointer<FILE>

View File

@@ -22,10 +22,10 @@ import Musl
import CRT
#endif
@_spi(Formatting) import _Backtracing
@_spi(Contexts) import _Backtracing
@_spi(Registers) import _Backtracing
@_spi(MemoryReaders) import _Backtracing
@_spi(Formatting) import Runtime
@_spi(Contexts) import Runtime
@_spi(Registers) import Runtime
@_spi(MemoryReaders) import Runtime
@main
internal struct SwiftBacktrace {
@@ -144,8 +144,8 @@ internal struct SwiftBacktrace {
}
static func measureDuration(_ body: () -> ()) -> timespec {
var startTime = timespec()
var endTime = timespec()
var startTime = timespec(tv_sec: 0, tv_nsec: 0)
var endTime = timespec(tv_sec: 0, tv_nsec: 0)
clock_gettime(CLOCK_MONOTONIC, &startTime)
body()
@@ -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":

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,7 @@
// REQUIRES: OS=linux-gnu
// REQUIRES: backtracing
@_spi(DwarfTest) import _Backtracing
@_spi(DwarfTest) import Runtime
#if canImport(Darwin)
import Darwin
#elseif canImport(SwiftWASILibc)

View File

@@ -15,7 +15,7 @@
// REQUIRES: OS=linux-gnu
// REQUIRES: backtracing
@_spi(ElfTest) import _Backtracing
@_spi(ElfTest) import Runtime
#if canImport(Darwin)
import Darwin
#elseif canImport(SwiftWASILibc)

View File

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

View File

@@ -10,7 +10,7 @@
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: back_deployment_runtime
import _Backtracing
import Runtime
func level1() {
level2()

View File

@@ -9,7 +9,7 @@
// REQUIRES: backtracing
// REQUIRES: OS=macosx || OS=linux-gnu
import _Backtracing
import Runtime
func kablam() {
kerpow()
@@ -32,7 +32,9 @@ func splat() {
}
func pow() {
let backtrace = try! Backtrace.capture().symbolicated(useSymbolCache: false)!
let backtrace = try! Backtrace.capture().symbolicated(
options: [ .showInlineFrames, .showSourceLocations ]
)!
// CHECK: 0{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [0] SymbolicatedBacktrace pow()
// CHECK: 1{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [0] SymbolicatedBacktrace splat()

View File

@@ -13,7 +13,7 @@
// which presumably doesn't have a frame pointer. When we add the Dwarf EH
// unwinder, we should be able to turn this test on.
import _Backtracing
import Runtime
func kablam() {
kerpow()
@@ -36,7 +36,9 @@ func splat() {
}
func pow() {
let backtrace = try! Backtrace.capture().symbolicated(useSymbolCache: false)!
let backtrace = try! Backtrace.capture().symbolicated(
options: [ .showInlineFrames, .showSourceLocations ]
)!
// CHECK: 0{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [0] SymbolicatedBacktraceInline pow()
// CHECK: 1{{[ \t]+}}0x{{[0-9a-f]+}} [ra] [inlined] [0] SymbolicatedBacktraceInline splat()

View File

@@ -82,6 +82,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()

View File

@@ -1326,7 +1326,7 @@ if run_vendor == 'apple':
"swiftDarwin", "swiftSwiftPrivateThreadExtras",
"swiftSwiftOnoneSupport", "swift_Concurrency"]
if backtracing is not None:
libraries.append('swift_Backtracing')
libraries.append('swiftRuntime')
for library in libraries:
swift_execution_tests_extra_flags += ' -Xlinker -l%s'% library
@@ -2208,12 +2208,12 @@ config.substitutions.append(('%concurrency_module', concurrency_module))
config.substitutions.append(('%/concurrency_module',
'/'.join(os.path.normpath(concurrency_module).split(os.sep))))
# Add 'backtracing_module' as the path to the _Backtracing .swiftmodule file
backtracing_module = os.path.join(stdlib_dir, "_Backtracing.swiftmodule",
# Add 'runtime_module' as the path to the Runtime .swiftmodule file
runtime_module = os.path.join(stdlib_dir, "Runtime.swiftmodule",
target_specific_module_triple + ".swiftmodule")
config.substitutions.append(('%backtracing_module', backtracing_module))
config.substitutions.append(('%/backtracing_module',
'/'.join(os.path.normpath(backtracing_module).split(os.sep))))
config.substitutions.append(('%runtime_module', runtime_module))
config.substitutions.append(('%/runtime_module',
'/'.join(os.path.normpath(runtime_module).split(os.sep))))
# Add 'distributed_module' as the path to the Distributed .swiftmodule file
distributed_module = os.path.join(stdlib_dir, "Distributed.swiftmodule",

View File

@@ -116,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}>
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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