Skip to content

Commit c9c5dc0

Browse files
committed
[Backtracing] Add platform and architecture information.
It's useful to capture the platform and platform version with the image map. Also, display both the platform and architecture information when generating a crash log. rdar://124913332
1 parent 432c138 commit c9c5dc0

13 files changed

+414
-38
lines changed

docs/CompactImageMapFormat.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,14 @@ follows:
3939
| 10 | 64-bit |
4040
| 11 | Reserved |
4141

42-
This is followed immediately by a field encoding the number of images
43-
in the image map; this field is encoded as a sequence of bytes, each
44-
holding seven bits of data, with the top bit clear for the final byte.
45-
The most significant byte is the first. e.g.
42+
This is followed immediately by a field containing the name of the platform
43+
that generated this image map. This field consists of a single byte length
44+
followed by a UTF-8 string of that length.
45+
46+
After that is a field encoding the number of images in the image map;
47+
this field is encoded as a sequence of bytes, each holding seven bits
48+
of data, with the top bit clear for the final byte. The most
49+
significant byte is the first. e.g.
4650

4751
| `count` | Encoding |
4852
| ------: | :---------- |

stdlib/public/RuntimeModule/Address.swift

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ extension FixedWidthInteger {
164164
///
165165
/// This initializer will return nil if the address width is larger than the
166166
/// type you are attempting to convert into.
167-
init?(_ address: Backtrace.Address) {
167+
public init?(_ address: Backtrace.Address) {
168168
guard let result = address.toFixedWidth(type: Self.self) else {
169169
return nil
170170
}
@@ -174,7 +174,7 @@ extension FixedWidthInteger {
174174

175175
extension Backtrace.Address {
176176
/// Convert from a UInt16.
177-
init(_ value: UInt16) {
177+
public init(_ value: UInt16) {
178178
if value == 0 {
179179
self.representation = .null
180180
return
@@ -183,7 +183,7 @@ extension Backtrace.Address {
183183
}
184184

185185
/// Convert from a UInt32.
186-
init(_ value: UInt32) {
186+
public init(_ value: UInt32) {
187187
if value == 0 {
188188
self.representation = .null
189189
return
@@ -192,7 +192,7 @@ extension Backtrace.Address {
192192
}
193193

194194
/// Convert from a UInt64.
195-
init(_ value: UInt64) {
195+
public init(_ value: UInt64) {
196196
if value == 0 {
197197
self.representation = .null
198198
return
@@ -201,21 +201,16 @@ extension Backtrace.Address {
201201
}
202202

203203
/// Convert from a FixedWidthInteger
204-
init<T: FixedWidthInteger>(_ value: T) {
205-
if value == 0 {
206-
self.representation = .null
207-
return
208-
}
209-
204+
public init?<T: FixedWidthInteger>(_ value: T) {
210205
switch T.bitWidth {
211206
case 16:
212-
self.representation = .sixteenBit(UInt16(value))
207+
self.init(UInt16(value))
213208
case 32:
214-
self.representation = .thirtyTwoBit(UInt32(value))
209+
self.init(UInt32(value))
215210
case 64:
216-
self.representation = .sixtyFourBit(UInt64(value))
211+
self.init(UInt64(value))
217212
default:
218-
fatalError("Unsupported address width")
213+
return nil
219214
}
220215
}
221216
}

stdlib/public/RuntimeModule/Backtrace.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ public struct Backtrace: CustomStringConvertible, Sendable {
229229
}
230230

231231
/// The architecture of the system that captured this backtrace.
232-
public var architecture: String
232+
public internal(set) var architecture: String
233233

234234
/// The actual backtrace data, stored in Compact Backtrace Format.
235235
var representation: [UInt8]
@@ -345,7 +345,7 @@ public struct Backtrace: CustomStringConvertible, Sendable {
345345

346346
/// Provide a textual version of the backtrace.
347347
public var description: String {
348-
var lines: [String] = []
348+
var lines: [String] = ["Architecture: \(architecture)", ""]
349349

350350
var n = 0
351351
for frame in frames {

stdlib/public/RuntimeModule/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ set(RUNTIME_SOURCES
4848
Libc.swift
4949
LimitSequence.swift
5050
MemoryReader.swift
51+
OSReleaseScanner.swift
5152
ProcMapsScanner.swift
5253
Registers.swift
5354
Runtime.swift

stdlib/public/RuntimeModule/CompactImageMap.swift

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,25 @@ public enum CompactImageMapFormat {
101101
return value
102102
}
103103

104+
mutating func decodeString() -> String? {
105+
guard let utf8Length = iterator.next() else {
106+
return nil
107+
}
108+
109+
var bytes: [UInt8] = []
110+
bytes.reserveCapacity(Int(utf8Length))
111+
112+
for _ in 0..<utf8Length {
113+
guard let byte = iterator.next() else {
114+
return nil
115+
}
116+
117+
bytes.append(byte)
118+
}
119+
120+
return String(decoding: bytes, as: UTF8.self)
121+
}
122+
104123
mutating func decodeAddress(_ count: Int) -> UInt64? {
105124
var word: UInt64
106125
guard let firstByte = iterator.next() else {
@@ -250,7 +269,7 @@ public enum CompactImageMapFormat {
250269
}
251270
}
252271

253-
mutating func decode() -> ([ImageMap.Image], ImageMap.WordSize)? {
272+
mutating func decode() -> (String, [ImageMap.Image], ImageMap.WordSize)? {
254273
// Check the version and decode the size
255274
guard let infoByte = iterator.next() else {
256275
return nil
@@ -274,6 +293,11 @@ public enum CompactImageMapFormat {
274293
wordMask = 0xffffffffffffff00
275294
}
276295

296+
// Now decode the platform
297+
guard let platform = decodeString() else {
298+
return nil
299+
}
300+
277301
// Next is the image count
278302
guard let count = decodeCount() else {
279303
return nil
@@ -392,7 +416,7 @@ public enum CompactImageMapFormat {
392416
wsMap = .sixtyFourBit
393417
}
394418

395-
return (images, wsMap)
419+
return (platform, images, wsMap)
396420
}
397421
}
398422

@@ -414,6 +438,7 @@ public enum CompactImageMapFormat {
414438
public struct Iterator: IteratorProtocol {
415439
enum State {
416440
case start
441+
case platform(Int)
417442
case count(Int)
418443
case image
419444
case baseAddress(Int)
@@ -483,14 +508,39 @@ public enum CompactImageMapFormat {
483508
size = .sixtyFourBit
484509
}
485510

486-
let count = source.images.count
487-
let bits = Int.bitWidth - count.leadingZeroBitCount
488-
state = .count(7 * (bits / 7))
511+
state = .platform(-1)
489512

490513
let version: UInt8 = 0
491514
let infoByte = (version << 2) | size.rawValue
492515
return infoByte
493516

517+
case let .platform(ndx):
518+
let length = UInt8(source.platform.utf8.count)
519+
let byte: UInt8
520+
521+
if ndx == -1 {
522+
// The length byte comes first
523+
byte = length
524+
} else {
525+
byte = source.platform.utf8[
526+
source.platform.utf8.index(
527+
source.platform.utf8.startIndex,
528+
offsetBy: ndx
529+
)
530+
]
531+
}
532+
533+
// If we're done, move to the .count state
534+
if ndx + 1 == length {
535+
let count = source.images.count
536+
let bits = Int.bitWidth - count.leadingZeroBitCount
537+
state = .count(7 * (bits / 7))
538+
} else {
539+
state = .platform(ndx + 1)
540+
}
541+
542+
return byte
543+
494544
case let .count(ndx):
495545
let count = source.images.count
496546
let byte = UInt8(truncatingIfNeeded:(count >> ndx) & 0x7f)

stdlib/public/RuntimeModule/ImageMap+Darwin.swift

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,44 @@ import Swift
2121
internal import Darwin
2222
internal import BacktracingImpl.OS.Darwin
2323

24+
fileprivate func getSysCtlString(_ name: String) -> String? {
25+
return withUnsafeTemporaryAllocation(byteCount: 256, alignment: 16) {
26+
(buffer: UnsafeMutableRawBufferPointer) -> String? in
27+
28+
var len = buffer.count
29+
let ret = sysctlbyname(name,
30+
buffer.baseAddress, &len,
31+
nil, 0)
32+
if ret != 0 {
33+
return nil
34+
}
35+
36+
return String(validatingUTF8:
37+
buffer.baseAddress!.assumingMemoryBound(to: CChar.self))
38+
}
39+
}
40+
2441
extension ImageMap {
2542

43+
private static let platform = {
44+
#if os(macOS)
45+
var platform = "macOS"
46+
#elseif os(iOS)
47+
var platform = "iOS"
48+
#elseif os(watchOS)
49+
var platform = "watchOS"
50+
#elseif os(tvOS)
51+
var platform = "tvOS"
52+
#elseif os(visionOS)
53+
var platform = "visionOS"
54+
#endif
55+
56+
let osVersion = getSysCtlString("kern.osversion") ?? "<unknown>"
57+
let osProductVersion = getSysCtlString("kern.osproductversion") ?? "<unknown>"
58+
59+
return "\(platform) \(osProductVersion) (\(osVersion))"
60+
}()
61+
2662
private static func withDyldProcessInfo<T>(for task: task_t,
2763
fn: (OpaquePointer?) throws -> T)
2864
rethrows -> T {
@@ -83,7 +119,11 @@ extension ImageMap {
83119

84120
images.sort(by: { $0.baseAddress < $1.baseAddress })
85121

86-
return ImageMap(images: images, wordSize: .sixtyFourBit)
122+
return ImageMap(
123+
platform: ImageMap.platform,
124+
images: images,
125+
wordSize: .sixtyFourBit
126+
)
87127
}
88128

89129
}

stdlib/public/RuntimeModule/ImageMap+Linux.swift

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,52 @@ internal import Musl
2626

2727
internal import BacktracingImpl.ImageFormats.Elf
2828

29+
fileprivate func readOSRelease(fd: CInt) -> [String:String]? {
30+
let len = lseek(fd, 0, SEEK_END)
31+
guard len >= 0 else {
32+
return nil
33+
}
34+
return withUnsafeTemporaryAllocation(byteCount: len, alignment: 16) {
35+
(buffer: UnsafeMutableRawBufferPointer) -> [String:String]? in
36+
37+
_ = lseek(fd, 0, SEEK_SET)
38+
let bytesRead = read(fd, buffer.baseAddress, buffer.count)
39+
guard bytesRead == buffer.count else {
40+
return nil
41+
}
42+
43+
let asString = String(decoding: buffer, as: UTF8.self)
44+
return Dictionary(OSReleaseScanner(asString),
45+
uniquingKeysWith: { $1 })
46+
}
47+
}
48+
49+
fileprivate func readOSRelease() -> [String:String]? {
50+
var fd = open("/etc/os-release", O_RDONLY)
51+
if fd == -1 {
52+
fd = open("/usr/lib/os-release", O_RDONLY)
53+
}
54+
if fd == -1 {
55+
return nil
56+
}
57+
defer {
58+
close(fd)
59+
}
60+
61+
return readOSRelease(fd: fd)
62+
}
63+
2964
extension ImageMap {
3065

66+
private static var platform = {
67+
guard let info = readOSRelease(),
68+
let pretty = info["PRETTY_NAME"] else {
69+
return "Linux (unknown)"
70+
}
71+
72+
return "Linux (\(pretty))"
73+
}()
74+
3175
private struct AddressRange {
3276
var low: Address = 0
3377
var high: Address = 0
@@ -59,7 +103,7 @@ extension ImageMap {
59103
}
60104

61105
guard let procMaps = readString(from: path) else {
62-
return ImageMap(images: [], wordSize: wordSize)
106+
return ImageMap(platform: ImageMap.platform, images: [], wordSize: wordSize)
63107
}
64108

65109
// Find all the mapped files and get high/low ranges
@@ -113,7 +157,11 @@ extension ImageMap {
113157

114158
images.sort(by: { $0.baseAddress < $1.baseAddress })
115159

116-
return ImageMap(images: images, wordSize: wordSize)
160+
return ImageMap(
161+
platform: ImageMap.platform,
162+
images: images,
163+
wordSize: wordSize
164+
)
117165
}
118166

119167
}

0 commit comments

Comments
 (0)