├── .gitignore ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── DLKit │ ├── DLKit.swift │ ├── Demangling.swift │ ├── FileSymbols.swift │ ├── ImageSymbols.swift │ ├── Interposing.swift │ └── Iterators.swift ├── DLKitC │ ├── DLKitC.c │ ├── include │ │ └── DLKitC.h │ ├── trie_dladdr.mm │ └── trie_dlops.mm ├── DLKitCD │ ├── DLKitC.c │ ├── include │ │ └── DLKitC.h │ ├── trie_dladdr.mm │ └── trie_dlops.mm └── DLKitD └── Tests ├── DLKitTests ├── DLKitTests.swift └── XCTestManifests.swift └── LinuxMain.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 John Holdsworth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "DLKit", 8 | products: [ 9 | // Products define the executables and libraries a package produces, and make them visible to other packages. 10 | .library( 11 | name: "DLKit", 12 | targets: ["DLKit"]), 13 | .library( 14 | name: "DLKitC", 15 | targets: ["DLKitC"]), 16 | .library( 17 | name: "DLKitD", 18 | targets: ["DLKitD"]), 19 | .library( 20 | name: "DLKitCD", 21 | targets: ["DLKitCD"]), 22 | ], 23 | dependencies: [ 24 | // Dependencies declare other packages that this package depends on. 25 | // .package(url: /* package url */, from: "1.0.0"), 26 | .package(url: "https://github.com/johnno1962/fishhook", 27 | .upToNextMinor(from: "1.2.1")), 28 | ], 29 | targets: [ 30 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 31 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 32 | .target( 33 | name: "DLKit", 34 | dependencies: ["DLKitC", "fishhook"]), 35 | .target( 36 | name: "DLKitC", 37 | linkerSettings: [.linkedFramework("Foundation")]), 38 | .target( 39 | name: "DLKitD", 40 | dependencies: ["DLKitCD", .product(name: "fishhookD", package: "fishhook")], 41 | swiftSettings: [.define("DEBUG_ONLY")]), 42 | .target( 43 | name: "DLKitCD", 44 | dependencies: [.product(name: "fishhookD", package: "fishhook")], 45 | cSettings: [.define("DEBUG_ONLY")], 46 | linkerSettings: [.linkedFramework("Foundation")]), 47 | .testTarget( 48 | name: "DLKitTests", 49 | dependencies: ["DLKit"]), 50 | ] 51 | ) 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DLKit 2 | 3 | ### A subscript oriented interface to the symbol tables maintained by the dynamic linker. 4 | 5 | DLKit is intended to be a "Swifty" encapsulation of the dynamic linker APIs gleaned from 6 | assorted manual pages and system headers. Loaded images (the main executable 7 | or frameworks) are wrapped in the class `ImageSymbols` which you can use to lookup 8 | symbols or find the symbol at an address less than or equal to a pointer and it's 9 | wrapped image. ImageSymbols instances can also be iterated over in a loop 10 | to find all symbol definitions in that image. Think of them as representing 11 | the image/object file as bi-directional dictionaries. 12 | 13 | A couple of `ImageSymbols` subclasses represent groups of images such as 14 | `DLKit.allImages` for all images loaded in an application or `DLKit.appImages` 15 | for this list of images filtered to include only those in the app bundle. 16 | 17 | To look up a symbol use the allImages pseudo image: 18 | ``` 19 | if let pointer = DLKit.allImages["main"] { 20 | ``` 21 | 22 | Or you can find the symbol and wrapped image for an address: 23 | ``` 24 | if let (name, image) = DLKit.allImages[pointer] else { 25 | ``` 26 | 27 | There is a typealias of `UnsafePointer` to `ImageSymbols.SymbolName` 28 | and an extension on this typealias to `"demangle"` Swift symbols to 29 | the Swift language representation of the symbol. There is also a method 30 | `mangle` on an image which can "re-mangle" this representation to a 31 | symbol name provided the symbol is defined or referred to in the image. 32 | ``` 33 | let swift = name.demangled 34 | let name = image.mangle(swift: swift) 35 | ``` 36 | 37 | The subscript operators are settable and you can also "rebind" or "interpose" 38 | a symbol in your application by assigning it to point to a new implementation: 39 | ``` 40 | DLKit.appImages["function_name"] = new_implementation 41 | ``` 42 | 43 | This "rebinding" works across framework boundaries or inside an application if it has 44 | been linked with "Other Linker Flags" -Xlinker -interposable and uses facebook's 45 | [fishhook](https://github.com/facebook/fishhook) for rebinding indirect symbols. 46 | 47 | $Date: 2024/04/03 $ 48 | -------------------------------------------------------------------------------- /Sources/DLKit/DLKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DLKit.swift 3 | // DLKit 4 | // 5 | // Created by John Holdsworth on 19/04/2021. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | // Repo: https://github.com/johnno1962/DLKit 9 | // $Id: //depot/DLKit/Sources/DLKit/DLKit.swift#77 $ 10 | // 11 | 12 | #if DEBUG || !DEBUG_ONLY 13 | #if canImport(Darwin) 14 | import Foundation 15 | #if SWIFT_PACKAGE 16 | #if DEBUG_ONLY 17 | #if canImport(DLKitCD) 18 | @_exported import DLKitCD 19 | #endif 20 | #else 21 | @_exported import DLKitC 22 | #endif 23 | #endif 24 | 25 | /// Interface to symbols of dynamically loaded images (executable or frameworks). 26 | public struct DLKit { 27 | /// Offset into table of loaded images 28 | public typealias ImageNumber = UInt32 29 | /// Alias for symbol name type 30 | public typealias SymbolName = UnsafePointer 31 | /// Pseudo image for all images loaded in the process. 32 | public static let allImages = AllImages() 33 | /// Pseudo image for images loaded from the app bundle. 34 | public static let appImages = AppImages() 35 | /// Main execuatble image. 36 | public static let mainImage = MainImage() 37 | /// Total number of images. 38 | public static var imageCount: ImageNumber { 39 | return _dyld_image_count() 40 | } 41 | /// Last dynamically loaded image. 42 | public static var lastImage: ImageSymbols { 43 | return ImageSymbols(imageNumber: imageCount-1) 44 | } 45 | /// Image of code referencing this property 46 | public static var selfImage: ImageSymbols { 47 | return allImages[self_caller_address()!]!.image! 48 | } 49 | /// List of all loaded images in order 50 | public static var imageList: [ImageSymbols] { 51 | return allImages.imageList 52 | } 53 | /// Map of all loaded images 54 | public static var imageMap: [String: ImageSymbols] { 55 | return allImages.imageMap 56 | } 57 | public static let RTLD_DEFAULT = UnsafeMutableRawPointer(bitPattern: -2) 58 | public static let RTLD_MAIN_ONLY = UnsafeMutableRawPointer(bitPattern: -5) 59 | public static var logger = { (msg: String) in 60 | NSLog("DLKit: %@", msg) 61 | } 62 | public static func load(dylib: String) -> ImageSymbols? { 63 | let index = imageCount 64 | guard let handle = dlopen(dylib, RTLD_NOW) else { 65 | logger("⚠️ dlopen failed \(String(cString: dlerror()))") 66 | return nil 67 | } 68 | let image = ImageSymbols(imageNumber: index) 69 | image.imageHandle = handle 70 | return image 71 | } 72 | 73 | /// Pseudo image representing all images 74 | open class AllImages: ImageSymbols.MultiImage { 75 | public init() { 76 | super.init(imageNumber: ~0) 77 | imageHandle = DLKit.RTLD_DEFAULT 78 | } 79 | open override var imageNumbers: [ImageNumber] { 80 | return (0..<_dyld_image_count()).map {$0} 81 | } 82 | } 83 | 84 | /// Pseudo image representing images in the app bundle 85 | open class AppImages: AllImages { 86 | open override var imageNumbers: [ImageNumber] { 87 | let mainExecutable = Bundle.main.executablePath 88 | let bundleFrameworks = Bundle.main.privateFrameworksPath ?? "~~" 89 | let frameworkPathLength = strlen(bundleFrameworks) 90 | return super.imageNumbers.filter { 91 | let imageName = $0.imageName 92 | return strcmp(imageName, mainExecutable) == 0 || 93 | strncmp(imageName, bundleFrameworks, frameworkPathLength) == 0 || 94 | (strstr(imageName, "/DerivedData/") != nil && 95 | strstr(imageName, ".framework/") != nil) || 96 | strstr(imageName, ".xctest/") != nil || 97 | strstr(imageName, "/eval") != nil || 98 | strstr(imageName, ".debug.dylib") != nil // Xcode16 99 | } 100 | } 101 | } 102 | 103 | /// Pseudo image representing main executable image 104 | open class MainImage: ImageSymbols { 105 | public init() { 106 | let mainExecutable = Bundle.main.executablePath 107 | guard let mainImageNumber = mainExecutable?.withCString({ mainPath in 108 | DLKit.allImages.imageNumbers.first(where: { 109 | strcmp(mainPath, $0.imageName) == 0 110 | })}) else { 111 | DLKit.logger("Could not find image for main executable \(mainExecutable ?? "nil")") 112 | fatalError() 113 | } 114 | super.init(imageNumber: mainImageNumber) 115 | imageHandle = DLKit.RTLD_MAIN_ONLY 116 | } 117 | } 118 | } 119 | 120 | @_cdecl("DLKit_appImagesContain") 121 | public func appImagesContain(symbol: UnsafePointer) -> UnsafeMutableRawPointer? { 122 | return DLKit.appImages.imageList.compactMap { 123 | trie_dlsym($0.imageHeader, symbol) }.first 124 | // return DLKit.appImages.imageList.compactMap { 125 | // $0.entry(named: symbol) }.first?.value 126 | } 127 | 128 | public protocol ImageInfo { 129 | var imageNumber: DLKit.ImageNumber { get } 130 | } 131 | 132 | extension DLKit.ImageNumber: ImageInfo { 133 | public var imageNumber: Self { return self } 134 | } 135 | 136 | public extension ImageInfo { 137 | /// Base address of image (pointer to mach_header at beginning of file) 138 | var imageHeader: UnsafePointer { 139 | guard imageNumber < DLKit.imageCount else { 140 | fatalError("Invalid image: \(imageNumber) !< \(DLKit.imageCount)") 141 | } 142 | return _dyld_get_image_header(imageNumber) 143 | .withMemoryRebound(to: mach_header_t.self, capacity: 1) {$0} 144 | } 145 | /// Amount image has been slid on load (after ASLR) 146 | var imageSlide: intptr_t { 147 | return _dyld_get_image_vmaddr_slide(imageNumber) 148 | } 149 | /// Path to image as cString 150 | var imageName: UnsafePointer { 151 | return _dyld_get_image_name(imageNumber) 152 | } 153 | /// Path to image 154 | var imagePath: String { 155 | return FileSymbols.filePaths[imageNumber] ?? String(cString: imageName) 156 | } 157 | /// Short name for image 158 | var imageKey: String { 159 | return URL(fileURLWithPath: imagePath).lastPathComponent 160 | } 161 | } 162 | #endif 163 | #endif 164 | -------------------------------------------------------------------------------- /Sources/DLKit/Demangling.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Demangling.swift 3 | // DLKit 4 | // 5 | // Created by John Holdsworth on 14/10/2023. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | // Repo: https://github.com/johnno1962/DLKit 9 | // $Id: //depot/DLKit/Sources/DLKit/Demangling.swift#5 $ 10 | // 11 | 12 | #if DEBUG || !DEBUG_ONLY 13 | #if canImport(Darwin) 14 | import Foundation 15 | 16 | /// Extension for easy demangling of Swift symbols 17 | public extension DLKit.SymbolName { 18 | @_silgen_name("swift_demangle") 19 | private 20 | func _stdlib_demangleImpl( 21 | _ mangledName: UnsafePointer?, 22 | mangledNameLength: UInt, 23 | outputBuffer: UnsafeMutablePointer?, 24 | outputBufferSize: UnsafeMutablePointer?, 25 | flags: UInt32 26 | ) -> UnsafeMutablePointer? 27 | 28 | @_silgen_name("__cxa_demangle") 29 | func __cxa_demangle(_ mangled_name: UnsafePointer?, 30 | output_buffer: UnsafePointer?, 31 | length: UnsafeMutablePointer?, 32 | status: UnsafeMutablePointer?) 33 | -> UnsafeMutablePointer? 34 | 35 | /// return Swif tlanguage description of symbol 36 | var demangled: String? { 37 | if let demangledNamePtr = _stdlib_demangleImpl( 38 | self, mangledNameLength: UInt(strlen(self)), 39 | outputBuffer: nil, outputBufferSize: nil, flags: 0) ?? 40 | __cxa_demangle(self+1, output_buffer: nil, 41 | length: nil, status: nil) { 42 | let demangledName = String(cString: demangledNamePtr) 43 | free(demangledNamePtr) 44 | return demangledName 45 | } 46 | return nil 47 | } 48 | } 49 | 50 | extension String { 51 | public var swiftDemangle: String? { 52 | return withCString { $0.demangled } 53 | } 54 | } 55 | #endif 56 | #endif 57 | -------------------------------------------------------------------------------- /Sources/DLKit/FileSymbols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileSymbols.swift 3 | // DLKit 4 | // 5 | // Created by John Holdsworth on 14/10/2023. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | // Repo: https://github.com/johnno1962/DLKit 9 | // $Id: //depot/DLKit/Sources/DLKit/FileSymbols.swift#8 $ 10 | // 11 | 12 | #if DEBUG || !DEBUG_ONLY 13 | #if canImport(Darwin) 14 | import Foundation 15 | 16 | open class FileSymbols: ImageSymbols { 17 | 18 | /// Fake imageNumber for files 19 | public static var fileNumber: ImageNumber = 1_000_000 20 | /// Record of paths to images read in rather than loaded 21 | public static var filePaths = [ImageNumber: String]() 22 | 23 | public let data: NSMutableData 24 | public let header: UnsafePointer 25 | 26 | override open var imageHeader: UnsafePointer { 27 | return header 28 | } 29 | override open var imageList: [ImageSymbols] { 30 | return [self] 31 | } 32 | 33 | static func parseFAT(bytes: UnsafeRawPointer, forArch: Int32?) 34 | -> UnsafePointer? { 35 | let header = bytes.assumingMemoryBound(to: fat_header.self) 36 | if header.pointee.magic.bigEndian == FAT_MAGIC { 37 | let archs = bytes.advanced(by: MemoryLayout.size) 38 | .assumingMemoryBound(to: fat_arch.self) 39 | for slice in 0.. Bool { 68 | return data.write(toFile: path, atomically: true) 69 | } 70 | } 71 | #endif 72 | #endif 73 | -------------------------------------------------------------------------------- /Sources/DLKit/ImageSymbols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageSymbols.swift 3 | // DLKit 4 | // 5 | // Copyright © 2020 John Holdsworth. All rights reserved. 6 | // Created by John Holdsworth on 14/10/2023. 7 | // 8 | // Repo: https://github.com/johnno1962/DLKit 9 | // $Id: //depot/DLKit/Sources/DLKit/ImageSymbols.swift#10 $ 10 | // 11 | 12 | #if DEBUG || !DEBUG_ONLY 13 | #if canImport(Darwin) 14 | import Foundation 15 | 16 | /// Abstraction for an image as operations on it's symbol table 17 | open class ImageSymbols: ImageInfo, Equatable, CustomStringConvertible { 18 | /// For compatability 19 | public typealias ImageNumber = DLKit.ImageNumber 20 | /// Index into loaded images 21 | public typealias SymbolValue = UnsafeMutableRawPointer 22 | /// Symbols included if these bits not set 23 | public static var mask: Int32 = N_STAB 24 | public static func == (lhs: ImageSymbols, rhs: ImageSymbols) -> Bool { 25 | return lhs.imageNumber == rhs.imageNumber 26 | } 27 | public var description: String { 28 | return "#\(imageNumber) \(imagePath) \(imageHeader)" 29 | } 30 | 31 | /// Index into loaded images 32 | public let imageNumber: ImageNumber 33 | /// Skip if any of these bits set in "n_type" 34 | let typeMask: UInt8 35 | 36 | /// Lazilly recovered handle returned by dlopen 37 | var imageHandle: UnsafeMutableRawPointer? 38 | /// Base address of image (pointer to mach_header at beginning of file) 39 | public var imageHeader: UnsafePointer { 40 | return imageNumber.imageHeader 41 | } 42 | 43 | public init(imageNumber: ImageNumber, typeMask: Int32 = ImageSymbols.mask) { 44 | self.imageNumber = imageNumber 45 | self.typeMask = UInt8(typeMask) 46 | } 47 | 48 | /// Array of image numbers covered - used to represent MultiImage 49 | open var imageNumbers: [ImageNumber] { 50 | return [imageNumber] 51 | } 52 | /// List of wrapped images 53 | open var imageList: [ImageSymbols] { 54 | return imageNumbers.map { 55 | if typeMask != ImageSymbols.mask, let path = FileSymbols.filePaths[$0], 56 | let masked = FileSymbols(path: path, typeMask: Int32(typeMask)) { 57 | return masked 58 | } else { 59 | return ImageSymbols(imageNumber: $0, typeMask: Int32(typeMask)) 60 | } 61 | } 62 | } 63 | /// Produce a map of images keyed by lastPathComponent of the imagePath 64 | open var imageMap: [String: ImageSymbols] { 65 | return Dictionary(imageList.map { 66 | ($0.imageKey, $0) 67 | }, uniquingKeysWith: { (first, last) in 68 | print("ℹ️DLKit: Duplicate framework key \(first) c.f. \(last)") 69 | return last }) 70 | } 71 | /// Entries in the symbol table 72 | open var entries: AnySequence { 73 | return AnySequence(self) 74 | } 75 | /// Default iterator now removes debug and undefined symbols 76 | open func makeIterator() -> AnyIterator { 77 | return AnyIterator(SymbolIterator(image: self)) 78 | } 79 | /// Implement custom symbol filtering here... 80 | open func skipFiltered(iterator: inout SymbolIterator) { 81 | while iterator.next_symbol < iterator.state.symbol_count && typeMask != 0, 82 | let sym = iterator.state.symbols?.advanced(by: iterator.next_symbol), 83 | sym.pointee.n_type & typeMask != 0 || sym.pointee.n_sect == NO_SECT { 84 | iterator.next_symbol += 1 85 | } 86 | } 87 | 88 | /// Determine symbol associated with mangled name. 89 | /// ("self" must contain definition of or reference to symbol) 90 | /// - Parameter swift: Swift language version of symbol 91 | /// - Returns: Mangled version of String + value if there is one 92 | open func mangle(swift: String) -> Entry? { 93 | return entries.first(where: { $0.name.demangled == swift }) 94 | } 95 | /// Swift symbols encode their type as a suffix 96 | /// - Parameter withSuffixes: Suffixes to search for or none for all symbols 97 | /// - Returns: Swift symbols with any of the suffixes 98 | open func swiftSymbols(withSuffixes: [String]? = nil) ->[Entry] { 99 | return entries.filter { 100 | let (name, value) = ($0.name, $0.value) 101 | guard value != nil && strncmp(name, "$s", 2) == 0 else { return false } 102 | guard let suffixes = withSuffixes else { return true } 103 | let symbol = String(cString: name) 104 | return suffixes.first(where: { symbol.hasSuffix($0) }) != nil 105 | } 106 | } 107 | 108 | /// Linear scan for symbols with prefix 109 | open func entries(withPrefix: DLKit.SymbolName) -> [Entry] { 110 | let prefixLen = strlen(withPrefix) 111 | return entries.filter({ strncmp($0.name, withPrefix, prefixLen) == 0 }) 112 | } 113 | /// Linear scan for symbols with suffix 114 | open func entries(withSuffix: DLKit.SymbolName) -> [Entry] { 115 | let suffixLen = strlen(withSuffix) 116 | return entries.filter({ strcmp($0.name+strlen($0.name)-suffixLen, withSuffix) == 0 }) 117 | } 118 | /// Linear scan for symbol by name 119 | open func entry(named: DLKit.SymbolName) -> Entry? { 120 | return entries.first(where: { strcmp($0.name, named) == 0 }) 121 | } 122 | 123 | open func trieSymbols() -> [TrieSymbol]? { 124 | guard let iterator = trie_iterator(imageHeader), 125 | let trie_symbols = iterator.pointee.trie_symbols else { return nil } 126 | return Array(unsafeUninitializedCapacity: iterator.pointee.trie_symbol_count, 127 | initializingWith: { buffer, initializedCount in 128 | let bytesToCopy = 129 | iterator.pointee.trie_symbol_count*MemoryLayout.stride 130 | memcpy(buffer.baseAddress, trie_symbols, bytesToCopy) 131 | initializedCount = iterator.pointee.trie_symbol_count 132 | }) 133 | } 134 | } 135 | 136 | extension TrieSymbol: CustomStringConvertible { 137 | public var description: String { 138 | return "\(value != nil ? "\(value!)" : "nil"): " + 139 | (name.demangled ?? String(cString: name)) 140 | } 141 | } 142 | #endif 143 | #endif 144 | -------------------------------------------------------------------------------- /Sources/DLKit/Interposing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Interposing.swift 3 | // DLKit 4 | // 5 | // Created by John Holdsworth on 03/03/2023. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | // Repo: https://github.com/johnno1962/DLKit 9 | // $Id: //depot/DLKit/Sources/DLKit/Interposing.swift#19 $ 10 | // 11 | 12 | #if DEBUG || !DEBUG_ONLY 13 | #if canImport(Darwin) 14 | #if SWIFT_PACKAGE 15 | #if DEBUG_ONLY 16 | #if canImport(fishhookD) 17 | import fishhookD 18 | #endif 19 | #else 20 | import fishhook 21 | #endif 22 | #endif 23 | 24 | internal extension UnsafeMutablePointer { 25 | init(recast: UnsafeMutablePointer) { 26 | self = recast.withMemoryRebound(to: Pointee.self, capacity: 1) {$0} 27 | } 28 | } 29 | 30 | extension ImageSymbols { 31 | public func rebind(mapping: [String: String], 32 | scope: UnsafeMutableRawPointer? = DLKit.RTLD_DEFAULT, 33 | warn: Bool = true) -> [DLKit.SymbolName] { 34 | return rebind(symbols: Array(mapping.keys), 35 | values: Array(mapping.values), 36 | scope: scope, warn: warn) 37 | } 38 | public func rebind(symbols: [String], values: [String], 39 | scope: UnsafeMutableRawPointer? = DLKit.RTLD_DEFAULT, 40 | warn: Bool = true) -> [DLKit.SymbolName] { 41 | return rebind(names: symbols.map {$0.withCString {$0}}, values: 42 | values.map { if let replacement = dlsym(scope, $0) { 43 | return replacement 44 | } 45 | DLKit.logger(""" 46 | Unable to find replacement for symbol: \($0) 47 | """) 48 | return nil 49 | }, warn: warn) 50 | } 51 | public func rebind(symbols: [String], values: [SymbolValue?], 52 | warn: Bool = false) -> [DLKit.SymbolName] { 53 | let names = symbols.map {$0.withCString {$0}} 54 | return rebind(names: names, values: values, warn: warn) 55 | } 56 | public func rebind(names: [DLKit.SymbolName], values: [SymbolValue?], 57 | warn: Bool = false) -> [DLKit.SymbolName] { 58 | var rebindings: [rebinding] = (0.. [DLKit.SymbolName] { 74 | var replaced = [DLKit.SymbolName]() 75 | for image in imageNumbers { 76 | // Have the rebind operation store the previous value 77 | // to determine when rebindings have been successful. 78 | for i in 0.. DLKInfo? { 113 | var info = Dl_info() 114 | guard dladdr(address, &info) != 0 || 115 | trie_dladdr(address, &info) != 0 else { return nil } 116 | return DLKInfo(info: info, owner: self) 117 | } 118 | /// Inverse lookup returning image symbol name and wrapped image for an address. 119 | public subscript (ptr: SymbolValue) -> DLKInfo? { 120 | return getInfo(address: ptr) 121 | } 122 | /// Loook up an individual symbol by name 123 | public subscript (name: DLKit.SymbolName) -> SymbolValue? { 124 | get { return self[[name]][0] } 125 | set (newValue) { self[[name]] = [newValue] } 126 | } 127 | /// Loook up an array of symbols 128 | public subscript (names: [DLKit.SymbolName]) -> [SymbolValue?] { 129 | get { 130 | if imageHandle == nil { 131 | imageHandle = dlopen(imageName, RTLD_LAZY) 132 | } 133 | return names.map {dlsym(imageHandle, $0)} 134 | } 135 | set (newValue) { 136 | /// Use fishhook to replace references to the named symbol with new values 137 | /// Works for symbols references across framework boundaries or inside 138 | /// an image if it has been linked with -Xlinker -interposable. 139 | _ = rebind(names: names, values: newValue, warn: true) 140 | } 141 | } 142 | /// Array of Strings version of subscript 143 | public subscript (symbols: [String]) -> [SymbolValue?] { 144 | get { return self[symbols.map {$0.withCString {$0}}] } 145 | set (newValue) { 146 | _ = rebind(symbols: symbols, values: newValue, warn: true) 147 | } 148 | } 149 | /// Array of Strings version of subscript with lookup 150 | public subscript (symbols: [String]) -> [String] { 151 | get { return symbols } 152 | set (newValue) { 153 | _ = rebind(symbols: symbols, values: newValue, warn: true) 154 | } 155 | } 156 | } 157 | #endif 158 | #endif 159 | -------------------------------------------------------------------------------- /Sources/DLKit/Iterators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Iterators.swift 3 | // DLKit 4 | // 5 | // Created by John Holdsworth on 03/03/2023. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | // Repo: https://github.com/johnno1962/DLKit 9 | // $Id: //depot/DLKit/Sources/DLKit/Iterators.swift#26 $ 10 | // 11 | 12 | #if DEBUG || !DEBUG_ONLY 13 | #if canImport(Darwin) 14 | 15 | /// Extend Image wrapper to be iterable over the symbols defined 16 | extension ImageSymbols: Sequence { 17 | public typealias Element = Entry 18 | public struct Entry: ImageInfo, CustomStringConvertible { 19 | public let imageNumber: ImageNumber, 20 | name: DLKit.SymbolName, 21 | value: SymbolValue?, 22 | entry: UnsafeMutablePointer 23 | 24 | public var isDebugging: Bool { 25 | return entry.pointee.n_type & UInt8(N_STAB) != 0 26 | } 27 | public var description: String { 28 | return String(format: "#%-3d %p: 0x%02x %s ", imageNumber, 29 | unsafeBitCast(value, to: uintptr_t.self), 30 | entry.pointee.n_type, name)+imageKey 31 | } 32 | public var symbol: String { return String(cString: name) } 33 | public var offset: UInt64 { return entry.pointee.n_value } 34 | } 35 | 36 | public struct SymbolIterator: IteratorProtocol { 37 | let owner: ImageSymbols 38 | var state = symbol_iterator() 39 | var next_symbol = 0; 40 | public init(image: ImageSymbols) { 41 | self.owner = image 42 | init_symbol_iterator(image.imageHeader, &state, image is FileSymbols) 43 | } 44 | mutating public func next() -> Element? { 45 | owner.skipFiltered(iterator: &self) 46 | guard next_symbol < state.symbol_count else { return nil } 47 | let symbol = state.symbols.advanced(by: next_symbol) 48 | next_symbol += 1 49 | return Entry(imageNumber: owner.imageNumber, 50 | name: state.strings_base + 1 + 51 | Int(symbol.pointee.n_un.n_strx), 52 | value: symbol.pointee.n_sect == NO_SECT ? nil : SymbolValue( 53 | bitPattern: state.address_base + Int(symbol[0].n_value)), 54 | entry: symbol) 55 | } 56 | } 57 | 58 | /// Overrides for iterating multiple images 59 | open class MultiImage: ImageSymbols { 60 | open override var entries: AnySequence { 61 | return AnySequence(imageList.flatMap { $0 }) 62 | } 63 | open override func makeIterator() -> AnyIterator { 64 | return AnyIterator(entries.makeIterator()) 65 | } 66 | } 67 | 68 | /// Represent filtered iteration on multiple images 69 | open class RefilteredSymbols: MultiImage { 70 | let owner: ImageSymbols 71 | public init(owner: ImageSymbols, typeMask: Int32) { 72 | self.owner = owner 73 | super.init(imageNumber: owner.imageNumber, typeMask: typeMask) 74 | } 75 | override open var imageHeader: UnsafePointer { 76 | return owner.imageHeader 77 | } 78 | open override var imageNumbers: [ImageNumber] { 79 | return owner.imageNumbers 80 | } 81 | } 82 | 83 | /// Iterate kitchen sink of all entries in the symbol table 84 | public var unfiltered: ImageSymbols { 85 | return RefilteredSymbols(owner: self, typeMask: 0) } 86 | 87 | /// Iterates over non-debug, non-private symbol definitions only. 88 | public var globals: ImageSymbols { 89 | return RefilteredSymbols(owner: self, typeMask: N_STAB | N_PEXT) } 90 | } 91 | #endif 92 | #endif 93 | -------------------------------------------------------------------------------- /Sources/DLKitC/DLKitC.c: -------------------------------------------------------------------------------- 1 | // 2 | // DLKit.c 3 | // DLKit 4 | // 5 | // Created by John Holdsworth on 19/04/2021. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | // Repo: https://github.com/johnno1962/DLKit 9 | // $Id: //depot/DLKit/Sources/DLKitC/DLKitC.c#19 $ 10 | // 11 | // Provides state for a symbol table iterator. 12 | // 13 | 14 | #if DEBUG || !DEBUG_ONLY 15 | #if __has_include() 16 | #define DLKit_C 17 | #include "DLKitC.h" 18 | #include 19 | 20 | // Derived from: https://stackoverflow.com/questions/20481058/find-pathname-from-dlopen-handle-on-osx 21 | // Imagine trying to write this in Swift. Would it be at all clearer?? 22 | 23 | void init_symbol_iterator(const mach_header_t *header, 24 | symbol_iterator *state, 25 | bool isFile) { 26 | state->header = header; 27 | segment_command_t *seg_text = NULL; 28 | segment_command_t *seg_linkedit = NULL; 29 | struct symtab_command *symtab = NULL; 30 | struct load_command *cmd = 31 | (struct load_command *)((intptr_t)header + sizeof(mach_header_t)), *cmd0 = cmd; 32 | uint64_t textUnslidVMAddr = 0; 33 | uint64_t linkeditUnslidVMAddr = 0; 34 | uint64_t linkeditFileOffset = 0; 35 | for (uint32_t i = 0, segno = 0; i < header->ncmds; i++, 36 | cmd = (struct load_command *)((intptr_t)cmd + cmd->cmdsize)) { 37 | switch(cmd->cmd) { 38 | case LC_SEGMENT: 39 | case LC_SEGMENT_64: { 40 | segment_command_t *segment = (segment_command_t *)cmd; 41 | state->image_end = (char *)header + segment->fileoff + segment->filesize; 42 | if (segno < sizeof state->segments / sizeof state->segments[0]) 43 | state->segments[segno++] = segment; 44 | if (!strcmp(((segment_command_t *)cmd)->segname, SEG_TEXT)) { 45 | seg_text = (segment_command_t *)cmd; 46 | textUnslidVMAddr = seg_text->vmaddr; 47 | } 48 | else if (!strcmp(((segment_command_t *)cmd)->segname, SEG_LINKEDIT)) { 49 | seg_linkedit = (segment_command_t *)cmd; 50 | linkeditUnslidVMAddr = seg_linkedit->vmaddr; 51 | linkeditFileOffset = seg_linkedit->fileoff; 52 | } 53 | break; 54 | } 55 | 56 | case LC_SYMTAB: { 57 | symtab = (struct symtab_command *)cmd; 58 | state->symbol_count = symtab->nsyms; 59 | state->file_slide = isFile ? 0 : ((intptr_t)seg_linkedit->vmaddr - 60 | (intptr_t)seg_text->vmaddr) - seg_linkedit->fileoff; 61 | state->symbols = (nlist_t *)((intptr_t)header + 62 | (symtab->symoff + state->file_slide)); 63 | state->strings_base = (const char *)header + 64 | (symtab->stroff + state->file_slide); 65 | if (seg_text) 66 | state->address_base = (intptr_t)header - seg_text->vmaddr; 67 | } 68 | } 69 | } 70 | 71 | cmd = cmd0; 72 | uint32_t fileOffset = ~0U; 73 | for (uint32_t i = 0; i < header->ncmds; i++, 74 | cmd = (struct load_command *)((intptr_t)cmd + cmd->cmdsize)) { 75 | switch(cmd->cmd) { 76 | case LC_DYLD_INFO: 77 | case LC_DYLD_INFO_ONLY: { 78 | const struct dyld_info_command *dyldInfo = 79 | (const struct dyld_info_command*)cmd; 80 | fileOffset = dyldInfo->export_off; 81 | state->trie_size = dyldInfo->export_size; 82 | break; 83 | } 84 | case LC_DYLD_EXPORTS_TRIE: { 85 | const struct linkedit_data_command* linkeditCmd = 86 | (const struct linkedit_data_command*)cmd; 87 | fileOffset = linkeditCmd->dataoff; 88 | state->trie_size = linkeditCmd->datasize; 89 | break; 90 | } 91 | } 92 | } 93 | 94 | if (fileOffset != ~0U) 95 | state->exports_trie = (uint8_t *)header + (uint32_t)(isFile ? fileOffset : 96 | (fileOffset - linkeditFileOffset) + (linkeditUnslidVMAddr - textUnslidVMAddr)); 97 | } 98 | 99 | void *self_caller_address(void) { 100 | return __builtin_return_address(1); 101 | } 102 | #endif 103 | #endif 104 | -------------------------------------------------------------------------------- /Sources/DLKitC/include/DLKitC.h: -------------------------------------------------------------------------------- 1 | // 2 | // DLKit.swift 3 | // DLKit 4 | // 5 | // Created by John Holdsworth on 19/04/2021. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | // Repo: https://github.com/johnno1962/DLKit 9 | // $Id: //depot/DLKit/Sources/DLKitC/include/DLKitC.h#17 $ 10 | // 11 | // Provides state for a symbol table iterator. 12 | // 13 | 14 | #if __has_include() 15 | #import 16 | #import 17 | #import 18 | #import 19 | #import 20 | 21 | #ifdef __LP64__ 22 | typedef struct mach_header_64 mach_header_t; 23 | typedef struct segment_command_64 segment_command_t; 24 | typedef struct nlist_64 nlist_t; 25 | typedef uint64_t sectsize_t; 26 | #define getsectdatafromheader_f getsectdatafromheader_64 27 | #else 28 | typedef struct mach_header mach_header_t; 29 | typedef struct segment_command segment_command_t; 30 | typedef struct nlist nlist_t; 31 | typedef uint32_t sectsize_t; 32 | #define getsectdatafromheader_f getsectdatafromheader 33 | #endif 34 | 35 | typedef struct { const void *value; const char *name; } TrieSymbol; 36 | typedef void (^triecb)(const void *value, const char *name); 37 | 38 | typedef struct { 39 | const mach_header_t *header; 40 | const void *image_end; 41 | intptr_t file_slide; 42 | 43 | nlist_t *symbols; 44 | uint32_t symbol_count; 45 | intptr_t address_base; 46 | const char *strings_base; 47 | 48 | const uint8_t *exports_trie; 49 | uint32_t trie_size; 50 | TrieSymbol *trie_symbols; 51 | size_t trie_symbol_count; 52 | 53 | segment_command_t *segments[99]; 54 | } symbol_iterator; 55 | 56 | #if __cplusplus 57 | extern "C" { 58 | #endif 59 | extern void *self_caller_address(void); 60 | extern void init_symbol_iterator(const mach_header_t *header, 61 | symbol_iterator *state, 62 | bool isFile); 63 | 64 | #ifndef DLKit_C 65 | //#import 66 | @class NSArray, NSString; 67 | extern NSArray/* */ *trie_stackSymbols(); 68 | #endif 69 | extern int trie_dladdr(const void *value, Dl_info *info); 70 | extern void *trie_dlsym(const mach_header_t *image, const char *symbol); 71 | extern const symbol_iterator *trie_iterator(const void *header); 72 | extern void trie_register(const char *path, const mach_header_t *header); 73 | extern const void *exportsLookup(const symbol_iterator *state, const char *symbol); 74 | extern const void *exportsTrieTraverse(const symbol_iterator *state, const uint8_t *p, 75 | const char *buffer, char *bptr, triecb cb); 76 | #endif 77 | #if __cplusplus 78 | } 79 | #endif 80 | -------------------------------------------------------------------------------- /Sources/DLKitC/trie_dladdr.mm: -------------------------------------------------------------------------------- 1 | // 2 | // trie_dladdr.mm 3 | // DLKit 4 | // 5 | // Created by John Holdsworth on 08/04/2024. 6 | // Copyright © 2024 John Holdsworth. All rights reserved. 7 | // 8 | // Repo: https://github.com/johnno1962/DLKit 9 | // $Id: //depot/DLKit/Sources/DLKitC/trie_dladdr.mm#9 $ 10 | // 11 | // dladdr() able to resolve symbols from "exports trie". 12 | // 13 | 14 | #if DEBUG || !DEBUG_ONLY 15 | #if __has_include() 16 | #import 17 | #import "DLKitC.h" 18 | #include 19 | #include 20 | 21 | class ImageSymbols { 22 | public: 23 | const char *path; 24 | const void *header; 25 | symbol_iterator state; 26 | std::vector symbols; 27 | bool isFile; 28 | 29 | ImageSymbols(const char *path, const mach_header_t *header, bool isFile = false) { 30 | this->path = path; 31 | this->header = header; 32 | this->isFile = isFile; 33 | memset(&state, 0, sizeof state); 34 | } 35 | void trie_populate() { 36 | /// not initialised, add symbols found in "exports trie" 37 | char *buffer = (char *)malloc(state.trie_size+1); 38 | __block std::map exists; 39 | exportsTrieTraverse(&state, state.exports_trie, buffer, buffer, 40 | ^(const void *value, const char *name) { 41 | if (exists[value]) 42 | return; 43 | TrieSymbol entry = {value, strdup(name)}; 44 | exists[entry.value] = entry.name; 45 | symbols.push_back(entry); 46 | }); 47 | free(buffer); 48 | 49 | /// Fold in any other legacy symbols found 50 | for (int sno=0; sno < state.symbol_count; sno++) { 51 | TrieSymbol entry; 52 | entry.value = (char *)state.address_base + state.symbols[sno].n_value; 53 | if (state.symbols[sno].n_sect == NO_SECT || exists[entry.value]) 54 | continue; 55 | entry.name = state.strings_base + state.symbols[sno].n_un.n_strx; 56 | exists[entry.value] = entry.name; 57 | symbols.push_back(entry); 58 | } 59 | 60 | sort(symbols.begin(), symbols.end()); 61 | 62 | state.trie_symbols = symbols.data(); 63 | state.trie_symbol_count = symbols.size(); 64 | 65 | #if VERIFY_DLADDR 66 | int i=0; 67 | for (auto &s : symbols) { 68 | void *v = dlsym(RTLD_DEFAULT, s.name+1); 69 | if (s.value != v && v) 70 | printf("%d %d %p %p %s\n", symbols.size(), i++, s.value, v, strrchr(path, '/')); 71 | } 72 | #endif 73 | } 74 | }; 75 | 76 | static bool operator < (const ImageSymbols &s1, const ImageSymbols &s2) { 77 | return s1.header < s2.header; 78 | } 79 | 80 | static bool operator < (const TrieSymbol &s1, const TrieSymbol &s2) { 81 | return s1.value < s2.value; 82 | } 83 | 84 | template static ptrdiff_t equalOrGreater( 85 | const std::vector &array, T &value) { 86 | auto it = upper_bound(array.begin(), array.end(), value); 87 | if (it == array.end()) 88 | return -2; 89 | return distance(array.begin(), it)-1; 90 | } 91 | 92 | static std::vector image_store; 93 | 94 | void trie_register(const char *path, const mach_header_t *header) { 95 | image_store.push_back(ImageSymbols(strdup(path), header, true)); 96 | sort(image_store.begin(), image_store.end()); 97 | } 98 | 99 | static const ImageSymbols *trie_symbols(const void *ptr) { 100 | /// Maintain data for all loaded images 101 | if (image_store.size() < _dyld_image_count()) { 102 | for (uint32_t i=(uint32_t)image_store.size(); i<_dyld_image_count(); i++) { 103 | image_store.push_back(ImageSymbols(_dyld_get_image_name(i), 104 | (mach_header_t *)_dyld_get_image_header(i))); 105 | } 106 | sort(image_store.begin(), image_store.end()); 107 | } 108 | 109 | /// Find relevant image 110 | ImageSymbols finder(nullptr, (mach_header_t *)ptr); 111 | intptr_t imageno = equalOrGreater(image_store, finder); 112 | if (imageno<0) 113 | return nullptr; 114 | 115 | ImageSymbols &store = image_store[imageno]; 116 | if (!store.state.header) 117 | init_symbol_iterator((mach_header_t *)store.header, &store.state, store.isFile); 118 | if (ptr > store.state.image_end) 119 | return nullptr; 120 | if (!store.symbols.size()) 121 | store.trie_populate(); 122 | return &store; 123 | } 124 | 125 | const symbol_iterator *trie_iterator(const void *header) { 126 | if (const ImageSymbols *store = trie_symbols(header)) 127 | return &store->state; 128 | return nullptr; 129 | } 130 | 131 | int trie_dladdr(const void *ptr, Dl_info *info) { 132 | const ImageSymbols *store = trie_symbols(ptr); 133 | info->dli_fname = "Image not found"; 134 | info->dli_sname = "Symbol not found"; 135 | if (!store) 136 | return 0; 137 | 138 | info->dli_fbase = const_cast(store->header); 139 | info->dli_fname = store->path; 140 | 141 | /// Find actual symbol 142 | TrieSymbol finder; 143 | finder.value = ptr; 144 | ptrdiff_t found = equalOrGreater(store->symbols, finder); 145 | if (found < 0) 146 | return 0; 147 | 148 | /// Populate Dl_info output struct 149 | const TrieSymbol &entry = store->symbols[found]; 150 | info->dli_saddr = const_cast(entry.value); 151 | info->dli_sname = entry.name; 152 | if (*info->dli_sname == '_') 153 | info->dli_sname++; 154 | return 1; 155 | } 156 | 157 | void *trie_dlsym(const mach_header_t *image, const char *symbol) { 158 | if (const ImageSymbols *store = trie_symbols(image)) 159 | return (void *)exportsLookup(&store->state, symbol); 160 | return nullptr; 161 | } 162 | 163 | NSArray/* */ *trie_stackSymbols() { 164 | NSMutableArray *out = [NSMutableArray new]; 165 | Dl_info info; 166 | for (NSValue *caller in [NSThread callStackReturnAddresses]) { 167 | void *pointer = caller.pointerValue; 168 | if (trie_dladdr(pointer, &info)) 169 | [out addObject:[NSString stringWithUTF8String:info.dli_sname]]; 170 | else 171 | [out addObject:[NSString stringWithFormat:@"%p", pointer]]; 172 | } 173 | return out; 174 | } 175 | #endif 176 | #endif 177 | -------------------------------------------------------------------------------- /Sources/DLKitC/trie_dlops.mm: -------------------------------------------------------------------------------- 1 | // 2 | // trie_dlops.mm 3 | // DLKit 4 | // 5 | // Created by John Holdsworth on 08/04/2024. 6 | // Copyright © 2024 John Holdsworth. All rights reserved. 7 | // 8 | // Repo: https://github.com/johnno1962/DLKit 9 | // $Id: //depot/DLKit/Sources/DLKitC/trie_dlops.mm#9 $ 10 | // 11 | // Lookup/traversal of symbols in "exports trie" for trie_dladdr(). 12 | // 13 | // 14 | 15 | #if DEBUG || !DEBUG_ONLY 16 | #if __has_include() 17 | #import 18 | #import "DLKitC.h" 19 | #import 20 | 21 | // Derived from https://github.com/apple-oss-distributions/dyld/blob/main/common/MachOFile.cpp#L2768 22 | 23 | /* 24 | * Copyright (c) 2017 Apple Inc. All rights reserved. 25 | * 26 | * @APPLE_LICENSE_HEADER_START@ 27 | * 28 | * This file contains Original Code and/or Modifications of Original Code 29 | * as defined in and that are subject to the Apple Public Source License 30 | * Version 2.0 (the 'License'). You may not use this file except in 31 | * compliance with the License. Please obtain a copy of the License at 32 | * http://www.opensource.apple.com/apsl/ and read it before using this 33 | * file. 34 | * 35 | * The Original Code and all software distributed under the License are 36 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 37 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 38 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 39 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 40 | * Please see the License for the specific language governing rights and 41 | * limitations under the License. 42 | * 43 | * @APPLE_LICENSE_HEADER_END@ 44 | */ 45 | 46 | static uint64_t read_uleb128(const uint8_t*& p, const uint8_t* end, bool& malformed) { 47 | uint64_t result = 0; 48 | int bit = 0; 49 | malformed = false; 50 | do { 51 | if ( p == end ) { 52 | malformed = true; 53 | break; 54 | } 55 | uint64_t slice = *p & 0x7f; 56 | 57 | if ( bit > 63 ) { 58 | malformed = true; 59 | break; 60 | } 61 | else { 62 | result |= (slice << bit); 63 | bit += 7; 64 | } 65 | } 66 | while (*p++ & 0x80); 67 | return result; 68 | } 69 | 70 | // Lookup a symbol using the "exports trie" (requires the initial "_"). 71 | const void *exportsLookup(const symbol_iterator *state, const char *symbol) { 72 | return exportsTrieTraverse(state, state->exports_trie, symbol, nullptr, (triecb)0); 73 | } 74 | 75 | // Dynamic linker lookup code adapted to perform a complete traversal of the "trie". 76 | const void *exportsTrieTraverse(const symbol_iterator *state, const uint8_t *p, 77 | const char *symbol, char *bptr, triecb callback) { 78 | const uint8_t *start = state->exports_trie; 79 | const uint8_t *end = start + state->trie_size; 80 | if (!start) 81 | return nullptr; 82 | std::vector visitedNodeOffsets; 83 | bool malformed = false; 84 | while ( p < end ) { 85 | uint64_t terminalSize = *p++; 86 | if ( terminalSize > 127 ) { 87 | // except for re-export-with-rename, all terminal sizes fit in one byte 88 | --p; 89 | terminalSize = read_uleb128(/*diag, */p, end, malformed); 90 | if ( malformed ) 91 | return nullptr; 92 | } 93 | if ( (callback || *symbol == '\0') && (terminalSize != 0) ) { 94 | const uint8_t *ptmp = p; 95 | (void)read_uleb128(ptmp, end, malformed); 96 | void *value = (char *)state->header + read_uleb128(ptmp, end, malformed); 97 | if (!callback) 98 | return value; 99 | *bptr = 0; 100 | callback(value, symbol); 101 | } 102 | const uint8_t* children = p + terminalSize; 103 | if ( children > end ) { 104 | fprintf(stderr, "malformed trie node, terminalSize=0x%llX extends past end of trie\n", terminalSize); 105 | return nullptr; 106 | } 107 | uint8_t childrenRemaining = *children++; 108 | p = children; 109 | uint64_t nodeOffset = 0; 110 | char *sbptr = bptr; 111 | for (; childrenRemaining > 0; --childrenRemaining) { 112 | const char* ss = symbol; 113 | bptr = sbptr; 114 | bool wrongEdge = false; 115 | // scan whole edge to get to next edge 116 | // if edge is longer than target symbol name, don't read past end of symbol name 117 | char c = *p; 118 | while ( c != '\0' ) { 119 | if (callback) 120 | *bptr++ = c; 121 | else if ( !wrongEdge ) { 122 | if ( c != *ss ) 123 | wrongEdge = true; 124 | ++ss; 125 | } 126 | ++p; 127 | c = *p; 128 | } 129 | if ( wrongEdge ) { 130 | // advance to next child 131 | ++p; // skip over zero terminator 132 | // skip over uleb128 until last byte is found 133 | while ( (*p & 0x80) != 0 ) 134 | ++p; 135 | ++p; // skip over last byte of uleb128 136 | if ( p > end ) { 137 | fprintf(stderr, "malformed trie node, child node extends past end of trie\n"); 138 | return nullptr; 139 | } 140 | } 141 | else { 142 | // the symbol so far matches this edge (child) 143 | // so advance to the child's node 144 | ++p; 145 | nodeOffset = read_uleb128(/*diag,*/ p, end, malformed); 146 | if ( malformed ) 147 | return nullptr; 148 | if ( (nodeOffset == 0) || ( &start[nodeOffset] > end) ) { 149 | fprintf(stderr, "malformed trie child, nodeOffset=0x%llX out of range\n", nodeOffset); 150 | return nullptr; 151 | } 152 | if (!callback) { 153 | symbol = ss; 154 | break; 155 | } 156 | exportsTrieTraverse(state, &state->exports_trie[nodeOffset], symbol, bptr, callback); 157 | nodeOffset = 0; 158 | } 159 | } 160 | if ( nodeOffset != 0 ) { 161 | if ( nodeOffset > (uint64_t)(end-start) ) { 162 | fprintf(stderr, "malformed trie child, nodeOffset=0x%llX out of range\n", nodeOffset); 163 | return nullptr; 164 | } 165 | // check for cycles 166 | for (uint32_t aVisitedNodeOffset : visitedNodeOffsets) { 167 | if ( aVisitedNodeOffset == nodeOffset ) { 168 | fprintf(stderr, "malformed trie child, cycle to nodeOffset=0x%llX\n", nodeOffset); 169 | return nullptr; 170 | } 171 | } 172 | visitedNodeOffsets.push_back((uint32_t)nodeOffset); 173 | p = &start[nodeOffset]; 174 | } 175 | else 176 | p = end; 177 | } 178 | return nullptr; 179 | } 180 | #endif 181 | #endif 182 | -------------------------------------------------------------------------------- /Sources/DLKitCD/DLKitC.c: -------------------------------------------------------------------------------- 1 | ../DLKitC/DLKitC.c -------------------------------------------------------------------------------- /Sources/DLKitCD/include/DLKitC.h: -------------------------------------------------------------------------------- 1 | ../../DLKitC/include/DLKitC.h -------------------------------------------------------------------------------- /Sources/DLKitCD/trie_dladdr.mm: -------------------------------------------------------------------------------- 1 | ../DLKitC/trie_dladdr.mm -------------------------------------------------------------------------------- /Sources/DLKitCD/trie_dlops.mm: -------------------------------------------------------------------------------- 1 | ../DLKitC/trie_dlops.mm -------------------------------------------------------------------------------- /Sources/DLKitD: -------------------------------------------------------------------------------- 1 | DLKit -------------------------------------------------------------------------------- /Tests/DLKitTests/DLKitTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DLKitTests.swift 3 | // DLKit 4 | // 5 | // Created by John Holdsworth on 19/04/2021. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | // Repo: https://github.com/johnno1962/DLKit 9 | // $Id: //depot/DLKit/Tests/DLKitTests/DLKitTests.swift#15 $ 10 | // 11 | 12 | import XCTest 13 | // @testable 14 | import DLKit 15 | 16 | final class DLKitTests: XCTestCase { 17 | func testExample() { 18 | // This is an example of a functional test case. 19 | // Use XCTAssert and related functions to verify your tests produce the correct 20 | // results. 21 | print(DLKit.mainImage) 22 | let mangledTestClassSymbol = "$s10DLKitTestsAACN" 23 | let testClassSwift = "type metadata for DLKitTests.DLKitTests" 24 | guard let testImage = DLKit.imageMap["DLKitTests"] else { 25 | XCTFail("Could not locate test image") 26 | return 27 | } 28 | guard let mangled = testImage.mangle(swift: testClassSwift)?.name, 29 | mangledTestClassSymbol == String(cString: mangled) else { 30 | XCTFail("Re-mangling test") 31 | return 32 | } 33 | XCTAssertEqual(DLKit.selfImage, testImage, "Images equal") 34 | for entry in testImage where entry.value != nil { 35 | print(entry.name.demangled ?? 36 | String(cString: entry.name), entry.value as Any) 37 | } 38 | guard let pointer1 = testImage[[mangledTestClassSymbol]][0] else { 39 | XCTFail("Symbol lookup fails") 40 | return 41 | } 42 | guard testImage.swiftSymbols(withSuffixes: ["CN"]) 43 | .map({ $0.name.demangled }).contains(testClassSwift) else { 44 | XCTFail("Symbol filter fails") 45 | return 46 | } 47 | guard let pointer2 = DLKit.allImages[mangledTestClassSymbol] else { 48 | XCTFail("Global symbol lookup fails") 49 | return 50 | } 51 | XCTAssertEqual(pointer1, pointer2, "Pointers equal") 52 | guard let info = DLKit.allImages[pointer2] else { 53 | XCTFail("Reverse lookup fails") 54 | return 55 | } 56 | XCTAssertEqual(info.image, testImage, "Images equal") 57 | XCTAssertEqual(String(cString: info.name), 58 | mangledTestClassSymbol, "Symbol names equal") 59 | 60 | // let sui = DLKit.imageMap["SwiftUI"]! 61 | // XCTAssert(Array(sui.entries).count < sui.trieSymbols()!.count) 62 | } 63 | 64 | static var allTests = [ 65 | ("testExample", testExample), 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /Tests/DLKitTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(DLKitTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import DLKitTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += DLKitTests.allTests() 7 | XCTMain(tests) 8 | --------------------------------------------------------------------------------