//===--- 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 @_implementationOnly import _SwiftBacktracingShims @_silgen_name("_swift_isThunkFunction") func _swift_isThunkFunction( _ rawName: UnsafePointer? ) -> CBool /// 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 var description: String { if let symbol = symbol { let isInlined = inlined ? " [inlined]" : "" let isThunk = isSwiftThunk ? " [thunk]" : "" return "\(captured)\(isInlined)\(isThunk) \(symbol)" } else { return captured.description } } } /// 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("") } /// True if this symbol is a Swift thunk function. public var isSwiftThunk: Bool { return _swift_isThunkFunction(rawName) } /// 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 } if rawName.hasSuffix("5$mainyyFZ") || rawName.hasSuffix("5$mainyyYaFZTQ0_") || rawName == "_async_MainTQ0_" { return true } if rawName == "__ZL23completeTaskWithClosurePN5swift12AsyncContextEPNS_10SwiftErrorE" && imageName == "libswift_Concurrency.dylib" { return true } if let location = sourceLocation, location.line == 0 && location.column == 0 && !_swift_isThunkFunction(rawName) { return true } #endif 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 { // We don't actually need this function on macOS because we're using // CoreSymbolication, which demangles the name when it does the lookup // anyway. We will need it for Linux and Windows though. 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)" } } /// 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]) -> __swift_backtrace_CFUUIDBytes { return withUnsafeTemporaryAllocation(of: __swift_backtrace_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(images: [Backtrace.Image], sharedCacheInfo: Backtrace.SharedCacheInfo?, useSymbolCache: Bool, fn: (CSSymbolicatorRef) throws -> T) rethrows -> T { let binaryImageList = images.map{ image in BinaryImageInformation( base: __swift_vm_address_t(image.baseAddress), extent: __swift_vm_address_t(image.endOfText), uuid: uuidBytesFromBuildID(image.buildID!), arch: HostContext.coreSymbolicationArchitecture, path: image.path, relocations: [ BinaryRelocationInformation( base: __swift_vm_address_t(image.baseAddress), extent: __swift_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) ?? "" let name = CSSymbolGetName(symbol) ?? rawName let range = CSSymbolGetRange(symbol) let location: SourceLocation? if !CSIsNull(sourceInfo) { let path = CSSourceInfoGetPath(sourceInfo) ?? "" 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 - UInt(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, 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 = __swift_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 { 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 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] = [] var n = 0 for frame in frames { lines.append("\(n)\t\(frame)") 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)") } if let sharedCacheInfo = sharedCacheInfo { lines.append("") lines.append("Shared Cache:") lines.append("") lines.append(" UUID: \(hex(sharedCacheInfo.uuid))") lines.append(" Base: \(hex(sharedCacheInfo.baseAddress))") lines.append(" Active: \(!sharedCacheInfo.noCache)") } return lines.joined(separator: "\n") } }