mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[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:
@@ -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.
|
||||
|
||||
@@ -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 |
|
||||
|
||||
226
docs/CompactImageMapFormat.md
Normal file
226
docs/CompactImageMapFormat.md
Normal 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%.
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
730
stdlib/public/RuntimeModule/CompactImageMap.swift
Normal file
730
stdlib/public/RuntimeModule/CompactImageMap.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
91
stdlib/public/RuntimeModule/ImageMap+Darwin.swift
Normal file
91
stdlib/public/RuntimeModule/ImageMap+Darwin.swift
Normal 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)
|
||||
121
stdlib/public/RuntimeModule/ImageMap+Linux.swift
Normal file
121
stdlib/public/RuntimeModule/ImageMap+Linux.swift
Normal 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)
|
||||
174
stdlib/public/RuntimeModule/ImageMap.swift
Normal file
174
stdlib/public/RuntimeModule/ImageMap.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
66
test/Backtracing/CompactImageMap.swift
Normal file
66
test/Backtracing/CompactImageMap.swift
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
27
test/Backtracing/ImageMap.swift
Normal file
27
test/Backtracing/ImageMap.swift
Normal file
@@ -0,0 +1,27 @@
|
||||
// RUN: %empty-directory(%t)
|
||||
// RUN: %target-build-swift %s -parse-as-library -Onone -o %t/ImageMap
|
||||
// RUN: %target-codesign %t/ImageMap
|
||||
// RUN: %target-run %t/ImageMap | %FileCheck %s
|
||||
|
||||
// UNSUPPORTED: use_os_stdlib
|
||||
// UNSUPPORTED: back_deployment_runtime
|
||||
// REQUIRES: executable_test
|
||||
// REQUIRES: backtracing
|
||||
// REQUIRES: OS=macosx || OS=linux-gnu
|
||||
|
||||
import Runtime
|
||||
|
||||
@main
|
||||
struct ImageMapTest {
|
||||
static func main() {
|
||||
let map = ImageMap.capture()
|
||||
|
||||
// We expect ImageMap, followed by one or more additional lines
|
||||
|
||||
// CHECK: {{0x[0-9a-f]*-0x[0-9a-f]*}} {{(<no build ID>|[0-9a-f]*)}} ImageMap {{.*}}/ImageMap
|
||||
// CHECK-NEXT: {{0x[0-9a-f]*-0x[0-9a-f]*}} {{(<no build ID>|[0-9a-f]*)}} [[NAME:[^ ]*]] {{.*}}/[[NAME]]
|
||||
print(map)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user