//===--- PeCoff.swift - PE-COFF support for Swift -------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2025 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 various PE-COFF structures and provides types for working with // PE-COFF images on disk and in memory. // //===----------------------------------------------------------------------===// import Swift #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) internal import Darwin #elseif os(Windows) internal import WinSDK #elseif canImport(Glibc) internal import Glibc #elseif canImport(Musl) internal import Musl #endif internal import BacktracingImpl.ImageFormats.PeCoff internal import BacktracingImpl.ImageFormats.CodeView // .. Typealiases .............................................................. // We define our types in the swift.runtime namespace to avoid any possibility // of type clashes elsewhere. That means they have verbose names, so declare // type aliases to fix that. typealias pe_dos_signature = swift.runtime.pe_dos_signature typealias pe_signature = swift.runtime.pe_signature typealias pe_machine = swift.runtime.pe_machine typealias pe_characteristics = swift.runtime.pe_characteristics typealias pe_optional_magic = swift.runtime.pe_optional_magic typealias pe_subsystem = swift.runtime.pe_subsystem typealias pe_dll_characteristics = swift.runtime.pe_dll_characteristics typealias pe_section_characteristics = swift.runtime.pe_section_characteristics typealias pe_symbol_type = swift.runtime.pe_symbol_type typealias pe_symbol_class = swift.runtime.pe_symbol_class typealias pe_debug_directory_type = swift.runtime.pe_debug_directory_type typealias pe_debug_characteristics = swift.runtime.pe_debug_characteristics typealias pe_dos_header = swift.runtime.pe_dos_header typealias pe_header = swift.runtime.pe_header typealias pe_optional_header = swift.runtime.pe_optional_header typealias pe_data_directory_entry = swift.runtime.pe_data_directory_entry typealias pe_windows_header32 = swift.runtime.pe_windows_header32 typealias pe_windows_header64 = swift.runtime.pe_windows_header64 typealias pe_section = swift.runtime.pe_section typealias pe_debug_directory = swift.runtime.pe_debug_directory typealias pe_symbol = swift.runtime.pe_symbol // .. Byte swapping ............................................................ extension pe_dos_signature: ByteSwappable { var byteSwapped: Self { pe_dos_signature(rawValue: rawValue.byteSwapped)! } } extension pe_signature: ByteSwappable { var byteSwapped: Self { pe_signature(rawValue: rawValue.byteSwapped)! } } extension pe_machine: ByteSwappable { var byteSwapped: Self { pe_machine(rawValue: rawValue.byteSwapped)! } } extension pe_optional_magic: ByteSwappable { var byteSwapped: Self { pe_optional_magic(rawValue: rawValue.byteSwapped)! } } extension pe_subsystem: ByteSwappable { var byteSwapped: Self { pe_subsystem(rawValue: rawValue.byteSwapped)! } } extension pe_symbol_type: ByteSwappable { var byteSwapped: Self { pe_symbol_type(rawValue: rawValue.byteSwapped)! } } extension pe_dos_header: ByteSwappable { var byteSwapped: Self { return pe_dos_header( e_magic: e_magic.byteSwapped, e_cblp: e_cblp.byteSwapped, e_cp: e_cp.byteSwapped, e_crlc: e_crlc.byteSwapped, e_cparhdr: e_cparhdr.byteSwapped, e_minalloc: e_minalloc.byteSwapped, e_maxalloc: e_maxalloc.byteSwapped, e_ss: e_ss.byteSwapped, e_sp: e_sp.byteSwapped, e_csum: e_csum.byteSwapped, e_ip: e_ip.byteSwapped, e_cs: e_cs.byteSwapped, e_lfarlc: e_lfarlc.byteSwapped, e_ovno: e_ovno.byteSwapped, e_res: e_res, e_oemid: e_oemid.byteSwapped, e_oeminfo: e_oeminfo.byteSwapped, e_res2: e_res2, e_lfanew: e_lfanew.byteSwapped ) } } extension pe_header: ByteSwappable { var byteSwapped: Self { return pe_header( Machine: Machine.byteSwapped, NumberOfSections: NumberOfSections.byteSwapped, TimeDateStamp: TimeDateStamp.byteSwapped, PointerToSymbolTable: PointerToSymbolTable.byteSwapped, NumberOfSymbols: NumberOfSymbols.byteSwapped, SizeOfOptionalHeader: SizeOfOptionalHeader.byteSwapped, Characteristics: Characteristics.byteSwapped ) } } extension pe_optional_header: ByteSwappable { var byteSwapped: Self { return pe_optional_header( Magic: Magic.byteSwapped, MajorLinkerVersion: MajorLinkerVersion, MinorLinkerVersion: MinorLinkerVersion, SizeOfCode: SizeOfCode.byteSwapped, SizeOfInitializedData: SizeOfInitializedData.byteSwapped, SizeOfUninitializedData: SizeOfUninitializedData.byteSwapped, AddressOfEntryPoint: AddressOfEntryPoint.byteSwapped, BaseOfCode: BaseOfCode.byteSwapped ) } } extension pe_data_directory_entry: ByteSwappable { var byteSwapped: Self { return pe_data_directory_entry( VirtualAddress: VirtualAddress.byteSwapped, Size: Size.byteSwapped ) } } extension pe_windows_header32: ByteSwappable { var byteSwapped: Self { return pe_windows_header32( ImageBase: ImageBase.byteSwapped, SectionAlignment: SectionAlignment.byteSwapped, FileAlignment: FileAlignment.byteSwapped, MajorOperatingSystemVersion: MajorOperatingSystemVersion.byteSwapped, MinorOperatingSystemVersion: MinorOperatingSystemVersion.byteSwapped, MajorImageVersion: MajorImageVersion.byteSwapped, MinorImageVersion: MinorImageVersion.byteSwapped, MajorSubsystemVersion: MajorSubsystemVersion.byteSwapped, MinorSubsystemVersion: MinorSubsystemVersion.byteSwapped, Win32VersionValue: Win32VersionValue.byteSwapped, SizeOfImage: SizeOfImage.byteSwapped, SizeOfHeaders: SizeOfHeaders.byteSwapped, CheckSum: CheckSum.byteSwapped, Subsystem: Subsystem.byteSwapped, DllCharacteristics: DllCharacteristics.byteSwapped, SizeOfStackReserve: SizeOfStackReserve.byteSwapped, SizeOfStackCommit: SizeOfStackCommit.byteSwapped, SizeOfHeapReserve: SizeOfHeapReserve.byteSwapped, SizeOfHeapCommit: SizeOfHeapCommit.byteSwapped, LoaderFlags: LoaderFlags.byteSwapped, NumberOfRvaAndSizes: NumberOfRvaAndSizes.byteSwapped ) } } extension pe_windows_header64: ByteSwappable { var byteSwapped: Self { return pe_windows_header64( ImageBase: ImageBase.byteSwapped, SectionAlignment: SectionAlignment.byteSwapped, FileAlignment: FileAlignment.byteSwapped, MajorOperatingSystemVersion: MajorOperatingSystemVersion.byteSwapped, MinorOperatingSystemVersion: MinorOperatingSystemVersion.byteSwapped, MajorImageVersion: MajorImageVersion.byteSwapped, MinorImageVersion: MinorImageVersion.byteSwapped, MajorSubsystemVersion: MajorSubsystemVersion.byteSwapped, MinorSubsystemVersion: MinorSubsystemVersion.byteSwapped, Win32VersionValue: Win32VersionValue.byteSwapped, SizeOfImage: SizeOfImage.byteSwapped, SizeOfHeaders: SizeOfHeaders.byteSwapped, CheckSum: CheckSum.byteSwapped, Subsystem: Subsystem.byteSwapped, DllCharacteristics: DllCharacteristics.byteSwapped, SizeOfStackReserve: SizeOfStackReserve.byteSwapped, SizeOfStackCommit: SizeOfStackCommit.byteSwapped, SizeOfHeapReserve: SizeOfHeapReserve.byteSwapped, SizeOfHeapCommit: SizeOfHeapCommit.byteSwapped, LoaderFlags: LoaderFlags.byteSwapped, NumberOfRvaAndSizes: NumberOfRvaAndSizes.byteSwapped ) } } extension pe_section: ByteSwappable { var byteSwapped: Self { return pe_section( Name: Name, VirtualSize: VirtualSize.byteSwapped, VirtualAddress: VirtualAddress.byteSwapped, SizeOfRawData: SizeOfRawData.byteSwapped, PointerToRawData: PointerToRawData.byteSwapped, PointerToRelocations: PointerToRelocations.byteSwapped, PointerToLinenumbers: PointerToLinenumbers.byteSwapped, NumberOfRelocations: NumberOfRelocations.byteSwapped, NumberOfLinenumbers: NumberOfLinenumbers.byteSwapped, Characteristics: Characteristics.byteSwapped ) } } extension pe_symbol: ByteSwappable { var byteSwapped: Self { var symbol = pe_symbol() if LongName.Zeroes == 0 { symbol.LongName.Zeroes = 0 symbol.LongName.Offset = LongName.Offset.byteSwapped } else { symbol.ShortName = ShortName } symbol.Value = Value.byteSwapped symbol.SectionNumber = SectionNumber.byteSwapped symbol.Type = Type.byteSwapped symbol.StorageClass = StorageClass symbol.NumberOfAuxSymbols = NumberOfAuxSymbols return symbol } } @inline(__always) fileprivate func maybeSwap(_ x: T) -> T { #if _endian(big) return x.byteSwapped #else return x #endif } @inline(__always) fileprivate func maybeSwap(_ x: T) -> T { #if _endian(big) return x.byteSwapped #else return x #endif } // .. PeCoffStringTable ........................................................ struct PeCoffStringTable { let source: ImageSource func getStringAt(index: Int) -> String? { if index < 0 || index >= source.bytes.count { return nil } let slice = UnsafeRawBufferPointer(rebasing: source.bytes[index...]) var len: Int = 0 len = strnlen(slice.baseAddress!, slice.count) return String(decoding: source.bytes[index...size { throw PeCoffImageError.missingOptionalHeader } pos += Address(MemoryLayout.size) let optionalHeaderStart = pos let optionalHeader = maybeSwap(try source.fetch(from: pos, as: pe_optional_header.self)) pos += Address(MemoryLayout.size) // PE32 has an extra BaseOfData field (skip this) if optionalHeader.Magic == .PE_PE32 { pos += 4 } let windowsHeader: pe_windows_header64 if optionalHeader.Magic == .PE_PE32 { let windowsHeader32 = maybeSwap( try source.fetch( from: pos, as: pe_windows_header32.self ) ) pos += Address(MemoryLayout.size) // "Upgrade" this to the 64-bit version windowsHeader = pe_windows_header64( ImageBase: UInt64(windowsHeader32.ImageBase), SectionAlignment: windowsHeader32.SectionAlignment, FileAlignment: windowsHeader32.FileAlignment, MajorOperatingSystemVersion: windowsHeader32.MajorOperatingSystemVersion, MinorOperatingSystemVersion: windowsHeader32.MinorOperatingSystemVersion, MajorImageVersion: windowsHeader32.MajorImageVersion, MinorImageVersion: windowsHeader32.MinorImageVersion, MajorSubsystemVersion: windowsHeader32.MajorSubsystemVersion, MinorSubsystemVersion: windowsHeader32.MinorSubsystemVersion, Win32VersionValue: windowsHeader32.Win32VersionValue, SizeOfImage: windowsHeader32.SizeOfImage, SizeOfHeaders: windowsHeader32.SizeOfHeaders, CheckSum: windowsHeader32.CheckSum, Subsystem: windowsHeader32.Subsystem, DllCharacteristics: windowsHeader32.DllCharacteristics, SizeOfStackReserve: UInt64(windowsHeader32.SizeOfStackReserve), SizeOfStackCommit: UInt64(windowsHeader32.SizeOfStackCommit), SizeOfHeapReserve: UInt64(windowsHeader32.SizeOfHeapReserve), SizeOfHeapCommit: UInt64(windowsHeader32.SizeOfHeapCommit), LoaderFlags: windowsHeader32.LoaderFlags, NumberOfRvaAndSizes: windowsHeader32.NumberOfRvaAndSizes ) } else if optionalHeader.Magic == .PE_PE32_PLUS { windowsHeader = maybeSwap( try source.fetch( from: pos, as: pe_windows_header64.self ) ) pos += Address(MemoryLayout.size) } else { throw PeCoffImageError.badOptionalHeader } // Read the data directory var dataDirectory = try source.fetch( from: pos, count: Int(windowsHeader.NumberOfRvaAndSizes), as: pe_data_directory_entry.self ) #if _endian(big) dataDirectory.swapBytes() #endif // Now read the sections let sectionStart = optionalHeaderStart + Address(peHeader.SizeOfOptionalHeader) var sections = try source.fetch( from: sectionStart, count: Int(peHeader.NumberOfSections), as: pe_section.self) #if _endian(big) sections.swapBytes() #endif var symbols: [pe_symbol]? = nil var stringTable: PeCoffStringTable? = nil var functions: [PeFunction]? = nil if !source.isMappedImage && peHeader.PointerToSymbolTable != 0 { // For images loaded from disk, if we find there are symbols, we // should read them, and the corresponding string table symbols = try source.fetch( from: Address(peHeader.PointerToSymbolTable), count: Int(peHeader.NumberOfSymbols), as: pe_symbol.self ) #if _endian(big) symbols.swapBytes() #endif let stringTableOffset = Address(peHeader.PointerToSymbolTable) + Address(MemoryLayout.size * Int(peHeader.NumberOfSymbols)) let stringTableSize = maybeSwap( try source.fetch( from: stringTableOffset, as: UInt32.self ) ) let stringTableEnd = stringTableOffset + Address(stringTableSize) let stringSource = source[stringTableOffset.. 0 { skip -= 1 continue } if symbol.NumberOfAuxSymbols > 0 { skip = Int(symbol.NumberOfAuxSymbols) } // We are only interested in functions if symbol.StorageClass != .PE_SYMBOL_CLASS_FUNCTION { continue } // And, at that, only those that have a section number if symbol.SectionNumber <= 0 { continue } let name: String if symbol.LongName.Zeroes == 0 { if let theName = stringTable!.getStringAt(index: Int(symbol.LongName.Offset)) { name = theName } else { continue } } else { var shortName = symbol.ShortName name = withUnsafeBytes(of: &shortName) { String(decoding: $0, as: UTF8.self) } } let sectionBase = Address( sections[Int(symbol.SectionNumber) - 1].VirtualAddress ) let address = sectionBase + Address(symbol.Value) theFunctions.append(PeFunction(name: name, address: address)) } theFunctions.sort { $0.address < $1.address } functions = theFunctions } // Fill in our member variables self.source = source self.baseAddress = baseAddress self.endAddress = endAddress self.timestamp = peHeader.TimeDateStamp self.characteristics = peHeader.Characteristics self.entryPoint = optionalHeader.AddressOfEntryPoint self.imageBase = windowsHeader.ImageBase self.operatingSystemVersion = PeVersion( major: windowsHeader.MajorOperatingSystemVersion, minor: windowsHeader.MinorOperatingSystemVersion ) self.imageVersion = PeVersion( major: windowsHeader.MajorImageVersion, minor: windowsHeader.MinorImageVersion ) self.subsystemVersion = PeVersion( major: windowsHeader.MajorSubsystemVersion, minor: windowsHeader.MinorSubsystemVersion ) self.checksum = windowsHeader.CheckSum self.subsystem = windowsHeader.Subsystem self.dllCharacteristics = windowsHeader.DllCharacteristics self.dataDirectory = dataDirectory self.sections = [] self.sectionMap = [:] self.functions = functions for section in sections { var theName = section.Name var name = withUnsafeBytes(of: &theName) { buffer in let len = strnlen(buffer.baseAddress!, buffer.count) return String(decoding: buffer[0.. PeImageDirectoryEntry.debug.rawValue { let debugInfo = dataDirectory[PeImageDirectoryEntry.debug.rawValue] if debugInfo.VirtualAddress != 0 && debugInfo.Size != 0 { var pos: Address if source.isMappedImage { pos = Address(debugInfo.VirtualAddress) + self.imageBase } else { pos = Address(filePointer(from: debugInfo.VirtualAddress)!) } let end = pos + Address(debugInfo.Size) while pos < end { let entry = try source.fetch(from: pos, as: pe_debug_directory.self) pos += Address(MemoryLayout.size) let dataPos: Address if source.isMappedImage { dataPos = Address(entry.AddressOfRawData) + self.imageBase } else { dataPos = Address(entry.PointerToRawData) } let dataEnd = dataPos + Address(entry.SizeOfData) let entrySource = source[dataPos.. 4 { let len = maybeSwap(try entrySource.fetch(from: 0, as: UInt32.self)) reproHash = try entrySource.fetch(from: 4, count: Int(len), as: UInt8.self) } default: break } } } } } /// Convert a virtual address into a file pointer func filePointer(from virtualAddress: UInt32) -> UInt32? { for section in sections { if virtualAddress >= section.virtualAddress { let offset = virtualAddress - section.virtualAddress if offset < section.virtualSize && offset < section.sizeOfRawData { return section.pointerToRawData + offset } } } return nil } /// Convert a file pointer into a virtual address func virtualAddress(from filePointer: UInt32) -> UInt32? { for section in sections { if filePointer >= section.pointerToRawData { let offset = filePointer - section.pointerToRawData if offset < section.sizeOfRawData { return section.virtualAddress + offset } } } return nil } /// Find the named section and return an ImageSource pointing at it. func getSection(_ name: String) -> ImageSource? { guard let ndx = sectionMap[name] else { return nil } let section = sections[ndx] if source.isMappedImage { let base = Address(section.virtualAddress) let end = base + Address(section.virtualSize) return source[base...CallSiteInfo func inlineCallSites( at relativeAddress: Address ) -> ArraySlice { // ###TODO: PDB support let address = relativeAddress + Address(imageBase) guard let dwarfReader else { return [][0..<0] } return dwarfReader.lookupInlineCallSites( at: DwarfReader.Address(address) ) } typealias SourceLocation = SymbolicatedBacktrace.SourceLocation func sourceLocation( for relativeAddress: Address ) throws -> SourceLocation? { // ###TODO: PDB support let address = relativeAddress + Address(imageBase) guard let dwarfReader else { return nil } return try dwarfReader.sourceLocation( for: DwarfReader.Address(address) ) } /// Look-up an address and find the corresponding function func lookupSymbol( address relativeAddress: Address ) -> ImageSymbol? { // ###TODO: PDB support let address = relativeAddress + Address(imageBase) if let function = dwarfReader?.lookupFunction(at: address) { let offset = address - function.lowPC return ImageSymbol(name: function.rawName, offset: Int(offset)) } else if let functions { // If we don't have a DWARF reader, but we do have a function list, // try looking in the function list. var min = 0, max = functions.count while min < max { let mid = min + (max - min) / 2 if address < functions[mid].address { max = mid continue } if address >= functions[mid].address { if mid + 1 == functions.count || address < functions[mid + 1].address { let offset = address - functions[mid].address return ImageSymbol(name: functions[mid].name, offset: Int(offset)) } min = mid + 1 } } } return nil } } extension PeCoffImage: DwarfSource { static var pathSeparator: String { "\\" } func getDwarfSection(_ section: DwarfSection) -> ImageSource? { // If linked with `link.exe`, the DWARF section names get // truncated; this unfortunately means that `.debug_line` // and `.debug_loc` get munged together. // // On the other hand, if linked with `lld`, long names are // used, in spite of the Microsoft documentation saying that // this is not valid in an `.exe` file. We can only locate // the DWARF sections in that case if we're looking at the // *file*, because the actual name is in the COFF string // table, which isn't normally loaded. // // We intentionally choose to only support a small handful // of truncated sections here. switch section { case .debugAbbrev: if let abbrevs = getSection(".debug_abbrev") { return abbrevs } return getSection(".debug_a") case .debugInfo: if let info = getSection(".debug_info") { return info } return getSection(".debug_i") case .debugLine: if let line = getSection(".debug_line") { return line } return getSection(".debug_l") case .debugNames: if let names = getSection(".debug_names") { return names } return getSection(".debug_n") case .debugRanges: if let ranges = getSection(".debug_ranges") { return ranges } return getSection(".debug_r") case .debugStr: if let str = getSection(".debug_str") { return str } return getSection(".debug_s") case .debugAddr: return getSection(".debug_addr") case .debugARanges: return getSection(".debug_aranges") case .debugFrame: return getSection(".debug_frame") case .debugLineStr: return getSection(".debug_line_str") case .debugLoc: return getSection(".debug_loc") case .debugLocLists: return getSection(".debug_loclists") case .debugMacInfo: return getSection(".debug_macinfo") case .debugMacro: return getSection(".debug_macro") case .debugPubNames: return getSection(".debug_pubnames") case .debugPubTypes: return getSection(".debug_pubtypes") case .debugRngLists: return getSection(".debug_rnglists") case .debugStrOffsets: return getSection(".debug_str_offsets") case .debugSup: return getSection(".debug_sup") case .debugTypes: return getSection(".debug_types") case .debugCuIndex: return getSection(".debug_cu_index") case .debugTuIndex: return getSection(".debug_tu_index") } } }