Files
swift-mirror/stdlib/public/Backtracing/SymbolicatedBacktrace.swift
Alastair Houghton dd0b826235 [Runtime] Improved symbolication option for backtracing.
Rather than just on or off, I've changed it to allow "off", "fast",
or "full".  "fast" means that we'll do symbol lookup, but we won't
try to find inline frames and we won't run line number programs
(those are the things that are taking considerable time in some
cases).

rdar://122302117
2024-02-06 17:38:20 +00:00

638 lines
22 KiB
Swift

//===--- Backtrace.swift --------------------------------------*- swift -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Defines the `SymbolicatedBacktrace` struct that represents a captured
// backtrace with symbols.
//
//===----------------------------------------------------------------------===//
import Swift
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
@_implementationOnly import OS.Darwin
#endif
@_implementationOnly import OS.Libc
@_implementationOnly import Runtime
// Because we've turned off the OS/SDK modules, and we don't have a module for
// stddef.h, and we sometimes build with -fbuiltin-headers-in-system-modules for
// vfs reasons, stddef.h can be absorbed into a random module. Sometimes it's
// SwiftOverlayShims.
@_implementationOnly import SwiftOverlayShims
/// A symbolicated backtrace
public struct SymbolicatedBacktrace: CustomStringConvertible {
/// The `Backtrace` from which this was constructed
public var backtrace: Backtrace
/// Represents a location in source code.
///
/// The information in this structure comes from compiler-generated
/// debug information and may not correspond to the current state of
/// the filesystem --- it might even hold a path that only works
/// from an entirely different machine.
public struct SourceLocation: CustomStringConvertible, Sendable, Hashable {
/// The path of the source file.
public var path: String
/// The line number.
public var line: Int
/// The column number.
public var column: Int
/// Provide a textual description.
public var description: String {
if column > 0 && line > 0 {
return "\(path):\(line):\(column)"
} else if line > 0 {
return "\(path):\(line)"
} else {
return path
}
}
}
/// Represents an individual frame in the backtrace.
public struct Frame: CustomStringConvertible {
/// The captured frame from the `Backtrace`.
public var captured: Backtrace.Frame
/// The result of doing a symbol lookup for this frame.
public var symbol: Symbol?
/// If `true`, then this frame was inlined
public var inlined: Bool = false
/// `true` if this frame represents a Swift runtime failure.
public var isSwiftRuntimeFailure: Bool {
symbol?.isSwiftRuntimeFailure ?? false
}
/// `true` if this frame represents a Swift thunk function.
public var isSwiftThunk: Bool {
symbol?.isSwiftThunk ?? false
}
/// `true` if this frame is a system frame.
public var isSystem: Bool {
symbol?.isSystemFunction ?? false
}
/// A textual description of this frame.
public func description(width: Int) -> String {
if let symbol = symbol {
let isInlined = inlined ? " [inlined]" : ""
let isThunk = isSwiftThunk ? " [thunk]" : ""
return "\(captured.description(width: width))\(isInlined)\(isThunk) \(symbol)"
} else {
return captured.description(width: width)
}
}
/// A textual description of this frame.
public var description: String {
return description(width: MemoryLayout<Backtrace.Address>.size * 2)
}
}
/// Represents a symbol we've located
public class Symbol: CustomStringConvertible {
/// The index of the image in which the symbol for this address is located.
public var imageIndex: Int
/// The name of the image in which the symbol for this address is located.
public var imageName: String
/// The raw symbol name, before demangling.
public var rawName: String
/// The demangled symbol name.
public lazy var name: String = demangleRawName()
/// The offset from the symbol.
public var offset: Int
/// The source location, if available.
public var sourceLocation: SourceLocation?
/// True if this symbol represents a Swift runtime failure.
public var isSwiftRuntimeFailure: Bool {
guard let sourceLocation = sourceLocation else {
return false
}
let symName: Substring
if rawName.hasPrefix("_") {
symName = rawName.dropFirst()
} else {
symName = rawName.dropFirst(0)
}
return symName.hasPrefix("Swift runtime failure: ")
&& sourceLocation.line == 0
&& sourceLocation.column == 0
&& sourceLocation.path.hasSuffix("<compiler-generated>")
}
/// True if this symbol is a Swift thunk function.
public var isSwiftThunk: Bool {
return _swift_backtrace_isThunkFunction(rawName)
}
private func maybeUnderscore(_ sym: String) -> String {
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
return "_" + sym
#else
return sym
#endif
}
private func dylibName(_ dylib: String) -> String {
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
return dylib + ".dylib"
#else
return dylib + ".so"
#endif
}
/// True if this symbol represents a system function.
///
/// For instance, the `start` function from `dyld` on macOS is a system
/// function, and we don't need to display it under normal circumstances.
public var isSystemFunction: Bool {
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
if rawName == "start" && imageName == "dyld" {
return true
}
#endif
if rawName.hasSuffix("5$mainyyFZ")
|| rawName.hasSuffix("5$mainyyYaFZTQ0_")
|| rawName == maybeUnderscore("async_MainTQ0_") {
return true
}
if rawName == maybeUnderscore("_ZL23completeTaskWithClosurePN5swift12AsyncContextEPNS_10SwiftErrorE") && imageName == dylibName("libswift_Concurrency") {
return true
}
if let location = sourceLocation,
((location.line == 0 && location.column == 0)
|| location.path.hasSuffix("<compiler-generated>"))
&& !_swift_backtrace_isThunkFunction(rawName) {
return true
}
return false
}
/// Construct a new Symbol.
public init(imageIndex: Int, imageName: String,
rawName: String, offset: Int, sourceLocation: SourceLocation?) {
self.imageIndex = imageIndex
self.imageName = imageName
self.rawName = rawName
self.offset = offset
self.sourceLocation = sourceLocation
}
/// Demangle the raw name, if possible.
private func demangleRawName() -> String {
var length: size_t = 0
if let demangled = _swift_backtrace_demangle(rawName, rawName.utf8.count,
nil, &length) {
defer { free(demangled) }
// length is the size of the buffer that was allocated, *not* the
// length of the string.
let stringLen = strlen(demangled)
if stringLen > 0 {
return demangled.withMemoryRebound(to: UInt8.self,
capacity: stringLen) {
let demangledBytes = UnsafeBufferPointer(start: $0,
count: stringLen)
return String(decoding: demangledBytes, as: UTF8.self)
}
}
}
return rawName
}
/// A textual description of this symbol.
public var description: String {
let symPlusOffset: String
if offset > 0 {
symPlusOffset = "\(name) + \(offset)"
} else if offset < 0 {
symPlusOffset = "\(name) - \(-offset)"
} else {
symPlusOffset = name
}
let location: String
if let sourceLocation = sourceLocation {
location = " at \(sourceLocation)"
} else {
location = ""
}
return "[\(imageIndex)] \(imageName) \(symPlusOffset)\(location)"
}
}
/// The width, in bits, of an address in this backtrace.
public var addressWidth: Int {
return backtrace.addressWidth
}
/// A list of captured frame information.
public var frames: [Frame]
/// A list of images found in the process.
public var images: [Backtrace.Image]
/// Shared cache information.
public var sharedCacheInfo: Backtrace.SharedCacheInfo?
/// True if this backtrace is a Swift runtime failure.
public var isSwiftRuntimeFailure: Bool {
guard let frame = frames.first else { return false }
return frame.isSwiftRuntimeFailure
}
/// If this backtrace is a Swift runtime failure, return the description.
public var swiftRuntimeFailure: String? {
guard let frame = frames.first else { return nil }
if !frame.isSwiftRuntimeFailure { return nil }
let symbolName = frame.symbol!.rawName
if symbolName.hasPrefix("_") {
return String(symbolName.dropFirst())
}
return symbolName
}
/// Construct a SymbolicatedBacktrace from a backtrace and a list of images.
private init(backtrace: Backtrace, images: [Backtrace.Image],
sharedCacheInfo: Backtrace.SharedCacheInfo?,
frames: [Frame]) {
self.backtrace = backtrace
self.images = images
self.sharedCacheInfo = sharedCacheInfo
self.frames = frames
}
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
/// Convert a build ID to a CFUUIDBytes.
private static func uuidBytesFromBuildID(_ buildID: [UInt8])
-> CFUUIDBytes {
return withUnsafeTemporaryAllocation(of: CFUUIDBytes.self,
capacity: 1) { buf in
buf.withMemoryRebound(to: UInt8.self) {
_ = $0.initialize(from: buildID)
}
return buf[0]
}
}
/// Create a symbolicator.
private static func withSymbolicator<T>(images: [Backtrace.Image],
sharedCacheInfo: Backtrace.SharedCacheInfo?,
useSymbolCache: Bool,
fn: (CSSymbolicatorRef) throws -> T) rethrows -> T {
let binaryImageList = images.map{ image in
BinaryImageInformation(
base: vm_address_t(image.baseAddress),
extent: vm_address_t(image.endOfText),
uuid: uuidBytesFromBuildID(image.buildID!),
arch: HostContext.coreSymbolicationArchitecture,
path: image.path,
relocations: [
BinaryRelocationInformation(
base: vm_address_t(image.baseAddress),
extent: vm_address_t(image.endOfText),
name: "__TEXT"
)
],
flags: 0
)
}
let symbolicator = CSSymbolicatorCreateWithBinaryImageList(
binaryImageList,
useSymbolCache ? 0 : kCSSymbolicatorDisallowDaemonCommunication,
nil
)
defer { CSRelease(symbolicator) }
return try fn(symbolicator)
}
/// Generate a frame from a symbol and source info pair
private static func buildFrame(from capturedFrame: Backtrace.Frame,
with owner: CSSymbolOwnerRef,
isInline: Bool,
symbol: CSSymbolRef,
sourceInfo: CSSourceInfoRef?,
images: [Backtrace.Image]) -> Frame {
if CSIsNull(symbol) {
return Frame(captured: capturedFrame, symbol: nil)
}
let address = capturedFrame.originalProgramCounter
let rawName = CSSymbolGetMangledName(symbol) ?? "<unknown>"
let name = CSSymbolGetName(symbol) ?? rawName
let range = CSSymbolGetRange(symbol)
let location: SourceLocation?
if let sourceInfo = sourceInfo, !CSIsNull(sourceInfo) {
let path = CSSourceInfoGetPath(sourceInfo) ?? "<unknown>"
let line = CSSourceInfoGetLineNumber(sourceInfo)
let column = CSSourceInfoGetColumn(sourceInfo)
location = SourceLocation(
path: path,
line: Int(line),
column: Int(column)
)
} else {
location = nil
}
let imageBase = CSSymbolOwnerGetBaseAddress(owner)
var imageIndex = -1
var imageName = ""
for (ndx, image) in images.enumerated() {
if image.baseAddress == imageBase {
imageIndex = ndx
imageName = image.name
break
}
}
let theSymbol = Symbol(imageIndex: imageIndex,
imageName: imageName,
rawName: rawName,
offset: Int(address - UInt64(range.location)),
sourceLocation: location)
theSymbol.name = name
return Frame(captured: capturedFrame, symbol: theSymbol, inlined: isInline)
}
#endif
/// Actually symbolicate.
internal static func symbolicate(backtrace: Backtrace,
images: [Backtrace.Image]?,
sharedCacheInfo: Backtrace.SharedCacheInfo?,
showInlineFrames: Bool,
showSourceLocations: Bool,
useSymbolCache: Bool)
-> SymbolicatedBacktrace? {
let theImages: [Backtrace.Image]
if let images = images {
theImages = images
} else if let images = backtrace.images {
theImages = images
} else {
theImages = Backtrace.captureImages()
}
let theCacheInfo: Backtrace.SharedCacheInfo?
if let sharedCacheInfo = sharedCacheInfo {
theCacheInfo = sharedCacheInfo
} else if let sharedCacheInfo = backtrace.sharedCacheInfo {
theCacheInfo = sharedCacheInfo
} else {
theCacheInfo = Backtrace.captureSharedCacheInfo()
}
var frames: [Frame] = []
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
withSymbolicator(images: theImages,
sharedCacheInfo: theCacheInfo,
useSymbolCache: useSymbolCache) { symbolicator in
for frame in backtrace.frames {
switch frame {
case .omittedFrames(_), .truncated:
frames.append(Frame(captured: frame, symbol: nil))
default:
let address = vm_address_t(frame.adjustedProgramCounter)
let owner
= CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator,
address,
kCSBeginningOfTime)
if CSIsNull(owner) {
frames.append(Frame(captured: frame, symbol: nil))
} else if showInlineFrames {
// These present in *reverse* order (i.e. the real one first,
// then the inlined frames from callee to caller).
let pos = frames.count
var first = true
_ = CSSymbolOwnerForEachStackFrameAtAddress(owner, address) {
symbol, sourceInfo in
frames.insert(buildFrame(from: frame,
with: owner,
isInline: !first,
symbol: symbol,
sourceInfo: sourceInfo,
images: theImages),
at: pos)
first = false
}
} else if showSourceLocations {
let symbol = CSSymbolOwnerGetSymbolWithAddress(owner, address)
let sourceInfo = CSSymbolOwnerGetSourceInfoWithAddress(owner,
address)
frames.append(buildFrame(from: frame,
with: owner,
isInline: false,
symbol: symbol,
sourceInfo: sourceInfo,
images: theImages))
} else {
let symbol = CSSymbolOwnerGetSymbolWithAddress(owner, address)
frames.append(buildFrame(from: frame,
with: owner,
isInline: false,
symbol: symbol,
sourceInfo: nil,
images: theImages))
}
}
}
}
#elseif os(Linux)
var elf32Cache: [Int:Elf32Image<FileImageSource>] = [:]
var elf64Cache: [Int:Elf64Image<FileImageSource>] = [:]
// This could be more efficient; at the moment we execute the line
// number programs once per frame, whereas we could just run them once
// for all the addresses we're interested in.
for frame in backtrace.frames {
let address = FileImageSource.Address(frame.adjustedProgramCounter)
if let imageNdx = theImages.firstIndex(
where: { address >= $0.baseAddress
&& address < $0.endOfText }
) {
let relativeAddress = address - FileImageSource.Address(theImages[imageNdx].baseAddress)
var symbol: Symbol = Symbol(imageIndex: imageNdx,
imageName: theImages[imageNdx].name,
rawName: "<unknown>",
offset: 0,
sourceLocation: nil)
var elf32Image = elf32Cache[imageNdx]
var elf64Image = elf64Cache[imageNdx]
if elf32Image == nil && elf64Image == nil {
if let source = try? FileImageSource(path: theImages[imageNdx].path) {
if let elfImage = try? Elf32Image(source: source) {
elf32Image = elfImage
elf32Cache[imageNdx] = elfImage
} else if let elfImage = try? Elf64Image(source: source) {
elf64Image = elfImage
elf64Cache[imageNdx] = elfImage
}
}
}
if let theSymbol = elf32Image?.lookupSymbol(address: relativeAddress) {
var location: SourceLocation?
if showSourceLocations || showInlineFrames {
location = try? elf32Image!.sourceLocation(for: relativeAddress)
} else {
location = nil
}
if showInlineFrames {
for inline in elf32Image!.inlineCallSites(at: relativeAddress) {
let fakeSymbol = Symbol(imageIndex: imageNdx,
imageName: theImages[imageNdx].name,
rawName: inline.rawName ?? "<unknown>",
offset: 0,
sourceLocation: location)
frames.append(Frame(captured: frame,
symbol: fakeSymbol,
inlined: true))
location = SourceLocation(path: inline.filename,
line: inline.line,
column: inline.column)
}
}
symbol = Symbol(imageIndex: imageNdx,
imageName: theImages[imageNdx].name,
rawName: theSymbol.name,
offset: theSymbol.offset,
sourceLocation: location)
} else if let theSymbol = elf64Image?.lookupSymbol(address: relativeAddress) {
var location: SourceLocation?
if showSourceLocations || showInlineFrames {
location = try? elf64Image!.sourceLocation(for: relativeAddress)
} else {
location = nil
}
if showInlineFrames {
for inline in elf64Image!.inlineCallSites(at: relativeAddress) {
let fakeSymbol = Symbol(imageIndex: imageNdx,
imageName: theImages[imageNdx].name,
rawName: inline.rawName ?? "<unknown>",
offset: 0,
sourceLocation: location)
frames.append(Frame(captured: frame,
symbol: fakeSymbol,
inlined: true))
location = SourceLocation(path: inline.filename,
line: inline.line,
column: inline.column)
}
}
symbol = Symbol(imageIndex: imageNdx,
imageName: theImages[imageNdx].name,
rawName: theSymbol.name,
offset: theSymbol.offset,
sourceLocation: location)
} else {
symbol = Symbol(imageIndex: imageNdx,
imageName: theImages[imageNdx].name,
rawName: "<unknown>",
offset: 0,
sourceLocation: nil)
}
frames.append(Frame(captured: frame, symbol: symbol))
continue
}
frames.append(Frame(captured: frame, symbol: nil))
}
#else
frames = backtrace.frames.map{ Frame(captured: $0, symbol: nil) }
#endif
return SymbolicatedBacktrace(backtrace: backtrace,
images: theImages,
sharedCacheInfo: theCacheInfo,
frames: frames)
}
/// Provide a textual version of the backtrace.
public var description: String {
var lines: [String] = []
let addressChars = (backtrace.addressWidth + 3) / 4
var n = 0
for frame in frames {
lines.append("\(n)\t\(frame.description(width: addressChars))")
switch frame.captured {
case let .omittedFrames(count):
n += count
default:
n += 1
}
}
lines.append("")
lines.append("Images:")
lines.append("")
for (n, image) in images.enumerated() {
lines.append("\(n)\t\(image.description(width: addressChars))")
}
if let sharedCacheInfo = sharedCacheInfo {
lines.append("")
lines.append("Shared Cache:")
lines.append("")
lines.append(" UUID: \(hex(sharedCacheInfo.uuid))")
lines.append(" Base: \(hex(sharedCacheInfo.baseAddress, width: addressChars))")
lines.append(" Active: \(!sharedCacheInfo.noCache)")
}
return lines.joined(separator: "\n")
}
}