//===--- DefaultSymbolLocator.swift - Symbol file finding for Swift -------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2026 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 // //===----------------------------------------------------------------------===// // // The default symbol locator used by the Runtime module. // //===----------------------------------------------------------------------===// import Swift internal import BacktracingImpl.Runtime // We look for symbols at SWIFT_SYMBOL_PATH. If that is not set, // then on UNIX systems we look in /usr/lib/debug/, while on Windows // we look in C:\Symbols.. #if os(Windows) let defaultSymbolPath = "C:\\Symbols" #else let defaultSymbolPath = "/usr/lib/debug" #endif fileprivate let symbolPath: String = { return getEnv("SWIFT_SYMBOL_PATH") ?? defaultSymbolPath }() #if os(Windows) fileprivate let sep = "\\" fileprivate let elfSymbolPath = "\(symbolPath)\\ELF" #else fileprivate let sep = "/" fileprivate let elfSymbolPath = "\(symbolPath)/.build-id" #endif @_spi(SymbolLocation) open class DefaultSymbolLocator: SymbolLocator { public init() {} public typealias Image = SymbolLocator.Image public typealias ImageFormat = SymbolLocator.ImageFormat public static var shared: DefaultSymbolLocator { return BacktracerThreadLocals.threadLocal.defaultSymbolLocator } enum CacheKey: Equatable, Hashable { case path(String) case uuid([UInt8]) case uuidAndAge([UInt8], UInt32) } var cache: [CacheKey: any SymbolSource] = [:] private func loadElfImage(path: String, uuid: [UInt8]?) -> ElfImageCache.Result? { let elfCache = ElfImageCache.threadLocal guard let result = elfCache.lookup(path: path) else { return nil } switch result { case let .elf32Image(elfImage): if elfImage.uuid != uuid { return nil } case let .elf64Image(elfImage): if elfImage.uuid != uuid { return nil } } return result } private func loadPeCoffImage( path: String, uuid: [UInt8]?, age: UInt32? ) -> PeCoffImage? { let peCache = PeImageCache.threadLocal guard let peImage = peCache.lookup(path: path) else { return nil } if let codeview = peImage.codeview, let uuid, let age, codeview.uuid != uuid || codeview.age != age { return nil } return peImage } public func elfDebugSymSubPath(for uuidString: String) -> String { let uuidSuffix = uuidString.dropFirst(2) let uuidPrefix = uuidString.prefix(2) return "\(uuidPrefix)\(sep)\(uuidSuffix).debug" } public func elfDebugSymSubPath(for uuid: [UInt8]) -> String { return elfDebugSymSubPath(for: hex(uuid)) } private func elfDebugSymPath(for image: any Image) -> String? { guard let uuid = image.uuid else { return nil } return "\(elfSymbolPath)\(sep)\(elfDebugSymSubPath(for: uuid))" } private func findSymbolsForElf(image: any Image) -> (any SymbolSource)? { func toSymbolSource(_ result: ElfImageCache.Result) -> (any SymbolSource)? { switch result { case let .elf32Image(elfImage): return elfImage case let .elf64Image(elfImage): return elfImage } } // Check the cache first if let uuid = image.uuid, let source = cache[.uuid(uuid)] { return source } if let path = image.path, let source = cache[.path(path)] { return source } if let uuid = image.uuid, let (_, result) = findElf(image: image, paths: findElfSymbolPaths(image: image)) { let source = toSymbolSource(result) cache[.uuid(uuid)] = source return source } // We must be able to load the original image (as an ELF image) guard let (imagePath, result) = findElf(image: image, paths: findImagePaths(image: image)) else { return nil } if let realImagePath = realPath(imagePath) { let imageDir = dirname(realImagePath) let debugLink: String? let debugAltLink: String? switch result { case let .elf32Image(elfImage): debugLink = elfImage.getDebugLink()?.link debugAltLink = elfImage.getDebugAltLink()?.link case let .elf64Image(elfImage): debugLink = elfImage.getDebugLink()?.link debugAltLink = elfImage.getDebugAltLink()?.link } let tryLink = { [self] (_ link: String) -> (any SymbolSource)? in let path1 = "\(imageDir)\(sep)\(link)" if let result = loadElfImage(path: path1, uuid: image.uuid) { return toSymbolSource(result) } let path2 = "\(imageDir)\(sep).debug\(sep)\(link)" if let result = loadElfImage(path: path2, uuid: image.uuid) { return toSymbolSource(result) } let path3 = "\(elfSymbolPath)\(sep)\(imageDir)\(sep)\(link)" if let result = loadElfImage(path: path3, uuid: image.uuid) { return toSymbolSource(result) } return nil } if let debugAltLink = debugAltLink, let source = tryLink(debugAltLink) { if let uuid = image.uuid { cache[.uuid(uuid)] = source } else if let path = image.path { cache[.path(path)] = source } return source } if let debugLink = debugLink, let source = tryLink(debugLink) { if let uuid = image.uuid { cache[.uuid(uuid)] = source } else if let path = image.path { cache[.path(path)] = source } return source } } let debugData: ImageSource? switch result { case let .elf32Image(elfImage): debugData = elfImage.getSection(".gnu_debugdata") case let .elf64Image(elfImage): debugData = elfImage.getSection(".gnu_debugdata") } if let debugData { do { let source = try ImageSource(lzmaCompressedImageSource: debugData) let symbolSource: any SymbolSource switch result { case .elf32Image(_): symbolSource = try Elf32Image(source: source) case .elf64Image(_): symbolSource = try Elf64Image(source: source) } if let uuid = image.uuid { cache[.uuid(uuid)] = symbolSource } else if let path = image.path { cache[.path(path)] = symbolSource } return symbolSource } catch let CompressedImageSourceError.libraryNotFound(library) { swift_reportWarning(0, """ swift-runtime: warning: \(library) not found, \ unable to decode the .gnu_debugdata section in \ \(image.name ?? "") """) } catch { } } // Otherwise, return the image itself let source = toSymbolSource(result) if let uuid = image.uuid { cache[.uuid(uuid)] = source } else if let path = image.path { cache[.path(path)] = source } return source } private func findSymbolsForPeCoff(image: any Image) -> (any SymbolSource)? { var result: (any SymbolSource)? = nil let uuid: [UInt8]? let age: UInt32? // Break the UUID into a (uuid, age) pair. if let theUUID = image.uuid { if let theAge = image.age { uuid = theUUID age = theAge } else { // PE-COFF image IDs are a UUID with a DWORD appended if theUUID.count != 20 { return nil } uuid = Array(theUUID[0..<16]) age = theUUID[16..<20].withUnsafeBytes { return $0.assumingMemoryBound(to: UInt32.self).baseAddress!.pointee } } } else { uuid = nil age = nil } // Check the cache first if let uuid, let age, let source = cache[.uuidAndAge(uuid, age)] { return source } if let path = image.path, let source = cache[.path(path)] { return source } // See if we can load the image let peImage: PeCoffImage? if let peCoff = findPeCoff( image: image, uuid: uuid, age: age, paths: findImagePaths(image: image)) { peImage = peCoff.result } else { peImage = nil } // Try PDB first if let uuid, let age { let cache = PDBCache.threadLocal // First, try the path from the image itself if result == nil, let peImage, let codeview = peImage.codeview, let pdbFile = cache.lookup(path: codeview.pdbPath), uuid == pdbFile.signature && age == pdbFile.age { result = pdbFile } for sourcePath in findPeCoffSymbolPaths(image: image) { if result != nil { break } // Break apart the image name let (rawDir, imageName) = splitpath(sourcePath) let imageDir = realPath(String(rawDir)) ?? String(rawDir) let pdbName: String let ext: Substring? if let dotNdx = imageName.lastIndex(of: ".") { let stem = imageName.prefix(upTo: dotNdx) ext = imageName.suffix(from: imageName.index(after: dotNdx)) pdbName = "\(stem).pdb" } else { ext = nil pdbName = "\(imageName).pdb" } // Try the directory containing the image let cwd = imageDir if result == nil, let pdbFile = cache.lookup(path: "\(cwd)\(sep)\(pdbName)"), uuid == pdbFile.signature && age == pdbFile.age { result = pdbFile } // Also try /.pdb and symbols//.pdb if let ext { let pdbPath1 = "\(cwd)\(sep)\(ext)\(sep)\(pdbName)" if result == nil, let pdbFile = PDBFile(path: pdbPath1), uuid == pdbFile.signature && age == pdbFile.age { result = pdbFile } // As well as symbols/ext/filename.pdb let pdbPath2 = "\(cwd)\(sep)symbols\(sep)\(ext)\(sep)\(pdbName)" if result == nil, let pdbFile = PDBFile(path: pdbPath2), uuid == pdbFile.signature && age == pdbFile.age { result = pdbFile } } // Finally, try the hard-coded symbol path let uuidElt = "\(hex(uuid))\(String(age, radix: 16))" let pdbPath = "\(symbolPath)\(sep)\(pdbName)\(sep)\(uuidElt)\(sep)\(pdbName)" if result == nil, let pdbFile = PDBFile(path: pdbPath), uuid == pdbFile.signature && age == pdbFile.age { result = pdbFile } } } // Next, if we loaded the image, see if it has a DWARF line table if peImage?.getDwarfSection(.debugLine) != nil { if let pdbSource = result { // If we have both, prioritise DWARF as it's richer result = AggregatingSymbolSource(sources: [peImage!, pdbSource]) } else { result = peImage! } } if let result { if let uuid, let age { cache[.uuidAndAge(uuid, age)] = result } else if let path = image.path { cache[.path(path)] = result } } // Note: for PE-COFF, we assume that the COFF symbol table is empty, // which is usually the case, so we only return the PE-COFF image if // it has DWARF data. return result } open func findSymbols(for image: any Image, format: ImageFormat) -> (any SymbolSource)? { if format == .auto || format == .elf { if let source = findSymbolsForElf(image: image) { return source } } if format == .auto || format == .peCoff { if let source = findSymbolsForPeCoff(image: image) { return source } } return nil } open func findImagePaths(image: any Image) -> [String] { guard let imagePath = image.path else { return [] } return [imagePath] } open func findElfSymbolPaths(image: any Image) -> [String] { guard let symbolPath = elfDebugSymPath(for: image) else { return [] } return [symbolPath] } open func findPeCoffSymbolPaths(image: any Image) -> [String] { guard let imagePath = image.path else { return [] } return [imagePath] } private func findElf(image: any Image, paths: [String]) -> (path: String, result: ElfImageCache.Result)? { for path in paths { if let result = loadElfImage(path: path, uuid: image.uuid) { return (path,result) } } return nil } private func findPeCoff( image: any Image, uuid: [UInt8]?, age: UInt32?, paths: [String]) -> (path: String, result: PeCoffImage)? { for path in paths { if let peCoff = loadPeCoffImage( path: path, uuid: uuid, age: age) { return (path, peCoff) } } return nil } open func find(image: any Image) -> String? { if let elfImageInfo = findElf( image: image, paths: findImagePaths(image: image)) { return elfImageInfo.path } if let peCoffImageInfo = findPeCoff( image: image, uuid: image.uuid, age: image.age, paths: findImagePaths(image: image)) { return peCoffImageInfo.path } return nil } }