From 2e8512311dc9e316b5f4f99075ae306a86fb03f7 Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Sat, 21 Dec 2024 23:22:48 +0900 Subject: [PATCH] Improve rebase resolution logic in MachOFile to simplify pointer handling --- .../MachOFile+DyldChainedFixups.swift | 288 +++++++++++++----- Sources/MachOKit/MachOFile.swift | 65 ++-- 2 files changed, 231 insertions(+), 122 deletions(-) diff --git a/Sources/MachOKit/MachOFile+DyldChainedFixups.swift b/Sources/MachOKit/MachOFile+DyldChainedFixups.swift index 8173775..d0daf53 100644 --- a/Sources/MachOKit/MachOFile+DyldChainedFixups.swift +++ b/Sources/MachOKit/MachOFile+DyldChainedFixups.swift @@ -227,95 +227,223 @@ extension MachOFile.DyldChainedFixups { var chainEnd = false while !stop && !chainEnd { - data.withUnsafeBytes { - guard let baseAddress = $0.baseAddress else { return } - let ptr = baseAddress.advanced(by: offset) + guard let fixupInfo = _fixupInfo( + at: offset, + in: data, + pointerFormat: pointerFormat + ) else { + stop = true + continue + } - var fixupInfo: DyldChainedFixupPointerInfo? - - if pointerFormat.is64Bit { - let rawValue = ptr.load(as: UInt64.self) - switch pointerFormat { - case .arm64e, .arm64e_kernel, .arm64e_userland, .arm64e_firmware: - let content = DyldChainedFixupPointerInfo.ARM64E(rawValue: rawValue) - switch pointerFormat { - case .arm64e: - fixupInfo = .arm64e(content) - case .arm64e_kernel: - fixupInfo = .arm64e_kernel(content) - case .arm64e_userland: - fixupInfo = .arm64e_userland(content) - case .arm64e_firmware: - fixupInfo = .arm64e_firmware(content) - default: break - } - - case .arm64e_userland24: - let content = DyldChainedFixupPointerInfo.ARM64EUserland24(rawValue: rawValue) - fixupInfo = .arm64e_userland24(content) - - case ._64, ._64_offset: - let content = DyldChainedFixupPointerInfo.General64(rawValue: rawValue) - switch pointerFormat { - case ._64: fixupInfo = ._64(content) - case ._64_offset: fixupInfo = ._64_offset(content) - default: break - } - - case ._64_kernel_cache, .x86_64_kernel_cache: - let content = DyldChainedFixupPointerInfo.General64Cache(rawValue: rawValue) - switch pointerFormat { - case ._64_kernel_cache: - fixupInfo = ._64_kernel_cache(content) - case .x86_64_kernel_cache: - fixupInfo = .x86_64_kernel_cache(content) - default: break - } - - case .arm64e_shared_cache: - let content = DyldChainedFixupPointerInfo.ARM64ESharedCache(rawValue: rawValue) - fixupInfo = .arm64e_shared_cache(content) - - default: - // unknown format - stop = true - } + let pointerOffset = numericCast(startsInSegment.segment_offset) + offset + + pointers.append( + DyldChainedFixupPointer( + offset: pointerOffset, + fixupInfo: fixupInfo + ) + ) + + if fixupInfo.next == 0 { + chainEnd = true + } else { + offset += stride * fixupInfo.next + } + } + } +} + +extension MachOFile.DyldChainedFixups { + public func pointer(for offset: UInt64, in machO: MachOFile) -> DyldChainedFixupPointer? { + guard let startsInImage = startsInImage else { return nil } + guard let startsInSegment = startsInSegments(of: startsInImage) + .first(where: { + let segmentSize = UInt64($0.page_size) * UInt64($0.page_count) + return $0.segment_offset <= offset && offset < $0.segment_offset + segmentSize + }) else { + return nil + } + + let pages = pages(of: startsInSegment) + let pagesData = machO.fileHandle.readData( + offset: numericCast(machO.headerStartOffset) + startsInSegment.segment_offset, + size: pages.count * numericCast(startsInSegment.page_size) + ) + + for (index, page) in pages.enumerated() { + var offsetInPage = page.offset + + if page.isNone { continue } + if page.isMulti { + var overflowIndex = Int(offsetInPage & ~UInt16(DYLD_CHAINED_PTR_START_MULTI)) + var chainEnd = false + while !chainEnd { + chainEnd = pages[overflowIndex].offset & UInt16(DYLD_CHAINED_PTR_START_LAST) != 0 + offsetInPage = pages[overflowIndex].offset & ~UInt16(DYLD_CHAINED_PTR_START_LAST) + let pageContentStart: Int = index * numericCast(startsInSegment.page_size) + let chainOffset = pageContentStart + numericCast(offsetInPage) - } else { - let rawValue = ptr.load(as: UInt32.self) - switch pointerFormat { - case ._32: - let content = DyldChainedFixupPointerInfo.General32(rawValue: rawValue) - fixupInfo = ._32(content) - case ._32_cache: - let content = DyldChainedFixupPointerInfo.General32Cache(rawValue: rawValue) - fixupInfo = ._32_cache(content) - case ._32_firmware: - let content = DyldChainedFixupPointerInfo.General32Firmware(rawValue: rawValue) - fixupInfo = ._32_firmware(content) - default: - // unknown format - stop = true + if let pointer = walkChainAndFindPointer( + for: offset, + chainOffset: chainOffset, + data: pagesData, + of: startsInSegment + ) { + return pointer } + + overflowIndex += 1 + } + } else { + let pageContentStart: Int = index * numericCast(startsInSegment.page_size) + let chainOffset = pageContentStart + numericCast(offsetInPage) + + if let pointer = walkChainAndFindPointer( + for: offset, + chainOffset: chainOffset, + data: pagesData, + of: startsInSegment + ) { + return pointer } - if let fixupInfo { - let pointerOffset = numericCast(startsInSegment.segment_offset) + offset + } + } - pointers.append( - DyldChainedFixupPointer( - offset: pointerOffset, - fixupInfo: fixupInfo - ) - ) + return nil + } + private func walkChainAndFindPointer( + for targetOffset: UInt64, + chainOffset: Int, + data: Data, + of startsInSegment: DyldChainedStartsInSegment + ) -> DyldChainedFixupPointer? { + guard let pointerFormat = startsInSegment.pointerFormat else { + return nil + } + var chainOffset = chainOffset - if fixupInfo.next == 0 { - chainEnd = true - } else { - offset += stride * fixupInfo.next - } + let stride = pointerFormat.stride + var stop = false + var chainEnd = false + + while !stop && !chainEnd { + guard let fixupInfo = _fixupInfo( + at: chainOffset, + in: data, + pointerFormat: pointerFormat + ) else { + stop = true + continue + } + + let pointerOffset = numericCast(startsInSegment.segment_offset) + chainOffset + + if pointerOffset == targetOffset { + return DyldChainedFixupPointer( + offset: pointerOffset, + fixupInfo: fixupInfo + ) + } + + if fixupInfo.next == 0 { + chainEnd = true + } else { + chainOffset += stride * fixupInfo.next + } + } + + return nil + } +} + +extension MachOFile.DyldChainedFixups { + @inline(__always) + private func _fixupInfo( + at offset: Int, + in data: Data, + pointerFormat: DyldChainedFixupPointerFormat + ) -> DyldChainedFixupPointerInfo? { + var fixupInfo: DyldChainedFixupPointerInfo? + + if pointerFormat.is64Bit { + // faster than below code + // let rawValue = data.advanced(by: offset).withUnsafeBytes { + // $0.load(as: UInt64.self) + // } + guard let rawValue = data.withUnsafeBytes ({ bytes -> UInt64? in + guard let baseAddress = bytes.baseAddress else { return nil } + let ptr = baseAddress.advanced(by: offset) + return ptr.load(as: UInt64.self) + }) else { return nil } + + switch pointerFormat { + case .arm64e, .arm64e_kernel, .arm64e_userland, .arm64e_firmware: + let content = DyldChainedFixupPointerInfo.ARM64E(rawValue: rawValue) + switch pointerFormat { + case .arm64e: + fixupInfo = .arm64e(content) + case .arm64e_kernel: + fixupInfo = .arm64e_kernel(content) + case .arm64e_userland: + fixupInfo = .arm64e_userland(content) + case .arm64e_firmware: + fixupInfo = .arm64e_firmware(content) + default: break } + + case .arm64e_userland24: + let content = DyldChainedFixupPointerInfo.ARM64EUserland24(rawValue: rawValue) + fixupInfo = .arm64e_userland24(content) + + case ._64, ._64_offset: + let content = DyldChainedFixupPointerInfo.General64(rawValue: rawValue) + switch pointerFormat { + case ._64: fixupInfo = ._64(content) + case ._64_offset: fixupInfo = ._64_offset(content) + default: break + } + + case ._64_kernel_cache, .x86_64_kernel_cache: + let content = DyldChainedFixupPointerInfo.General64Cache(rawValue: rawValue) + switch pointerFormat { + case ._64_kernel_cache: + fixupInfo = ._64_kernel_cache(content) + case .x86_64_kernel_cache: + fixupInfo = .x86_64_kernel_cache(content) + default: break + } + + case .arm64e_shared_cache: + let content = DyldChainedFixupPointerInfo.ARM64ESharedCache(rawValue: rawValue) + fixupInfo = .arm64e_shared_cache(content) + + default: + break + } + } else { + guard let rawValue = data.withUnsafeBytes ({ bytes -> UInt32? in + guard let baseAddress = bytes.baseAddress else { return nil } + let ptr = baseAddress.advanced(by: offset) + return ptr.load(as: UInt32.self) + }) else { return nil } + + switch pointerFormat { + case ._32: + let content = DyldChainedFixupPointerInfo.General32(rawValue: rawValue) + fixupInfo = ._32(content) + case ._32_cache: + let content = DyldChainedFixupPointerInfo.General32Cache(rawValue: rawValue) + fixupInfo = ._32_cache(content) + case ._32_firmware: + let content = DyldChainedFixupPointerInfo.General32Firmware(rawValue: rawValue) + fixupInfo = ._32_firmware(content) + default: + break } } + + return fixupInfo } } diff --git a/Sources/MachOKit/MachOFile.swift b/Sources/MachOKit/MachOFile.swift index 87c4736..0abeb8e 100644 --- a/Sources/MachOKit/MachOFile.swift +++ b/Sources/MachOKit/MachOFile.swift @@ -3,7 +3,7 @@ // // // Created by p-x9 on 2023/12/04. -// +// // import Foundation @@ -492,25 +492,15 @@ extension MachOFile { } guard let chainedFixup = dyldChainedFixups, - let startsInImage = chainedFixup.startsInImage else { + let pointer = chainedFixup.pointer(for: offset, in: self) else { return nil } - let startsInSegments = chainedFixup.startsInSegments( - of: startsInImage - ) - for segment in startsInSegments { - let pointers = chainedFixup.pointers(of: segment, in: self) - guard let pointer = pointers.first(where: { - $0.offset == offset - }) else { continue } - guard pointer.fixupInfo.rebase != nil, - let offset = pointer.rebaseTargetRuntimeOffset(for: self) else { - return nil - } - return offset + guard pointer.fixupInfo.rebase != nil, + let offset = pointer.rebaseTargetRuntimeOffset(for: self) else { + return nil } - return nil + return offset } public func resolveOptionalRebase(at offset: UInt64) -> UInt64? { @@ -520,38 +510,29 @@ extension MachOFile { } guard let chainedFixup = dyldChainedFixups, - let startsInImage = chainedFixup.startsInImage else { + let pointer = chainedFixup.pointer(for: offset, in: self) else { return nil } - let startsInSegments = chainedFixup.startsInSegments( - of: startsInImage - ) - for segment in startsInSegments { - let pointers = chainedFixup.pointers(of: segment, in: self) - guard let pointer = pointers.first(where: { - $0.offset == offset - }) else { continue } - guard pointer.fixupInfo.rebase != nil, - let offset = pointer.rebaseTargetRuntimeOffset(for: self) else { - return nil - } - if is64Bit { - let value: UInt64 = fileHandle.read( - offset: numericCast(headerStartOffset + pointer.offset) - ) - if value == 0 { return nil } - } else { - let value: UInt32 = fileHandle.read( - offset: numericCast(headerStartOffset + pointer.offset) - ) - if value == 0 { return nil } - } - return offset + guard pointer.fixupInfo.rebase != nil, + let offset = pointer.rebaseTargetRuntimeOffset(for: self) else { + return nil } - return nil + if is64Bit { + let value: UInt64 = fileHandle.read( + offset: numericCast(headerStartOffset + pointer.offset) + ) + if value == 0 { return nil } + } else { + let value: UInt32 = fileHandle.read( + offset: numericCast(headerStartOffset + pointer.offset) + ) + if value == 0 { return nil } + } + return offset } + public func resolveBind( at offset: UInt64 ) -> (DyldChainedImport, addend: UInt64)? {