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
|
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.
|
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
|
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:
|
follows:
|
||||||
|
|
||||||
| `size` | Machine word size |
|
| `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
|
import Swift
|
||||||
|
|
||||||
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
|
// #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
|
||||||
internal import Darwin
|
// internal import Darwin
|
||||||
#elseif os(Windows)
|
// #elseif os(Windows)
|
||||||
internal import ucrt
|
// internal import ucrt
|
||||||
#elseif canImport(Glibc)
|
// #elseif canImport(Glibc)
|
||||||
internal import Glibc
|
// internal import Glibc
|
||||||
#elseif canImport(Musl)
|
// #elseif canImport(Musl)
|
||||||
internal import Musl
|
// internal import Musl
|
||||||
#endif
|
// #endif
|
||||||
|
|
||||||
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
|
|
||||||
internal import BacktracingImpl.OS.Darwin
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if os(Linux)
|
|
||||||
internal import BacktracingImpl.ImageFormats.Elf
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// Holds a backtrace.
|
/// Holds a backtrace.
|
||||||
public struct Backtrace: CustomStringConvertible, Sendable {
|
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
|
/// This is intentionally _not_ a pointer, because you shouldn't be
|
||||||
/// dereferencing them; they may refer to some other process, for
|
/// dereferencing them; they may refer to some other process, for
|
||||||
/// example.
|
/// example.
|
||||||
public struct Address: Hashable, Codable, Sendable {
|
public struct Address: Hashable, Sendable {
|
||||||
enum Representation: Hashable, Codable, Sendable {
|
enum Representation: Hashable, Sendable {
|
||||||
case null
|
case null
|
||||||
case sixteenBit(UInt16)
|
case sixteenBit(UInt16)
|
||||||
case thirtyTwoBit(UInt32)
|
case thirtyTwoBit(UInt32)
|
||||||
@@ -253,8 +245,8 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
|||||||
/// Some backtracing algorithms may require this information, in which case
|
/// Some backtracing algorithms may require this information, in which case
|
||||||
/// it will be filled in by the `capture()` method. Other algorithms may
|
/// 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
|
/// not, in which case it will be `nil` and you can capture an image list
|
||||||
/// separately yourself using `captureImages()`.
|
/// separately yourself using `ImageMap.capture()`.
|
||||||
public var images: [Image]?
|
public var images: ImageMap?
|
||||||
|
|
||||||
/// Capture a backtrace from the current program location.
|
/// Capture a backtrace from the current program location.
|
||||||
///
|
///
|
||||||
@@ -284,10 +276,10 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
|||||||
limit: Int? = 64,
|
limit: Int? = 64,
|
||||||
offset: Int = 0,
|
offset: Int = 0,
|
||||||
top: Int = 16,
|
top: Int = 16,
|
||||||
images: [Image]? = nil) throws -> Backtrace {
|
images: ImageMap? = nil) throws -> Backtrace {
|
||||||
#if os(Linux)
|
#if os(Linux)
|
||||||
// On Linux, we need the captured images to resolve async functions
|
// On Linux, we need the captured images to resolve async functions
|
||||||
let theImages = images ?? captureImages()
|
let theImages = images ?? ImageMap.capture()
|
||||||
#else
|
#else
|
||||||
let theImages = images
|
let theImages = images
|
||||||
#endif
|
#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.
|
/// Specifies options for the `symbolicated` method.
|
||||||
public struct SymbolicationOptions: OptionSet {
|
public struct SymbolicationOptions: OptionSet {
|
||||||
public let rawValue: Int
|
public let rawValue: Int
|
||||||
@@ -353,7 +333,7 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
|||||||
/// used; otherwise we will capture images at this point.
|
/// used; otherwise we will capture images at this point.
|
||||||
///
|
///
|
||||||
/// - options: Symbolication options; see `SymbolicationOptions`.
|
/// - options: Symbolication options; see `SymbolicationOptions`.
|
||||||
public func symbolicated(with images: [Image]? = nil,
|
public func symbolicated(with images: ImageMap? = nil,
|
||||||
options: SymbolicationOptions = .default)
|
options: SymbolicationOptions = .default)
|
||||||
-> SymbolicatedBacktrace? {
|
-> SymbolicatedBacktrace? {
|
||||||
return SymbolicatedBacktrace.symbolicate(
|
return SymbolicatedBacktrace.symbolicate(
|
||||||
@@ -391,9 +371,10 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Initialise a Backtrace from a sequence of `RichFrame`s
|
/// 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>>,
|
frames: some Sequence<RichFrame<Address>>,
|
||||||
images: [Image]?) {
|
images: ImageMap?) {
|
||||||
self.architecture = architecture
|
self.architecture = architecture
|
||||||
self.representation = Array(CompactBacktraceFormat.Encoder(frames))
|
self.representation = Array(CompactBacktraceFormat.Encoder(frames))
|
||||||
self.images = images
|
self.images = images
|
||||||
@@ -403,20 +384,26 @@ public struct Backtrace: CustomStringConvertible, Sendable {
|
|||||||
// -- Capture Implementation -------------------------------------------------
|
// -- Capture Implementation -------------------------------------------------
|
||||||
|
|
||||||
extension Backtrace {
|
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)
|
@_spi(Internal)
|
||||||
@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == UnsafeLocalMemoryReader)
|
//@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == UnsafeLocalMemoryReader)
|
||||||
@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == RemoteMemoryReader)
|
//@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == RemoteMemoryReader)
|
||||||
#if os(Linux)
|
//#if os(Linux)
|
||||||
@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == MemserverMemoryReader)
|
//@_specialize(exported: true, kind: full, where Ctx == HostContext, Rdr == MemserverMemoryReader)
|
||||||
#endif
|
//#endif
|
||||||
|
@inlinable
|
||||||
public static func capture<Ctx: Context, Rdr: MemoryReader>(
|
public static func capture<Ctx: Context, Rdr: MemoryReader>(
|
||||||
from context: Ctx,
|
from context: Ctx,
|
||||||
using memoryReader: Rdr,
|
using memoryReader: Rdr,
|
||||||
images: [Image]?,
|
images: ImageMap?,
|
||||||
algorithm: UnwindAlgorithm,
|
algorithm: UnwindAlgorithm,
|
||||||
limit: Int?,
|
limit: Int? = 64,
|
||||||
offset: Int,
|
offset: Int = 0,
|
||||||
top: Int
|
top: Int = 16
|
||||||
) throws -> Backtrace {
|
) throws -> Backtrace {
|
||||||
switch algorithm {
|
switch algorithm {
|
||||||
// All of them, for now, use the frame pointer unwinder. In the long
|
// All of them, for now, use the frame pointer unwinder. In the long
|
||||||
@@ -428,6 +415,8 @@ extension Backtrace {
|
|||||||
memoryReader: memoryReader)
|
memoryReader: memoryReader)
|
||||||
|
|
||||||
if let limit = limit {
|
if let limit = limit {
|
||||||
|
print("limit = \(limit), offset = \(offset), top = \(top)")
|
||||||
|
|
||||||
let limited = LimitSequence(unwinder,
|
let limited = LimitSequence(unwinder,
|
||||||
limit: limit,
|
limit: limit,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
@@ -439,160 +428,8 @@ extension Backtrace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Backtrace(architecture: context.architecture,
|
return Backtrace(architecture: context.architecture,
|
||||||
frames: unwinder,
|
frames: unwinder.dropFirst(offset),
|
||||||
images: images)
|
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
|
ByteSwapping.swift
|
||||||
CachingMemoryReader.swift
|
CachingMemoryReader.swift
|
||||||
CompactBacktrace.swift
|
CompactBacktrace.swift
|
||||||
|
CompactImageMap.swift
|
||||||
Compression.swift
|
Compression.swift
|
||||||
Context.swift
|
Context.swift
|
||||||
CoreSymbolication.swift
|
CoreSymbolication.swift
|
||||||
@@ -38,6 +39,9 @@ set(RUNTIME_SOURCES
|
|||||||
ElfImageCache.swift
|
ElfImageCache.swift
|
||||||
FramePointerUnwinder.swift
|
FramePointerUnwinder.swift
|
||||||
Image.swift
|
Image.swift
|
||||||
|
ImageMap.swift
|
||||||
|
ImageMap+Darwin.swift
|
||||||
|
ImageMap+Linux.swift
|
||||||
ImageSource.swift
|
ImageSource.swift
|
||||||
Libc.swift
|
Libc.swift
|
||||||
LimitSequence.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
|
var done: Bool
|
||||||
|
|
||||||
#if os(Linux)
|
#if os(Linux)
|
||||||
var images: [Backtrace.Image]?
|
var images: ImageMap?
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var reader: MemoryReader
|
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)
|
@_specialize(exported: true, kind: full, where C == HostContext, M == MemserverMemoryReader)
|
||||||
#endif
|
#endif
|
||||||
public init(context: Context,
|
public init(context: Context,
|
||||||
images: [Backtrace.Image]?,
|
images: ImageMap?,
|
||||||
memoryReader: MemoryReader) {
|
memoryReader: MemoryReader) {
|
||||||
|
|
||||||
pc = Address(context.programCounter)
|
pc = Address(context.programCounter)
|
||||||
@@ -87,10 +87,7 @@ public struct FramePointerUnwinder<C: Context, M: MemoryReader>: Sequence, Itera
|
|||||||
let address = MemoryReader.Address(pc)
|
let address = MemoryReader.Address(pc)
|
||||||
|
|
||||||
if let images = images,
|
if let images = images,
|
||||||
let imageNdx = images.firstIndex(
|
let imageNdx = images.indexOfImage(at: Backtrace.Address(address)) {
|
||||||
where: { address >= MemoryReader.Address($0.baseAddress)!
|
|
||||||
&& address < MemoryReader.Address($0.endOfText)! }
|
|
||||||
) {
|
|
||||||
let base = MemoryReader.Address(images[imageNdx].baseAddress)!
|
let base = MemoryReader.Address(images[imageNdx].baseAddress)!
|
||||||
let relativeAddress = address - base
|
let relativeAddress = address - base
|
||||||
let cache = ElfImageCache.threadLocal
|
let cache = ElfImageCache.threadLocal
|
||||||
@@ -259,7 +256,6 @@ public struct FramePointerUnwinder<C: Context, M: MemoryReader>: Sequence, Itera
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
asyncContext = next
|
asyncContext = next
|
||||||
|
|
||||||
return .asyncResumePoint(pc)
|
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
|
/// Sequences you wish to use with `LimitSequence` must use an element type
|
||||||
/// that implements this protocol, so that `LimitSequence` can indicate when
|
/// that implements this protocol, so that `LimitSequence` can indicate when
|
||||||
/// it omits or truncates the sequence.
|
/// it omits or truncates the sequence.
|
||||||
|
@usableFromInline
|
||||||
protocol LimitableElement {
|
protocol LimitableElement {
|
||||||
static func omitted(_: Int) -> Self
|
static func omitted(_: Int) -> Self
|
||||||
static var truncated: Self { get }
|
static var truncated: Self { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `Sequence` that adds the ability to limit the output of another sequence.
|
/// A `Sequence` that adds the ability to limit the output of another sequence.
|
||||||
|
@usableFromInline
|
||||||
struct LimitSequence<T: LimitableElement, S: Sequence>: Sequence
|
struct LimitSequence<T: LimitableElement, S: Sequence>: Sequence
|
||||||
where S.Element == T
|
where S.Element == T
|
||||||
{
|
{
|
||||||
/// The element type, which must conform to `LimitableElement`
|
/// The element type, which must conform to `LimitableElement`
|
||||||
|
@usableFromInline
|
||||||
typealias Element = T
|
typealias Element = T
|
||||||
|
|
||||||
/// The source sequence
|
/// The source sequence
|
||||||
|
@usableFromInline
|
||||||
typealias Source = S
|
typealias Source = S
|
||||||
|
|
||||||
var source: Source
|
var source: Source
|
||||||
@@ -62,6 +66,7 @@ struct LimitSequence<T: LimitableElement, S: Sequence>: Sequence
|
|||||||
///
|
///
|
||||||
/// When `LimitSequence` omits items or truncates the sequence, it will
|
/// When `LimitSequence` omits items or truncates the sequence, it will
|
||||||
/// insert `.omitted(count)` or `.truncated` items into its output.
|
/// insert `.omitted(count)` or `.truncated` items into its output.
|
||||||
|
@usableFromInline
|
||||||
init(_ source: Source, limit: Int, offset: Int = 0, top: Int = 0) {
|
init(_ source: Source, limit: Int, offset: Int = 0, top: Int = 0) {
|
||||||
self.source = source
|
self.source = source
|
||||||
self.limit = limit
|
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
|
/// 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
|
/// sequence, so that it can tell whether or not there is more input to
|
||||||
/// follow at any given point.
|
/// follow at any given point.
|
||||||
|
@usableFromInline
|
||||||
struct Iterator: IteratorProtocol {
|
struct Iterator: IteratorProtocol {
|
||||||
/// The iterator for the input sequence.
|
/// The iterator for the input sequence.
|
||||||
var iterator: Source.Iterator
|
var iterator: Source.Iterator
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
|
|||||||
public var frames: [Frame]
|
public var frames: [Frame]
|
||||||
|
|
||||||
/// A list of images found in the process.
|
/// 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.
|
/// True if this backtrace is a Swift runtime failure.
|
||||||
public var isSwiftRuntimeFailure: Bool {
|
public var isSwiftRuntimeFailure: Bool {
|
||||||
@@ -270,8 +270,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a SymbolicatedBacktrace from a backtrace and a list of images.
|
/// Construct a SymbolicatedBacktrace from a backtrace and a list of images.
|
||||||
private init(backtrace: Backtrace, images: [Backtrace.Image],
|
private init(backtrace: Backtrace, images: ImageMap, frames: [Frame]) {
|
||||||
frames: [Frame]) {
|
|
||||||
self.backtrace = backtrace
|
self.backtrace = backtrace
|
||||||
self.images = images
|
self.images = images
|
||||||
self.frames = frames
|
self.frames = frames
|
||||||
@@ -291,7 +290,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a symbolicator.
|
/// Create a symbolicator.
|
||||||
private static func withSymbolicator<T>(images: [Backtrace.Image],
|
private static func withSymbolicator<T>(images: ImageMap,
|
||||||
useSymbolCache: Bool,
|
useSymbolCache: Bool,
|
||||||
fn: (CSSymbolicatorRef) throws -> T) rethrows -> T {
|
fn: (CSSymbolicatorRef) throws -> T) rethrows -> T {
|
||||||
let binaryImageList = images.map{ image in
|
let binaryImageList = images.map{ image in
|
||||||
@@ -329,7 +328,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
|
|||||||
isInline: Bool,
|
isInline: Bool,
|
||||||
symbol: CSSymbolRef,
|
symbol: CSSymbolRef,
|
||||||
sourceInfo: CSSourceInfoRef?,
|
sourceInfo: CSSourceInfoRef?,
|
||||||
images: [Backtrace.Image]) -> Frame {
|
images: ImageMap) -> Frame {
|
||||||
if CSIsNull(symbol) {
|
if CSIsNull(symbol) {
|
||||||
return Frame(captured: capturedFrame, symbol: nil)
|
return Frame(captured: capturedFrame, symbol: nil)
|
||||||
}
|
}
|
||||||
@@ -379,17 +378,17 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
|
|||||||
|
|
||||||
/// Actually symbolicate.
|
/// Actually symbolicate.
|
||||||
internal static func symbolicate(backtrace: Backtrace,
|
internal static func symbolicate(backtrace: Backtrace,
|
||||||
images: [Backtrace.Image]?,
|
images: ImageMap?,
|
||||||
options: Backtrace.SymbolicationOptions)
|
options: Backtrace.SymbolicationOptions)
|
||||||
-> SymbolicatedBacktrace? {
|
-> SymbolicatedBacktrace? {
|
||||||
|
|
||||||
let theImages: [Backtrace.Image]
|
let theImages: ImageMap
|
||||||
if let images = images {
|
if let images = images {
|
||||||
theImages = images
|
theImages = images
|
||||||
} else if let images = backtrace.images {
|
} else if let images = backtrace.images {
|
||||||
theImages = images
|
theImages = images
|
||||||
} else {
|
} else {
|
||||||
theImages = Backtrace.captureImages()
|
theImages = ImageMap.capture()
|
||||||
}
|
}
|
||||||
|
|
||||||
var frames: [Frame] = []
|
var frames: [Frame] = []
|
||||||
@@ -463,9 +462,7 @@ public struct SymbolicatedBacktrace: CustomStringConvertible {
|
|||||||
|
|
||||||
for frame in backtrace.frames {
|
for frame in backtrace.frames {
|
||||||
let address = frame.adjustedProgramCounter
|
let address = frame.adjustedProgramCounter
|
||||||
if let imageNdx = theImages.firstIndex(
|
if let imageNdx = theImages.indexOfImage(at: address) {
|
||||||
where: { address >= $0.baseAddress && address < $0.endOfText }
|
|
||||||
) {
|
|
||||||
let relativeAddress = ImageSource.Address(
|
let relativeAddress = ImageSource.Address(
|
||||||
address - theImages[imageNdx].baseAddress
|
address - theImages[imageNdx].baseAddress
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class Target {
|
|||||||
var faultAddress: Address
|
var faultAddress: Address
|
||||||
var crashingThread: TargetThread.ThreadID
|
var crashingThread: TargetThread.ThreadID
|
||||||
|
|
||||||
var images: [Backtrace.Image] = []
|
var images: ImageMap
|
||||||
|
|
||||||
var threads: [TargetThread] = []
|
var threads: [TargetThread] = []
|
||||||
var crashingThreadNdx: Int = -1
|
var crashingThreadNdx: Int = -1
|
||||||
@@ -133,8 +133,7 @@ class Target {
|
|||||||
signal = crashInfo.signal
|
signal = crashInfo.signal
|
||||||
faultAddress = crashInfo.fault_address
|
faultAddress = crashInfo.fault_address
|
||||||
|
|
||||||
images = Backtrace.captureImages(using: reader,
|
images = ImageMap.capture(using: reader, forProcess: Int(pid))
|
||||||
forProcess: Int(pid))
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try fetchThreads(threadListHead: Address(crashInfo.thread_list),
|
try fetchThreads(threadListHead: Address(crashInfo.thread_list),
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class Target {
|
|||||||
var crashingThread: TargetThread.ThreadID
|
var crashingThread: TargetThread.ThreadID
|
||||||
|
|
||||||
var task: task_t
|
var task: task_t
|
||||||
var images: [Backtrace.Image] = []
|
var images: ImageMap
|
||||||
|
|
||||||
var threads: [TargetThread] = []
|
var threads: [TargetThread] = []
|
||||||
var crashingThreadNdx: Int = -1
|
var crashingThreadNdx: Int = -1
|
||||||
@@ -193,7 +193,7 @@ class Target {
|
|||||||
|
|
||||||
mcontext = mctx
|
mcontext = mctx
|
||||||
|
|
||||||
images = Backtrace.captureImages(for: task)
|
images = ImageMap.capture(for: task)
|
||||||
|
|
||||||
fetchThreads(limit: limit, top: top, cache: cache, symbolicate: symbolicate)
|
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