Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions Sources/MachOKit/MachOFile+ExportTrie.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
)!
Copy link
Preview

Copilot AI Aug 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Force unwrapping with ! can cause crashes if _readLinkEditData returns nil. Consider using guard statements or proper error handling instead of force unwrapping.

Copilot uses AI. Check for mistakes.


self.init(
exportOffset: exportOffset,
Expand Down
7 changes: 3 additions & 4 deletions Sources/MachOKit/MachOFile+FunctionStarts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
)!
Copy link
Preview

Copilot AI Aug 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Force unwrapping with ! can cause crashes if _readLinkEditData returns nil. Consider using guard statements or proper error handling instead of force unwrapping.

Copilot uses AI. Check for mistakes.


self.init(
data: data,
Expand Down
12 changes: 12 additions & 0 deletions Sources/MachOKit/MachOFile+Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
28 changes: 14 additions & 14 deletions Sources/MachOKit/MachOFile+Symbols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,15 @@ extension MachOFile.Symbols64 {
machO: MachOFile,
symtab: LoadCommandInfo<symtab_command>
) {
let stringsSlice = try! machO.fileHandle.fileSlice(
offset: machO.headerStartOffset + numericCast(symtab.stroff),
let stringsSlice = machO._fileSliceForLinkEditData(
offset: numericCast(symtab.stroff),
length: numericCast(symtab.strsize)
)
)!
Copy link
Preview

Copilot AI Aug 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Force unwrapping with ! can cause crashes if _fileSliceForLinkEditData returns nil. Consider using guard statements or proper error handling instead of force unwrapping.

Copilot uses AI. Check for mistakes.


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<nlist_64>.size
)
)!
Copy link
Preview

Copilot AI Aug 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Force unwrapping with ! can cause crashes if _fileSliceForLinkEditData returns nil. Consider using guard statements or proper error handling instead of force unwrapping.

Copilot uses AI. Check for mistakes.


self.init(
symtab: symtab,
Expand Down Expand Up @@ -214,15 +214,15 @@ extension MachOFile.Symbols {
machO: MachOFile,
symtab: LoadCommandInfo<symtab_command>
) {
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)
)!
Copy link
Preview

Copilot AI Aug 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Force unwrapping with ! can cause crashes if _fileSliceForLinkEditData returns nil. Consider using guard statements or proper error handling instead of force unwrapping.

Copilot uses AI. Check for mistakes.


let symbolsSlice = try! machO.fileHandle.fileSlice(
offset: machO.headerStartOffset + numericCast(symtab.symoff),
length: Int(symtab.nsyms) * MemoryLayout<nlist>.size
)
let symbolsSlice = machO._fileSliceForLinkEditData(
offset: numericCast(symtab.symoff),
length: numericCast(symtab.nsyms) * MemoryLayout<nlist>.size
)!
Copy link
Preview

Copilot AI Aug 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Force unwrapping with ! can cause crashes if _fileSliceForLinkEditData returns nil. Consider using guard statements or proper error handling instead of force unwrapping.

Copilot uses AI. Check for mistakes.


self.init(
symtab: symtab,
Expand Down
181 changes: 138 additions & 43 deletions Sources/MachOKit/MachOFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -218,36 +218,46 @@ 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<UInt32>.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
)
}
}

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
)
}
}

Expand Down Expand Up @@ -421,8 +431,13 @@ extension MachOFile {
return nil
}

let entries: DataSequence<DataInCodeEntry> = 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<DataInCodeEntry> = .init(
data: data,
numberOfElements: numericCast(dataInCode.datasize) / DataInCodeEntry.layoutSize
)

Expand All @@ -449,12 +464,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
)
}
Expand All @@ -465,18 +481,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<UInt64>.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
)
}

Expand Down Expand Up @@ -508,11 +533,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
)
}
}
Expand All @@ -522,6 +549,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()
)
}
Copy link
Preview

Copilot AI Aug 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These computed properties will create new cache instances on every access. Consider implementing lazy stored properties or caching mechanisms to avoid repeated initialization overhead, especially since these caches are accessed multiple times throughout the codebase.

Suggested change
}
internal lazy var cache: DyldCache? = {
try? DyldCache(url: url)
}()
internal lazy var fullCache: FullDyldCache? = {
try? FullDyldCache(
url: url
.deletingPathExtension()
.deletingPathExtension()
)
}()

Copilot uses AI. Check for mistakes.

}

extension MachOFile {
Expand Down Expand Up @@ -587,11 +626,67 @@ extension MachOFile {
}
}

extension MachOFile {
internal func _fileSliceForLinkEditData(
offset: Int, // linkedit_data_command->dataoff (linkedit.fileoff + x)
length: Int
) -> 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 }

// 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.fileSlice(
offset: numericCast(fileOffset) - segment.offset,
length: length
)
} else {
return try? fileHandle.fileSlice(
offset: headerStartOffset + offset,
length: length
)
}
}

/// 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
) -> Data? {
guard let fileSlice = _fileSliceForLinkEditData(
offset: offset,
length: length
) else { return nil }
return try? fileSlice.readAllData()
}
}

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)
}

Expand All @@ -609,7 +704,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)
}

Expand Down Expand Up @@ -759,7 +854,7 @@ extension MachOFile {
}

if header.isInDyldCache,
let cache = try? DyldCache(url: url) {
let cache = self.cache {
return cache.header.platform == .macOS
}

Expand Down