From 77546a27f731e88d8a4c81dd83df5fef1164ed90 Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Sun, 17 Aug 2025 22:54:45 +0900 Subject: [PATCH 1/7] Add method to read linkedit data for `MachOFile` --- Sources/MachOKit/MachOFile.swift | 53 ++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/Sources/MachOKit/MachOFile.swift b/Sources/MachOKit/MachOFile.swift index a7a9c0c..eda7c36 100644 --- a/Sources/MachOKit/MachOFile.swift +++ b/Sources/MachOKit/MachOFile.swift @@ -522,6 +522,18 @@ extension MachOFile { public var isLoadedFromDyldCache: Bool { headerStartOffsetInCache > 0 } + + internal var cache: DyldCache? { + try? .init(url: url) + } + + internal var fullCache: FullDyldCache? { + try? .init( + url: url + .deletingPathExtension() + .deletingPathExtension() + ) + } } extension MachOFile { @@ -587,11 +599,46 @@ extension MachOFile { } } +extension MachOFile { + internal func _readLinkEditData( + offset: Int, // linkedit_data_command->dataoff (linkedit.fileoff + x) + length: Int + ) -> Data? { + let linkedit: (any SegmentCommandProtocol)? = loadCommands.linkedit64 ?? loadCommands.linkedit + guard let linkedit else { return nil } + guard linkedit.fileOffset + linkedit.fileSize >= offset + length else { return nil } + + // The linkeditdata in iOS is stored together in a separate, independent cache. + // (.0x.linkeditdata) + if isLoadedFromDyldCache { + let offset = offset - numericCast(linkedit.fileOffset) + guard let fullCache = self.fullCache, + let fileOffset = fullCache.fileOffset( + of: numericCast(linkedit.virtualMemoryAddress + offset) + ), + let (_, segment) = fullCache.urlAndFileSegment( + forOffset: fileOffset + ) else { + return nil + } + return try? segment._file.readData( + offset: numericCast(fileOffset) - segment.offset, + length: length + ) + } else { + return try? fileHandle.readData( + offset: headerStartOffset + offset, + length: length + ) + } + } +} + extension MachOFile { // https://github.com/apple-oss-distributions/dyld/blob/d552c40cd1de105f0ec95008e0e0c0972de43456/common/MetadataVisitor.cpp#L262 public func resolveRebase(at offset: UInt64) -> UInt64? { if isLoadedFromDyldCache, - let cache = try? DyldCache(url: url) { + let cache = self.cache { return cache.resolveRebase(at: offset) } @@ -609,7 +656,7 @@ extension MachOFile { public func resolveOptionalRebase(at offset: UInt64) -> UInt64? { if isLoadedFromDyldCache, - let cache = try? DyldCache(url: url) { + let cache = self.cache { return cache.resolveOptionalRebase(at: offset) } @@ -759,7 +806,7 @@ extension MachOFile { } if header.isInDyldCache, - let cache = try? DyldCache(url: url) { + let cache = self.cache { return cache.header.platform == .macOS } From 4fca90ebf077f7f1faf9e218d3782549d647fd68 Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Sun, 17 Aug 2025 23:18:11 +0900 Subject: [PATCH 2/7] Fix to read correctly even when linkedit does not exist in the same cache --- Sources/MachOKit/MachOFile+ExportTrie.swift | 7 ++- .../MachOKit/MachOFile+FunctionStarts.swift | 7 ++- Sources/MachOKit/MachOFile.swift | 47 +++++++++++++------ 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/Sources/MachOKit/MachOFile+ExportTrie.swift b/Sources/MachOKit/MachOFile+ExportTrie.swift index aa37f17..f15b2f9 100644 --- a/Sources/MachOKit/MachOFile+ExportTrie.swift +++ b/Sources/MachOKit/MachOFile+ExportTrie.swift @@ -113,11 +113,10 @@ extension MachOFile.ExportTrie { exportSize: Int, ldVersion: Version? ) { - let offset = machO.headerStartOffset + exportOffset - let data = try! machO.fileHandle.readData( - offset: offset, + let data = machO._readLinkEditData( + offset: exportOffset, length: exportSize - ) + )! self.init( exportOffset: exportOffset, diff --git a/Sources/MachOKit/MachOFile+FunctionStarts.swift b/Sources/MachOKit/MachOFile+FunctionStarts.swift index d887495..ae0bdcb 100644 --- a/Sources/MachOKit/MachOFile+FunctionStarts.swift +++ b/Sources/MachOKit/MachOFile+FunctionStarts.swift @@ -31,11 +31,10 @@ extension MachOFile.FunctionStarts { functionStartsSize: Int, functionStartBase: UInt ) { - let offset = machO.headerStartOffset + functionStartsOffset - let data = try! machO.fileHandle.readData( - offset: offset, + let data = machO._readLinkEditData( + offset: functionStartsOffset, length: functionStartsSize - ) + )! self.init( data: data, diff --git a/Sources/MachOKit/MachOFile.swift b/Sources/MachOKit/MachOFile.swift index eda7c36..dc96a6b 100644 --- a/Sources/MachOKit/MachOFile.swift +++ b/Sources/MachOKit/MachOFile.swift @@ -421,8 +421,13 @@ extension MachOFile { return nil } - let entries: DataSequence = fileHandle.readDataSequence( - offset: numericCast(headerStartOffset) + numericCast(dataInCode.dataoff), + guard let data = _readLinkEditData( + offset: numericCast(dataInCode.dataoff), + length: numericCast(dataInCode.datasize) + ) else { return nil } + + let entries: DataSequence = .init( + data: data, numberOfElements: numericCast(dataInCode.datasize) / DataInCodeEntry.layoutSize ) @@ -449,12 +454,13 @@ extension MachOFile { guard let info = loadCommands.dyldChainedFixups else { return nil } + guard let fileSlice = _fileSliceForLinkEditData( + offset: numericCast(info.dataoff), + length: numericCast(info.datasize) + ) else { return nil } return .init( - fileSice: try! fileHandle.fileSlice( - offset: headerStartOffset + numericCast(info.dataoff), - length: numericCast(info.datasize) - ), + fileSice: fileSlice, isSwapped: isSwapped ) } @@ -508,11 +514,13 @@ extension MachOFile { guard let info = loadCommands.codeSignature else { return nil } + guard let fileSlice = _fileSliceForLinkEditData( + offset: numericCast(info.dataoff), + length: numericCast(info.datasize) + ) else { return nil } + return .init( - fileSice: try! fileHandle.fileSlice( - offset: headerStartOffset + numericCast(info.dataoff), - length: numericCast(info.datasize) - ) + fileSice: fileSlice ) } } @@ -600,10 +608,10 @@ extension MachOFile { } extension MachOFile { - internal func _readLinkEditData( + internal func _fileSliceForLinkEditData( offset: Int, // linkedit_data_command->dataoff (linkedit.fileoff + x) length: Int - ) -> Data? { + ) -> File.FileSlice? { let linkedit: (any SegmentCommandProtocol)? = loadCommands.linkedit64 ?? loadCommands.linkedit guard let linkedit else { return nil } guard linkedit.fileOffset + linkedit.fileSize >= offset + length else { return nil } @@ -621,17 +629,28 @@ extension MachOFile { ) else { return nil } - return try? segment._file.readData( + return try? segment._file.fileSlice( offset: numericCast(fileOffset) - segment.offset, length: length ) } else { - return try? fileHandle.readData( + return try? fileHandle.fileSlice( offset: headerStartOffset + offset, length: length ) } } + + internal func _readLinkEditData( + offset: Int, // linkedit_data_command->dataoff (linkedit.fileoff + x) + length: Int + ) -> Data? { + guard let fileSlice = _fileSliceForLinkEditData( + offset: offset, + length: length + ) else { return nil } + return try? fileSlice.readAllData() + } } extension MachOFile { From 993ce727c78de234247ca33ecb0b343effa3ea17 Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Sun, 17 Aug 2025 23:47:56 +0900 Subject: [PATCH 3/7] `symtab` also exists in the linkedit segment --- Sources/MachOKit/MachOFile+Symbols.swift | 28 ++++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Sources/MachOKit/MachOFile+Symbols.swift b/Sources/MachOKit/MachOFile+Symbols.swift index f74d30f..56d3cee 100644 --- a/Sources/MachOKit/MachOFile+Symbols.swift +++ b/Sources/MachOKit/MachOFile+Symbols.swift @@ -68,15 +68,15 @@ extension MachOFile.Symbols64 { machO: MachOFile, symtab: LoadCommandInfo ) { - let stringsSlice = try! machO.fileHandle.fileSlice( - offset: machO.headerStartOffset + numericCast(symtab.stroff), + let stringsSlice = machO._fileSliceForLinkEditData( + offset: numericCast(symtab.stroff), length: numericCast(symtab.strsize) - ) + )! - let symbolsSlice = try! machO.fileHandle.fileSlice( - offset: machO.headerStartOffset + numericCast(symtab.symoff), + let symbolsSlice = machO._fileSliceForLinkEditData( + offset: numericCast(symtab.symoff), length: numericCast(symtab.nsyms) * MemoryLayout.size - ) + )! self.init( symtab: symtab, @@ -214,15 +214,15 @@ extension MachOFile.Symbols { machO: MachOFile, symtab: LoadCommandInfo ) { - let stringsSlice = try! machO.fileHandle.fileSlice( - offset: machO.headerStartOffset + numericCast(symtab.stroff), - length: Int(symtab.strsize) - ) + let stringsSlice = machO._fileSliceForLinkEditData( + offset: numericCast(symtab.stroff), + length: numericCast(symtab.strsize) + )! - let symbolsSlice = try! machO.fileHandle.fileSlice( - offset: machO.headerStartOffset + numericCast(symtab.symoff), - length: Int(symtab.nsyms) * MemoryLayout.size - ) + let symbolsSlice = machO._fileSliceForLinkEditData( + offset: numericCast(symtab.symoff), + length: numericCast(symtab.nsyms) * MemoryLayout.size + )! self.init( symtab: symtab, From 13b3ee0fd63a88ef4ade73e5684710bc82d63f7d Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Mon, 18 Aug 2025 00:09:13 +0900 Subject: [PATCH 4/7] Make `_readLinkEditData` public --- Sources/MachOKit/MachOFile.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/MachOKit/MachOFile.swift b/Sources/MachOKit/MachOFile.swift index dc96a6b..a7bf6c5 100644 --- a/Sources/MachOKit/MachOFile.swift +++ b/Sources/MachOKit/MachOFile.swift @@ -641,7 +641,7 @@ extension MachOFile { } } - internal func _readLinkEditData( + public func _readLinkEditData( offset: Int, // linkedit_data_command->dataoff (linkedit.fileoff + x) length: Int ) -> Data? { From 6e674a7f989eaabc8cb540113ff65f09b45d5f5d Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Mon, 18 Aug 2025 00:59:34 +0900 Subject: [PATCH 5/7] `dysymtab` also exists in the linkedit segment --- Sources/MachOKit/MachOFile.swift | 58 ++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/Sources/MachOKit/MachOFile.swift b/Sources/MachOKit/MachOFile.swift index a7bf6c5..1b4581a 100644 --- a/Sources/MachOKit/MachOFile.swift +++ b/Sources/MachOKit/MachOFile.swift @@ -218,21 +218,26 @@ extension MachOFile { public var indirectSymbols: IndirectSymbols? { guard let dysymtab = loadCommands.dysymtab else { return nil } - let offset: UInt64 = numericCast(headerStartOffset) + numericCast(dysymtab.indirectsymoff) + let offset: UInt64 = numericCast(dysymtab.indirectsymoff) let numberOfElements: Int = numericCast(dysymtab.nindirectsyms) - return fileHandle.readDataSequence( - offset: offset, - numberOfElements: numberOfElements, - swapHandler: { data in - guard self.isSwapped else { return } - data.withUnsafeMutableBytes { - let buffer = $0.assumingMemoryBound(to: UInt32.self) - for i in 0 ..< numberOfElements { - buffer[i] = buffer[i].byteSwapped - } + guard var data = _readLinkEditData( + offset: numericCast(offset), + length: MemoryLayout.size * numberOfElements + ) else { return nil } + + if isSwapped { + data.withUnsafeMutableBytes { + let buffer = $0.assumingMemoryBound(to: UInt32.self) + for i in 0 ..< numberOfElements { + buffer[i] = buffer[i].byteSwapped } } + } + + return .init( + data: data, + numberOfElements: numberOfElements ) } } @@ -471,18 +476,27 @@ extension MachOFile { guard let dysymtab = loadCommands.dysymtab else { return nil } - return fileHandle.readDataSequence( - offset: numericCast(dysymtab.extreloff), - numberOfElements: numericCast(dysymtab.nextrel), - swapHandler: { data in - guard self.isSwapped else { return } - data.withUnsafeMutableBytes { - guard let baseAddress = $0.baseAddress else { return } - let ptr = baseAddress - .assumingMemoryBound(to: relocation_info.self) - swap_relocation_info(ptr, dysymtab.nextrel, NXHostByteOrder()) - } + + let offset: UInt64 = numericCast(dysymtab.extreloff) + let numberOfElements: Int = numericCast(dysymtab.nextrel) + + guard var data = _readLinkEditData( + offset: numericCast(offset), + length: MemoryLayout.size * numberOfElements + ) else { return nil } + + if isSwapped { + data.withUnsafeMutableBytes { + guard let baseAddress = $0.baseAddress else { return } + let ptr = baseAddress + .assumingMemoryBound(to: relocation_info.self) + swap_relocation_info(ptr, dysymtab.nextrel, NXHostByteOrder()) } + } + + return .init( + data: data, + numberOfElements: numberOfElements ) } From a100df7097f35a294995498e459c2cbba41382e9 Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Mon, 18 Aug 2025 01:05:23 +0900 Subject: [PATCH 6/7] symbol string table also exists in the linkedit segment --- Sources/MachOKit/MachOFile+Strings.swift | 12 ++++++++++++ Sources/MachOKit/MachOFile.swift | 21 +++++++++++++-------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/Sources/MachOKit/MachOFile+Strings.swift b/Sources/MachOKit/MachOFile+Strings.swift index 4893010..da51529 100644 --- a/Sources/MachOKit/MachOFile+Strings.swift +++ b/Sources/MachOKit/MachOFile+Strings.swift @@ -30,6 +30,18 @@ extension MachOFile { public let isSwapped: Bool + init( + fileSlice: FileSlice, + offset: Int, + size: Int, + isSwapped: Bool + ) { + self.fileSlice = fileSlice + self.offset = offset + self.size = size + self.isSwapped = isSwapped + } + public func makeIterator() -> Iterator { .init(fileSlice: fileSlice, isSwapped: isSwapped) } diff --git a/Sources/MachOKit/MachOFile.swift b/Sources/MachOKit/MachOFile.swift index 1b4581a..5c15db8 100644 --- a/Sources/MachOKit/MachOFile.swift +++ b/Sources/MachOKit/MachOFile.swift @@ -244,15 +244,20 @@ extension MachOFile { extension MachOFile { public var symbolStrings: Strings? { - if let symtab = loadCommands.symtab { - return Strings( - machO: self, - offset: headerStartOffset + Int(symtab.stroff), - size: Int(symtab.strsize), - isSwapped: isSwapped - ) + guard let symtab = loadCommands.symtab else { + return nil } - return nil + guard let fileSlice = _fileSliceForLinkEditData( + offset: numericCast(symtab.stroff), + length: numericCast(symtab.strsize) + ) else { return nil } + + return .init( + fileSlice: fileSlice, + offset: numericCast(symtab.stroff), + size: numericCast(symtab.strsize), + isSwapped: isSwapped + ) } } From 02c13d738f243567e5a6bdb8354c3540c11c6afc Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Mon, 18 Aug 2025 01:11:12 +0900 Subject: [PATCH 7/7] Add comments for `_readLinkEditData` --- Sources/MachOKit/MachOFile.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Sources/MachOKit/MachOFile.swift b/Sources/MachOKit/MachOFile.swift index 5c15db8..0c9588e 100644 --- a/Sources/MachOKit/MachOFile.swift +++ b/Sources/MachOKit/MachOFile.swift @@ -660,6 +660,16 @@ extension MachOFile { } } + /// Reads the data in the linkedit segment appropriately. + /// + /// The linkedit data in the machO file obtained from the dyld cache may be separated in a separate sub cache file. + /// (e.g. dyld cache in iOS except Simulator) + /// + /// The data related to the following load command exists in linkedit. + /// - symtab + /// - dysymtab + /// - linkedit_data_command + /// - exports trie public func _readLinkEditData( offset: Int, // linkedit_data_command->dataoff (linkedit.fileoff + x) length: Int