[Backtracing] Add ImageMap instead of just using an Array.

We want to be able to efficiently serialise lists of images, and to do so
it makes most sense to create a separate `ImageMap` type.  This also provides
a useful place to put methods to e.g. find an image by address or by build
ID.

rdar://124913332
This commit is contained in:
Alastair Houghton
2024-11-15 12:36:38 +00:00
parent 760cc57bef
commit 0e3e9efcd3
16 changed files with 1501 additions and 224 deletions

View File

@@ -327,3 +327,6 @@ Backtraces are stored internally in a format called :download:`Compact Backtrace
Format <CompactBacktraceFormat.md>`. This provides us with a way to store a
large number of frames in a much smaller space than would otherwise be possible.
Similarly, where we need to store address to image mappings, we
use :download:`Compact ImageMap Format <CompactImageMapFormat.md>` to minimise
storage requirements.

View File

@@ -25,7 +25,7 @@ information byte:
~~~
The `version` field identifies the version of CBF that is in use; this
document describes version `0`. The `size` field is encoded as
document describes version `0`. The `size` field is encqoded as
follows:
| `size` | Machine word size |

View File

@@ -0,0 +1,226 @@
Compact ImageMap Format
=======================
A process' address space contains (among other things) the set of
dynamically loaded images that have been mapped into that address
space. When generating crash logs or symbolicating backtraces, we
need to be able to capture and potentially store the list of images
that has been loaded, as well as some of the attributes of those
images, including each image's
- Path
- Build ID (aka UUID)
- Base address
- End-of-text address
Compact ImageMap Format (CIF) is a binary format for holding this
information.
### General Format
Compact ImageMap Format data is byte aligned and starts with an
information byte:
~~~
7 6 5 4 3 2 1 0
┌───────────────────────┬───────┐
│ version │ size │
└───────────────────────┴───────┘
~~~
The `version` field identifies the version of CIF that is in use; this
document describes version `0`. The `size` field is encoded as
follows:
| `size` | Machine word size |
| :----: | :---------------- |
| 00 | 16-bit |
| 01 | 32-bit |
| 10 | 64-bit |
| 11 | Reserved |
This is followed immediately by a field encoding the number of images
in the image map; this field is encoded as a sequence of bytes, each
holding seven bits of data, with the top bit clear for the final byte.
The most significant byte is the first. e.g.
| `count` | Encoding |
| ------: | :---------- |
| 0 | 00 |
| 1 | 01 |
| 127 | 7f |
| 128 | 81 00 |
| 129 | 81 01 |
| 700 | 85 3c |
| 1234 | 89 52 |
| 16384 | 81 80 00 |
| 65535 | 83 ff 7f |
| 2097152 | 81 80 80 00 |
This in turn is followed by the list of images, stored in order of
increasing base address. For each image, we start with a header byte:
~~~
7 6 5 4 3 2 1 0
┌───┬───┬───────────┬───────────┐
│ r │ 0 │ acount │ ecount │
└───┴───┴───────────┴───────────┘
~~~
If `r` is set, then the base address is understood to be relative to
the previously computed base address.
This byte is followed by `acount + 1` bytes of base address, then
`ecount + 1` bytes of offset to the end of text.
Following this is an encoded count of bytes in the build ID,
encoded using the 7-bit scheme we used to encode the image count, and
then after that come the build ID bytes themselves.
Finally, we encode the path string using the scheme below.
### String Encoding
Image paths contain a good deal of redundancy; paths are therefore
encoded using a prefix compression scheme. The basic idea here is
that while generating or reading the data, we maintain a mapping from
small integers to path prefix segments.
The mapping is initialised with the following fixed list that never
need to be stored in CIF data:
| code | Path prefix |
| :--: | :---------------------------------- |
| 0 | `/lib` |
| 1 | `/usr/lib` |
| 2 | `/usr/local/lib` |
| 3 | `/opt/lib` |
| 4 | `/System/Library/Frameworks` |
| 5 | `/System/Library/PrivateFrameworks` |
| 6 | `/System/iOSSupport` |
| 7 | `/Library/Frameworks` |
| 8 | `/System/Applications` |
| 9 | `/Applications` |
| 10 | `C:\Windows\System32` |
| 11 | `C:\Program Files\` |
Codes below 32 are reserved for future expansion of the fixed list.
Strings are encoded as a sequence of bytes, as follows:
| `opcode` | Mnemonic | Meaning |
| :--------: | :-------- | :---------------------------------------- |
| `00000000` | `end` | Marks the end of the string |
| `00xxxxxx` | `str` | Raw string data |
| `01xxxxxx` | `framewk` | Names a framework |
| `1exxxxxx` | `expand` | Identifies a prefix in the table |
#### `end`
##### Encoding
~~~
7 6 5 4 3 2 1 0
┌───────────────────────────────┐
│ 0 0 0 0 0 0 0 0 │ end
└───────────────────────────────┘
~~~
#### Meaning
Marks the end of the string
#### `str`
##### Encoding
~~~
7 6 5 4 3 2 1 0
┌───────┬───────────────────────┐
│ 0 0 │ count │ str
└───────┴───────────────────────┘
~~~
##### Meaning
The next `count` bytes are included in the string verbatim.
Additionally, all path prefixes of this string data will be added to
the current prefix table. For instance, if the string data is
`/swift/linux/x86_64/libfoo.so`, then the prefix `/swift` will be
assigned the next available code, `/swift/linux` the code after that,
and `/swift/linux/x86_64` the code following that one.
#### `framewk`
##### Encoding
~~~
7 6 5 4 3 2 1 0
┌───────┬───────────────────────┐
│ 0 1 │ count │ framewk
└───────┴───────────────────────┘
~~~
##### Meaning
The next byte is a version character (normally `A`, but some
frameworks use higher characters), after which there are `count + 1`
bytes of name.
This is expanded using the pattern
`/<name>.framework/Versions/<version>/<name>`. This also marks the
end of the string.
#### `expand`
##### Encoding
~~~
7 6 5 4 3 2 1 0
┌───┬───┬───────────────────────┐
│ 1 │ e │ code │ expand
└───┴───┴───────────────────────┘
~~~
##### Meaning
If `e` is `0`, `code` is the index into the prefix table for the
prefix that should be appended to the string at this point.
If `e` is `1`, this opcode is followed by `code + 1` bytes that give
a value `v` such that `v + 64` is the index into the prefix table for
the prefix that should be appended to the string at this point.
#### Example
Let's say we wish to encode the following strings:
/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit
/System/Library/Frameworks/Photos.framework/Versions/A/Photos
/usr/lib/libobjc.A.dylib
/usr/lib/libz.1.dylib
/usr/lib/swift/libswiftCore.dylib
/usr/lib/libSystem.B.dylib
/usr/lib/libc++.1.dylib
We would encode
<84> <45> CAppKit <00>
We then follow with
<84> <45> APhotos <00>
Next we have
<81> <10> /libobjc.A.dylib <00>
<81> <0d> /libz.1.dylib <00>
<81> <19> /swift/libswiftCore.dylib <00>
assigning code 32 to `/swift`, then
<81> <12> /libSystem.B.dylib <00>
<81> <0f> /libc++.1.dylib <00>
In total the original data would have taken up 256 bytes. Instead, we
have used 122 bytes, a saving of over 50%.

View File

@@ -16,23 +16,15 @@
import Swift
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
internal import Darwin
#elseif os(Windows)
internal import ucrt
#elseif canImport(Glibc)
internal import Glibc
#elseif canImport(Musl)
internal import Musl
#endif
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
internal import BacktracingImpl.OS.Darwin
#endif
#if os(Linux)
internal import BacktracingImpl.ImageFormats.Elf
#endif
// #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
// internal import Darwin
// #elseif os(Windows)
// internal import ucrt
// #elseif canImport(Glibc)
// internal import Glibc
// #elseif canImport(Musl)
// internal import Musl
// #endif
/// Holds a backtrace.
public struct Backtrace: CustomStringConvertible, Sendable {
@@ -45,8 +37,8 @@ public struct Backtrace: CustomStringConvertible, Sendable {
/// This is intentionally _not_ a pointer, because you shouldn't be
/// dereferencing them; they may refer to some other process, for
/// example.
public struct Address: Hashable, Codable, Sendable {
enum Representation: Hashable, Codable, Sendable {
public struct Address: Hashable, Sendable {
enum Representation: Hashable, Sendable {
case null
case sixteenBit(UInt16)
case thirtyTwoBit(UInt32)
@@ -253,8 +245,8 @@ public struct Backtrace: CustomStringConvertible, Sendable {
/// Some backtracing algorithms may require this information, in which case
/// it will be filled in by the `capture()` method. Other algorithms may
/// not, in which case it will be `nil` and you can capture an image list
/// separately yourself using `captureImages()`.
public var images: [Image]?
/// separately yourself using `ImageMap.capture()`.
public var images: ImageMap?
/// Capture a backtrace from the current program location.
///
@@ -284,10 +276,10 @@ public struct Backtrace: CustomStringConvertible, Sendable {
limit: Int? = 64,
offset: Int = 0,
top: Int = 16,
images: [Image]? = nil) throws -> Backtrace {
images: ImageMap? = nil) throws -> Backtrace {
#if os(Linux)
// On Linux, we need the captured images to resolve async functions
let theImages = images ?? captureImages()
let theImages = images ?? ImageMap.capture()
#else
let theImages = images
#endif
@@ -305,18 +297,6 @@ public struct Backtrace: CustomStringConvertible, Sendable {
}
}
/// Capture a list of the images currently mapped into the calling
/// process.
///
/// @returns A list of `Image`s.
public static func captureImages() -> [Image] {
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
return captureImages(for: mach_task_self())
#else
return captureImages(using: UnsafeLocalMemoryReader())
#endif
}
/// Specifies options for the `symbolicated` method.
public struct SymbolicationOptions: OptionSet {
public let rawValue: Int
@@ -353,7 +333,7 @@ public struct Backtrace: CustomStringConvertible, Sendable {
/// used; otherwise we will capture images at this point.
///
/// - options: Symbolication options; see `SymbolicationOptions`.
public func symbolicated(with images: [Image]? = nil,
public func symbolicated(with images: ImageMap? = nil,
options: SymbolicationOptions = .default)
-> SymbolicatedBacktrace? {
return SymbolicatedBacktrace.symbolicate(
@@ -391,9 +371,10 @@ public struct Backtrace: CustomStringConvertible, Sendable {
}
/// Initialise a Backtrace from a sequence of `RichFrame`s
init<Address: FixedWidthInteger>(architecture: String,
@_spi(Internal)
public init<Address: FixedWidthInteger>(architecture: String,
frames: some Sequence<RichFrame<Address>>,
images: [Image]?) {
images: ImageMap?) {
self.architecture = architecture
self.representation = Array(CompactBacktraceFormat.Encoder(frames))
self.images = images
@@ -403,20 +384,26 @@ public struct Backtrace: CustomStringConvertible, Sendable {
// -- Capture Implementation -------------------------------------------------
extension Backtrace {
// ###FIXME: There is a problem with @_specialize here that results in the
// arguments not lining up properly when this gets used from
// swift-backtrace.
@_spi(Internal)
@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == UnsafeLocalMemoryReader)
@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == RemoteMemoryReader)
#if os(Linux)
@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == MemserverMemoryReader)
#endif
//@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == UnsafeLocalMemoryReader)
//@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == RemoteMemoryReader)
//#if os(Linux)
//@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == MemserverMemoryReader)
//#endif
@inlinable
public static func capture<Ctx: Context, Rdr: MemoryReader>(
from context: Ctx,
using memoryReader: Rdr,
images: [Image]?,
images: ImageMap?,
algorithm: UnwindAlgorithm,
limit: Int?,
offset: Int,
top: Int
limit: Int? = 64,
offset: Int = 0,
top: Int = 16
) throws -> Backtrace {
switch algorithm {
// All of them, for now, use the frame pointer unwinder. In the long
@@ -428,6 +415,8 @@ extension Backtrace {
memoryReader: memoryReader)
if let limit = limit {
print("limit = \(limit), offset = \(offset), top = \(top)")
let limited = LimitSequence(unwinder,
limit: limit,
offset: offset,
@@ -439,160 +428,8 @@ extension Backtrace {
}
return Backtrace(architecture: context.architecture,
frames: unwinder,
frames: unwinder.dropFirst(offset),
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 {
var kret = kern_return_t(KERN_SUCCESS)
let dyldInfo = _dyld_process_info_create(task, 0, &kret)
if kret != KERN_SUCCESS {
fatalError("error: cannot create dyld process info")
}
defer {
_dyld_process_info_release(dyldInfo)
}
return try fn(dyldInfo)
}
@_spi(Internal)
public static func captureImages(for process: Any) -> [Image] {
var images: [Image] = []
let task = process as! task_t
withDyldProcessInfo(for: task) { dyldInfo in
_dyld_process_info_for_each_image(dyldInfo) {
(machHeaderAddress, uuid, path) in
if let path = path, let uuid = uuid {
let pathString = String(cString: path)
let theUUID = Array(UnsafeBufferPointer(start: uuid,
count: MemoryLayout<uuid_t>.size))
let name: String
if let slashIndex = pathString.lastIndex(of: "/") {
name = String(pathString.suffix(from:
pathString.index(after:slashIndex)))
} else {
name = pathString
}
// Find the end of the __TEXT segment
var endOfText = machHeaderAddress + 4096
_dyld_process_info_for_each_segment(dyldInfo, machHeaderAddress) {
address, size, name in
if let name = String(validatingCString: name!), name == "__TEXT" {
endOfText = address + size
}
}
images.append(Image(name: name,
path: pathString,
uniqueID: theUUID,
baseAddress: Address(machHeaderAddress),
endOfText: Address(endOfText)))
}
}
}
return images.sorted(by: { $0.baseAddress < $1.baseAddress })
}
}
#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)
@_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] = []
let path: String
if let pid = pid {
path = "/proc/\(pid)/maps"
} else {
path = "/proc/self/maps"
}
guard let procMaps = readString(from: path) else {
return []
}
// Find all the mapped files and get high/low ranges
var mappedFiles: [Substring:AddressRange] = [:]
for match in ProcMapsScanner(procMaps) {
let path = stripWhitespace(match.pathname)
if match.inode == "0" || path == "" {
continue
}
guard let start = Address(match.start),
let end = Address(match.end) else {
continue
}
if let range = mappedFiles[path] {
mappedFiles[path] = AddressRange(low: min(start, range.low),
high: max(end, range.high))
} else {
mappedFiles[path] = AddressRange(low: start,
high: end)
}
}
// Look at each mapped file to see if it's an ELF image
for (path, range) in mappedFiles {
// Extract the filename from path
let name: Substring
if let slashIndex = path.lastIndex(of: "/") {
name = path.suffix(from: path.index(after: slashIndex))
} else {
name = path
}
// Inspect the image and extract the UUID and end of text
guard let (endOfText, uuid) = getElfImageInfo(at: M.Address(range.low)!,
using: reader) else {
// Not an ELF iamge
continue
}
let image = Image(name: String(name),
path: String(path),
uniqueID: uuid,
baseAddress: range.low,
endOfText: Address(endOfText))
images.append(image)
}
return images.sorted(by: { $0.baseAddress < $1.baseAddress })
}
}
#endif // os(Linux)

View File

@@ -29,6 +29,7 @@ set(RUNTIME_SOURCES
ByteSwapping.swift
CachingMemoryReader.swift
CompactBacktrace.swift
CompactImageMap.swift
Compression.swift
Context.swift
CoreSymbolication.swift
@@ -38,6 +39,9 @@ set(RUNTIME_SOURCES
ElfImageCache.swift
FramePointerUnwinder.swift
Image.swift
ImageMap.swift
ImageMap+Darwin.swift
ImageMap+Linux.swift
ImageSource.swift
Libc.swift
LimitSequence.swift

View File

@@ -0,0 +1,730 @@
//===--- CompactImageMap.swift -------------------------------*- swift -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Definitions for Compact ImageMap Format
//
//===----------------------------------------------------------------------===//
import Swift
@_spi(Internal)
public enum CompactImageMapFormat {
/// The list of fixed prefixes used to encode paths.
static let fixedPathPrefixes = [
// Traditional UNIX
(0, "/lib"),
(1, "/usr/lib"),
(2, "/usr/local/lib"),
(3, "/opt/lib"),
// NeXT/Darwin
(4, "/System/Library/Frameworks"),
(5, "/System/Library/PrivateFrameworks"),
(6, "/System/iOSSupport"),
(7, "/Library/Frameworks"),
(8, "/System/Applications"),
(9, "/Applications"),
// Windows
(10, "C:\\Windows\\System32"),
(11, "C:\\Program Files")
]
/// Tells us what size of machine words were used when generating the
/// image map.
enum WordSize: UInt8 {
case sixteenBit = 0
case thirtyTwoBit = 1
case sixtyFourBit = 2
}
/// Run a closure for each prefix of the specified string
static func forEachPrefix(of str: String.UTF8View.SubSequence,
body: (String) -> ()) {
let base = str.startIndex
let end = str.endIndex
var pos = base
while pos < end {
let ch = str[pos]
if pos > base && (ch == 0x2f || ch == 0x5c) {
let range = base..<pos
let prefix = String(str[range])!
body(prefix)
}
pos = str.index(after: pos)
}
}
/// Decodes a Sequence containing Compact ImageMap Format data into am
/// ImageMap.
@_spi(Internal)
public struct Decoder<S: Sequence<UInt8>> {
var sequence: S
var iterator: S.Iterator
var imageCount: Int = 0
var wordSize: WordSize = .sixtyFourBit
var wordMask: UInt64 = 0
var pathPrefixes = Dictionary(uniqueKeysWithValues: fixedPathPrefixes)
var nextCode = 32
public init(_ sequence: S) {
self.sequence = sequence
self.iterator = sequence.makeIterator()
}
mutating func decodeCount() -> Int? {
var value: Int = 0
while true {
guard let byte = iterator.next() else {
return nil
}
value = (value << 7) | Int(byte & 0x7f)
if (byte & 0x80) == 0 {
break
}
}
return value
}
mutating func decodeAddress(_ count: Int) -> UInt64? {
var word: UInt64
guard let firstByte = iterator.next() else {
return nil
}
// Sign extend
if (firstByte & 0x80) != 0 {
word = wordMask | UInt64(firstByte)
} else {
word = UInt64(firstByte)
}
for _ in 1..<count {
guard let byte = iterator.next() else {
return nil
}
word = (word << 8) | UInt64(byte)
}
return word
}
mutating func decodePath() -> String? {
var byte: UInt8
guard let b = iterator.next() else {
return nil
}
byte = b
// `end` here means no string at all
if byte == 0x00 {
return nil
}
var resultBytes: [UInt8] = []
var stringBase: Int? = nil
while true {
if byte == 0x00 {
// `end`
#if DEBUG_COMPACT_IMAGE_MAP
print("end")
#endif
return String(decoding: resultBytes, as: UTF8.self)
} else if byte < 0x40 {
// `str`
let count = Int(byte)
resultBytes.reserveCapacity(resultBytes.count + count)
let base = resultBytes.count
if stringBase == nil {
stringBase = base
}
for n in 0..<count {
guard let char = iterator.next() else {
return nil
}
if n > 0 && char == 0x2f {
let prefix = String(decoding: resultBytes[stringBase!..<base+n],
as: UTF8.self)
#if DEBUG_COMPACT_IMAGE_MAP
print("define \(nextCode) = \(prefix)")
#endif
pathPrefixes[nextCode] = prefix
nextCode += 1
}
resultBytes.append(char)
#if DEBUG_COMPACT_IMAGE_MAP
var hex = String(char, radix: 16)
if hex.count == 1 {
hex = "0" + hex
}
#endif
}
#if DEBUG_COMPACT_IMAGE_MAP
let theString = String(decoding: resultBytes[base...], as: UTF8.self)
print("str '\(theString)'")
#endif
} else if byte < 0x80 {
// `framewk`
let count = Int((byte & 0x3f) + 1)
guard let version = iterator.next() else {
return nil
}
var nameBytes: [UInt8] = []
nameBytes.reserveCapacity(count)
for _ in 0..<count {
guard let char = iterator.next() else {
return nil
}
nameBytes.append(char)
}
#if DEBUG_COMPACT_IMAGE_MAP
let name = String(decoding: nameBytes, as: UTF8.self)
let versionChar = String(Unicode.Scalar(version))
print("framewk version='\(versionChar)' name='\(name)'")
#endif
resultBytes.append(0x2f) // '/'
resultBytes.append(contentsOf: nameBytes)
resultBytes.append(contentsOf: ".framework/Versions/".utf8)
resultBytes.append(version)
resultBytes.append(0x2f)
resultBytes.append(contentsOf: nameBytes)
return String(decoding: resultBytes, as: UTF8.self)
} else {
// `expand`
var code: Int
if (byte & 0x40) == 0 {
code = Int(byte & 0x3f)
} else {
let byteCount = Int(byte & 0x3f)
code = 0
for _ in 0..<byteCount {
guard let byte = iterator.next() else {
return nil
}
code = (code << 8) | Int(byte)
}
}
#if DEBUG_COMPACT_IMAGE_MAP
print("expand \(code) = \(String(describing: pathPrefixes[code]))")
#endif
guard let prefix = pathPrefixes[code] else {
return nil
}
resultBytes.append(contentsOf: prefix.utf8)
}
guard let b = iterator.next() else {
return nil
}
byte = b
}
}
public mutating func decode() -> ImageMap? {
// Check the version and decode the size
guard let infoByte = iterator.next() else {
return nil
}
let version = infoByte >> 2
guard let size = WordSize(rawValue: infoByte & 0x3) else {
return nil
}
wordSize = size
guard version == 0 else {
return nil
}
// Set up the word mask
switch wordSize {
case .sixteenBit:
wordMask = 0xff00
case .thirtyTwoBit:
wordMask = 0xffffff00
case .sixtyFourBit:
wordMask = 0xffffffffffffff00
}
// Next is the image count
guard let count = decodeCount() else {
return nil
}
imageCount = count
// Now decode all of the images
var images: [ImageMap.Image] = []
var lastAddress: UInt64 = 0
images.reserveCapacity(count)
for _ in 0..<count {
// Decode the header byte
guard let header = iterator.next() else {
return nil
}
let relative = (header & 0x80) != 0
let acount = Int(((header >> 3) & 0x7) + 1)
let ecount = Int((header & 0x7) + 1)
// Now the base and end of text addresses
guard let address = decodeAddress(acount) else {
return nil
}
let baseAddress: UInt64
if relative {
baseAddress = lastAddress &+ address
} else {
baseAddress = address
}
lastAddress = baseAddress
guard let eotOffset = decodeAddress(ecount) else {
return nil
}
let endOfText = baseAddress &+ eotOffset
// Next, get the build ID byte count
guard let buildIdBytes = decodeCount() else {
return nil
}
// Read the build ID
var buildId: [UInt8]? = nil
if buildIdBytes > 0 {
buildId = []
buildId!.reserveCapacity(buildIdBytes)
for _ in 0..<buildIdBytes {
guard let byte = iterator.next() else {
return nil
}
buildId!.append(byte)
}
}
// Decode the path
let path = decodePath()
let name: String?
// Extract the name from the path
if let path = path {
if let lastSlashNdx = path.utf8.lastIndex(
where: { $0 == 0x2f || $0 == 0x5c }
) {
let nameNdx = path.index(after: lastSlashNdx)
name = String(path[nameNdx...])
} else {
name = path
}
} else {
name = nil
}
let image = ImageMap.Image(
name: name,
path: path,
uniqueID: buildId,
baseAddress: baseAddress,
endOfText: endOfText
)
images.append(image)
}
let wsMap: ImageMap.WordSize
switch wordSize {
case .sixteenBit:
wsMap = .sixteenBit
case .thirtyTwoBit:
wsMap = .thirtyTwoBit
case .sixtyFourBit:
wsMap = .sixtyFourBit
}
return ImageMap(images: images, wordSize: wsMap)
}
}
/// Encodes an ImageMap as a Sequence<UInt8>
@_spi(Internal)
public struct Encoder: Sequence {
public typealias Element = UInt8
private var source: ImageMap
public init(_ source: ImageMap) {
self.source = source
}
public func makeIterator() -> Iterator {
return Iterator(source)
}
public struct Iterator: IteratorProtocol {
enum State {
case start
case count(Int)
case image
case baseAddress(Int)
case endOfText(Int)
case uniqueID(Int)
case uniqueIDBytes(Int)
case path
case pathCode(Int)
case pathString
case pathStringChunk(Int)
case version
case framework
case done
}
var abytes = EightByteBuffer()
var ebytes = EightByteBuffer()
var acount: Int = 0
var ecount: Int = 0
var version: UInt8 = 0
var lastAddress: UInt64 = 0
var ndx: Int = 0
var state: State = .start
var source: ImageMap
var pathPrefixes = fixedPathPrefixes
var nextCode = 32
var remainingPath: String.UTF8View.SubSequence?
func signExtend(_ value: UInt64) -> UInt64 {
let mask: UInt64
let topBit: UInt64
switch source.wordSize {
case .sixteenBit:
topBit = 0x8000
mask = 0xffffffffffff0000
case .thirtyTwoBit:
topBit = 0x80000000
mask = 0xffffffff00000000
case .sixtyFourBit:
return value
}
if (value & topBit) != 0 {
return value | mask
}
return value
}
init(_ source: ImageMap) {
self.source = source
}
public mutating func next() -> UInt8? {
switch state {
case .done:
return nil
case .start:
// The first thing we emit is the info byte
let size: WordSize
switch source.wordSize {
case .sixteenBit:
size = .sixteenBit
case .thirtyTwoBit:
size = .thirtyTwoBit
case .sixtyFourBit:
size = .sixtyFourBit
}
let count = source.images.count
let bits = Int.bitWidth - count.leadingZeroBitCount
state = .count(7 * (bits / 7))
let version: UInt8 = 0
let infoByte = (version << 2) | size.rawValue
return infoByte
case let .count(ndx):
let count = source.images.count
let byte = UInt8(truncatingIfNeeded:(count >> ndx) & 0x7f)
if ndx == 0 {
state = .image
return byte
} else {
state = .count(ndx - 7)
return 0x80 | byte
}
case .image:
if ndx == source.images.count {
state = .done
return nil
}
let baseAddress = signExtend(source.images[ndx].baseAddress)
let delta = baseAddress &- lastAddress
let endOfText = signExtend(source.images[ndx].endOfText)
let endOfTextOffset = endOfText - baseAddress
let eotCount: Int
if endOfTextOffset & (1 << 63) != 0 {
let ones = ((~endOfTextOffset).leadingZeroBitCount - 1) >> 3
eotCount = 8 - ones
} else {
let zeroes = (endOfTextOffset.leadingZeroBitCount - 1) >> 3
eotCount = 8 - zeroes
}
ebytes = EightByteBuffer(endOfTextOffset)
ecount = eotCount
let absCount: Int
if baseAddress & (1 << 63) != 0 {
let ones = ((~baseAddress).leadingZeroBitCount - 1) >> 3
absCount = 8 - ones
} else {
let zeroes = (baseAddress.leadingZeroBitCount - 1) >> 3
absCount = 8 - zeroes
}
let deltaCount: Int
if delta & (1 << 63) != 0 {
let ones = ((~delta).leadingZeroBitCount - 1) >> 3
deltaCount = 8 - ones
} else {
let zeroes = (delta.leadingZeroBitCount - 1) >> 3
deltaCount = 8 - zeroes
}
lastAddress = baseAddress
let relativeFlag: UInt8
if absCount <= deltaCount {
abytes = EightByteBuffer(baseAddress)
acount = absCount
relativeFlag = 0
} else {
abytes = EightByteBuffer(delta)
acount = deltaCount
relativeFlag = 0x80
}
state = .baseAddress(8 - acount)
return relativeFlag
| UInt8(truncatingIfNeeded: (acount - 1) << 3)
| UInt8(truncatingIfNeeded: ecount - 1)
case let .baseAddress(ndx):
let byte = abytes[ndx]
if ndx + 1 == 8 {
state = .endOfText(8 - ecount)
} else {
state = .baseAddress(ndx + 1)
}
return byte
case let .endOfText(ndx):
let byte = ebytes[ndx]
if ndx + 1 == 8 {
let count = source.images[self.ndx].uniqueID?.count ?? 0
let bits = Int.bitWidth - count.leadingZeroBitCount
state = .uniqueID(7 * (bits / 7))
} else {
state = .endOfText(ndx + 1)
}
return byte
case let .uniqueID(cndx):
guard let count = source.images[self.ndx].uniqueID?.count else {
state = .path
if let path = source.images[self.ndx].path {
remainingPath = path.utf8[...]
} else {
remainingPath = nil
}
return 0
}
let byte = UInt8(truncatingIfNeeded: (count >> cndx) & 0x7f)
if cndx == 0 {
state = .uniqueIDBytes(0)
return byte
} else {
state = .uniqueID(cndx - 7)
return 0x80 | byte
}
case let .uniqueIDBytes(byteNdx):
let uniqueID = source.images[self.ndx].uniqueID!
let byte = uniqueID[byteNdx]
if byteNdx + 1 == uniqueID.count {
state = .path
if let path = source.images[self.ndx].path {
remainingPath = path.utf8[...]
} else {
remainingPath = nil
}
} else {
state = .uniqueIDBytes(byteNdx + 1)
}
return byte
case .path:
guard let remainingPath = remainingPath,
remainingPath.count > 0 else {
ndx += 1
state = .image
return 0x00
}
// Find the longest prefix match
var longestMatchLen = 0
var matchedPrefix: Int? = nil
for (ndx, (_, prefix)) in pathPrefixes.enumerated() {
let prefixUTF8 = prefix.utf8
if prefixUTF8.count > remainingPath.count {
continue
}
if prefixUTF8.count > longestMatchLen
&& remainingPath.starts(with: prefixUTF8) {
longestMatchLen = prefixUTF8.count
matchedPrefix = ndx
}
}
if let ndx = matchedPrefix {
let (code, prefix) = pathPrefixes[ndx]
self.remainingPath = remainingPath.dropFirst(prefix.utf8.count)
if code <= 0x3f {
return 0x80 | UInt8(exactly: code)!
}
let theCode = UInt64(exactly: code - 0x40)!
abytes = EightByteBuffer(theCode)
let codeBytes = Swift.max(
(64 - theCode.leadingZeroBitCount) >> 3, 1
)
state = .pathCode(8 - codeBytes)
return 0xc0 | UInt8(exactly: codeBytes - 1)!
}
// Check for /<name>.framework/Versions/<version>/<name>
if let name = source.images[ndx].name, !name.isEmpty {
let nameCount = name.utf8.count
let expectedLen = 1 // '/'
+ nameCount // <name>
+ 20 // .framework/Versions/
+ 1 // <version>
+ 1 // '/'
+ nameCount // <name>
if remainingPath.count == expectedLen {
let framework = "/\(name).framework/Versions/"
if remainingPath.starts(with: framework.utf8) {
var verNdx = remainingPath.startIndex
remainingPath.formIndex(&verNdx, offsetBy: framework.utf8.count)
version = remainingPath[verNdx]
let slashNdx = remainingPath.index(after: verNdx)
if remainingPath[slashNdx] == 0x2f {
let nameNdx = remainingPath.index(after: slashNdx)
if remainingPath[nameNdx...].elementsEqual(name.utf8) {
self.remainingPath = remainingPath[nameNdx...]
state = .version
return 0x40 | UInt8(exactly: nameCount - 1)!
}
}
}
}
}
// Add any new prefixes
forEachPrefix(of: remainingPath) { prefix in
pathPrefixes.append((nextCode, prefix))
nextCode += 1
}
fallthrough
case .pathString:
if remainingPath!.count == 0 {
ndx += 1
state = .image
return 0x00
}
let chunkLength = Swift.min(remainingPath!.count, 0x3f)
state = .pathStringChunk(chunkLength)
return UInt8(truncatingIfNeeded: chunkLength)
case let .pathStringChunk(length):
let byte = remainingPath!.first!
remainingPath = remainingPath!.dropFirst()
if length == 1 {
state = .pathString
} else {
state = .pathStringChunk(length - 1)
}
return byte
case .version:
state = .framework
return version
case .framework:
let byte = remainingPath!.first!
remainingPath = remainingPath!.dropFirst()
if remainingPath!.count == 0 {
ndx += 1
state = .image
}
return byte
case let .pathCode(ndx):
let byte = abytes[ndx]
if ndx + 1 == 8 {
state = .path
} else {
state = .pathCode(ndx + 1)
}
return byte
}
}
}
}
}

View File

@@ -30,7 +30,7 @@ public struct FramePointerUnwinder<C: Context, M: MemoryReader>: Sequence, Itera
var done: Bool
#if os(Linux)
var images: [Backtrace.Image]?
var images: ImageMap?
#endif
var reader: MemoryReader
@@ -41,7 +41,7 @@ public struct FramePointerUnwinder<C: Context, M: MemoryReader>: Sequence, Itera
@_specialize(exported: true, kind: full, where C == HostContext, M == MemserverMemoryReader)
#endif
public init(context: Context,
images: [Backtrace.Image]?,
images: ImageMap?,
memoryReader: MemoryReader) {
pc = Address(context.programCounter)
@@ -87,10 +87,7 @@ public struct FramePointerUnwinder<C: Context, M: MemoryReader>: Sequence, Itera
let address = MemoryReader.Address(pc)
if let images = images,
let imageNdx = images.firstIndex(
where: { address >= MemoryReader.Address($0.baseAddress)!
&& address < MemoryReader.Address($0.endOfText)! }
) {
let imageNdx = images.indexOfImage(at: Backtrace.Address(address)) {
let base = MemoryReader.Address(images[imageNdx].baseAddress)!
let relativeAddress = address - base
let cache = ElfImageCache.threadLocal
@@ -259,7 +256,6 @@ public struct FramePointerUnwinder<C: Context, M: MemoryReader>: Sequence, Itera
#endif
asyncContext = next
return .asyncResumePoint(pc)
}
}

View File

@@ -0,0 +1,91 @@
//===--- ImageMap+Darwin.swift --------------------------------*- swift -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Darwin specifics for ImageMap capture.
//
//===----------------------------------------------------------------------===//
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
import Swift
internal import Darwin
internal import BacktracingImpl.OS.Darwin
extension ImageMap {
private static func withDyldProcessInfo<T>(for task: task_t,
fn: (OpaquePointer?) throws -> T)
rethrows -> T {
var kret = kern_return_t(KERN_SUCCESS)
let dyldInfo = _dyld_process_info_create(task, 0, &kret)
if kret != KERN_SUCCESS {
fatalError("error: cannot create dyld process info")
}
defer {
_dyld_process_info_release(dyldInfo)
}
return try fn(dyldInfo)
}
@_spi(Internal)
public static func capture(for process: Any) -> ImageMap {
var images: [Image] = []
let task = process as! task_t
withDyldProcessInfo(for: task) { dyldInfo in
_dyld_process_info_for_each_image(dyldInfo) {
(machHeaderAddress, uuid, path) in
if let path = path, let uuid = uuid {
let pathString = String(cString: path)
let theUUID = Array(UnsafeBufferPointer(start: uuid,
count: MemoryLayout<uuid_t>.size))
let name: String
if let slashIndex = pathString.lastIndex(of: "/") {
name = String(pathString.suffix(from:
pathString.index(after:slashIndex)))
} else {
name = pathString
}
// Find the end of the __TEXT segment
var endOfText = machHeaderAddress + 4096
_dyld_process_info_for_each_segment(dyldInfo, machHeaderAddress) {
address, size, name in
if let name = String(validatingCString: name!), name == "__TEXT" {
endOfText = address + size
}
}
images.append(Image(name: name,
path: pathString,
uniqueID: theUUID,
baseAddress: machHeaderAddress,
endOfText: endOfText))
}
}
}
images.sort(by: { $0.baseAddress < $1.baseAddress })
return ImageMap(images: images, wordSize: .sixtyFourBit)
}
}
#endif // os(macOS) || os(iOS) || os(watchOS) || os(tvOS)

View File

@@ -0,0 +1,121 @@
//===--- ImageMap+Linux.swift --------------------------------*- swift -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Linux specifics for ImageMap capture.
//
//===----------------------------------------------------------------------===//
#if os(Linux)
import Swift
#if canImport(Glibc)
internal import Glibc
#elseif canImport(Musl)
internal import Musl
#endif
internal import BacktracingImpl.ImageFormats.Elf
extension ImageMap {
private struct AddressRange {
var low: Address = 0
var high: Address = 0
}
@_specialize(exported: true, kind: full, where M == UnsafeLocalMemoryReader)
@_specialize(exported: true, kind: full, where M == RemoteMemoryReader)
@_specialize(exported: true, kind: full, where M == LocalMemoryReader)
@_spi(Internal)
public static func capture<M: MemoryReader>(
using reader: M,
forProcess pid: Int? = nil
) -> ImageMap {
var images: [Image] = []
let wordSize: WordSize
#if arch(x86_64) || arch(arm64) || arch(arm64_32)
wordSize = .sixtyFourBit
#elseif arch(i386) || arch(arm)
wordSize = .thirtyTwoBit
#endif
let path: String
if let pid = pid {
path = "/proc/\(pid)/maps"
} else {
path = "/proc/self/maps"
}
guard let procMaps = readString(from: path) else {
return ImageMap(images: [], wordSize: wordSize)
}
// Find all the mapped files and get high/low ranges
var mappedFiles: [Substring:AddressRange] = [:]
for match in ProcMapsScanner(procMaps) {
let path = stripWhitespace(match.pathname)
if match.inode == "0" || path == "" {
continue
}
guard let start = Address(match.start, radix: 16),
let end = Address(match.end, radix: 16) else {
continue
}
if let range = mappedFiles[path] {
mappedFiles[path] = AddressRange(low: Swift.min(start, range.low),
high: Swift.max(end, range.high))
} else {
mappedFiles[path] = AddressRange(low: start,
high: end)
}
}
// Look at each mapped file to see if it's an ELF image
for (path, range) in mappedFiles {
// Extract the filename from path
let name: Substring
if let slashIndex = path.lastIndex(of: "/") {
name = path.suffix(from: path.index(after: slashIndex))
} else {
name = path
}
// Inspect the image and extract the UUID and end of text
guard let (endOfText, uuid) = getElfImageInfo(
at: M.Address(exactly: range.low)!,
using: reader
) else {
// Not an ELF image
continue
}
let image = Image(name: String(name),
path: String(path),
uniqueID: uuid,
baseAddress: range.low,
endOfText: Address(endOfText))
images.append(image)
}
images.sort(by: { $0.baseAddress < $1.baseAddress })
return ImageMap(images: images, wordSize: wordSize)
}
}
#endif // os(Linux)

View File

@@ -0,0 +1,174 @@
//===--- ImageMap.swift ----------------------------------------*- swift -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Defines the `ImageMap` struct that represents a captured list of loaded
// images.
//
//===----------------------------------------------------------------------===//
import Swift
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
internal import Darwin
internal import BacktracingImpl.OS.Darwin
#endif
/// Holds a map of the process's address space.
public struct ImageMap: Collection, Sendable, Hashable {
/// A type representing the sequence's elements.
public typealias Element = Backtrace.Image
/// A type that represents a position in the collection.
public typealias Index = Int
/// Tells us what size of machine words were used when capturing the
/// image map.
enum WordSize {
case sixteenBit
case thirtyTwoBit
case sixtyFourBit
}
/// We use UInt64s for addresses here.
typealias Address = UInt64
/// The internal representation of an image.
struct Image: Sendable, Hashable {
var name: String?
var path: String?
var uniqueID: [UInt8]?
var baseAddress: Address
var endOfText: Address
}
/// The actual image storage.
var images: [Image]
/// The size of words used when capturing.
var wordSize: WordSize
/// The position of the first element in a non-empty collection.
public var startIndex: Self.Index {
return 0
}
/// The collection's "past the end" position---that is, the position one
/// greater than the last valid subscript argument.
public var endIndex: Self.Index {
return images.count
}
/// Accesses the element at the specified position.
public subscript(_ ndx: Self.Index) -> Self.Element {
return Backtrace.Image(images[ndx], wordSize: wordSize)
}
/// Look-up an image by address.
public func indexOfImage(at address: Backtrace.Address) -> Int? {
let addr = UInt64(address)!
var lo = 0, hi = images.count
while lo < hi {
let mid = (lo + hi) / 2
if images[mid].baseAddress > addr {
hi = mid
} else if images[mid].endOfText <= addr {
lo = mid + 1
} else {
return mid
}
}
return nil
}
/// Returns the position immediately after the given index.
public func index(after ndx: Self.Index) -> Self.Index {
return ndx + 1
}
/// Capture the image map for the current process.
public static func capture() -> ImageMap {
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
return capture(for: mach_task_self())
#else
return capture(using: UnsafeLocalMemoryReader())
#endif
}
}
extension ImageMap: CustomStringConvertible {
/// Generate a description of an ImageMap
public var description: String {
var lines: [String] = []
let addressWidth: Int
switch wordSize {
case .sixteenBit: addressWidth = 4
case .thirtyTwoBit: addressWidth = 8
case .sixtyFourBit: addressWidth = 16
}
for image in images {
let hexBase = hex(image.baseAddress, width: addressWidth)
let hexEnd = hex(image.endOfText, width: addressWidth)
let buildId: String
if let bytes = image.uniqueID {
buildId = hex(bytes)
} else {
buildId = "<no build ID>"
}
let path = image.path ?? "<unknown>"
let name = image.name ?? "<unknown>"
lines.append("\(hexBase)-\(hexEnd) \(buildId) \(name) \(path)")
}
return lines.joined(separator: "\n")
}
}
extension Backtrace.Image {
/// Convert an ImageMap.Image to a Backtrace.Image.
///
/// Backtrace.Image is the public, user-visible type; ImageMap.Image
/// is an in-memory representation.
init(_ image: ImageMap.Image, wordSize: ImageMap.WordSize) {
let baseAddress: Backtrace.Address
let endOfText: Backtrace.Address
switch wordSize {
case .sixteenBit:
baseAddress = Backtrace.Address(
UInt16(truncatingIfNeeded: image.baseAddress)
)
endOfText = Backtrace.Address(
UInt16(truncatingIfNeeded: image.endOfText)
)
case .thirtyTwoBit:
baseAddress = Backtrace.Address(
UInt32(truncatingIfNeeded: image.baseAddress)
)
endOfText = Backtrace.Address(
UInt32(truncatingIfNeeded: image.endOfText)
)
case .sixtyFourBit:
baseAddress = Backtrace.Address(image.baseAddress)
endOfText = Backtrace.Address(image.endOfText)
}
self.init(name: image.name,
path: image.path,
uniqueID: image.uniqueID,
baseAddress: baseAddress,
endOfText: endOfText)
}
}

View File

@@ -20,19 +20,23 @@ import Swift
/// Sequences you wish to use with `LimitSequence` must use an element type
/// that implements this protocol, so that `LimitSequence` can indicate when
/// it omits or truncates the sequence.
@usableFromInline
protocol LimitableElement {
static func omitted(_: Int) -> Self
static var truncated: Self { get }
}
/// A `Sequence` that adds the ability to limit the output of another sequence.
@usableFromInline
struct LimitSequence<T: LimitableElement, S: Sequence>: Sequence
where S.Element == T
{
/// The element type, which must conform to `LimitableElement`
@usableFromInline
typealias Element = T
/// The source sequence
@usableFromInline
typealias Source = S
var source: Source
@@ -62,6 +66,7 @@ struct LimitSequence<T: LimitableElement, S: Sequence>: Sequence
///
/// When `LimitSequence` omits items or truncates the sequence, it will
/// insert `.omitted(count)` or `.truncated` items into its output.
@usableFromInline
init(_ source: Source, limit: Int, offset: Int = 0, top: Int = 0) {
self.source = source
self.limit = limit
@@ -79,6 +84,7 @@ struct LimitSequence<T: LimitableElement, S: Sequence>: Sequence
/// This works by buffering an element ahead of where we are in the input
/// sequence, so that it can tell whether or not there is more input to
/// follow at any given point.
@usableFromInline
struct Iterator: IteratorProtocol {
/// The iterator for the input sequence.
var iterator: Source.Iterator

View File

@@ -249,7 +249,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
public var frames: [Frame]
/// A list of images found in the process.
public var images: [Backtrace.Image]
public var images: ImageMap
/// True if this backtrace is a Swift runtime failure.
public var isSwiftRuntimeFailure: Bool {
@@ -270,8 +270,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
}
/// Construct a SymbolicatedBacktrace from a backtrace and a list of images.
private init(backtrace: Backtrace, images: [Backtrace.Image],
frames: [Frame]) {
private init(backtrace: Backtrace, images: ImageMap, frames: [Frame]) {
self.backtrace = backtrace
self.images = images
self.frames = frames
@@ -291,7 +290,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
}
/// Create a symbolicator.
private static func withSymbolicator<T>(images: [Backtrace.Image],
private static func withSymbolicator<T>(images: ImageMap,
useSymbolCache: Bool,
fn: (CSSymbolicatorRef) throws -> T) rethrows -> T {
let binaryImageList = images.map{ image in
@@ -329,7 +328,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
isInline: Bool,
symbol: CSSymbolRef,
sourceInfo: CSSourceInfoRef?,
images: [Backtrace.Image]) -> Frame {
images: ImageMap) -> Frame {
if CSIsNull(symbol) {
return Frame(captured: capturedFrame, symbol: nil)
}
@@ -379,17 +378,17 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
/// Actually symbolicate.
internal static func symbolicate(backtrace: Backtrace,
images: [Backtrace.Image]?,
images: ImageMap?,
options: Backtrace.SymbolicationOptions)
-> SymbolicatedBacktrace? {
let theImages: [Backtrace.Image]
let theImages: ImageMap
if let images = images {
theImages = images
} else if let images = backtrace.images {
theImages = images
} else {
theImages = Backtrace.captureImages()
theImages = ImageMap.capture()
}
var frames: [Frame] = []
@@ -463,9 +462,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
for frame in backtrace.frames {
let address = frame.adjustedProgramCounter
if let imageNdx = theImages.firstIndex(
where: { address >= $0.baseAddress && address < $0.endOfText }
) {
if let imageNdx = theImages.indexOfImage(at: address) {
let relativeAddress = ImageSource.Address(
address - theImages[imageNdx].baseAddress
)

View File

@@ -52,7 +52,7 @@ class Target {
var faultAddress: Address
var crashingThread: TargetThread.ThreadID
var images: [Backtrace.Image] = []
var images: ImageMap
var threads: [TargetThread] = []
var crashingThreadNdx: Int = -1
@@ -133,8 +133,7 @@ class Target {
signal = crashInfo.signal
faultAddress = crashInfo.fault_address
images = Backtrace.captureImages(using: reader,
forProcess: Int(pid))
images = ImageMap.capture(using: reader, forProcess: Int(pid))
do {
try fetchThreads(threadListHead: Address(crashInfo.thread_list),

View File

@@ -69,7 +69,7 @@ class Target {
var crashingThread: TargetThread.ThreadID
var task: task_t
var images: [Backtrace.Image] = []
var images: ImageMap
var threads: [TargetThread] = []
var crashingThreadNdx: Int = -1
@@ -193,7 +193,7 @@ class Target {
mcontext = mctx
images = Backtrace.captureImages(for: task)
images = ImageMap.capture(for: task)
fetchThreads(limit: limit, top: top, cache: cache, symbolicate: symbolicate)
}

View File

@@ -0,0 +1,66 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -parse-as-library -Onone -o %t/ImageMap
// RUN: %target-codesign %t/ImageMap
// RUN: %target-run %t/ImageMap | %FileCheck %s
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: back_deployment_runtime
// REQUIRES: executable_test
// REQUIRES: backtracing
// REQUIRES: OS=macosx || OS=linux-gnu
import Runtime
@_spi(Internal) import Runtime
@main
struct ImageMapTest {
static func main() {
let map = ImageMap.capture()
let encoder = CompactImageMapFormat.Encoder(map)
let encoded = Array(encoder)
print(map)
print("Encoded \(map.count) images in \(encoded.count) bytes")
for (ndx, byte) in encoded.enumerated() {
let separator: String
if ((ndx + 1) & 0xf) == 0 {
separator = "\n"
} else {
separator = " "
}
var hex = String(byte, radix: 16)
if hex.count < 2 {
hex = "0" + hex
}
print(hex, terminator: separator)
}
print("")
var decoder = CompactImageMapFormat.Decoder(encoded)
guard let decodedMap = decoder.decode() else {
print("Unable to decode")
return
}
print("Decoded \(decodedMap.count) images")
print(decodedMap)
if map.description != decodedMap.description {
print("Maps do not match")
} else {
print("Maps match")
}
// CHECK: Encoded [[COUNT:[0-9]+]] images in [[BYTES:[0-9]+]] bytes
// CHECK-NOT: Unable to decode
// CHECK: Decoded [[COUNT]] images
// CHECK-NOT: Maps do not match
// CHECK: Maps match
}
}

View File

@@ -0,0 +1,27 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -parse-as-library -Onone -o %t/ImageMap
// RUN: %target-codesign %t/ImageMap
// RUN: %target-run %t/ImageMap | %FileCheck %s
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: back_deployment_runtime
// REQUIRES: executable_test
// REQUIRES: backtracing
// REQUIRES: OS=macosx || OS=linux-gnu
import Runtime
@main
struct ImageMapTest {
static func main() {
let map = ImageMap.capture()
// We expect ImageMap, followed by one or more additional lines
// CHECK: {{0x[0-9a-f]*-0x[0-9a-f]*}} {{(<no build ID>|[0-9a-f]*)}} ImageMap {{.*}}/ImageMap
// CHECK-NEXT: {{0x[0-9a-f]*-0x[0-9a-f]*}} {{(<no build ID>|[0-9a-f]*)}} [[NAME:[^ ]*]] {{.*}}/[[NAME]]
print(map)
}
}