diff --git a/Sources/MachOObjCSection/Extension/DyldCache+.swift b/Sources/MachOObjCSection/Extension/DyldCache+.swift index a9b6867..09d7f6b 100644 --- a/Sources/MachOObjCSection/Extension/DyldCache+.swift +++ b/Sources/MachOObjCSection/Extension/DyldCache+.swift @@ -28,89 +28,136 @@ extension DyldCache { } } +// MARK: - locate value extension DyldCache { - var headerOptimizationRO64: ObjCHeaderOptimizationRO64? { - guard cpu.is64Bit else { - return nil - } - if let objcOptimization { - return objcOptimization.headerOptimizationRO64(in: self) - } - if let oldObjcOptimization { - return oldObjcOptimization.headerOptimizationRO64(in: self) - } - return nil + /// A tuple containing the DyldCache where the value was found and the resolved value itself. + /// Useful because values may be located either in the current cache, the main cache, + /// or one of its subcaches. + typealias LocatedValue = (cache: DyldCache, value: V) + + /// Locate a value for a given optional KeyPath within this cache hierarchy. + /// + /// This resolves the value by checking: + /// 1. This cache + /// 2. The main cache + /// 3. Any subcaches derived from `mainCache` + /// + /// - Parameter keyPath: A keyPath returning an optional value. + /// - Returns: A tuple of `(cache, value)` if resolved, or `nil` if not found. + func locateValue( + _ keyPath: KeyPath + ) -> LocatedValue? { + locateValue({ $0[keyPath: keyPath] }) } - var headerOptimizationRO32: ObjCHeaderOptimizationRO32? { - guard cpu.is64Bit else { + /// Locate a value using a custom resolver function running against each cache in the hierarchy. + /// + /// Resolution order: + /// 1. This cache + /// 2. The main cache + /// 3. Each subcache of the main cache + /// + /// - Parameter resolver: A closure returning an optional value for a given DyldCache. + /// - Returns: A tuple of `(cache, value)` if resolution is successful; otherwise `nil`. + func locateValue( + _ resolver: (DyldCache) -> V? + ) -> LocatedValue? { + if let value = resolver(self) { return (self, value) } + + guard let mainCache else { return nil } + if let value = resolver(mainCache) { return (mainCache, value) } + + guard let subCaches = mainCache.subCaches else { return nil } - if let objcOptimization { - return objcOptimization.headerOptimizationRO32(in: self) - } - if let oldObjcOptimization { - return oldObjcOptimization.headerOptimizationRO32(in: self) + for subCache in subCaches { + guard let cache = try? subCache.subcache(for: mainCache) else { + continue + } + if let value = resolver(cache) { + return (cache, value) + } } return nil } +} - var headerOptimizationRW64: ObjCHeaderOptimizationRW64? { - guard cpu.is64Bit else { - return nil - } - if let objcOptimization { - return objcOptimization.headerOptimizationRW64(in: self) +// MARK: - Objective-C +extension DyldCache { + var _objcOptimization: LocatedValue? { + locateValue(\.objcOptimization) + } + + var _oldObjcOptimization: LocatedValue? { + locateValue(\.oldObjcOptimization) + } +} + +extension DyldCache { + var headerOptimizationRO64: ObjCHeaderOptimizationRO64? { + guard cpu.is64Bit else { return nil } + if let _objcOptimization { + return _objcOptimization.value.headerOptimizationRO64(in: self) } - if let oldObjcOptimization { - return oldObjcOptimization.headerOptimizationRW64(in: self) + if let _oldObjcOptimization { + return _oldObjcOptimization.value.headerOptimizationRO64(in: self) } return nil } - var headerOptimizationRW32: ObjCHeaderOptimizationRW32? { - guard cpu.is64Bit else { - return nil - } - if let objcOptimization { - return objcOptimization.headerOptimizationRW32(in: self) + var headerOptimizationRO32: ObjCHeaderOptimizationRO32? { + guard !cpu.is64Bit else { return nil } + if let _objcOptimization { + return _objcOptimization.value.headerOptimizationRO32(in: self) } - if let oldObjcOptimization { - return oldObjcOptimization.headerOptimizationRW32(in: self) + if let _oldObjcOptimization { + return _oldObjcOptimization.value.headerOptimizationRO32(in: self) } return nil } } extension DyldCache { - func machO(at index: Int) -> MachOFile? { - guard let mainCache else { return nil } - if let ro = mainCache.headerOptimizationRO64, + var _headerOptimizationRO64: LocatedValue? { + locateValue(\.headerOptimizationRO64) + } + + var _headerOptimizationRO32: LocatedValue? { + locateValue(\.headerOptimizationRO32) + } +} + +extension DyldCache { + func _machO(at index: Int) -> LocatedValue? { + if let ro = _headerOptimizationRO64?.value, ro.contains(index: index) { - guard let header = ro.headerInfos(in: mainCache)?.first( + let headers = locateValue({ ro.headerInfos(in: $0) })?.value + guard let header = headers?.first( where: { $0.index == index } ) else { return nil } - return header._machO(mainCache: mainCache) + return locateValue { header.machO(in: $0) } } - if let ro = mainCache.headerOptimizationRO32, + if let ro = _headerOptimizationRO32?.value, ro.contains(index: index) { - guard let header = ro.headerInfos(in: mainCache)?.first( + let headers = locateValue({ ro.headerInfos(in: $0) })?.value + guard let header = headers?.first( where: { $0.index == index } ) else { return nil } - return header._machO(mainCache: mainCache) + return locateValue { header.machO(in: $0) } } return nil } } +// MARK: - mach-o extension DyldCache { func machO(containing unslidAddress: UInt64) -> MachOFile? { for machO in self.machOFiles() { diff --git a/Sources/MachOObjCSection/Extension/ObjCHeaderOptimizationRO+.swift b/Sources/MachOObjCSection/Extension/ObjCHeaderOptimizationRO+.swift index 9a22c17..ad4d4ac 100644 --- a/Sources/MachOObjCSection/Extension/ObjCHeaderOptimizationRO+.swift +++ b/Sources/MachOObjCSection/Extension/ObjCHeaderOptimizationRO+.swift @@ -13,23 +13,3 @@ extension ObjCHeaderOptimizationROProtocol { (0 ..< count).contains(index) } } - -extension ObjCHeaderInfoROProtocol { - func _machO(mainCache: DyldCache) -> MachOFile? { - if let machO = machO(in: mainCache) { - return machO - } - guard let subCaches = mainCache.subCaches else { - return nil - } - for subCache in subCaches { - guard let cache = try? subCache.subcache(for: mainCache) else { - continue - } - if let machO = machO(in: cache) { - return machO - } - } - return nil - } -} diff --git a/Sources/MachOObjCSection/Model/Method/ObjCMethodRelativeListList.swift b/Sources/MachOObjCSection/Model/Method/ObjCMethodRelativeListList.swift index 4cad2e1..224a4c0 100644 --- a/Sources/MachOObjCSection/Model/Method/ObjCMethodRelativeListList.swift +++ b/Sources/MachOObjCSection/Model/Method/ObjCMethodRelativeListList.swift @@ -29,7 +29,7 @@ public struct ObjCMethodRelativeListList: RelativeListListProtocol { #if canImport(MachO) guard let cache: DyldCacheLoaded = .current else { return nil } - guard let machO = entry.machO(in: cache) else { return nil } + guard let machO = cache.machO(at: entry.imageIndex) else { return nil } let list = List( ptr: ptr, @@ -52,7 +52,7 @@ extension ObjCMethodRelativeListList { return nil } - guard let machO = entry.machO(in: cache) else { return nil } + guard let machO = cache._machO(at: entry.imageIndex)?.value else { return nil } let data = try! cache.fileHandle.readData( offset: numericCast(resolvedOffset), diff --git a/Sources/MachOObjCSection/Model/Property/ObjCPropertyRelativeListList.swift b/Sources/MachOObjCSection/Model/Property/ObjCPropertyRelativeListList.swift index 9c6c56f..3e9a630 100644 --- a/Sources/MachOObjCSection/Model/Property/ObjCPropertyRelativeListList.swift +++ b/Sources/MachOObjCSection/Model/Property/ObjCPropertyRelativeListList.swift @@ -29,7 +29,7 @@ public struct ObjCPropertyRelativeListList: RelativeListListProtocol { #if canImport(MachO) guard let cache: DyldCacheLoaded = .current else { return nil } - guard let machO = entry.machO(in: cache) else { return nil } + guard let machO = cache.machO(at: entry.imageIndex) else { return nil } let list = List( ptr: ptr, @@ -50,7 +50,7 @@ public struct ObjCPropertyRelativeListList: RelativeListListProtocol { return nil } - guard let machO = entry.machO(in: cache) else { return nil } + guard let machO = cache._machO(at: entry.imageIndex)?.value else { return nil } let data = try! cache.fileHandle.readData( offset: numericCast(resolvedOffset), diff --git a/Sources/MachOObjCSection/Model/Protocol/ObjCProtocolRelativeListList.swift b/Sources/MachOObjCSection/Model/Protocol/ObjCProtocolRelativeListList.swift index c75547b..0e9c41c 100644 --- a/Sources/MachOObjCSection/Model/Protocol/ObjCProtocolRelativeListList.swift +++ b/Sources/MachOObjCSection/Model/Protocol/ObjCProtocolRelativeListList.swift @@ -30,7 +30,7 @@ public struct ObjCProtocolRelativeListList64: ObjCProtocolRelativeListListProtoc #if canImport(MachO) guard let cache: DyldCacheLoaded = .current else { return nil } - guard let machO = entry.machO(in: cache) else { return nil } + guard let machO = cache.machO(at: entry.imageIndex) else { return nil } let list = List( ptr: ptr, @@ -50,7 +50,7 @@ public struct ObjCProtocolRelativeListList64: ObjCProtocolRelativeListListProtoc return nil } - guard let machO = entry.machO(in: cache) else { return nil } + guard let machO = cache._machO(at: entry.imageIndex)?.value else { return nil } let data = try! cache.fileHandle.readData( offset: numericCast(resolvedOffset), @@ -123,7 +123,7 @@ public struct ObjCProtocolRelativeListList32: ObjCProtocolRelativeListListProtoc return nil } - guard let listMachO = cache.machO(at: entry.imageIndex) else { + guard let listMachO = cache._machO(at: entry.imageIndex)?.value else { return nil } diff --git a/Sources/MachOObjCSection/Model/Util/RelativeListList.swift b/Sources/MachOObjCSection/Model/Util/RelativeListList.swift index 7f4c96e..ab49858 100644 --- a/Sources/MachOObjCSection/Model/Util/RelativeListList.swift +++ b/Sources/MachOObjCSection/Model/Util/RelativeListList.swift @@ -69,25 +69,16 @@ extension RelativeListListProtocol { extension RelativeListListProtocol { public func entries(in machO: MachOFile) -> [Entry] { - let offset = offset + machO.headerStartOffset - - var resolvedOffset: UInt64 = numericCast(offset) - - var fileHandle = machO.fileHandle - - if let (_cache, _offset) = machO.cacheAndFileOffset( - fromStart: UInt64(offset) - ) { - resolvedOffset = _offset - fileHandle = _cache.fileHandle + guard let (fileHandle, fileOffset) = machO.fileHandleAndOffset(forOffset: numericCast(offset)) else { + return [] } let sequence: DataSequence = fileHandle.readDataSequence( - offset: resolvedOffset + numericCast(MemoryLayout
.size), + offset: fileOffset + numericCast(MemoryLayout
.size), numberOfElements: numericCast(header.count) ) - let baseOffset = offset + MemoryLayout
.size - machO.headerStartOffset + let baseOffset = offset + MemoryLayout
.size let entrySize = MemoryLayout.size return sequence.enumerated() .map { i, layout in @@ -105,13 +96,3 @@ extension RelativeListListProtocol { } } } - -extension RelativeListListEntry { - public func machO(in cache: DyldCacheLoaded) -> MachOImage? { - cache.machO(at: imageIndex) - } - - public func machO(in cache: DyldCache) -> MachOFile? { - cache.machO(at: imageIndex) - } -} diff --git a/Sources/MachOObjCSection/Support/ObjCDump.swift b/Sources/MachOObjCSection/Support/ObjCDump.swift index 10db56d..516567f 100644 --- a/Sources/MachOObjCSection/Support/ObjCDump.swift +++ b/Sources/MachOObjCSection/Support/ObjCDump.swift @@ -10,6 +10,15 @@ import Foundation import MachOKit import ObjCDump +extension MachOFile { + // WORKAROUND: Due to a bug in MachOKit, the imagePath of a machO obtained from ObjCHeaderInfoRO may sometimes be an empty string. + fileprivate var imagePath: String { + loadCommands.info(of: LoadCommand.idDylib)? + .dylib(in: self) + .name ?? "" + } +} + // MARK: - IVar extension ObjCIvarProtocol { public func info(in machO: MachOFile) -> ObjCIvarInfo? {