Skip to content

Commit a0f42bc

Browse files
authored
Merge pull request #159 from p-x9/feature/improve-resolve-rebase-performance
Improve rebase resolution logic to simplify pointer handling
2 parents c453d26 + 74e8354 commit a0f42bc

File tree

2 files changed

+231
-122
lines changed

2 files changed

+231
-122
lines changed

Sources/MachOKit/MachOFile+DyldChainedFixups.swift

Lines changed: 208 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -227,95 +227,223 @@ extension MachOFile.DyldChainedFixups {
227227
var chainEnd = false
228228

229229
while !stop && !chainEnd {
230-
data.withUnsafeBytes {
231-
guard let baseAddress = $0.baseAddress else { return }
232-
let ptr = baseAddress.advanced(by: offset)
230+
guard let fixupInfo = _fixupInfo(
231+
at: offset,
232+
in: data,
233+
pointerFormat: pointerFormat
234+
) else {
235+
stop = true
236+
continue
237+
}
233238

234-
var fixupInfo: DyldChainedFixupPointerInfo?
235-
236-
if pointerFormat.is64Bit {
237-
let rawValue = ptr.load(as: UInt64.self)
238-
switch pointerFormat {
239-
case .arm64e, .arm64e_kernel, .arm64e_userland, .arm64e_firmware:
240-
let content = DyldChainedFixupPointerInfo.ARM64E(rawValue: rawValue)
241-
switch pointerFormat {
242-
case .arm64e:
243-
fixupInfo = .arm64e(content)
244-
case .arm64e_kernel:
245-
fixupInfo = .arm64e_kernel(content)
246-
case .arm64e_userland:
247-
fixupInfo = .arm64e_userland(content)
248-
case .arm64e_firmware:
249-
fixupInfo = .arm64e_firmware(content)
250-
default: break
251-
}
252-
253-
case .arm64e_userland24:
254-
let content = DyldChainedFixupPointerInfo.ARM64EUserland24(rawValue: rawValue)
255-
fixupInfo = .arm64e_userland24(content)
256-
257-
case ._64, ._64_offset:
258-
let content = DyldChainedFixupPointerInfo.General64(rawValue: rawValue)
259-
switch pointerFormat {
260-
case ._64: fixupInfo = ._64(content)
261-
case ._64_offset: fixupInfo = ._64_offset(content)
262-
default: break
263-
}
264-
265-
case ._64_kernel_cache, .x86_64_kernel_cache:
266-
let content = DyldChainedFixupPointerInfo.General64Cache(rawValue: rawValue)
267-
switch pointerFormat {
268-
case ._64_kernel_cache:
269-
fixupInfo = ._64_kernel_cache(content)
270-
case .x86_64_kernel_cache:
271-
fixupInfo = .x86_64_kernel_cache(content)
272-
default: break
273-
}
274-
275-
case .arm64e_shared_cache:
276-
let content = DyldChainedFixupPointerInfo.ARM64ESharedCache(rawValue: rawValue)
277-
fixupInfo = .arm64e_shared_cache(content)
278-
279-
default:
280-
// unknown format
281-
stop = true
282-
}
239+
let pointerOffset = numericCast(startsInSegment.segment_offset) + offset
240+
241+
pointers.append(
242+
DyldChainedFixupPointer(
243+
offset: pointerOffset,
244+
fixupInfo: fixupInfo
245+
)
246+
)
247+
248+
if fixupInfo.next == 0 {
249+
chainEnd = true
250+
} else {
251+
offset += stride * fixupInfo.next
252+
}
253+
}
254+
}
255+
}
256+
257+
extension MachOFile.DyldChainedFixups {
258+
public func pointer(for offset: UInt64, in machO: MachOFile) -> DyldChainedFixupPointer? {
259+
guard let startsInImage = startsInImage else { return nil }
260+
guard let startsInSegment = startsInSegments(of: startsInImage)
261+
.first(where: {
262+
let segmentSize = UInt64($0.page_size) * UInt64($0.page_count)
263+
return $0.segment_offset <= offset && offset < $0.segment_offset + segmentSize
264+
}) else {
265+
return nil
266+
}
267+
268+
let pages = pages(of: startsInSegment)
269+
let pagesData = machO.fileHandle.readData(
270+
offset: numericCast(machO.headerStartOffset) + startsInSegment.segment_offset,
271+
size: pages.count * numericCast(startsInSegment.page_size)
272+
)
273+
274+
for (index, page) in pages.enumerated() {
275+
var offsetInPage = page.offset
276+
277+
if page.isNone { continue }
278+
if page.isMulti {
279+
var overflowIndex = Int(offsetInPage & ~UInt16(DYLD_CHAINED_PTR_START_MULTI))
280+
var chainEnd = false
281+
while !chainEnd {
282+
chainEnd = pages[overflowIndex].offset & UInt16(DYLD_CHAINED_PTR_START_LAST) != 0
283+
offsetInPage = pages[overflowIndex].offset & ~UInt16(DYLD_CHAINED_PTR_START_LAST)
284+
let pageContentStart: Int = index * numericCast(startsInSegment.page_size)
285+
let chainOffset = pageContentStart + numericCast(offsetInPage)
283286

284-
} else {
285-
let rawValue = ptr.load(as: UInt32.self)
286-
switch pointerFormat {
287-
case ._32:
288-
let content = DyldChainedFixupPointerInfo.General32(rawValue: rawValue)
289-
fixupInfo = ._32(content)
290-
case ._32_cache:
291-
let content = DyldChainedFixupPointerInfo.General32Cache(rawValue: rawValue)
292-
fixupInfo = ._32_cache(content)
293-
case ._32_firmware:
294-
let content = DyldChainedFixupPointerInfo.General32Firmware(rawValue: rawValue)
295-
fixupInfo = ._32_firmware(content)
296-
default:
297-
// unknown format
298-
stop = true
287+
if let pointer = walkChainAndFindPointer(
288+
for: offset,
289+
chainOffset: chainOffset,
290+
data: pagesData,
291+
of: startsInSegment
292+
) {
293+
return pointer
299294
}
295+
296+
overflowIndex += 1
297+
}
298+
} else {
299+
let pageContentStart: Int = index * numericCast(startsInSegment.page_size)
300+
let chainOffset = pageContentStart + numericCast(offsetInPage)
301+
302+
if let pointer = walkChainAndFindPointer(
303+
for: offset,
304+
chainOffset: chainOffset,
305+
data: pagesData,
306+
of: startsInSegment
307+
) {
308+
return pointer
300309
}
301310

302-
if let fixupInfo {
303-
let pointerOffset = numericCast(startsInSegment.segment_offset) + offset
311+
}
312+
}
304313

305-
pointers.append(
306-
DyldChainedFixupPointer(
307-
offset: pointerOffset,
308-
fixupInfo: fixupInfo
309-
)
310-
)
314+
return nil
315+
}
316+
private func walkChainAndFindPointer(
317+
for targetOffset: UInt64,
318+
chainOffset: Int,
319+
data: Data,
320+
of startsInSegment: DyldChainedStartsInSegment
321+
) -> DyldChainedFixupPointer? {
322+
guard let pointerFormat = startsInSegment.pointerFormat else {
323+
return nil
324+
}
325+
var chainOffset = chainOffset
311326

312-
if fixupInfo.next == 0 {
313-
chainEnd = true
314-
} else {
315-
offset += stride * fixupInfo.next
316-
}
327+
let stride = pointerFormat.stride
328+
var stop = false
329+
var chainEnd = false
330+
331+
while !stop && !chainEnd {
332+
guard let fixupInfo = _fixupInfo(
333+
at: chainOffset,
334+
in: data,
335+
pointerFormat: pointerFormat
336+
) else {
337+
stop = true
338+
continue
339+
}
340+
341+
let pointerOffset = numericCast(startsInSegment.segment_offset) + chainOffset
342+
343+
if pointerOffset == targetOffset {
344+
return DyldChainedFixupPointer(
345+
offset: pointerOffset,
346+
fixupInfo: fixupInfo
347+
)
348+
}
349+
350+
if fixupInfo.next == 0 {
351+
chainEnd = true
352+
} else {
353+
chainOffset += stride * fixupInfo.next
354+
}
355+
}
356+
357+
return nil
358+
}
359+
}
360+
361+
extension MachOFile.DyldChainedFixups {
362+
@inline(__always)
363+
private func _fixupInfo(
364+
at offset: Int,
365+
in data: Data,
366+
pointerFormat: DyldChainedFixupPointerFormat
367+
) -> DyldChainedFixupPointerInfo? {
368+
var fixupInfo: DyldChainedFixupPointerInfo?
369+
370+
if pointerFormat.is64Bit {
371+
// faster than below code
372+
// let rawValue = data.advanced(by: offset).withUnsafeBytes {
373+
// $0.load(as: UInt64.self)
374+
// }
375+
guard let rawValue = data.withUnsafeBytes ({ bytes -> UInt64? in
376+
guard let baseAddress = bytes.baseAddress else { return nil }
377+
let ptr = baseAddress.advanced(by: offset)
378+
return ptr.load(as: UInt64.self)
379+
}) else { return nil }
380+
381+
switch pointerFormat {
382+
case .arm64e, .arm64e_kernel, .arm64e_userland, .arm64e_firmware:
383+
let content = DyldChainedFixupPointerInfo.ARM64E(rawValue: rawValue)
384+
switch pointerFormat {
385+
case .arm64e:
386+
fixupInfo = .arm64e(content)
387+
case .arm64e_kernel:
388+
fixupInfo = .arm64e_kernel(content)
389+
case .arm64e_userland:
390+
fixupInfo = .arm64e_userland(content)
391+
case .arm64e_firmware:
392+
fixupInfo = .arm64e_firmware(content)
393+
default: break
317394
}
395+
396+
case .arm64e_userland24:
397+
let content = DyldChainedFixupPointerInfo.ARM64EUserland24(rawValue: rawValue)
398+
fixupInfo = .arm64e_userland24(content)
399+
400+
case ._64, ._64_offset:
401+
let content = DyldChainedFixupPointerInfo.General64(rawValue: rawValue)
402+
switch pointerFormat {
403+
case ._64: fixupInfo = ._64(content)
404+
case ._64_offset: fixupInfo = ._64_offset(content)
405+
default: break
406+
}
407+
408+
case ._64_kernel_cache, .x86_64_kernel_cache:
409+
let content = DyldChainedFixupPointerInfo.General64Cache(rawValue: rawValue)
410+
switch pointerFormat {
411+
case ._64_kernel_cache:
412+
fixupInfo = ._64_kernel_cache(content)
413+
case .x86_64_kernel_cache:
414+
fixupInfo = .x86_64_kernel_cache(content)
415+
default: break
416+
}
417+
418+
case .arm64e_shared_cache:
419+
let content = DyldChainedFixupPointerInfo.ARM64ESharedCache(rawValue: rawValue)
420+
fixupInfo = .arm64e_shared_cache(content)
421+
422+
default:
423+
break
424+
}
425+
} else {
426+
guard let rawValue = data.withUnsafeBytes ({ bytes -> UInt32? in
427+
guard let baseAddress = bytes.baseAddress else { return nil }
428+
let ptr = baseAddress.advanced(by: offset)
429+
return ptr.load(as: UInt32.self)
430+
}) else { return nil }
431+
432+
switch pointerFormat {
433+
case ._32:
434+
let content = DyldChainedFixupPointerInfo.General32(rawValue: rawValue)
435+
fixupInfo = ._32(content)
436+
case ._32_cache:
437+
let content = DyldChainedFixupPointerInfo.General32Cache(rawValue: rawValue)
438+
fixupInfo = ._32_cache(content)
439+
case ._32_firmware:
440+
let content = DyldChainedFixupPointerInfo.General32Firmware(rawValue: rawValue)
441+
fixupInfo = ._32_firmware(content)
442+
default:
443+
break
318444
}
319445
}
446+
447+
return fixupInfo
320448
}
321449
}

Sources/MachOKit/MachOFile.swift

Lines changed: 23 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//
44
//
55
// Created by p-x9 on 2023/12/04.
6-
//
6+
//
77
//
88

99
import Foundation
@@ -492,25 +492,15 @@ extension MachOFile {
492492
}
493493

494494
guard let chainedFixup = dyldChainedFixups,
495-
let startsInImage = chainedFixup.startsInImage else {
495+
let pointer = chainedFixup.pointer(for: offset, in: self) else {
496496
return nil
497497
}
498-
let startsInSegments = chainedFixup.startsInSegments(
499-
of: startsInImage
500-
)
501498

502-
for segment in startsInSegments {
503-
let pointers = chainedFixup.pointers(of: segment, in: self)
504-
guard let pointer = pointers.first(where: {
505-
$0.offset == offset
506-
}) else { continue }
507-
guard pointer.fixupInfo.rebase != nil,
508-
let offset = pointer.rebaseTargetRuntimeOffset(for: self) else {
509-
return nil
510-
}
511-
return offset
499+
guard pointer.fixupInfo.rebase != nil,
500+
let offset = pointer.rebaseTargetRuntimeOffset(for: self) else {
501+
return nil
512502
}
513-
return nil
503+
return offset
514504
}
515505

516506
public func resolveOptionalRebase(at offset: UInt64) -> UInt64? {
@@ -520,38 +510,29 @@ extension MachOFile {
520510
}
521511

522512
guard let chainedFixup = dyldChainedFixups,
523-
let startsInImage = chainedFixup.startsInImage else {
513+
let pointer = chainedFixup.pointer(for: offset, in: self) else {
524514
return nil
525515
}
526-
let startsInSegments = chainedFixup.startsInSegments(
527-
of: startsInImage
528-
)
529516

530-
for segment in startsInSegments {
531-
let pointers = chainedFixup.pointers(of: segment, in: self)
532-
guard let pointer = pointers.first(where: {
533-
$0.offset == offset
534-
}) else { continue }
535-
guard pointer.fixupInfo.rebase != nil,
536-
let offset = pointer.rebaseTargetRuntimeOffset(for: self) else {
537-
return nil
538-
}
539-
if is64Bit {
540-
let value: UInt64 = fileHandle.read(
541-
offset: numericCast(headerStartOffset + pointer.offset)
542-
)
543-
if value == 0 { return nil }
544-
} else {
545-
let value: UInt32 = fileHandle.read(
546-
offset: numericCast(headerStartOffset + pointer.offset)
547-
)
548-
if value == 0 { return nil }
549-
}
550-
return offset
517+
guard pointer.fixupInfo.rebase != nil,
518+
let offset = pointer.rebaseTargetRuntimeOffset(for: self) else {
519+
return nil
551520
}
552-
return nil
521+
if is64Bit {
522+
let value: UInt64 = fileHandle.read(
523+
offset: numericCast(headerStartOffset + pointer.offset)
524+
)
525+
if value == 0 { return nil }
526+
} else {
527+
let value: UInt32 = fileHandle.read(
528+
offset: numericCast(headerStartOffset + pointer.offset)
529+
)
530+
if value == 0 { return nil }
531+
}
532+
return offset
553533
}
554534

535+
555536
public func resolveBind(
556537
at offset: UInt64
557538
) -> (DyldChainedImport, addend: UInt64)? {

0 commit comments

Comments
 (0)