diff --git a/README.md b/README.md index 19a6bfc..0ada662 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ In addition to file reading, parsing of images in memory by `_dyld_get_image_hea ### Load from memory -For reading from memory, use the `MachO` structure. +For reading from memory, use the `MachOImage` structure. It can be initialized by using the Mach-O Header pointer obtained by `_dyld_get_image_header`. @@ -65,24 +65,29 @@ case .fat(let fatFile): // Fat file ### Main properties and methods -Both `MachO` and `MachOFile` can use essentially the same properties and methods. +Both `MachOImage` and `MachOFile` can use essentially the same properties and methods. The available methods are defined in the following file as the `MachORepresentable` protocol. [MachORepresentable](./Sources/MachOKit/Protocol/MachORepresentable.swift) - ### Dyld Cache -loading of `dyld_shared_cache` is also supported. +Loading of `dyld_shared_cache` is also supported. + +The available methods are defined in the following file as the `DyldCacheRepresentable` protocol. + +[DyldCacheRepresentable](./Sources/MachOKit/Protocol/DyldCacheRepresentable.swift) + +#### Dyld Cache (File) ```swift -let path = "/System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_x86_64h" +let path = "/System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_arm64e" let url = URL(fileURLWithPath: path) let cache = try! DyldCache(url: url) ``` -It is also possible to extract machO information contained in `dyld _shared _cache`. +It is also possible to extract machO information contained in `dyld_shared_cache`. The machO extracted is of type `MachOFile`. As with reading from a single MachO file, various analyses are possible. @@ -102,6 +107,38 @@ for machO in machOs { // ... ``` +#### Dyld Cache (on memory) + +On the Apple platform, the dyld cache is deployed in memory. + +```swift +var size = 0 +guard let ptr = _dyld_get_shared_cache_range(&size) else { + return +} +let cache = try! DyldCacheLoaded(ptr: ptr) +``` + +It is also possible to extract machO information contained in `dyld_shared_cache`. +The machO extracted is of type `MachOImage`. +As with reading from a single MachO image, various analyses are possible. + +```swift +let machOs = cache.machOImages() +for machO in machOs { + print( + String(Int(bitPattern: machO.ptr), radix: 16), + machO.path!, + machO.header.ncmds + ) +} + +// 193438000 /usr/lib/libobjc.A.dylib 24 +// 193489000 /usr/lib/dyld 15 +// 193513000 /usr/lib/system/libsystem_blocks.dylib 24 +// ... +``` + ### Example Codes There are a variety of uses, but most show a basic example that prints output to the Test directory. @@ -116,11 +153,16 @@ The following file contains sample code. The following file contains sample code. [MachOFilePrintTests](./Tests/MachOKitTests/MachOFilePrintTests.swift) -#### Dyld Cache +#### Dyld Cache (file) The following file contains sample code. [DyldCachePrintTests](./Tests/MachOKitTests/DyldCachePrintTests.swift) +#### Dyld Cache (on memory) + +The following file contains sample code. +[DyldCacheLoadedPrintTests](./Tests/MachOKitTests/DyldCacheLoadedPrintTests.swift) + ## Related Projects - [MachOKitSPM](https://github.com/p-x9/MachOKit-SPM) @@ -132,6 +174,10 @@ The following file contains sample code. - [AntiFishHook](https://github.com/p-x9/swift-anti-fishhook) A Swift library to deactivate fishhook. (Anti-FishHook) +### Other binary type +- [ELFKit](https://github.com/p-x9/ELFKit) + Elf format + ## License MachOKit is released under the MIT License. See [LICENSE](./LICENSE) diff --git a/Sources/MachOKit/DyldCache.swift b/Sources/MachOKit/DyldCache.swift index c63100b..4bbaad2 100644 --- a/Sources/MachOKit/DyldCache.swift +++ b/Sources/MachOKit/DyldCache.swift @@ -8,7 +8,7 @@ import Foundation -public class DyldCache { +public class DyldCache: DyldCacheRepresentable { /// URL of loaded dyld cache file public let url: URL let fileHandle: FileHandle @@ -346,74 +346,11 @@ extension DyldCache { } extension DyldCache { - public func fileOffset(of address: UInt64) -> UInt64? { - guard let mapping = mappingInfo(for: address) else { - return nil - } - return address - mapping.address + mapping.fileOffset - } - - public func address(of fileOffset: UInt64) -> UInt64? { - guard let mapping = mappingInfo(forFileOffset: fileOffset) else { - return nil - } - return fileOffset - mapping.fileOffset + mapping.address - } - - - public func mappingInfo(for address: UInt64) -> DyldCacheMappingInfo? { - guard let mappings = self.mappingInfos else { return nil } - for mapping in mappings { - if mapping.address <= address, - address < mapping.address + mapping.size { - return mapping - } - } - return nil - } - - public func mappingInfo( - forFileOffset offset: UInt64 - ) -> DyldCacheMappingInfo? { - guard let mappings = self.mappingInfos else { return nil } - for mapping in mappings { - if mapping.fileOffset <= offset, - offset < mapping.fileOffset + mapping.size { - return mapping - } - } - return nil - } - - public func mappingAndSlideInfo( - for address: UInt64 - ) -> DyldCacheMappingAndSlideInfo? { - guard let mappings = self.mappingAndSlideInfos else { return nil } - for mapping in mappings { - if mapping.address <= address, - address < mapping.address + mapping.size { - return mapping - } - } - return nil - } - - public func mappingAndSlideInfo( - forFileOffset offset: UInt64 - ) -> DyldCacheMappingAndSlideInfo? { - guard let mappings = self.mappingAndSlideInfos else { return nil } - for mapping in mappings { - if mapping.fileOffset <= offset, - offset < mapping.fileOffset + mapping.size { - return mapping - } - } - return nil - } -} - -extension DyldCache { - // https://github.com/apple-oss-distributions/dyld/blob/d552c40cd1de105f0ec95008e0e0c0972de43456/common/MetadataVisitor.cpp#L262 + /// File offset after rebasing performed on the specified file offset + /// - Parameter offset: target file offset + /// - Returns: rebased file offset + /// + /// [dyld Implementation](https://github.com/apple-oss-distributions/dyld/blob/d552c40cd1de105f0ec95008e0e0c0972de43456/common/MetadataVisitor.cpp#L262) public func resolveRebase(at offset: UInt64) -> UInt64? { guard let mappingInfos, let unslidLoadAddress = mappingInfos.first?.address else { @@ -489,7 +426,12 @@ extension DyldCache { return runtimeOffset + onDiskDylibChainedPointerBaseAddress } - // https://github.com/apple-oss-distributions/dyld/blob/a571176e8e00c47e95b95e3156820ebec0cbd5e6/common/MetadataVisitor.cpp#L424 + /// File offset after optional rebasing performed on the specified file offset + /// - Parameter offset: target file offset + /// - Returns: optional rebased file offset + /// + /// [dyld implementation](https://github.com/apple-oss-distributions/dyld/blob/a571176e8e00c47e95b95e3156820ebec0cbd5e6/common/MetadataVisitor.cpp#L424) + /// `resolveOptionalRebase` differs from `resolveRebase` in that rebasing may or may not actually take place. public func resolveOptionalRebase(at offset: UInt64) -> UInt64? { guard let mappingInfos, let unslidLoadAddress = mappingInfos.first?.address else { diff --git a/Sources/MachOKit/DyldCacheLoaded+SubCaches.swift b/Sources/MachOKit/DyldCacheLoaded+SubCaches.swift new file mode 100644 index 0000000..24a55ef --- /dev/null +++ b/Sources/MachOKit/DyldCacheLoaded+SubCaches.swift @@ -0,0 +1,72 @@ +// +// DyldCacheLoaded+SubCaches.swift +// +// +// Created by p-x9 on 2024/10/10 +// +// + +import Foundation + +extension DyldCacheLoaded { + public struct SubCaches: Sequence { + public let basePointer: UnsafeRawPointer + public let numberOfSubCaches: Int + public let subCacheEntryType: DyldSubCacheEntryType + + public func makeIterator() -> Iterator { + .init( + basePointer: basePointer, + numberOfSubCaches: numberOfSubCaches, + subCacheEntryType: subCacheEntryType + ) + } + } +} + +extension DyldCacheLoaded.SubCaches { + public struct Iterator: IteratorProtocol { + public typealias Element = DyldSubCacheEntry + + public let basePointer: UnsafeRawPointer + public let numberOfSubCaches: Int + public let subCacheEntryType: DyldSubCacheEntryType + + private var nextOffset: Int = 0 + private var nextIndex: Int = 0 + + public init( + basePointer: UnsafeRawPointer, + numberOfSubCaches: Int, + subCacheEntryType: DyldSubCacheEntryType + ) { + self.basePointer = basePointer + self.numberOfSubCaches = numberOfSubCaches + self.subCacheEntryType = subCacheEntryType + } + + public mutating func next() -> DyldSubCacheEntry? { + guard nextIndex < numberOfSubCaches else { + return nil + } + + defer { + nextIndex += 1 + nextOffset += subCacheEntryType.layoutSize + } + + switch subCacheEntryType { + case .general: + let ptr = UnsafeMutableRawPointer(mutating: basePointer) + .advanced(by: nextOffset) + .assumingMemoryBound(to: DyldSubCacheEntryGeneral.Layout.self) + return .general(.init(layout: ptr.pointee, index: nextIndex)) + case .v1: + let ptr = UnsafeMutableRawPointer(mutating: basePointer) + .advanced(by: nextOffset) + .assumingMemoryBound(to: DyldSubCacheEntryV1.Layout.self) + return .v1(.init(layout: ptr.pointee, index: nextIndex)) + } + } + } +} diff --git a/Sources/MachOKit/DyldCacheLoaded.swift b/Sources/MachOKit/DyldCacheLoaded.swift new file mode 100644 index 0000000..7c64180 --- /dev/null +++ b/Sources/MachOKit/DyldCacheLoaded.swift @@ -0,0 +1,380 @@ +// +// DyldCacheLoaded.swift +// +// +// Created by p-x9 on 2024/10/09 +// +// + +import Foundation + +public struct DyldCacheLoaded: DyldCacheRepresentable { + + /// Address of dyld cache header start + public let ptr: UnsafeRawPointer + + public var headerSize: Int { + DyldCacheHeader.layoutSize + } + + /// Header for dyld cache + public var header: DyldCacheHeader { + .init( + layout: ptr + .assumingMemoryBound(to: dyld_cache_header.self) + .pointee + ) + } + + /// virtural memory address slide + /// + /// [dyld implementation](https://github.com/apple-oss-distributions/dyld/blob/65bbeed63cec73f313b1d636e63f243964725a9d/common/DyldSharedCache.cpp#L817) + public var slide: Int? { + guard let mappingInfos, + let info = mappingInfos.first else { + return nil + } + return Int(bitPattern: ptr) - Int(info.address) + } + + /// Target CPU info. + /// + /// It is obtained based on magic. + public let cpu: CPU + + private var _mainCacheHeader: DyldCacheHeader? + + /// Header for main dyld cache + /// When this dyld cache is a subcache, represent the header of the main cache + /// + /// Some properties are only set for the main cache header + /// [dyld implementation](https://github.com/apple-oss-distributions/dyld/blob/d552c40cd1de105f0ec95008e0e0c0972de43456/cache_builder/SubCache.cpp#L1353) + public var mainCacheHeader: DyldCacheHeader { + _mainCacheHeader ?? header + } + + /// Initialized with the start pointer of dyld cache loaded on memory. + /// - Parameter ptr: start pointer of dyld cache header + /// + /// Using function named `_dyld_get_shared_cache_range`, start pointer to the dyld cache can be obtained. + public init(ptr: UnsafeRawPointer) throws { + self.ptr = .init(ptr) + + let header: DyldCacheHeader = ptr.autoBoundPointee() + + // check magic of header + guard header.magic.starts(with: "dyld_") else { + throw MachOKitError.invalidMagic + } + + guard let cpuType = header._cpuType, + let cpuSubType = header._cpuSubType else { + throw MachOKitError.invalidCpuType + } + self.cpu = .init( + typeRawValue: cpuType.rawValue, + subtypeRawValue: cpuSubType.rawValue + ) + } + + /// Load sub dyld cache + /// - Parameters: + /// - subcachePtr: start pointer of sub dyld cache header + /// - mainCacheHeader: header of main dyld cache + public init( + subcachePtr: UnsafeRawPointer, + mainCacheHeader: DyldCacheHeader + ) throws { + try self.init(ptr: subcachePtr) + self._mainCacheHeader = mainCacheHeader + } +} + +extension DyldCacheLoaded { + /// Sequence of mapping infos + public var mappingInfos: MemorySequence? { + guard header.mappingCount > 0 else { return nil } + return .init( + basePointer: ptr + .advanced(by: numericCast(header.mappingOffset)) + .assumingMemoryBound(to: DyldCacheMappingInfo.self), + numberOfElements: numericCast(header.mappingCount) + ) + } + + /// Sequence of mapping and slide infos + public var mappingAndSlideInfos: MemorySequence? { + guard header.mappingWithSlideCount > 0 else { return nil } + return .init( + basePointer: ptr + .advanced(by: numericCast(header.mappingWithSlideOffset)) + .assumingMemoryBound(to: DyldCacheMappingAndSlideInfo.self), + numberOfElements: numericCast(header.mappingWithSlideCount) + ) + } + + /// Sequence of image infos. + public var imageInfos: MemorySequence? { + guard header.imagesCount > 0 else { return nil } + return .init( + basePointer: ptr + .advanced(by: numericCast(header.imagesOffset)) + .assumingMemoryBound(to: DyldCacheImageInfo.self), + numberOfElements: numericCast(header.imagesCount) + ) + } + + /// Sequence of image text infos. + public var imageTextInfos: MemorySequence? { + guard header.imagesTextCount > 0 else { return nil } + return .init( + basePointer: ptr + .advanced(by: numericCast(header.imagesTextOffset)) + .assumingMemoryBound(to: DyldCacheImageTextInfo.self), + numberOfElements: numericCast(header.imagesTextCount) + ) + } + + /// Sub cache type + /// + /// Check if entry type is `dyld_subcache_entry_v1` or `dyld_subcache_entry` + public var subCacheEntryType: DyldSubCacheEntryType? { + guard header.subCacheArrayCount > 0 else { + return nil + } + + let layout: DyldSubCacheEntryGeneral.Layout = ptr + .advanced(by: numericCast(header.subCacheArrayOffset)) + .autoBoundPointee() + let subCache = DyldSubCacheEntryGeneral(layout: layout, index: 0) + + if subCache.fileSuffix.starts(with: ".") { + return .general + } else { + return .v1 + } + } + + /// Sequence of sub caches + public var subCaches: SubCaches? { + guard let subCacheEntryType else { return nil } + return .init( + basePointer: ptr + .advanced(by: numericCast(header.subCacheArrayOffset)), + numberOfSubCaches: numericCast(header.subCacheArrayCount), + subCacheEntryType: subCacheEntryType + ) + } + + /// Local symbol info + public var localSymbolsInfo: DyldCacheLocalSymbolsInfo? { + guard header.localSymbolsSize > 0 else { return nil } + return ptr + .advanced(by: numericCast(header.localSymbolsOffset)) + .autoBoundPointee() + } +} + +extension DyldCacheLoaded { + /// Sequence of MachO information contained in this cache + public func machOImages() -> AnySequence { + guard let slide, + let imageInfos = imageInfos else { + return AnySequence([]) + } + let machOFiles = imageInfos + .lazy + .compactMap { info in + UnsafeRawPointer(bitPattern: Int(info.address) + slide) + } + .compactMap { ptr in + MachOImage( + ptr: ptr.assumingMemoryBound(to: mach_header.self) + ) + } + + return AnySequence(machOFiles) + } +} + +extension DyldCacheLoaded { + public typealias DylibsTrieEntries = MemoryTrieTree + + /// Dylibs trie is for searching by dylib name. + /// + /// The ``dylibIndices`` are retrieved from this trie tree. + public var dylibsTrieEntries: DylibsTrieEntries? { + guard mainCacheHeader.dylibsTrieAddr > 0, + mainCacheHeader.dylibsTrieSize > 0, + let slide else { + return nil + } + + let address = UInt(mainCacheHeader.dylibsTrieAddr) + UInt(slide) + let size = mainCacheHeader.dylibsTrieSize + + guard let basePointer = UnsafeRawPointer(bitPattern: address) else { + return nil + } + + return .init( + basePointer: basePointer, + size: numericCast(size) + ) + } + + /// Array of Dylib name-index pairs + /// + /// This index matches the index in the dylib image list that can be retrieved from imagesOffset. + /// + /// If an alias exists, there may be another element with an equal index. + /// ``` + /// 0 /usr/lib/libobjc.A.dylib + /// 0 /usr/lib/libobjc.dylib + /// ``` + public var dylibIndices: [DylibIndex] { + guard let dylibsTrieEntries else { + return [] + } + return dylibsTrieEntries.dylibIndices + } +} + +extension DyldCacheLoaded { + public typealias ProgramsTrieEntries = MemoryTrieTree + + /// Pair of program name/cdhash and offset to prebuiltLoaderSet + /// + /// The ``programOffsets`` are retrieved from this trie tree. + public var programsTrieEntries: ProgramsTrieEntries? { + guard mainCacheHeader.programTrieAddr > 0, + mainCacheHeader.programTrieSize > 0, + let slide else { + return nil + } + let address = UInt(mainCacheHeader.programTrieAddr) + UInt(slide) + let size = mainCacheHeader.programTrieSize + + guard let basePointer = UnsafeRawPointer(bitPattern: address) else { + return nil + } + + return ProgramsTrieEntries( + basePointer: basePointer, + size: numericCast(size) + ) + } + + /// Pair of program name/cdhash and offset to prebuiltLoaderSet + /// + /// Example: + /// ``` + /// 0 /System/Applications/App Store.app/Contents/MacOS/App Store + /// 0 /cdhash/32caa391186c08b3b3cb7866995db1cb65b0376a + /// 131776 /System/Applications/Automator.app/Contents/MacOS/Automator + /// 131776 /cdhash/fed26a75645fed2a674b5c4d01001bfa69b9dbea + /// ``` + public var programOffsets: [ProgramOffset] { + guard let programsTrieEntries else { + return [] + } + return programsTrieEntries.programOffsets + } + + /// Get the prebuiltLoaderSet indicated by programOffset. + /// - Parameter programOffset: program name and offset pair + /// - Returns: prebuiltLoaderSet + public func prebuiltLoaderSet(for programOffset: ProgramOffset) -> PrebuiltLoaderSet? { + guard let slide else { return nil } + let address: Int = numericCast(mainCacheHeader.programsPBLSetPoolAddr) + numericCast(programOffset.offset) + slide + guard let basePointer = UnsafeRawPointer(bitPattern: address) else { + return nil + } + let layout: prebuilt_loader_set = basePointer + .autoBoundPointee() + return .init(layout: layout, address: .init(bitPattern: basePointer)) + } +} + +extension DyldCacheLoaded { + /// PrebuiltLoaderSet of all cached dylibs + public var dylibsPrebuiltLoaderSet: PrebuiltLoaderSet? { + guard let slide else { return nil } + let address: Int = numericCast(mainCacheHeader.dylibsPBLSetAddr) + slide + guard let basePointer = UnsafeRawPointer(bitPattern: address) else { + return nil + } + let layout: prebuilt_loader_set = basePointer + .autoBoundPointee() + return .init(layout: layout, address: .init(bitPattern: basePointer)) + } +} + +extension DyldCacheLoaded { + public var objcOptimization: ObjCOptimization? { + guard mainCacheHeader.objcOptsOffset > 0, + mainCacheHeader.objcOptsSize > 0 else { + return nil + } + return ptr + .advanced(by: numericCast(mainCacheHeader.objcOptsOffset)) + .autoBoundPointee() + } + + public var oldObjcOptimization: OldObjCOptimization? { + guard let libobjc = machOImages().first(where: { + guard let idDylib = $0.loadCommands.info(of: LoadCommand.idDylib) else { + return false + } + let dylib = idDylib.dylib(cmdsStart: $0.cmdsStartPtr) + return dylib.name == "/usr/lib/libobjc.A.dylib" + }) else { return nil } + guard let vmaddrSlide = libobjc.vmaddrSlide else { + return nil + } + + let text: any SegmentCommandProtocol + let __objc_opt_ro: any SectionProtocol + + if libobjc.is64Bit { + guard let _text = libobjc.loadCommands.text64, + let section = _text.sections(cmdsStart: libobjc.cmdsStartPtr).first(where: { + $0.sectionName == "__objc_opt_ro" + }) else { + return nil + } + text = _text + __objc_opt_ro = section + } else { + guard let _text = libobjc.loadCommands.text, + let section = _text.sections(cmdsStart: libobjc.cmdsStartPtr).first(where: { + $0.sectionName == "__objc_opt_ro" + }) else { + return nil + } + text = _text + __objc_opt_ro = section + } + + guard let start = __objc_opt_ro.startPtr( + in: text, + vmaddrSlide: vmaddrSlide + ) else { return nil } + let layout: OldObjCOptimization.Layout = start + .autoBoundPointee() + return .init( + layout: layout, + offset: Int(bitPattern: start) - Int(bitPattern: ptr) + ) + } + + public var swiftOptimization: SwiftOptimization? { + guard mainCacheHeader.swiftOptsOffset > 0, + mainCacheHeader.swiftOptsSize > 0 else { + return nil + } + return ptr + .advanced(by: numericCast(mainCacheHeader.swiftOptsOffset)) + .autoBoundPointee() + } +} diff --git a/Sources/MachOKit/MachOImage+Symbols.swift b/Sources/MachOKit/MachOImage+Symbols.swift index 738465e..b4f1b52 100644 --- a/Sources/MachOKit/MachOImage+Symbols.swift +++ b/Sources/MachOKit/MachOImage+Symbols.swift @@ -34,27 +34,29 @@ extension MachOImage.Symbol { // https://stackoverflow.com/questions/20481058/find-pathname-from-dlopen-handle-on-osx extension MachOImage { public struct Symbols64: Sequence { - public let ptr: UnsafeRawPointer - public let text: SegmentCommand64 - public let linkedit: SegmentCommand64 - public let symtab: LoadCommandInfo - public let stringBase: UnsafePointer public let addressStart: Int public let symbols: UnsafePointer public let numberOfSymbols: Int + init( + stringBase: UnsafePointer, + addressStart: Int, + symbols: UnsafePointer, + numberOfSymbols: Int + ) { + self.stringBase = stringBase + self.addressStart = addressStart + self.symbols = symbols + self.numberOfSymbols = numberOfSymbols + } + init( ptr: UnsafeRawPointer, text: SegmentCommand64, linkedit: SegmentCommand64, symtab: LoadCommandInfo ) { - self.ptr = ptr - self.text = text - self.linkedit = linkedit - self.symtab = symtab - let fileSlide: Int = numericCast(linkedit.vmaddr) - numericCast(text.vmaddr) - numericCast(linkedit.fileoff) stringBase = ptr @@ -82,27 +84,29 @@ extension MachOImage { extension MachOImage { public struct Symbols: Sequence { - public let ptr: UnsafeRawPointer - public let text: SegmentCommand - public let linkedit: SegmentCommand - public let symtab: LoadCommandInfo - public let stringBase: UnsafePointer public let addressStart: Int public let symbols: UnsafePointer public let numberOfSymbols: Int + init( + stringBase: UnsafePointer, + addressStart: Int, + symbols: UnsafePointer, + numberOfSymbols: Int + ) { + self.stringBase = stringBase + self.addressStart = addressStart + self.symbols = symbols + self.numberOfSymbols = numberOfSymbols + } + init( ptr: UnsafeRawPointer, text: SegmentCommand, linkedit: SegmentCommand, symtab: LoadCommandInfo ) { - self.ptr = ptr - self.text = text - self.linkedit = linkedit - self.symtab = symtab - let fileSlide: Int = numericCast(linkedit.vmaddr) - numericCast(text.vmaddr) - numericCast(linkedit.fileoff) stringBase = ptr diff --git a/Sources/MachOKit/Model/DyldCache/DyldCacheImageInfo.swift b/Sources/MachOKit/Model/DyldCache/DyldCacheImageInfo.swift index 8c8be85..6926753 100644 --- a/Sources/MachOKit/Model/DyldCache/DyldCacheImageInfo.swift +++ b/Sources/MachOKit/Model/DyldCache/DyldCacheImageInfo.swift @@ -23,4 +23,15 @@ extension DyldCacheImageInfo { offset: numericCast(layout.pathFileOffset) ) } + + /// Path for image + /// - Parameter cache: DyldCache to which this image belongs + /// - Returns: Path for image + public func path(in cache: DyldCacheLoaded) -> String? { + String( + cString: cache.ptr + .advanced(by: numericCast(layout.pathFileOffset)) + .assumingMemoryBound(to: CChar.self) + ) + } } diff --git a/Sources/MachOKit/Model/DyldCache/DyldCacheImageTextInfo.swift b/Sources/MachOKit/Model/DyldCache/DyldCacheImageTextInfo.swift index 4422d38..5655f1f 100644 --- a/Sources/MachOKit/Model/DyldCache/DyldCacheImageTextInfo.swift +++ b/Sources/MachOKit/Model/DyldCache/DyldCacheImageTextInfo.swift @@ -28,4 +28,15 @@ extension DyldCacheImageTextInfo { offset: numericCast(layout.pathOffset) ) } + + /// Path for image text + /// - Parameter cache: DyldCache to which this image belongs + /// - Returns: Path for image text + public func path(in cache: DyldCacheLoaded) -> String? { + String( + cString: cache.ptr + .advanced(by: numericCast(layout.pathOffset)) + .assumingMemoryBound(to: CChar.self) + ) + } } diff --git a/Sources/MachOKit/Model/DyldCache/DyldCacheLocalSymbolsInfo.swift b/Sources/MachOKit/Model/DyldCache/DyldCacheLocalSymbolsInfo.swift index a3358ec..7fdd461 100644 --- a/Sources/MachOKit/Model/DyldCache/DyldCacheLocalSymbolsInfo.swift +++ b/Sources/MachOKit/Model/DyldCache/DyldCacheLocalSymbolsInfo.swift @@ -77,6 +77,61 @@ extension DyldCacheLocalSymbolsInfo { } } +extension DyldCacheLocalSymbolsInfo { + /// Sequence of 64-bit architecture symbols + /// - Parameter cache: DyldCache to which `self` belongs + /// - Returns: Sequence of symbols + public func symbols64(in cache: DyldCacheLoaded) -> MachOImage.Symbols64? { + guard cache.cpu.is64Bit else { return nil } + + return .init( + stringBase: cache.ptr + .advanced(by: numericCast(cache.header.localSymbolsOffset)) + .advanced(by: numericCast(layout.stringsOffset)) + .assumingMemoryBound(to: CChar.self), + addressStart: 0, // FIXME: Fix + symbols: cache.ptr + .advanced(by: numericCast(cache.header.localSymbolsOffset)) + .advanced(by: numericCast(layout.nlistOffset)) + .assumingMemoryBound(to: nlist_64.self), + numberOfSymbols: numericCast(layout.nlistCount) + ) + } + + /// Sequence of 32-bit architecture symbols + /// - Parameter cache: DyldCache to which `self` belongs + /// - Returns: Sequence of symbols + public func symbols32(in cache: DyldCacheLoaded) -> MachOImage.Symbols? { + guard !cache.cpu.is64Bit else { return nil } + + return .init( + stringBase: cache.ptr + .advanced(by: numericCast(cache.header.localSymbolsOffset)) + .advanced(by: numericCast(layout.stringsOffset)) + .assumingMemoryBound(to: CChar.self), + addressStart: 0, // FIXME: Fix + symbols: cache.ptr + .advanced(by: numericCast(cache.header.localSymbolsOffset)) + .advanced(by: numericCast(layout.nlistOffset)) + .assumingMemoryBound(to: nlist.self), + numberOfSymbols: numericCast(layout.nlistCount) + ) + } + + /// Sequence of symbols + /// - Parameter cache: DyldCache to which `self` belongs + /// - Returns: Sequence of symbols + public func symbols(in cache: DyldCacheLoaded) -> AnyRandomAccessCollection { + if let symbols64 = symbols64(in: cache) { + return AnyRandomAccessCollection(symbols64) + } else if let symbols32 = symbols32(in: cache) { + return AnyRandomAccessCollection(symbols32) + } else { + return AnyRandomAccessCollection([]) + } + } +} + extension DyldCacheLocalSymbolsInfo { /// Sequence of 64-bit architecture symbols entries /// - Parameter cache: DyldCache to which `self` belongs @@ -132,3 +187,63 @@ extension DyldCacheLocalSymbolsInfo { } } } + +extension DyldCacheLocalSymbolsInfo { + /// Sequence of 64-bit architecture symbols entries + /// - Parameter cache: DyldCache to which `self` belongs + /// - Returns: Sequence of symbols entries + public func entries64( + in cache: DyldCacheLoaded + ) -> MemorySequence? { + guard cache.cpu.is64Bit else { return nil } + let offset: UInt64 = cache.header.localSymbolsOffset + numericCast(layout.entriesOffset) + + return .init( + basePointer: cache.ptr + .advanced(by: numericCast(offset)) + .autoBoundPointee(), + numberOfElements: numericCast(layout.entriesCount) + ) + } + + /// Sequence of 32-bit architecture symbols entries + /// - Parameter cache: DyldCache to which `self` belongs + /// - Returns: Sequence of symbols entries + public func entries32( + in cache: DyldCacheLoaded + ) -> MemorySequence? { + guard !cache.cpu.is64Bit else { return nil } + + let offset: UInt64 = cache.header.localSymbolsOffset + numericCast(layout.entriesOffset) + + return .init( + basePointer: cache.ptr + .advanced(by: numericCast(offset)) + .autoBoundPointee(), + numberOfElements: numericCast(layout.entriesCount) + ) + } + + /// Sequence of symbols entries + /// - Parameter cache: DyldCache to which `self` belongs + /// - Returns: Sequence of symbols entries + public func entries( + in cache: DyldCacheLoaded + ) -> AnyRandomAccessCollection { + if let entries = entries64(in: cache) { + return AnyRandomAccessCollection( + entries + .lazy + .map { $0 as (any DyldCacheLocalSymbolsEntryProtocol) } + ) + } else if let entries = entries32(in: cache) { + return AnyRandomAccessCollection( + entries + .lazy + .map { $0 as (any DyldCacheLocalSymbolsEntryProtocol) } + ) + } else { + return AnyRandomAccessCollection([]) + } + } +} diff --git a/Sources/MachOKit/Model/DyldCache/DyldSubCacheEntry.swift b/Sources/MachOKit/Model/DyldCache/DyldSubCacheEntry.swift index ef40647..f8c6982 100644 --- a/Sources/MachOKit/Model/DyldCache/DyldSubCacheEntry.swift +++ b/Sources/MachOKit/Model/DyldCache/DyldSubCacheEntry.swift @@ -62,10 +62,18 @@ public enum DyldSubCacheEntry { // cache extension DyldSubCacheEntry { - func subcache(for cache: DyldCache) throws -> DyldCache? { + public func subcache(for cache: DyldCache) throws -> DyldCache? { let url = URL(fileURLWithPath: cache.url.path + fileSuffix) return try DyldCache(subcacheUrl: url, mainCacheHeader: cache.header) } + + public func subcache(for cache: DyldCacheLoaded) throws -> DyldCacheLoaded? { + try DyldCacheLoaded( + subcachePtr: cache.ptr + .advanced(by: numericCast(cacheVMOffset)), + mainCacheHeader: cache.header + ) + } } public struct DyldSubCacheEntryV1: LayoutWrapper { diff --git a/Sources/MachOKit/Model/DyldCache/Loader/PrebuiltLoader.swift b/Sources/MachOKit/Model/DyldCache/Loader/PrebuiltLoader.swift index ef9a10c..a641821 100644 --- a/Sources/MachOKit/Model/DyldCache/Loader/PrebuiltLoader.swift +++ b/Sources/MachOKit/Model/DyldCache/Loader/PrebuiltLoader.swift @@ -24,7 +24,9 @@ extension PrebuiltLoader { public var ref: LoaderRef { .init(layout: layout.ref) } +} +extension PrebuiltLoader { public func path(in cache: DyldCache) -> String? { guard let offset = cache.fileOffset( of: numericCast(address) + numericCast(layout.pathOffset) @@ -45,3 +47,29 @@ extension PrebuiltLoader { ) } } + +extension PrebuiltLoader { + public func path(in cache: DyldCacheLoaded) -> String? { + guard let baseAddress = UnsafeRawPointer(bitPattern: address) else { + return nil + } + return String( + cString: baseAddress + .advanced(by: numericCast(layout.pathOffset)) + .assumingMemoryBound(to: CChar.self) + ) + } + + public func dependentLoaderRefs(in cache: DyldCache) -> MemorySequence? { + guard layout.dependentLoaderRefsArrayOffset != 0, + let baseAddress = UnsafeRawPointer(bitPattern: address) else { + return nil + } + return .init( + basePointer: baseAddress + .advanced(by: numericCast(layout.dependentLoaderRefsArrayOffset)) + .assumingMemoryBound(to: LoaderRef.self), + numberOfElements: numericCast(layout.depCount) + ) + } +} diff --git a/Sources/MachOKit/Model/DyldCache/Loader/PrebuiltLoaderSet.swift b/Sources/MachOKit/Model/DyldCache/Loader/PrebuiltLoaderSet.swift index 59731be..e57c632 100644 --- a/Sources/MachOKit/Model/DyldCache/Loader/PrebuiltLoaderSet.swift +++ b/Sources/MachOKit/Model/DyldCache/Loader/PrebuiltLoaderSet.swift @@ -42,6 +42,27 @@ extension PrebuiltLoaderSet { } } } + + public func loaders(in cache: DyldCacheLoaded) -> [PrebuiltLoader]? { + guard let basePointer = UnsafeRawPointer(bitPattern: address) else { + return nil + } + let offsets: MemorySequence = .init( + basePointer: basePointer + .advanced(by: numericCast(layout.loadersArrayOffset)) + .assumingMemoryBound(to: UInt32.self), + numberOfElements: numericCast(layout.loadersArrayCount) + ) + return offsets.compactMap { _offset -> PrebuiltLoader? in + let layout: prebuilt_loader = basePointer + .advanced(by: numericCast(_offset)) + .assumingMemoryBound(to: prebuilt_loader.self).pointee + return PrebuiltLoader( + layout: layout, + address: address + numericCast(_offset) + ) + } + } } extension PrebuiltLoaderSet { @@ -57,6 +78,10 @@ extension PrebuiltLoaderSet { } public func mustBeMissingPaths(in cache: DyldCache) -> [String]? { + guard layout.mustBeMissingPathsOffset != 0, + layout.mustBeMissingPathsCount != 0 else { + return nil + } guard layout.mustBeMissingPathsOffset != 0, var offset = cache.fileOffset( of: numericCast(address) + numericCast(layout.mustBeMissingPathsOffset) @@ -74,3 +99,42 @@ extension PrebuiltLoaderSet { return strings } } + +extension PrebuiltLoaderSet { + public func dyldCacheUUID(in cache: DyldCacheLoaded) -> UUID? { + guard layout.dyldCacheUUIDOffset != 0 else { return nil } + guard let basePointer = UnsafeRawPointer(bitPattern: address) else { + return nil + } + + let data: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) = basePointer + .advanced(by: numericCast(layout.dyldCacheUUIDOffset)) + .autoBoundPointee() + return .init(uuid: data) + } + + public func mustBeMissingPaths(in cache: DyldCacheLoaded) -> [String]? { + guard layout.mustBeMissingPathsOffset != 0, + layout.mustBeMissingPathsCount != 0 else { + return nil + } + guard let basePointer = UnsafeRawPointer(bitPattern: address) else { + return nil + } + var offset: Int = numericCast(layout.mustBeMissingPathsOffset) + var strings: [String] = [] + for _ in 0 ..< layout.mustBeMissingPathsCount { + guard let string = String( + cString: basePointer + .advanced(by: offset) + .assumingMemoryBound(to: CChar.self), + encoding: .utf8 + ) else { + break + } + strings.append(string) + offset += numericCast(string.utf8.count) + 1 // \0 + } + return strings + } +} diff --git a/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCHeaderInfoRO.swift b/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCHeaderInfoRO.swift new file mode 100644 index 0000000..9c6eb84 --- /dev/null +++ b/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCHeaderInfoRO.swift @@ -0,0 +1,298 @@ +// +// ObjCHeaderInfoRO.swift +// +// +// Created by p-x9 on 2024/10/14 +// +// + +import Foundation + +public protocol ObjCHeaderInfoROProtocol { + associatedtype HeaderOptimizationRO: ObjCHeaderOptimizationROProtocol & LayoutWrapper + /// offset from dyld cache starts + var offset: Int { get } + /// index of this header info + var index: Int { get } + + /// Description of an Objective-C image + /// - Parameter cache: DyldCache to which `self` belongs + /// - Returns: image info + func imageInfo(in cache: DyldCache) -> ObjCImageInfo? + /// Description of an Objective-C image + /// - Parameter cache: DyldCacheLoaded to which `self` belongs + /// - Returns: image info + func imageInfo(in cache: DyldCacheLoaded) -> ObjCImageInfo? + + /// Target mach-o image of header + /// - Parameters: + /// - objcOptimization: objc optimization to which `self` belongs + /// - roOptimizaion: ro optimization to which `self` belongs + /// - cache: DyldCache to which `self` belongs + /// - Returns: mach-o file + func machO( + objcOptimization: ObjCOptimization, + roOptimizaion: HeaderOptimizationRO, + in cache: DyldCache + ) -> MachOFile? + + /// Target mach-o image of header + /// - Parameters: + /// - objcOptimization: objc optimization to which `self` belongs + /// - roOptimizaion: ro optimization to which `self` belongs + /// - cache: DyldCache to which `self` belongs + /// - Returns: mach-o file + func machO( + objcOptimization: OldObjCOptimization, + roOptimizaion: HeaderOptimizationRO, + in cache: DyldCache + ) -> MachOFile? + + /// Target mach-o image of header + /// - Parameters: + /// - objcOptimization: objc optimization to which `self` belongs + /// - roOptimizaion: ro optimization to which `self` belongs + /// - cache: DyldCacheLoaded to which `self` belongs + /// - Returns: mach-o file + func machO( + objcOptimization: ObjCOptimization, + roOptimizaion: HeaderOptimizationRO, + in cache: DyldCacheLoaded + ) -> MachOImage? + + /// Target mach-o image of header + /// - Parameters: + /// - objcOptimization: objc optimization to which `self` belongs + /// - roOptimizaion: ro optimization to which `self` belongs + /// - cache: DyldCacheLoaded to which `self` belongs + /// - Returns: mach-o file + func machO( + objcOptimization: OldObjCOptimization, + roOptimizaion: HeaderOptimizationRO, + in cache: DyldCacheLoaded + ) -> MachOImage? +} + +// MARK: - ObjCHeaderInfoRO64 +public struct ObjCHeaderInfoRO64: LayoutWrapper, ObjCHeaderInfoROProtocol { + public typealias HeaderOptimizationRO = ObjCHeaderOptimizationRO64 + public typealias Layout = objc_header_info_ro_t_64 + + public var layout: Layout + public let offset: Int + public let index: Int + + public func imageInfo(in cache: DyldCache) -> ObjCImageInfo? { + let offset = offset + layoutOffset(of: \.info_offset) + numericCast(layout.info_offset) + return cache.fileHandle.read(offset: numericCast(offset)) + } + + public func imageInfo(in cache: DyldCacheLoaded) -> ObjCImageInfo? { + let offset = offset + layoutOffset(of: \.info_offset) + numericCast(layout.info_offset) + return cache.ptr + .advanced(by: numericCast(offset)) + .autoBoundPointee() + } + + public func machO( + objcOptimization: ObjCOptimization, + roOptimizaion: HeaderOptimizationRO, + in cache: DyldCache + ) -> MachOFile? { + _machO( + headerInfoROOffset: objcOptimization.headerInfoROCacheOffset, + roOptimizaion: roOptimizaion, + in: cache + ) + } + + public func machO( + objcOptimization: OldObjCOptimization, + roOptimizaion: HeaderOptimizationRO, + in cache: DyldCache + ) -> MachOFile? { + _machO( + headerInfoROOffset: numericCast(objcOptimization.offset) + numericCast(objcOptimization.headeropt_ro_offset), + roOptimizaion: roOptimizaion, + in: cache + ) + } + + public func machO( + objcOptimization: ObjCOptimization, + roOptimizaion: HeaderOptimizationRO, + in cache: DyldCacheLoaded + ) -> MachOImage? { + _machO( + headerInfoROOffset: objcOptimization.headerInfoROCacheOffset, + roOptimizaion: roOptimizaion, + in: cache + ) + } + + public func machO( + objcOptimization: OldObjCOptimization, + roOptimizaion: HeaderOptimizationRO, + in cache: DyldCacheLoaded + ) -> MachOImage? { + _machO( + headerInfoROOffset: numericCast(objcOptimization.offset) + numericCast(objcOptimization.headeropt_ro_offset), + roOptimizaion: roOptimizaion, + in: cache + ) + } + + private func _machO( + headerInfoROOffset: UInt64, + roOptimizaion: HeaderOptimizationRO, + in cache: DyldCache + ) -> MachOFile? { + let offsetFromRoHeader = roOptimizaion.layoutSize + index * roOptimizaion.entrySize + + let sharedRegionStart = cache.mainCacheHeader.sharedRegionStart + let roOffset = headerInfoROOffset + sharedRegionStart + let _offset: Int = numericCast(roOffset) + offsetFromRoHeader + numericCast(layout.mhdr_offset) + guard let offset = cache.fileOffset( + of: numericCast(_offset) + ) else { + return nil + } + return try? .init( + url: cache.url, + imagePath: "", // FIXME: path + headerStartOffsetInCache: numericCast(offset) + ) + } + + private func _machO( + headerInfoROOffset: UInt64, + roOptimizaion: HeaderOptimizationRO, + in cache: DyldCacheLoaded + ) -> MachOImage? { + guard let slide = cache.slide else { return nil } + let offsetFromRoHeader = roOptimizaion.layoutSize + index * roOptimizaion.entrySize + + let sharedRegionStart = cache.mainCacheHeader.sharedRegionStart + let roOffset = headerInfoROOffset + sharedRegionStart + numericCast(slide) + let _offset: Int = numericCast(roOffset) + offsetFromRoHeader + numericCast(layout.mhdr_offset) + guard let ptr = UnsafeRawPointer(bitPattern: _offset) else { + return nil + } + return .init( + ptr: ptr + .assumingMemoryBound(to: mach_header.self) + ) + } +} + +// MARK: - ObjCHeaderInfoRO32 +public struct ObjCHeaderInfoRO32: LayoutWrapper, ObjCHeaderInfoROProtocol { + public typealias HeaderOptimizationRO = ObjCHeaderOptimizationRO32 + public typealias Layout = objc_header_info_ro_t_32 + + public var layout: Layout + public let offset: Int + public let index: Int + + public func imageInfo(in cache: DyldCache) -> ObjCImageInfo? { + let offset = offset + layoutOffset(of: \.info_offset) + numericCast(layout.info_offset) + return cache.fileHandle.read(offset: numericCast(offset)) + } + + public func imageInfo(in cache: DyldCacheLoaded) -> ObjCImageInfo? { + let offset = offset + layoutOffset(of: \.info_offset) + numericCast(layout.info_offset) + return cache.ptr + .advanced(by: numericCast(offset)) + .autoBoundPointee() + } + + public func machO( + objcOptimization: ObjCOptimization, + roOptimizaion: HeaderOptimizationRO, + in cache: DyldCache + ) -> MachOFile? { + _machO( + headerInfoROOffset: objcOptimization.headerInfoROCacheOffset, + roOptimizaion: roOptimizaion, + in: cache + ) + } + + public func machO( + objcOptimization: OldObjCOptimization, + roOptimizaion: HeaderOptimizationRO, + in cache: DyldCache + ) -> MachOFile? { + _machO( + headerInfoROOffset: numericCast(objcOptimization.offset) + numericCast(objcOptimization.headeropt_ro_offset), + roOptimizaion: roOptimizaion, + in: cache + ) + } + + public func machO( + objcOptimization: ObjCOptimization, + roOptimizaion: HeaderOptimizationRO, + in cache: DyldCacheLoaded + ) -> MachOImage? { + _machO( + headerInfoROOffset: objcOptimization.headerInfoROCacheOffset, + roOptimizaion: roOptimizaion, + in: cache + ) + } + + public func machO( + objcOptimization: OldObjCOptimization, + roOptimizaion: HeaderOptimizationRO, + in cache: DyldCacheLoaded + ) -> MachOImage? { + _machO( + headerInfoROOffset: numericCast(objcOptimization.offset) + numericCast(objcOptimization.headeropt_ro_offset), + roOptimizaion: roOptimizaion, + in: cache + ) + } + + private func _machO( + headerInfoROOffset: UInt64, + roOptimizaion: HeaderOptimizationRO, + in cache: DyldCache + ) -> MachOFile? { + let offsetFromRoHeader = roOptimizaion.layoutSize + index * numericCast(roOptimizaion.entsize) + + let sharedRegionStart = cache.mainCacheHeader.sharedRegionStart + let roOffset = headerInfoROOffset + sharedRegionStart + let _offset: Int = numericCast(roOffset) + offsetFromRoHeader + numericCast(layout.mhdr_offset) + guard let offset = cache.fileOffset( + of: numericCast(_offset) + ) else { + return nil + } + return try? .init( + url: cache.url, + imagePath: "", // FIXME: path + headerStartOffsetInCache: numericCast(offset) + ) + } + + private func _machO( + headerInfoROOffset: UInt64, + roOptimizaion: HeaderOptimizationRO, + in cache: DyldCacheLoaded + ) -> MachOImage? { + guard let slide = cache.slide else { return nil } + let offsetFromRoHeader = roOptimizaion.layoutSize + index * numericCast(roOptimizaion.entsize) + + let sharedRegionStart = cache.mainCacheHeader.sharedRegionStart + let roOffset = headerInfoROOffset + sharedRegionStart + numericCast(slide) + let _offset: Int = numericCast(roOffset) + offsetFromRoHeader + numericCast(layout.mhdr_offset) + guard let ptr = UnsafeRawPointer(bitPattern: _offset) else { + return nil + } + return .init( + ptr: ptr + .assumingMemoryBound(to: mach_header.self) + ) + } +} diff --git a/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCHeaderInfoRW.swift b/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCHeaderInfoRW.swift new file mode 100644 index 0000000..da795d6 --- /dev/null +++ b/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCHeaderInfoRW.swift @@ -0,0 +1,34 @@ +// +// ObjCHeaderInfoRW.swift +// +// +// Created by p-x9 on 2024/10/14 +// +// + +import Foundation + +public protocol ObjCHeaderInfoRWProtocol { + /// A boolean value that indicates whether objc image is already loaded or not + var isLoaded: Bool { get } + /// A boolean value that indicates whether all objc classes contained in objc image are realized + var isAllClassesRelized: Bool { get } +} + +public struct ObjCHeaderInfoRW64: LayoutWrapper, ObjCHeaderInfoRWProtocol { + public typealias Layout = header_info_rw_64 + + public var layout: Layout + + public var isLoaded: Bool { layout.isLoaded == 1 } + public var isAllClassesRelized: Bool { layout.allClassesRealized == 1 } +} + +public struct ObjCHeaderInfoRW32: LayoutWrapper, ObjCHeaderInfoRWProtocol { + public typealias Layout = header_info_rw_32 + + public var layout: Layout + + public var isLoaded: Bool { layout.isLoaded == 1 } + public var isAllClassesRelized: Bool { layout.allClassesRealized == 1 } +} diff --git a/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCHeaderOptimizationRO.swift b/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCHeaderOptimizationRO.swift index efd0583..e771aa5 100644 --- a/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCHeaderOptimizationRO.swift +++ b/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCHeaderOptimizationRO.swift @@ -10,9 +10,18 @@ import Foundation public protocol ObjCHeaderOptimizationROProtocol { associatedtype HeaderInfo: LayoutWrapper + /// number of header infos var count: Int { get } + /// layout size of header info var entrySize: Int { get } + /// Sequence of header infos + /// - Parameter cache: DyldCache to which `self` belongs + /// - Returns: header infos func headerInfos(in cache: DyldCache) -> AnyRandomAccessCollection + /// Sequence of header infos + /// - Parameter cache: DyldCacheLoaded to which `self` belongs + /// - Returns: header infos + func headerInfos(in cache: DyldCacheLoaded) -> AnyRandomAccessCollection } public struct ObjCHeaderOptimizationRO64: LayoutWrapper, ObjCHeaderOptimizationROProtocol { @@ -46,6 +55,31 @@ public struct ObjCHeaderOptimizationRO64: LayoutWrapper, ObjCHeaderOptimizationR }) ) } + + public func headerInfos(in cache: DyldCacheLoaded) -> AnyRandomAccessCollection { + precondition( + layout.entsize >= HeaderInfo.layoutSize, + "entsize is smaller than HeaderInfo" + ) + let offset = offset + layoutSize + let layouts: MemorySequence = .init( + basePointer: cache.ptr + .advanced(by: offset) + .assumingMemoryBound(to: HeaderInfo.Layout.self), + entrySize: numericCast(layout.entsize), + numberOfElements: numericCast(layout.count) + ) + return AnyRandomAccessCollection( + layouts.enumerated() + .map { + HeaderInfo( + layout: $1, + offset: offset + HeaderInfo.layoutSize * $0, + index: $0 + ) + } + ) + } } public struct ObjCHeaderOptimizationRO32: LayoutWrapper, ObjCHeaderOptimizationROProtocol { @@ -79,122 +113,29 @@ public struct ObjCHeaderOptimizationRO32: LayoutWrapper, ObjCHeaderOptimizationR }) ) } -} - -public struct ObjCHeaderInfoRO64: LayoutWrapper { - public typealias Layout = objc_header_info_ro_t_64 - - public var layout: Layout - public let offset: Int - public let index: Int - - public func imageInfo(in cache: DyldCache) -> ObjCImageInfo? { - let offset = offset + layoutOffset(of: \.info_offset) + numericCast(layout.info_offset) - return cache.fileHandle.read(offset: numericCast(offset)) - } - - public func machO( - objcOptimization: ObjCOptimization, - roOptimizaion: ObjCHeaderOptimizationRO64, - in cache: DyldCache - ) -> MachOFile? { - _machO( - headerInfoROOffset: objcOptimization.headerInfoROCacheOffset, - roOptimizaion: roOptimizaion, - in: cache - ) - } - - public func machO( - objcOptimization: OldObjCOptimization, - roOptimizaion: ObjCHeaderOptimizationRO64, - in cache: DyldCache - ) -> MachOFile? { - _machO( - headerInfoROOffset: numericCast(objcOptimization.offset) + numericCast(objcOptimization.headeropt_ro_offset), - roOptimizaion: roOptimizaion, - in: cache - ) - } - - private func _machO( - headerInfoROOffset: UInt64, - roOptimizaion: ObjCHeaderOptimizationRO64, - in cache: DyldCache - ) -> MachOFile? { - let offsetFromRoHeader = roOptimizaion.layoutSize + index * numericCast(roOptimizaion.entsize) - - let sharedRegionStart = cache.mainCacheHeader.sharedRegionStart - let roOffset = headerInfoROOffset + sharedRegionStart - let _offset: Int = numericCast(roOffset) + offsetFromRoHeader + numericCast(layout.mhdr_offset) - guard let offset = cache.fileOffset( - of: numericCast(_offset) - ) else { - return nil - } - return try? .init( - url: cache.url, - imagePath: "", // FIXME: path - headerStartOffsetInCache: numericCast(offset) - ) - } -} - -public struct ObjCHeaderInfoRO32: LayoutWrapper { - public typealias Layout = objc_header_info_ro_t_32 - - public var layout: Layout - public let offset: Int - public let index: Int - - public func imageInfo(in cache: DyldCache) -> ObjCImageInfo? { - let offset = offset + layoutOffset(of: \.info_offset) + numericCast(layout.info_offset) - return cache.fileHandle.read(offset: numericCast(offset)) - } - public func machO( - objcOptimization: ObjCOptimization, - roOptimizaion: ObjCHeaderOptimizationRO32, - in cache: DyldCache - ) -> MachOFile? { - _machO( - headerInfoROOffset: objcOptimization.headerInfoROCacheOffset, - roOptimizaion: roOptimizaion, - in: cache + public func headerInfos(in cache: DyldCacheLoaded) -> AnyRandomAccessCollection { + precondition( + layout.entsize >= HeaderInfo.layoutSize, + "entsize is smaller than HeaderInfo" ) - } - - public func machO( - objcOptimization: OldObjCOptimization, - roOptimizaion: ObjCHeaderOptimizationRO32, - in cache: DyldCache - ) -> MachOFile? { - _machO( - headerInfoROOffset: numericCast(objcOptimization.offset) + numericCast(objcOptimization.headeropt_ro_offset), - roOptimizaion: roOptimizaion, - in: cache + let offset = offset + layoutSize + let layouts: MemorySequence = .init( + basePointer: cache.ptr + .advanced(by: offset) + .assumingMemoryBound(to: HeaderInfo.Layout.self), + entrySize: numericCast(layout.entsize), + numberOfElements: numericCast(layout.count) ) - } - - private func _machO( - headerInfoROOffset: UInt64, - roOptimizaion: ObjCHeaderOptimizationRO32, - in cache: DyldCache - ) -> MachOFile? { - let offsetFromRoHeader = roOptimizaion.layoutSize + index * numericCast(roOptimizaion.entsize) - - let sharedRegionStart = cache.mainCacheHeader.sharedRegionStart - let roOffset = headerInfoROOffset + sharedRegionStart - let _offset: Int = numericCast(roOffset) + offsetFromRoHeader + numericCast(layout.mhdr_offset) - guard let offset = cache.fileOffset( - of: numericCast(_offset) - ) else { - return nil - } - return try? .init( - url: cache.url, - imagePath: "", // FIXME: path - headerStartOffsetInCache: numericCast(offset) + return AnyRandomAccessCollection( + layouts.enumerated() + .map { + HeaderInfo( + layout: $1, + offset: offset + HeaderInfo.layoutSize * $0, + index: $0 + ) + } ) } } diff --git a/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCHeaderOptimizationRW.swift b/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCHeaderOptimizationRW.swift index d595522..d02dca5 100644 --- a/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCHeaderOptimizationRW.swift +++ b/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCHeaderOptimizationRW.swift @@ -10,9 +10,18 @@ import Foundation public protocol ObjCHeaderOptimizationRWProtocol { associatedtype HeaderInfo: LayoutWrapper + /// number of header infos var count: Int { get } + /// layout size of header info var entrySize: Int { get } + /// Sequence of header infos + /// - Parameter cache: DyldCache to which `self` belongs + /// - Returns: header infos func headerInfos(in cache: DyldCache) -> AnyRandomAccessCollection + /// Sequence of header infos + /// - Parameter cache: DyldCacheLoaded to which `self` belongs + /// - Returns: header infos + func headerInfos(in cache: DyldCacheLoaded) -> AnyRandomAccessCollection } public struct ObjCHeaderOptimizationRW64: LayoutWrapper, ObjCHeaderOptimizationRWProtocol { @@ -41,6 +50,25 @@ public struct ObjCHeaderOptimizationRW64: LayoutWrapper, ObjCHeaderOptimizationR ) ) } + + public func headerInfos( + in cache: DyldCacheLoaded + ) -> AnyRandomAccessCollection { + precondition( + layout.entsize >= HeaderInfo.layoutSize, + "entsize is smaller than HeaderInfo" + ) + let offset = offset + layoutSize + return AnyRandomAccessCollection( + MemorySequence( + basePointer: cache.ptr + .advanced(by: numericCast(offset)) + .assumingMemoryBound(to: HeaderInfo.self), + entrySize: numericCast(layout.entsize), + numberOfElements: numericCast(layout.count) + ) + ) + } } public struct ObjCHeaderOptimizationRW32: LayoutWrapper, ObjCHeaderOptimizationRWProtocol { @@ -69,22 +97,23 @@ public struct ObjCHeaderOptimizationRW32: LayoutWrapper, ObjCHeaderOptimizationR ) ) } -} - -public struct ObjCHeaderInfoRW64: LayoutWrapper { - public typealias Layout = header_info_rw_64 - - public var layout: Layout - public var isLoaded: Bool { layout.isLoaded == 1 } - public var isAllClassesRelized: Bool { layout.allClassesRealized == 1 } -} - -public struct ObjCHeaderInfoRW32: LayoutWrapper { - public typealias Layout = header_info_rw_32 - - public var layout: Layout - - public var isLoaded: Bool { layout.isLoaded == 1 } - public var isAllClassesRelized: Bool { layout.allClassesRealized == 1 } + public func headerInfos( + in cache: DyldCacheLoaded + ) -> AnyRandomAccessCollection { + precondition( + layout.entsize >= HeaderInfo.layoutSize, + "entsize is smaller than HeaderInfo" + ) + let offset = offset + layoutSize + return AnyRandomAccessCollection( + MemorySequence( + basePointer: cache.ptr + .advanced(by: numericCast(offset)) + .assumingMemoryBound(to: HeaderInfo.self), + entrySize: numericCast(layout.entsize), + numberOfElements: numericCast(layout.count) + ) + ) + } } diff --git a/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCOptimization.swift b/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCOptimization.swift index ae5152a..fc930ab 100644 --- a/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCOptimization.swift +++ b/Sources/MachOKit/Model/DyldCache/ObjCOptimization/ObjCOptimization.swift @@ -18,6 +18,9 @@ public struct ObjCOptimization: LayoutWrapper { // MARK: Header Optimization RW // https://github.com/apple-oss-distributions/dyld/blob/65bbeed63cec73f313b1d636e63f243964725a9d/common/DyldSharedCache.cpp#L1892 extension ObjCOptimization { + /// Header optimization rw info for 64bit + /// - Parameter cache: DyldCache to which `self` belongs + /// - Returns: header optimization rw public func headerOptimizationRW64( in cache: DyldCache ) -> ObjCHeaderOptimizationRW64? { @@ -37,6 +40,9 @@ extension ObjCOptimization { ) } + /// Header optimization rw info for 32bit + /// - Parameter cache: DyldCache to which `self` belongs + /// - Returns: header optimization rw public func headerOptimizationRW32( in cache: DyldCache ) -> ObjCHeaderOptimizationRW32? { @@ -57,9 +63,52 @@ extension ObjCOptimization { } } +extension ObjCOptimization { + /// Header optimization rw info for 64bit + /// - Parameter cache: DyldCacheLoaded to which `self` belongs + /// - Returns: header optimization rw + public func headerOptimizationRW64( + in cache: DyldCacheLoaded + ) -> ObjCHeaderOptimizationRW64? { + guard layout.headerInfoRWCacheOffset > 0 else { + return nil + } + let offset: Int = numericCast(layout.headerInfoRWCacheOffset) + let layout: ObjCHeaderOptimizationRW64.Layout = cache.ptr + .advanced(by: offset) + .autoBoundPointee() + return .init( + layout: layout, + offset: offset + ) + } + + /// Header optimization rw info for 32bit + /// - Parameter cache: DyldCacheLoaded to which `self` belongs + /// - Returns: header optimization rw + public func headerOptimizationRW32( + in cache: DyldCacheLoaded + ) -> ObjCHeaderOptimizationRW32? { + guard layout.headerInfoRWCacheOffset > 0 else { + return nil + } + let offset: Int = numericCast(layout.headerInfoRWCacheOffset) + let layout: ObjCHeaderOptimizationRW32.Layout = cache.ptr + .advanced(by: offset) + .autoBoundPointee() + return .init( + layout: layout, + offset: offset + ) + } +} + // MARK: Header Optimization RO // https://github.com/apple-oss-distributions/dyld/blob/65bbeed63cec73f313b1d636e63f243964725a9d/common/DyldSharedCache.cpp#L2017 extension ObjCOptimization { + /// Header optimization ro info for 64bit + /// - Parameter cache: DyldCache to which `self` belongs + /// - Returns: header optimization ro public func headerOptimizationRO64( in cache: DyldCache ) -> ObjCHeaderOptimizationRO64? { @@ -79,6 +128,9 @@ extension ObjCOptimization { ) } + /// Header optimization ro info for 32bit + /// - Parameter cache: DyldCache to which `self` belongs + /// - Returns: header optimization ro public func headerOptimizationRO32( in cache: DyldCache ) -> ObjCHeaderOptimizationRO32? { @@ -98,3 +150,43 @@ extension ObjCOptimization { ) } } + +extension ObjCOptimization { + /// Header optimization ro info for 64bit + /// - Parameter cache: DyldCacheLoaded to which `self` belongs + /// - Returns: header optimization ro + public func headerOptimizationRO64( + in cache: DyldCacheLoaded + ) -> ObjCHeaderOptimizationRO64? { + guard layout.headerInfoROCacheOffset > 0 else { + return nil + } + let offset: Int = numericCast(layout.headerInfoROCacheOffset) + let layout: ObjCHeaderOptimizationRO64.Layout = cache.ptr + .advanced(by: offset) + .autoBoundPointee() + return .init( + layout: layout, + offset: numericCast(offset) + ) + } + + /// Header optimization ro info for 32bit + /// - Parameter cache: DyldCacheLoaded to which `self` belongs + /// - Returns: header optimization ro + public func headerOptimizationRO32( + in cache: DyldCacheLoaded + ) -> ObjCHeaderOptimizationRO32? { + guard layout.headerInfoROCacheOffset > 0 else { + return nil + } + let offset: Int = numericCast(layout.headerInfoROCacheOffset) + let layout: ObjCHeaderOptimizationRO32.Layout = cache.ptr + .advanced(by: offset) + .autoBoundPointee() + return .init( + layout: layout, + offset: numericCast(offset) + ) + } +} diff --git a/Sources/MachOKit/Model/DyldCache/ObjCOptimization/OldObjCOptimization.swift b/Sources/MachOKit/Model/DyldCache/ObjCOptimization/OldObjCOptimization.swift index 57c99a4..7630d24 100644 --- a/Sources/MachOKit/Model/DyldCache/ObjCOptimization/OldObjCOptimization.swift +++ b/Sources/MachOKit/Model/DyldCache/ObjCOptimization/OldObjCOptimization.swift @@ -18,6 +18,21 @@ public struct OldObjCOptimization: LayoutWrapper { } extension OldObjCOptimization { + public func relativeMethodSelectorBaseAddress( + in cache: DyldCacheLoaded + ) -> UnsafeRawPointer { + cache.ptr + .advanced(by: offset) + .advanced( + by: numericCast(layout.relativeMethodSelectorBaseAddressOffset) + ) + } +} + +extension OldObjCOptimization { + /// Header optimization rw info for 64bit + /// - Parameter cache: DyldCache to which `self` belongs + /// - Returns: header optimization rw public func headerOptimizationRW64( in cache: DyldCache ) -> ObjCHeaderOptimizationRW64? { @@ -37,6 +52,9 @@ extension OldObjCOptimization { ) } + /// Header optimization rw info for 32bit + /// - Parameter cache: DyldCache to which `self` belongs + /// - Returns: header optimization rw public func headerOptimizationRW32( in cache: DyldCache ) -> ObjCHeaderOptimizationRW32? { @@ -58,6 +76,49 @@ extension OldObjCOptimization { } extension OldObjCOptimization { + /// Header optimization rw info for 64bit + /// - Parameter cache: DyldCacheLoaded to which `self` belongs + /// - Returns: header optimization rw + public func headerOptimizationRW64( + in cache: DyldCacheLoaded + ) -> ObjCHeaderOptimizationRW64? { + guard layout.headeropt_rw_offset > 0 else { + return nil + } + let offset: Int = numericCast(offset) + numericCast(layout.headeropt_rw_offset) + let layout: ObjCHeaderOptimizationRW64.Layout = cache.ptr + .advanced(by: offset) + .autoBoundPointee() + return .init( + layout: layout, + offset: numericCast(offset) + ) + } + + /// Header optimization rw info for 32bit + /// - Parameter cache: DyldCacheLoaded to which `self` belongs + /// - Returns: header optimization rw + public func headerOptimizationRW32( + in cache: DyldCacheLoaded + ) -> ObjCHeaderOptimizationRW32? { + guard layout.headeropt_rw_offset > 0 else { + return nil + } + let offset: Int = numericCast(offset) + numericCast(layout.headeropt_rw_offset) + let layout: ObjCHeaderOptimizationRW32.Layout = cache.ptr + .advanced(by: offset) + .autoBoundPointee() + return .init( + layout: layout, + offset: numericCast(offset) + ) + } +} + +extension OldObjCOptimization { + /// Header optimization ro info for 64bit + /// - Parameter cache: DyldCache to which `self` belongs + /// - Returns: header optimization ro public func headerOptimizationRO64( in cache: DyldCache ) -> ObjCHeaderOptimizationRO64? { @@ -77,6 +138,9 @@ extension OldObjCOptimization { ) } + /// Header optimization ro info for 32bit + /// - Parameter cache: DyldCache to which `self` belongs + /// - Returns: header optimization ro public func headerOptimizationRO32( in cache: DyldCache ) -> ObjCHeaderOptimizationRO32? { @@ -96,3 +160,43 @@ extension OldObjCOptimization { ) } } + +extension OldObjCOptimization { + /// Header optimization ro info for 64bit + /// - Parameter cache: DyldCacheLoaded to which `self` belongs + /// - Returns: header optimization ro + public func headerOptimizationRO64( + in cache: DyldCacheLoaded + ) -> ObjCHeaderOptimizationRO64? { + guard layout.headeropt_ro_offset > 0 else { + return nil + } + let offset: Int = numericCast(offset) + numericCast(layout.headeropt_ro_offset) + let layout: ObjCHeaderOptimizationRO64.Layout = cache.ptr + .advanced(by: offset) + .autoBoundPointee() + return .init( + layout: layout, + offset: numericCast(offset) + ) + } + + /// Header optimization ro info for 32bit + /// - Parameter cache: DyldCacheLoaded to which `self` belongs + /// - Returns: header optimization ro + public func headerOptimizationRO32( + in cache: DyldCacheLoaded + ) -> ObjCHeaderOptimizationRO32? { + guard layout.headeropt_ro_offset > 0 else { + return nil + } + let offset: Int = numericCast(offset) + numericCast(layout.headeropt_ro_offset) + let layout: ObjCHeaderOptimizationRO32.Layout = cache.ptr + .advanced(by: offset) + .autoBoundPointee() + return .init( + layout: layout, + offset: numericCast(offset) + ) + } +} diff --git a/Sources/MachOKit/Protocol/DyldCacheRepresentable.swift b/Sources/MachOKit/Protocol/DyldCacheRepresentable.swift new file mode 100644 index 0000000..e7e532c --- /dev/null +++ b/Sources/MachOKit/Protocol/DyldCacheRepresentable.swift @@ -0,0 +1,202 @@ +// +// DyldCacheRepresentable.swift +// +// +// Created by p-x9 on 2024/10/06 +// +// + +import Foundation + +public protocol DyldCacheRepresentable { + associatedtype MappingInfos: RandomAccessCollection + associatedtype MappingAndSlideInfos: RandomAccessCollection + associatedtype ImageInfos: RandomAccessCollection + associatedtype ImageTextInfos: RandomAccessCollection + associatedtype SubCaches: Sequence + associatedtype DylibsTrieEntries: TrieTreeProtocol + associatedtype ProgramsTrieEntries: TrieTreeProtocol + + /// Byte size of header + var headerSize: Int { get } + /// Header for dyld cache + var header: DyldCacheHeader { get } + /// Target CPU info. + /// + /// It is obtained based on magic in header. + var cpu: CPU { get } + /// Header for main dyld cache + /// When this dyld cache is a subcache, represent the header of the main cache + /// + /// Some properties are only set for the main cache header + /// https://github.com/apple-oss-distributions/dyld/blob/d552c40cd1de105f0ec95008e0e0c0972de43456/cache_builder/SubCache.cpp#L1353 + var mainCacheHeader: DyldCacheHeader { get } + + /// Sequence of mapping infos + var mappingInfos: MappingInfos? { get } + /// Sequence of mapping and slide infos + var mappingAndSlideInfos: MappingAndSlideInfos? { get } + + /// Sequence of image infos. + var imageInfos: ImageInfos? { get } + /// Sequence of image text infos. + var imageTextInfos: ImageTextInfos? { get } + + /// Sub cache type + /// + /// Check if entry type is `dyld_subcache_entry_v1` or `dyld_subcache_entry` + var subCacheEntryType: DyldSubCacheEntryType? { get } + /// Sequence of sub caches + var subCaches: SubCaches? { get } + + /// Local symbol info + var localSymbolsInfo: DyldCacheLocalSymbolsInfo? { get } + + /// Dylibs trie is for searching by dylib name. + /// + /// The ``dylibIndices`` are retrieved from this trie tree. + var dylibsTrieEntries: DylibsTrieEntries? { get } + /// Array of Dylib name-index pairs + /// + /// This index matches the index in the dylib image list that can be retrieved from imagesOffset. + /// + /// If an alias exists, there may be another element with an equal index. + /// ``` + /// 0 /usr/lib/libobjc.A.dylib + /// 0 /usr/lib/libobjc.dylib + /// ``` + var dylibIndices: [DylibIndex] { get } + + /// Pair of program name/cdhash and offset to prebuiltLoaderSet + /// + /// The ``programOffsets`` are retrieved from this trie tree. + var programsTrieEntries: ProgramsTrieEntries? { get } + /// Pair of program name/cdhash and offset to prebuiltLoaderSet + /// + /// Example: + /// ``` + /// 0 /System/Applications/App Store.app/Contents/MacOS/App Store + /// 0 /cdhash/32caa391186c08b3b3cb7866995db1cb65b0376a + /// 131776 /System/Applications/Automator.app/Contents/MacOS/Automator + /// 131776 /cdhash/fed26a75645fed2a674b5c4d01001bfa69b9dbea + /// ``` + var programOffsets: [ProgramOffset] { get } + /// PrebuiltLoaderSet of all cached dylibs + var dylibsPrebuiltLoaderSet: PrebuiltLoaderSet? { get } + + /// Optimization info for Objective-C + /// + /// [dyld implementation](https://github.com/apple-oss-distributions/dyld/blob/65bbeed63cec73f313b1d636e63f243964725a9d/common/DyldSharedCache.cpp#L1892-L1898) + var objcOptimization: ObjCOptimization? { get } + /// Old style of optimization info for Objective-C + /// + /// [dyld implementation](https://github.com/apple-oss-distributions/dyld/blob/65bbeed63cec73f313b1d636e63f243964725a9d/common/DyldSharedCache.cpp#L1906-L1942) + var oldObjcOptimization: OldObjCOptimization? { get } + /// Optimization info for Swift + /// + /// [dyld implementation](https://github.com/apple-oss-distributions/dyld/blob/65bbeed63cec73f313b1d636e63f243964725a9d/common/DyldSharedCache.cpp#L2088-L2098) + var swiftOptimization: SwiftOptimization? { get } + + /// Get the prebuiltLoaderSet indicated by programOffset. + /// - Parameter programOffset: program name and offset pair + /// - Returns: prebuiltLoaderSet + func prebuiltLoaderSet(for programOffset: ProgramOffset) -> PrebuiltLoaderSet? + + /// Convert vmaddr to file offset + /// - Parameter address: vmaddr + /// - Returns: file offset + /// + /// If nil is returned, it may be that a non-valid address was given or + /// an address that exists in the subcache. + func fileOffset(of address: UInt64) -> UInt64? + /// Convert file offset to vmaddr + /// - Parameter fileOffset: file offset + /// - Returns: vmaddr + /// + /// - Warning: If you want to convert a file offset read from one section or another, + /// you need to check that it is really contained in this file before using it. + /// Otherwise you may get the wrong address. + func address(of fileOffset: UInt64) -> UInt64? + /// Get mapping info containing the specified vmaddr + /// - Parameter address: vmaddr + /// - Returns: mapping info + func mappingInfo(for address: UInt64) -> DyldCacheMappingInfo? + /// Get mapping info containing the specified file offset + /// - Parameter offset: file offset + /// - Returns: mapping info + func mappingInfo(forFileOffset offset: UInt64) -> DyldCacheMappingInfo? + /// Get mapping and slideinfo containing the specified vmaddr + /// - Parameter address: vmaddr + /// - Returns: mapping and slide info + func mappingAndSlideInfo(for address: UInt64) -> DyldCacheMappingAndSlideInfo? + /// Get mapping and slide info containing the specified file offset + /// - Parameter offset: file offset + /// - Returns: mapping and slide info + func mappingAndSlideInfo(forFileOffset offset: UInt64) -> DyldCacheMappingAndSlideInfo? +} + +extension DyldCacheRepresentable { + public func fileOffset(of address: UInt64) -> UInt64? { + guard let mapping = mappingInfo(for: address) else { + return nil + } + return address - mapping.address + mapping.fileOffset + } + + public func address(of fileOffset: UInt64) -> UInt64? { + guard let mapping = mappingInfo(forFileOffset: fileOffset) else { + return nil + } + return fileOffset - mapping.fileOffset + mapping.address + } + + public func mappingInfo(for address: UInt64) -> DyldCacheMappingInfo? { + guard let mappings = self.mappingInfos else { return nil } + for mapping in mappings { + if mapping.address <= address, + address < mapping.address + mapping.size { + return mapping + } + } + return nil + } + + public func mappingInfo( + forFileOffset offset: UInt64 + ) -> DyldCacheMappingInfo? { + guard let mappings = self.mappingInfos else { return nil } + for mapping in mappings { + if mapping.fileOffset <= offset, + offset < mapping.fileOffset + mapping.size { + return mapping + } + } + return nil + } + + public func mappingAndSlideInfo( + for address: UInt64 + ) -> DyldCacheMappingAndSlideInfo? { + guard let mappings = self.mappingAndSlideInfos else { return nil } + for mapping in mappings { + if mapping.address <= address, + address < mapping.address + mapping.size { + return mapping + } + } + return nil + } + + public func mappingAndSlideInfo( + forFileOffset offset: UInt64 + ) -> DyldCacheMappingAndSlideInfo? { + guard let mappings = self.mappingAndSlideInfos else { return nil } + for mapping in mappings { + if mapping.fileOffset <= offset, + offset < mapping.fileOffset + mapping.size { + return mapping + } + } + return nil + } +} diff --git a/Sources/MachOKit/Util/TrieTree/DataTrieTree.swift b/Sources/MachOKit/Util/TrieTree/DataTrieTree.swift index 7e6a76e..a81b769 100644 --- a/Sources/MachOKit/Util/TrieTree/DataTrieTree.swift +++ b/Sources/MachOKit/Util/TrieTree/DataTrieTree.swift @@ -8,7 +8,7 @@ import Foundation -public struct DataTrieTree { +public struct DataTrieTree: TrieTreeProtocol { public let data: Data public var size: Int { data.count } diff --git a/Sources/MachOKit/Util/TrieTree/MemoryTrieTree.swift b/Sources/MachOKit/Util/TrieTree/MemoryTrieTree.swift index 0f8cfed..5d42ff4 100644 --- a/Sources/MachOKit/Util/TrieTree/MemoryTrieTree.swift +++ b/Sources/MachOKit/Util/TrieTree/MemoryTrieTree.swift @@ -8,7 +8,7 @@ import Foundation -public struct MemoryTrieTree { +public struct MemoryTrieTree: TrieTreeProtocol { public let basePointer: UnsafeRawPointer public let size: Int diff --git a/Sources/MachOKit/Util/TrieTree/Protocol/TrieTreeProtocol.swift b/Sources/MachOKit/Util/TrieTree/Protocol/TrieTreeProtocol.swift new file mode 100644 index 0000000..0ecbacc --- /dev/null +++ b/Sources/MachOKit/Util/TrieTree/Protocol/TrieTreeProtocol.swift @@ -0,0 +1,13 @@ +// +// TrieTreeProtocol.swift +// +// +// Created by p-x9 on 2024/10/06 +// +// + +import Foundation + +public protocol TrieTreeProtocol: Sequence where Element == TrieNode { + associatedtype Content: TrieNodeContent +} diff --git a/Sources/MachOKitC/include/dyld_cache.h b/Sources/MachOKitC/include/dyld_cache.h new file mode 100644 index 0000000..922d55f --- /dev/null +++ b/Sources/MachOKitC/include/dyld_cache.h @@ -0,0 +1,18 @@ +// +// dyld_cache.h +// +// +// Created by p-x9 on 2024/10/09 +// +// + +#ifndef dyld_cache_h +#define dyld_cache_h + +#ifndef __linux__ + +extern const void* _dyld_get_shared_cache_range(size_t* length); + +#endif + +#endif /* dyld_cache_h */ diff --git a/Tests/MachOKitTests/DyldCacheLoadedPrintTests.swift b/Tests/MachOKitTests/DyldCacheLoadedPrintTests.swift new file mode 100644 index 0000000..8163143 --- /dev/null +++ b/Tests/MachOKitTests/DyldCacheLoadedPrintTests.swift @@ -0,0 +1,280 @@ +// +// DyldCacheLoadedPrintTests.swift +// +// +// Created by p-x9 on 2024/10/10 +// +// + +import XCTest +@testable import MachOKit + +#if canImport(Darwin) + +final class DyldCacheLoadedPrintTests: XCTestCase { + var cache: DyldCacheLoaded! + var cache1: DyldCacheLoaded! + + override func setUp() { + print("----------------------------------------------------") + var size = 0 + guard let ptr = _dyld_get_shared_cache_range(&size) else { + return + } + cache = try? .init(ptr: ptr) + cache1 = try? Array(cache.subCaches!)[0] + .subcache(for: cache) + } + + func testHeader() throws { + let header = cache.header + print("Magic:", header.magic) + print("UUID:", header.uuid) + print("CacheType:", header.cacheType) + print("Mappings:", header.mappingCount) + print("Images:", header.imagesCount) + print("Platform:", header.platform) + print("OS Version:", header.osVersion) + print("isSimulator:", header.isSimulator) + print("Alt Platform:", header.altPlatform) + print("Alt OS Version:", header.altOsVersion) + print("CPU:", cache.cpu) +// dump(header.layout) + } + + func testMappingInfos() throws { + guard let infos = cache.mappingInfos else { + return + } + for info in infos { + print("----") + print("Address:", String(info.address, radix: 16)) + print("File Offset::", String(info.fileOffset, radix: 16)) + print("Size:", String(info.size, radix: 16)) + print("MaxProtection:", info.maxProtection.bits) + print("InitProtection:", info.initialProtection.bits) + } + } + + func testMappingAndSlideInfos() throws { + guard let infos = cache.mappingAndSlideInfos else { + return + } + for info in infos { + print("----") + print("Address:", String(info.address, radix: 16)) + print("File Offset::", String(info.fileOffset, radix: 16)) + print("Size:", String(info.size, radix: 16)) + print("Flags:", info.flags.bits) + print("MaxProtection:", info.maxProtection.bits) + print("InitProtection:", info.initialProtection.bits) + } + } + + func testImageInfos() throws { + guard let infos = cache.imageInfos else { + return + } + + print("Images:", cache.header.imagesCount) + for info in infos { + print("----") + print("Address:", String(info.address, radix: 16)) + print("Path:", info.path(in: cache) ?? "unknown") + } + } + + func testImageTextInfos() throws { + guard let infos = cache.imageTextInfos else { + return + } + print("ImageTexts:", cache.header.imagesTextCount) + for info in infos { + print("----") + print("Address:", String(info.loadAddress, radix: 16)) + print("Path:", info.path(in: cache) ?? "unknown") + print("UUID:", info.uuid) + } + } + + func testSubCaches() throws { + guard let subCaches = cache.subCaches else { return } + for subCache in subCaches { + print("----") + print("UUID:", subCache.uuid) + print("VM Offset:", String(subCache.cacheVMOffset, radix: 16)) + print("File Suffix:", subCache.fileSuffix) + print( + "Path:", + subCache.fileSuffix + ) + } + } + + func testLocalSymbolsInfo() throws { + guard let symbolsInfo = cache.localSymbolsInfo else { + return + } + + let symbols = symbolsInfo.symbols(in: cache) + for symbol in symbols { + print( + String(symbol.offset, radix: 16), + symbol.demangledName + ) + } + } + + func testLocalSymbolsInfoEntries() throws { + guard let symbolsInfo = cache.localSymbolsInfo else { + return + } + + let entries = symbolsInfo.entries(in: cache) + let symbols = Array(symbolsInfo.symbols(in: cache)) + + for entry in entries { + let start = entry.nlistStartIndex + let end = entry.nlistStartIndex + entry.nlistCount + print( + "Offset:", String(entry.dylibOffset, radix: 16), + "Symbols:", symbols[start ..< end].count + ) + } + } + + func testMachOImages() throws { + let machOs = cache.machOImages() + for machO in machOs { + print( + machO.loadCommands.info(of: LoadCommand.idDylib)? + .dylib(cmdsStart: machO.cmdsStartPtr) + .name ?? "Unknown", + machO.header.ncmds + ) + } + } + + func testDylibIndices() { + let cache = cache1! + let indices = cache.dylibIndices + .sorted(by: { lhs, rhs in + lhs.index < rhs.index + }) + for index in indices { + print(index.index, index.name) + } + } + + func testProgramOffsets() { + let cache = cache1! + let programOffsets = cache.programOffsets + for programOffset in programOffsets { + print(programOffset.offset, programOffset.name) + } + } + + func testProgramPreBuildLoaderSet() { + let cache = self.cache1! + let programOffsets = cache.programOffsets + for programOffset in programOffsets { + guard !programOffset.name.starts(with: "/cdhash") else { + continue + } + guard let loaderSet = cache.prebuiltLoaderSet(for: programOffset) else { + continue + } + print("Name:", programOffset.name) + print("Loaders:") + for loader in loaderSet.loaders(in: cache)! { + print(" \(loader.path(in: cache) ?? "unknown")") + } + let dyldCacheUUID = loaderSet.dyldCacheUUID(in: cache) + print("dyldCacheUUID:", dyldCacheUUID?.uuidString ?? "None") + let mustBeMissingPaths = loaderSet.mustBeMissingPaths(in: cache) ?? [] + print("mustBeMissingPaths:") + for path in mustBeMissingPaths { + print("", path) + } + } + } + + func testDylibsPreBuildLoaderSet() { + let cache = self.cache1! + guard let loaderSet = cache.dylibsPrebuiltLoaderSet else { + return + } + print("Loaders:") + for loader in loaderSet.loaders(in: cache)! { + print(" \(loader.path(in: cache) ?? "unknown")") + } + } + + func testObjCOptimization() throws { + guard let objcOptimization = cache.objcOptimization else { return } + print("Version:", objcOptimization.version) + print("Flags:", objcOptimization.flags) + print("Header Info RO Cache Offset:", objcOptimization.headerInfoROCacheOffset) + print("Header Info RW Cache Offset:", objcOptimization.headerInfoRWCacheOffset) + print("Selector Hash Table Cache Offset:", objcOptimization.selectorHashTableCacheOffset) + print("Class Hash Table Cache Offset:", objcOptimization.classHashTableCacheOffset) + print("Protocol Hash Table Cache Offset:", objcOptimization.protocolHashTableCacheOffset) + print("Relative Method Selector Base Address Offset:", objcOptimization.relativeMethodSelectorBaseAddressOffset) + } + + func testObjCHeaderOptimizationRW() throws { + guard let objcOptimization = cache.objcOptimization else { return } + let rw = objcOptimization.headerOptimizationRW64(in: cache)! + let rwHeaders = rw.headerInfos(in: cache) + print("Count:", rw.count) + print("EntrySize:", rw.entrySize) + for info in rwHeaders { + print(" isLoaded: \(info.isLoaded), isAllClassesRelized: \(info.isAllClassesRelized)") + } + } + + func testObjCHeaderOptimizationRO() throws { + guard let objcOptimization = cache.objcOptimization else { return } + let ro = objcOptimization.headerOptimizationRO64(in: cache)! + let roHeaders = ro.headerInfos(in: cache) + print("Count:", ro.count) + print("EntrySize:", ro.entrySize) + + print("Image Info:") + for info in roHeaders { + guard let imageInfo = info.imageInfo(in: cache) else { + print(" nil") + continue + } + print(" Flags: \(imageInfo.flags.bits), Version: \(imageInfo.version)") + } + + print("Image:") + for info in roHeaders { + guard let machO = info.machO( + objcOptimization: objcOptimization, + roOptimizaion: ro, + in: cache + ) else { + print(" nil") + continue + } + let path = machO.loadCommands + .info(of: LoadCommand.idDylib)? + .dylib(cmdsStart: machO.cmdsStartPtr) + .name ?? "unknown" + print(" \(path)") + } + } + + func testSwiftOptimization() throws { + guard let swiftOptimization = cache.swiftOptimization else { return } + print("Version:", swiftOptimization.version) + print("Padding:", swiftOptimization.padding) + print("Type Conformance Hash Table Cache Offset:", swiftOptimization.typeConformanceHashTableCacheOffset) + print("Metadata Conformance Hash Table Cache Offset:", swiftOptimization.metadataConformanceHashTableCacheOffset) + print("Foreign Type Conformance Hash Table Cache Offset:", swiftOptimization.foreignTypeConformanceHashTableCacheOffset) + } +} + +#endif diff --git a/Tests/MachOKitTests/DyldCachePrintTests.swift b/Tests/MachOKitTests/DyldCachePrintTests.swift index 54c71a8..2340624 100644 --- a/Tests/MachOKitTests/DyldCachePrintTests.swift +++ b/Tests/MachOKitTests/DyldCachePrintTests.swift @@ -196,6 +196,13 @@ final class DyldCachePrintTests: XCTestCase { for loader in loaderSet.loaders(in: cache)! { print(" \(loader.path(in: cache) ?? "unknown")") } + let dyldCacheUUID = loaderSet.dyldCacheUUID(in: cache) + print("dyldCacheUUID:", dyldCacheUUID?.uuidString ?? "None") + let mustBeMissingPaths = loaderSet.mustBeMissingPaths(in: cache) ?? [] + print("mustBeMissingPaths:") + for path in mustBeMissingPaths { + print("", path) + } } } @@ -219,7 +226,7 @@ final class DyldCachePrintTests: XCTestCase { print("Selector Hash Table Cache Offset:", objcOptimization.selectorHashTableCacheOffset) print("Class Hash Table Cache Offset:", objcOptimization.classHashTableCacheOffset) print("Protocol Hash Table Cache Offset:", objcOptimization.protocolHashTableCacheOffset) - print("Relative Method Selector Base Address Offset:", objcOptimization.relativeMethodSelectorBaseAddressOffset) + print("Relative Method Selector Base Address Offset:", objcOptimization.relativeMethodSelectorBaseAddressOffset) } func testObjCHeaderOptimizationRW() throws {