├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── Package.swift ├── README.md ├── SwiftTrace.gif ├── SwiftTrace.podspec ├── SwiftTrace ├── EasyPointer.swift ├── Info.plist ├── StringIndex.swift ├── SwiftArgs.swift ├── SwiftAspects.swift ├── SwiftInterpose.swift ├── SwiftInvoke.swift ├── SwiftLifetime.swift ├── SwiftMeta.swift ├── SwiftStack.swift ├── SwiftStats.swift ├── SwiftSwizzle.swift ├── SwiftTrace.h └── SwiftTrace.swift ├── SwiftTraceApp.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── SwiftTraceApp ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── DetailViewController.swift ├── Info.plist └── MasterViewController.swift ├── SwiftTraceD ├── SwiftTraceGuts ├── ObjCBridge.mm ├── SwiftTrace-Swift.h ├── SwiftTrace.mm ├── Trampolines.mm ├── fast_dladdr.mm ├── fishhook.c ├── include │ ├── SwiftTrace.h │ └── fishhook.h ├── xt_forwarding_trampoline_arm64.s ├── xt_forwarding_trampoline_arm7.s ├── xt_forwarding_trampoline_x64.s └── xt_forwarding_trampoline_x86.s ├── SwiftTraceGutsD ├── ObjCBridge.mm ├── SwiftTrace-Swift.h ├── SwiftTrace.mm ├── Trampolines.mm ├── fast_dladdr.mm ├── fishhook.c ├── include │ ├── SwiftTrace.h │ └── fishhook.h ├── xt_forwarding_trampoline_arm64.s ├── xt_forwarding_trampoline_arm7.s ├── xt_forwarding_trampoline_x64.s └── xt_forwarding_trampoline_x86.s ├── SwiftTraceOSX ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ └── MainMenu.xib ├── Info.plist └── SwiftTwaceOSX-Bridging-Header.h ├── SwiftTraceTests ├── Info.plist └── SwiftTraceTests.swift └── SwiftTraceXTests ├── Info.plist └── SwiftTraceXTests.swift /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: johnno1962 # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | *Library/* 3 | *xcuserdata* 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 John Holdsworth 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | This software contains code written by Oliver Letterer obtained from the 22 | following github project which is licensed under the terms of that project: 23 | 24 | https://github.com/OliverLetterer/imp_implementationForwardingToSelector 25 | 26 | Now uses the very handy https://github.com/facebook/fishhook. 27 | See the source and header files for licensing details. 28 | -------------------------------------------------------------------------------- /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 | // Repo: https://github.com/johnno1962/SwiftTrace 5 | // $Id: //depot/SwiftTrace/Package.swift#14 $ 6 | // 7 | 8 | import PackageDescription 9 | 10 | let package = Package( 11 | name: "SwiftTrace", 12 | platforms: [.macOS("10.12"), .iOS("10.0")], 13 | products: [ 14 | // SwiftTrace needs to be .dynamic for 15 | // the trampolines to work on Intel. 16 | .library(name: "SwiftTrace", type: .dynamic, targets: ["SwiftTrace"]), 17 | .library(name: "SwiftTraceGuts", type: .dynamic, targets: ["SwiftTraceGuts"]), 18 | .library(name: "SwiftTraceD", type: .dynamic, targets: ["SwiftTraceD"]), 19 | .library(name: "SwiftTraceGutsD", type: .dynamic, targets: ["SwiftTraceGutsD"]), 20 | ], 21 | dependencies: [], 22 | targets: [ 23 | .target(name: "SwiftTrace", dependencies: ["SwiftTraceGuts"], path: "SwiftTrace/"), 24 | .target(name: "SwiftTraceGuts", dependencies: [], path: "SwiftTraceGuts/"), 25 | .target(name: "SwiftTraceD", dependencies: ["SwiftTraceGutsD"], 26 | path: "SwiftTraceD/", swiftSettings: [.define("DEBUG_ONLY")]), 27 | .target(name: "SwiftTraceGutsD", dependencies: [], 28 | path: "SwiftTraceGutsD/", cSettings: [.define("DEBUG_ONLY")]), 29 | ], 30 | cxxLanguageStandard: .cxx11 31 | ) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftTrace 2 | 3 | Trace Swift and Objective-C method invocations of non-final classes in an app bundle or framework. 4 | Think [Xtrace](https://github.com/johnno1962/Xtrace) but for Swift and Objective-C. You can also 5 | add "aspects" to member functions of non-final Swift classes to have a closure called before or after 6 | a function implementation executes which in turn can modify incoming arguments or the return value! 7 | Apart from the logging functionality, with binary distribution of Swift frameworks on the horizon perhaps 8 | this will be of use in the same way "Swizzling" was in days of yore. 9 | 10 | ![SwiftTrace Example](SwiftTrace.gif) 11 | 12 | Note: none of these features will work on a class or method that is final or internal in 13 | a module compiled with whole module optimisation as the dispatch of the method 14 | will be "direct" i.e. linked to a symbol at the call site rather than going through the 15 | class' vtable. As such it is possible to trace calls to methods of a struct but only 16 | if they are referenced through a protocol as they use a `witness table` which 17 | can be patched. 18 | 19 | SwiftTrace can be used with the Swift Package Manager or as a CocoaPod by 20 | adding the following to your project's Podfile: 21 | 22 | ```Swift 23 | pod 'SwiftTrace' 24 | ``` 25 | Once the project has rebuilt, import SwiftTrace into the application's AppDelegate and add something like the following to the beginning of 26 | it's didFinishLaunchingWithOptions method: 27 | 28 | ```Swift 29 | SwiftTrace.traceBundle(containing: type(of: self)) 30 | ``` 31 | This traces all classes defined in the main application bundle. 32 | To trace, for example, all classes in the RxSwift Pod add the following 33 | 34 | ```Swift 35 | SwiftTrace.traceBundle(containing: RxSwift.DisposeBase.self) 36 | ``` 37 | This gives output in the Xcode debug console such as that above. 38 | 39 | To trace a system framework such as UIKit you can trace classes using a pattern: 40 | 41 | ```Swift 42 | SwiftTrace.traceClasses(matchingPattern:"^UI") 43 | ``` 44 | Individual classes can be traced using the underlying api: 45 | 46 | ```Swift 47 | SwiftTrace.trace(aClass: MyClass.self) 48 | ``` 49 | Or to trace all methods of instances of a particular class including those of their superclasses 50 | use the following: 51 | 52 | ```Swift 53 | SwiftTrace.traceInstances(ofClass: aClass) 54 | ``` 55 | Or to trace only a particular instance use the following: 56 | 57 | ```Swift 58 | SwiftTrace.trace(anInstance: anObject) 59 | ``` 60 | If you have specified `"-Xlinker -interposable"` in your project's `"Other Linker Flags"` 61 | it's possible to trace all methods in the application's main bundle at once which can be 62 | useful for profiling `SwiftUI` using the following call: 63 | 64 | ```Swift 65 | SwiftTrace.traceMainBundleMethods() 66 | ``` 67 | It is possible to trace methods of a structs or other types if they are messaged through 68 | protools as this would then be indirect via what is called a `witness table`. Tracing 69 | protocols is available at the bundle level where the bundle being traced is specified 70 | using a class instance. They can be further filtered by an optional regular expression. 71 | For example, the following: 72 | ```Swift 73 | SwiftTrace.traceProtocolsInBundle(containing: AClassInTheBundle.self, matchingPattern: "regexp") 74 | ``` 75 | For example, to trace internal calls made in the `SwiftUI` framework you can use the following: 76 | ```Swift 77 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 78 | SwiftTrace.traceProtocolsInBundle(containing: UIHostingController.self) 79 | return true 80 | } 81 | ``` 82 | 83 | Which traces are applied can be filtered using method name inclusion and exclusion regexps. 84 | ```swift 85 | SwiftTrace.methodInclusionPattern = "TestClass" 86 | SwiftTrace.methodExclusionPattern = "init|"+SwiftTrace.defaultMethodExclusions 87 | ``` 88 | These methods must be called before you start the trace as they are applied during the "Swizzle" phase. 89 | There is a default set of exclusions setup as a result of testing by tracing UIKit. 90 | 91 | open class var defaultMethodExclusions: String { 92 | return """ 93 | \\.getter| (?:retain|_tryRetain|release|_isDeallocating|.cxx_destruct|dealloc|description| debugDescription)]|initWithCoder|\ 94 | ^\\+\\[(?:Reader_Base64|UI(?:NibStringIDTable|NibDecoder|CollectionViewData|WebTouchEventsGestureRecognizer)) |\ 95 | ^.\\[(?:UIView|RemoteCapture) |UIDeviceWhiteColor initWithWhite:alpha:|UIButton _defaultBackgroundImageForType:andState:|\ 96 | UIImage _initWithCompositedSymbolImageLayers:name:alignUsingBaselines:|\ 97 | _UIWindowSceneDeviceOrientationSettingsDiffAction _updateDeviceOrientationWithSettingObserverContext:windowScene:transitionContext:|\ 98 | UIColorEffect colorEffectSaturate:|UIWindow _windowWithContextId:|RxSwift.ScheduledDisposable.dispose| ns(?:li|is)_ 99 | """ 100 | } 101 | 102 | If you want to further process output you can define your own custom tracing sub class: 103 | ```swift 104 | class MyTracer: SwiftTrace.Decorated { 105 | 106 | override func onEntry(stack: inout SwiftTrace.EntryStack) { 107 | print( ">> "+stack ) 108 | } 109 | } 110 | 111 | SwiftTrace.swizzleFactory = MyTracer.self 112 | ``` 113 | As the amount of of data logged can quickly get out of hand you can control what is 114 | logged by combing traces with the optional `subLevels` parameter to the above functions. 115 | For example, the following puts a trace on all of UIKit but will only log calls to methods 116 | of the target instance and up to three levels of calls those method make: 117 | ```Swift 118 | SwiftTrace.traceBundle(containing: UIView.self) 119 | SwiftTrace.trace(anInstance: anObject, subLevels: 3) 120 | ``` 121 | Or, the following will log methods of the application and calls to RxSwift they make: 122 | ```Swift 123 | SwiftTrace.traceBundle(containing: RxSwift.DisposeBase.self) 124 | SwiftTrace.traceMainBundle(subLevels: 3) 125 | ``` 126 | If this seems arbitrary the rules are reasonably simple. When you add a trace with a 127 | non-zero subLevels parameter all previous traces are inhibited unless they are being 128 | made up to subLevels inside a method in the most recent trace or if they where filtered 129 | anyway by a class or instance (traceInstances(ofClass:) and trace(anInstance:)). 130 | 131 | If you would like to extend SwiftTrace to be able to log one of your app's types 132 | there are two steps. First, you may need to extend the type to conform to 133 | SwiftTraceFloatArg if it contains only float only float types for example SwiftUI.EdgeInsets. 134 | ```Swift 135 | extension SwiftUI.EdgeInsets: SwiftTraceFloatArg {} 136 | ``` 137 | Then, add a handler for the type using the following api: 138 | ```Swift 139 | SwiftTrace.addFormattedType(SwiftUI.EdgeInsets.self, prefix: "SwiftUI") 140 | ``` 141 | Many of these API's are also available as a extension of NSObject which is useful 142 | when SwiftTrace is made available by dynamically loading bundle as in 143 | (InjectionIII)[https://github.com/johnno1962/InjectionIII]. 144 | ```Swift 145 | SwiftTrace.traceBundle(containing: UIView.class) 146 | // becomes 147 | UIView.traceBundle() 148 | 149 | SwiftTrace.trace(inInstance: anObject) 150 | // becomes 151 | anObject.swiftTraceInstance() 152 | ``` 153 | This is useful when SwiftTrace is made available by dynamically loading a bundle 154 | such as when using (InjectionIII)[https://github.com/johnno1962/InjectionIII]. Rather 155 | than having to include a CocoaPod, all you need to do is add SwiftTrace.h in the 156 | InjectionIII application's bundle to your bridging header and dynamically load the bundle. 157 | ```Swift 158 | Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load() 159 | ``` 160 | #### Benchmarking 161 | 162 | To benchmark an app or framework, trace it's methods then you can use one of the following: 163 | ``` 164 | SwiftTrace.sortedElapsedTimes(onlyFirst: 10)) 165 | SwiftTrace.sortedInvocationCounts(onlyFirst: 10)) 166 | ``` 167 | #### Object lifetime tracking 168 | 169 | You can track the allocations an deallocations of Swift and 170 | Objective-C classes using the [SwiftTrace.LifetimeTracker](https://github.com/johnno1962/SwiftTrace/blob/main/SwiftTrace/SwiftLifetime.swift) class: 171 | 172 | ```Swift 173 | SwiftTrace.swizzleFactory = SwiftTrace.LifetimeTracker.self 174 | SwiftTrace.traceMainBundleMethods() == 0 { 175 | print("⚠️ Tracing Swift methods can only work if you have -Xlinker -interposable to your project's \"Other Linker Flags\"") 176 | } 177 | SwiftTrace.traceMainBundle() 178 | ``` 179 | Each time an object is allocated you will see a `.__allocating_init` message 180 | followed by the result and the resulting count of live objects allocated 181 | since tracing was started. Each time an object is deallocated you will 182 | see a `cxx_destruct` message followed by the number of objects 183 | oustanding for that class. 184 | 185 | If you would like to track the lifecycle of Swift structs, create a marker 186 | class and add a property to the struct initialised to an instance of it. 187 | 188 | ```Swift 189 | class Marker {} 190 | 191 | struct MyView: SwiftUI.View { 192 | var marker = Marker() 193 | } 194 | ``` 195 | This idea is based on the [LifetimeTracker](https://github.com/krzysztofzablocki/LifetimeTracker) 196 | project by [Krzysztof Zabłocki](https://github.com/krzysztofzablocki). 197 | #### Aspects 198 | 199 | You can add an aspect to a particular method using the method's de-mangled name: 200 | ```swift 201 | print(SwiftTrace.addAspect(aClass: TestClass.self, 202 | methodName: "SwiftTwaceApp.TestClass.x() -> ()", 203 | onEntry: { (_, _) in print("ONE") }, 204 | onExit: { (_, _) in print("TWO") })) 205 | ``` 206 | This will print "ONE" when method "x" of TextClass is called and "TWO when it has exited. The 207 | two arguments are the Swizzle which is an object representing the "Swizzle" and the entry or 208 | exit stack. The full signature for the entry closure is: 209 | ```swift 210 | onEntry: { (swizzle: SwiftTrace.Swizzle, stack: inout SwiftTrace.EntryStack) in 211 | ``` 212 | If you understand how [registers are allocated](https://github.com/apple/swift/blob/master/docs/ABI/RegisterUsage.md) to arguments it is possible to poke into the 213 | stack to modify the incoming arguments and, for the exit aspect closure you can replace 214 | the return value and on a good day log (and prevent) an error being thrown. 215 | 216 | Replacing an input argument in the closure is relatively simple: 217 | ```swift 218 | stack.intArg1 = 99 219 | stack.floatArg3 = 77.3 220 | ``` 221 | Other types of argument a little more involved. They must be cast and String 222 | takes up two integer registers. 223 | ```swift 224 | swizzle.rebind(&stack.intArg2).pointee = "Grief" 225 | swizzle.rebind(&stack.intArg4).pointee = TestClass() 226 | ``` 227 | In an exit aspect closure, setting the return type is easier as it is generic: 228 | ```swift 229 | stack.setReturn(value: "Phew") 230 | ``` 231 | When a function throws you can access NSError objects. 232 | ```swift 233 | print(swizzle.rebind(&stack.thrownError, to: NSError.self).pointee) 234 | ``` 235 | It is possible to set `stack.thrownError` to zero to cancel the throw but you will need to set 236 | the return value. 237 | 238 | If this seems complicated there is a property `swizzle.arguments` which can be used 239 | `onEntry` which contains the arguments as an `Array` containing elements of type `Any` 240 | which can be cast to the expected type. Element 0 is `self`. 241 | 242 | #### Invocation interface 243 | 244 | Now we have a trampoline infrastructure, it is possible to implement an invocation api for Swift: 245 | ```swift 246 | print("Result: "+SwiftTrace.invoke(target: b, 247 | methodName: "SwiftTwaceApp.TestClass.zzz(_: Swift.Int, f: Swift.Double, g: Swift.Float, h: Swift.String, f1: Swift.Double, g1: Swift.Float, h1: Swift.Double, f2: Swift.Double, g2: Swift.Float, h2: Swift.Double, e: Swift.Int, ff: Swift.Int, o: SwiftTwaceApp.TestClass) throws -> Swift.String", 248 | args: 777, 101.0, Float(102.0), "2-2", 103.0, Float(104.0), 105.0, 106.0, Float(107.0), 108.0, 888, 999, TestClass())) 249 | ``` 250 | In order to determine the mangled name of a method you can get the full list for a class 251 | using this function: 252 | ```swift 253 | print(SwiftTrace.methodNames(ofClass: TestClass.self)) 254 | ``` 255 | There are limitations to this abbreviated interface in that it only supports Double, Float, 256 | String, Int, Object, CGRect, CGSize and CGPoint arguments. For other struct types that 257 | do not contain floating point values you can conform them to protocol `SwiftTraceArg` 258 | to be able to pass them on the argument list or `SwiftTraceFloatArg` if they contain 259 | only floats. These values and return values must fit into 32 bytes and not contain floats. 260 | 261 | #### How it works 262 | 263 | A Swift `AnyClass` instance has a layout similar to an Objective-C class with some 264 | additional data documented in the `ClassMetadataSwift` in SwiftMeta.swift. After this data 265 | there is the vtable of pointers to the class and instance member functions of the class up to 266 | the size of the class instance. SwiftTrace replaces these function pointers with a pointer 267 | to a unique assembly language "trampoline" entry point which has destination function and 268 | data pointers associated with it. Registers are saved and this function is called passing 269 | the data pointer to log the method name. The method name is determined by de-mangling the 270 | symbol name associated the function address of the implementing method. The registers are 271 | then restored and control is passed to the original function implementing the method. 272 | 273 | Please file an issue if you encounter a project that doesn't work while tracing. It should 274 | be far more reliable as it uses assembly language trampolines rather than Swizzling like 275 | Xtrace did. Otherwise, the author can be contacted on Twitter [@Injection4Xcode](https://twitter.com/@Injection4Xcode). 276 | 277 | Thanks to Oliver Letterer for the [imp_implementationForwardingToSelector](https://github.com/OliverLetterer/imp_implementationForwardingToSelector) project adapted to set up the 278 | trampolines, included under an MIT license. 279 | 280 | The repo includes a very slightly modified version of the very handy 281 | [https://github.com/facebook/fishhook](https://github.com/facebook/fishhook). 282 | See the source and header files for their licensing details. 283 | 284 | Thanks also to [@twostraws](https://twitter.com/twostraws)' 285 | [Unwrap](https://github.com/twostraws/Unwrap) and [@artsy](https://twitter.com/ArtsyOpenSource)'s 286 | [eidolon](https://github.com/artsy/eidolon) used extensively during testing. 287 | 288 | Enjoy! 289 | 290 | $Date: 2022/01/22 $ 291 | 292 | -------------------------------------------------------------------------------- /SwiftTrace.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/SwiftTrace/589f37149d5b32cbdd44c70809df23b4f4fa0260/SwiftTrace.gif -------------------------------------------------------------------------------- /SwiftTrace.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "SwiftTrace" 3 | s.version = "7.0.0" 4 | s.summary = "Log Swift or Objective-C method invocations" 5 | s.homepage = "https://github.com/johnno1962/SwiftTrace" 6 | s.social_media_url = "https://twitter.com/Injection4Xcode" 7 | s.documentation_url = "https://github.com/johnno1962/SwiftTrace/blob/master/README.md" 8 | s.license = { :type => "MIT" } 9 | s.authors = { "johnno1962" => "swifttrace@johnholdsworth.com" } 10 | 11 | s.osx.deployment_target = "10.10" 12 | s.ios.deployment_target = "10.0" 13 | s.source = { :git => "https://github.com/johnno1962/SwiftTrace.git", :tag => s.version } 14 | s.source_files = "{SwiftTrace,SwiftTraceGuts,SwiftTraceGuts/include}/*.{swift,h,mm,s}" 15 | end 16 | -------------------------------------------------------------------------------- /SwiftTrace/EasyPointer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EasyPointer.swift 3 | // EasyPointer 4 | // 5 | // Created by John Holdsworth on 29/10/2020. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | // Pragmatic extensions to unify conversions between various pointer types. 9 | // 10 | // You must watch this video for context on why this may be a very bad idea: 11 | // 12 | // https://developer.apple.com/videos/play/wwdc2020/10167/ 13 | // 14 | // Repo: https://github.com/johnno1962/EasyPointer.git 15 | // 16 | // $Id: //depot/EasyPointer/Sources/EasyPointer/EasyPointer.swift#7 $ 17 | // 18 | 19 | #if DEBUG || !DEBUG_ONLY 20 | public func autoBitCast(_ x: IN) -> OUT { 21 | if SwiftMeta.sizeof(anyType: IN.self) != 22 | SwiftMeta.sizeof(anyType: OUT.self) { 23 | print("⚠️ size mismatch, autoBitCast(\(IN.self)[\(SwiftMeta.sizeof(anyType: IN.self))]) -> \(OUT.self)[\(SwiftMeta.sizeof(anyType: OUT.self))]") 24 | } 25 | return unsafeBitCast(x, to: OUT.self) 26 | } 27 | 28 | extension UnsafePointer { 29 | public init(cast: UnsafePointer) { 30 | self = cast.withMemoryRebound(to: Pointee.self, capacity: 1) { $0 } 31 | } 32 | public init(cast: UnsafeMutablePointer) { 33 | self = autoBitCast(cast) 34 | } 35 | public init(cast: UnsafeMutableRawPointer) { 36 | self = autoBitCast(cast) 37 | } 38 | public init(cast: UnsafeRawPointer) { 39 | self = cast.bindMemory(to: Pointee.self, capacity: 1) 40 | } 41 | public init(cast: OpaquePointer) { 42 | self = autoBitCast(cast) 43 | } 44 | 45 | // It's handy to be able to compare Mutable and non-Mutable pointers 46 | public static func == (lhs: UnsafePointer, 47 | rhs: UnsafeMutablePointer) -> Bool { 48 | return lhs == UnsafePointer(cast: rhs) 49 | } 50 | public static func == (lhs: UnsafeMutablePointer, 51 | rhs: UnsafePointer) -> Bool { 52 | return UnsafePointer(cast: lhs) == lhs 53 | } 54 | public static func != (lhs: UnsafePointer, 55 | rhs: UnsafeMutablePointer) -> Bool { 56 | return lhs != UnsafePointer(cast: rhs) 57 | } 58 | public static func != (lhs: UnsafeMutablePointer, 59 | rhs: UnsafePointer) -> Bool { 60 | return UnsafePointer(cast: lhs) != lhs 61 | } 62 | public static func < (lhs: UnsafePointer, 63 | rhs: UnsafeMutablePointer) -> Bool { 64 | return lhs == UnsafePointer(cast: rhs) 65 | } 66 | public static func < (lhs: UnsafeMutablePointer, 67 | rhs: UnsafePointer) -> Bool { 68 | return UnsafePointer(cast: lhs) < lhs 69 | } 70 | } 71 | 72 | extension UnsafeMutablePointer { 73 | public init(cast: UnsafeMutablePointer) { 74 | self = cast.withMemoryRebound(to: Pointee.self, capacity: 1) { $0 } 75 | } 76 | public init(mutating cast: UnsafeRawPointer) { 77 | self = autoBitCast(cast) 78 | } 79 | public init(cast: UnsafeMutableRawPointer) { 80 | self = cast.bindMemory(to: Pointee.self, capacity: 1) 81 | } 82 | public init(cast: OpaquePointer) { 83 | self = autoBitCast(cast) 84 | } 85 | } 86 | 87 | // Mutable-unmutable comparisons 88 | extension UnsafeRawPointer { 89 | public static func == (lhs: UnsafeRawPointer, 90 | rhs: UnsafeMutableRawPointer) -> Bool { 91 | return lhs == UnsafeRawPointer(rhs) 92 | } 93 | public static func == (lhs: UnsafeMutableRawPointer, 94 | rhs: UnsafeRawPointer) -> Bool { 95 | return UnsafeRawPointer(lhs) == rhs 96 | } 97 | public static func != (lhs: UnsafeRawPointer, 98 | rhs: UnsafeMutableRawPointer) -> Bool { 99 | return lhs != UnsafeRawPointer(rhs) 100 | } 101 | public static func != (lhs: UnsafeMutableRawPointer, 102 | rhs: UnsafeRawPointer) -> Bool { 103 | return UnsafeRawPointer(lhs) != rhs 104 | } 105 | public static func < (lhs: UnsafeRawPointer, 106 | rhs: UnsafeMutableRawPointer) -> Bool { 107 | return lhs < UnsafeRawPointer(rhs) 108 | } 109 | public static func < (lhs: UnsafeMutableRawPointer, 110 | rhs: UnsafeRawPointer) -> Bool { 111 | return UnsafeRawPointer(lhs) < rhs 112 | } 113 | } 114 | #endif 115 | -------------------------------------------------------------------------------- /SwiftTrace/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SwiftTrace/StringIndex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringIndex.swift 3 | // StringIndex 4 | // 5 | // Created by John Holdsworth on 25/10/2020. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | // A few operators simplifying offsettting a String index 9 | // 10 | // Repo: https://github.com/johnno1962/StringIndex.git 11 | // 12 | // $Id: //depot/StringIndex/Sources/StringIndex/StringIndex.swift#34 $ 13 | // 14 | 15 | #if DEBUG || !DEBUG_ONLY 16 | import Foundation 17 | 18 | // Basic operators to offset String.Index when used in a subscript 19 | public func + (index: String.Index?, offset: Int) -> String.OffsetIndex { 20 | return .offsetIndex(index: index, offset: offset) 21 | } 22 | public func - (index: String.Index?, offset: Int) -> String.OffsetIndex { 23 | return index + -offset 24 | } 25 | public func + (index: String.Index?, offset: String.OffsetIndex) 26 | -> String.OffsetIndex { 27 | return .offsetIndex(index: index, offset: 0) + offset 28 | } 29 | 30 | // Mixed String.Index and OffsetIndex in range 31 | public func ..< (lhs: String.OffsetIndex, rhs: String.Index?) 32 | -> Range { 33 | return lhs ..< rhs+0 34 | } 35 | public func ..< (lhs: String.Index?, rhs: String.OffsetIndex) 36 | -> Range { 37 | return lhs+0 ..< rhs 38 | } 39 | 40 | extension Range where Bound == String.Index { 41 | public init?(_ range: Range, in string: S) { 42 | guard let lower = string.index(of: range.lowerBound), 43 | let upper = string.index(of: range.upperBound), 44 | lower <= upper else { 45 | return nil 46 | } 47 | self = lower ..< upper 48 | } 49 | } 50 | 51 | extension NSRange { 52 | public init?(_ range: Range, in string: S) { 53 | guard let lower = string.index(of: range.lowerBound), 54 | let upper = string.index(of: range.upperBound), 55 | lower <= upper else { 56 | return nil 57 | } 58 | self.init(lower ..< upper, in: string) 59 | } 60 | } 61 | 62 | extension String { 63 | 64 | /// Represents an index to be offset 65 | public indirect enum OffsetIndex: Comparable { 66 | case offsetIndex(index: Index?, offset: Int), start, end, 67 | first(of: String, regex: Bool = false, end: Bool = false), 68 | last(of: String, regex: Bool = false, end: Bool = false), 69 | either(_ index: OffsetIndex, or: OffsetIndex), 70 | // can chain either an OffsetIndex or an integer offset 71 | chained(previous: OffsetIndex, next: OffsetIndex?, offset: Int) 72 | 73 | // Chaining offsets in expressions 74 | public static func + (index: OffsetIndex, offset: Int) -> OffsetIndex { 75 | return .chained(previous: index, next: nil, offset: offset) 76 | } 77 | public static func - (index: OffsetIndex, offset: Int) -> OffsetIndex { 78 | return index + -offset 79 | } 80 | public static func + (index: OffsetIndex, 81 | offset: OffsetIndex) -> OffsetIndex { 82 | return .chained(previous: index, next: offset, offset: 0) 83 | } 84 | public static func || (either: OffsetIndex, 85 | or: OffsetIndex) -> OffsetIndex { 86 | return .either(either, or: or) 87 | } 88 | 89 | /// Required by Comparable to check when creating ranges 90 | public static func < (lhs: OffsetIndex, rhs: OffsetIndex) -> Bool { 91 | return false // slight cheat here as we don't know the string 92 | } 93 | } 94 | } 95 | 96 | extension StringProtocol { 97 | public typealias OffsetIndex = String.OffsetIndex 98 | public typealias OISubstring = String // Can/should? be Substring 99 | public typealias OOISubstring = OISubstring? // "safe:" prefixed subscripts 100 | 101 | /// realise index from OffsetIndex 102 | public func index(of offset: OffsetIndex, from: Index? = nil) -> Index? { 103 | switch offset { 104 | case .offsetIndex(let index, let offset): 105 | guard let index = index else { return nil } 106 | return safeIndex(index, offsetBy: offset) 107 | 108 | // Public interface 109 | case .start: 110 | return startIndex 111 | case .end: 112 | return endIndex 113 | case .first(let target, let regex, let end): 114 | return locate(target: target, from: from, 115 | last: false, regex: regex, end: end) 116 | case .last(let target, let regex, let end): 117 | return locate(target: target, from: from, 118 | last: true, regex: regex, end: end) 119 | case .either(let first, let second): 120 | return index(of: first) ?? index(of: second) 121 | 122 | case .chained(let previous, let next, let offset): 123 | guard let from = index(of: previous) else { return nil } 124 | return next != nil ? index(of: next!, from: from) : 125 | safeIndex(from, offsetBy: offset) 126 | } 127 | } 128 | 129 | /// nilable version of index(_ i: Self.Index, offsetBy: Int) 130 | public func safeIndex(_ from: Index, offsetBy: Int) -> Index? { 131 | var from = from, offset = offsetBy 132 | while offset < 0 && from > startIndex { 133 | from = index(before: from) 134 | offset += 1 135 | } 136 | while offset > 0 && from < endIndex { 137 | from = index(after: from) 138 | offset -= 1 139 | } 140 | return offset == 0 ? from : nil 141 | } 142 | 143 | public func locate(target: String, from: Index?, 144 | last: Bool, regex: Bool, end: Bool) -> Index? { 145 | let bounds = last ? 146 | startIndex..<(from ?? endIndex) : 147 | (from ?? startIndex).. Character { 178 | get { 179 | guard let result = self[safe: offset] else { 180 | fatalError("Invalid offset index \(offset), \(#function)") 181 | } 182 | return result 183 | } 184 | set (newValue) { 185 | guard let start = index(of: offset) else { 186 | fatalError("Invalid offset index \(offset), \(#function)") 187 | } 188 | // Assigning Chacater to endIndex is an append. 189 | let end = start + (start < endIndex ? 1 : 0) 190 | self[start ..< end] = OISubstring(String(newValue)) 191 | } 192 | } 193 | 194 | // lhs ..< rhs operator 195 | public subscript (range: Range) -> OISubstring { 196 | get { 197 | guard let result = self[safe: range] else { 198 | fatalError("Invalid range of offset index \(range), \(#function)") 199 | } 200 | return result 201 | } 202 | set (newValue) { 203 | guard let from = index(of: range.lowerBound), 204 | let to = index(of: range.upperBound) else { 205 | fatalError("Invalid range of offset index \(range), \(#function)") 206 | } 207 | let before = self[..) -> OISubstring { 213 | get { return self[.start ..< range.upperBound] } 214 | set (newValue) { self[.start ..< range.upperBound] = newValue } 215 | } 216 | // lhs... operator 217 | public subscript (range: PartialRangeFrom) -> OISubstring { 218 | get { return self[range.lowerBound ..< .end] } 219 | set (newValue) { self[range.lowerBound ..< .end] = newValue } 220 | } 221 | 222 | // ================================================================= 223 | // "safe" nil returning subscripts on StringProtocol for OffsetIndex 224 | // from: https://forums.swift.org/t/optional-safe-subscripting-for-arrays 225 | public subscript (safe offset: OffsetIndex) -> Character? { 226 | get { return index(of: offset).flatMap { self[$0] } } 227 | set (newValue) { self[offset] = newValue! } 228 | } 229 | // lhs ..< rhs operator 230 | public subscript (safe range: Range) -> OOISubstring { 231 | get { 232 | guard let from = index(of: range.lowerBound), 233 | let to = index(of: range.upperBound), 234 | from <= to else { return nil } 235 | return OISubstring(self[from ..< to]) 236 | } 237 | set (newValue) { self[range] = newValue! } 238 | } 239 | // ..) -> OOISubstring { 241 | get { return self[safe: .start ..< range.upperBound] } 242 | set (newValue) { self[range] = newValue! } 243 | } 244 | // lhs... operator 245 | public subscript (safe range: PartialRangeFrom) -> OOISubstring { 246 | get { return self[safe: range.lowerBound ..< .end] } 247 | set (newValue) { self[range] = newValue! } 248 | } 249 | 250 | // ================================================================= 251 | // Misc. 252 | public mutating func replaceSubrange(_ bounds: Range, 253 | with newElements: C) where C : Collection, C.Element == Character { 254 | self[bounds] = OISubstring(newElements) 255 | } 256 | public mutating func insert(contentsOf newElements: S, at i: OffsetIndex) 257 | where S : Collection, S.Element == Character { 258 | replaceSubrange(i ..< i, with: newElements) 259 | } 260 | public mutating func insert(_ newElement: Character, at i: OffsetIndex) { 261 | insert(contentsOf: String(newElement), at: i) 262 | } 263 | } 264 | #endif 265 | -------------------------------------------------------------------------------- /SwiftTrace/SwiftAspects.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftAspects.swift 3 | // SwiftTrace 4 | // 5 | // Created by John Holdsworth on 20/04/2020. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | // Repo: https://github.com/johnno1962/SwiftTrace 9 | // $Id: //depot/SwiftTrace/SwiftTrace/SwiftAspects.swift#17 $ 10 | // 11 | // Add aspects to Swift methods 12 | // ============================ 13 | // 14 | 15 | #if DEBUG || !DEBUG_ONLY 16 | import Foundation 17 | 18 | extension SwiftTrace { 19 | 20 | public typealias EntryAspect = (_ swizzle: Swizzle, _ stack: inout EntryStack) -> Void 21 | public typealias ExitAspect = (_ swizzle: Swizzle, _ stack: inout ExitStack) -> Void 22 | 23 | /** 24 | Add a closure aspect to be called before or after a "Swizzle" is called 25 | - parameter methodName: - unmangled name of Method for aspect 26 | - parameter onEntry: - closure to be called before "Swizzle" is called 27 | - parameter onExit: - closure to be called after "Swizzle" returns 28 | */ 29 | public class func addAspect(methodName: String, 30 | patchClass: Aspect.Type = Aspect.self, 31 | onEntry: EntryAspect? = nil, 32 | onExit: ExitAspect? = nil, 33 | replaceWith: nullImplementationType? = nil) -> Bool { 34 | return forAllClasses { 35 | (aClass, stop) in 36 | stop = addAspect(aClass: aClass, methodName: methodName, 37 | onEntry: onEntry, onExit: onExit, replaceWith: replaceWith) 38 | } 39 | } 40 | 41 | /** 42 | Add a closure aspect to be called before or after a "Swizzle" is called 43 | - parameter toClass: - specifying the class to add aspect is more efficient 44 | - parameter methodName: - unmangled name of Method for aspect 45 | - parameter onEntry: - closure to be called before "Swizzle" is called 46 | - parameter onExit: - closure to be called after "Swizzle" returns 47 | */ 48 | public class func addAspect(aClass: AnyClass, methodName: String, 49 | patchClass: Aspect.Type = Aspect.self, 50 | onEntry: EntryAspect? = nil, 51 | onExit: ExitAspect? = nil, 52 | replaceWith: nullImplementationType? = nil) -> Bool { 53 | return iterateMethods(ofClass: aClass) { 54 | (name, slotIndex, vtableSlot, stop) in 55 | if name == methodName, let method = patchClass.init(name: name, 56 | vtableSlot: vtableSlot, onEntry: onEntry, 57 | onExit: onExit, replaceWith: replaceWith) { 58 | vtableSlot.pointee = method.forwardingImplementation 59 | stop = true 60 | } 61 | } 62 | } 63 | 64 | /** 65 | Add a closure aspect to be called before or after a "Swizzle" is called 66 | - parameter methodName: - unmangled name of Method for aspect 67 | */ 68 | @discardableResult 69 | public class func removeAspect(methodName: String) -> Bool { 70 | return forAllClasses { 71 | (aClass, stop) in 72 | stop = removeAspect(aClass: aClass, methodName: methodName) 73 | } 74 | } 75 | 76 | /** 77 | Add a closure aspect to be called before or after a "Swizzle" is called 78 | - parameter aClass: - specifying the class to remove aspect is more efficient 79 | - parameter methodName: - unmangled name of Method for aspect 80 | */ 81 | @discardableResult 82 | public class func removeAspect(aClass: AnyClass, methodName: String) -> Bool { 83 | return iterateMethods(ofClass: aClass) { 84 | (name, slotIndex, vtableSlot, stop) in 85 | if name == methodName, 86 | let swizzle = SwiftTrace.lastSwiftTrace.activeSwizzles[unsafeBitCast(vtableSlot.pointee, to: IMP.self)] { 87 | swizzle.remove() 88 | stop = true 89 | } 90 | } 91 | } 92 | 93 | /** 94 | Internal class used in the implementation of aspects 95 | */ 96 | open class Aspect: Decorated { 97 | 98 | let entryAspect: EntryAspect? 99 | let exitAspect: ExitAspect? 100 | 101 | public required init?(name: String, vtableSlot: UnsafeMutablePointer? = nil, original: OpaquePointer? = nil, 102 | onEntry: EntryAspect? = nil, onExit: ExitAspect? = nil, 103 | replaceWith: nullImplementationType? = nil) { 104 | self.entryAspect = onEntry 105 | self.exitAspect = onExit 106 | super.init(name: name, vtableSlot: vtableSlot, 107 | original: original, replaceWith: replaceWith) 108 | } 109 | 110 | public required init?(name: String, vtableSlot: UnsafeMutablePointer? = nil, objcMethod: Method? = nil, objcClass: AnyClass?, original: OpaquePointer? = nil, replaceWith: nullImplementationType? = nil) { 111 | fatalError("Aspect.init(name:vtableSlot:objcMethod:objcClass:replaceWith:) should not be used") 112 | } 113 | 114 | open override func onEntry(stack: inout EntryStack, invocation: Invocation) { 115 | if entryAspect != nil || exitAspect != nil { 116 | entryAspect?(self, &stack) 117 | } else { 118 | super.onEntry(stack: &stack, invocation: invocation) 119 | } 120 | } 121 | 122 | open override func onExit(stack: inout ExitStack, invocation: Invocation) { 123 | if entryAspect != nil || exitAspect != nil { 124 | exitAspect?(self, &stack) 125 | } else { 126 | super.onExit(stack: &stack, invocation: invocation) 127 | } 128 | } 129 | } 130 | } 131 | #endif 132 | -------------------------------------------------------------------------------- /SwiftTrace/SwiftInterpose.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftInterpose.swift 3 | // SwiftTrace 4 | // 5 | // Created by John Holdsworth on 23/09/2020. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | // $Id: //depot/SwiftTrace/SwiftTrace/SwiftInterpose.swift#80 $ 9 | // 10 | // Extensions to SwiftTrace using dyld_dynamic_interpose 11 | // ===================================================== 12 | // 13 | 14 | #if DEBUG || !DEBUG_ONLY 15 | import Foundation 16 | 17 | #if canImport(Darwin) // Apple platforms only.. 18 | extension SwiftTrace { 19 | 20 | /// Function type suffixes at end of mangled symbol name 21 | /// to interpose i.e. constructors, functions (methods), 22 | /// getters of Opaque type (for SwiftUI body properties) 23 | /// and setters and destructors. 24 | public static var traceableFunctionSuffixes = ["fC", "F", "Qrvg", "s", "fD"] 25 | 26 | /// Regexp pattern for functions to exclude from interposing 27 | public static var interposeEclusions: NSRegularExpression? = nil 28 | 29 | /// Base rebindings applied to each injected file. 30 | public static var initialRebindings = [rebinding]() 31 | 32 | /// "interpose" aspects onto Swift function name. 33 | /// If the symbol is not in a different framework 34 | /// requires the linker flags -Xlinker -interposable. 35 | /// - Parameters: 36 | /// - aType: A type in the bundle continaing the function 37 | /// - methodName: The full name of the function 38 | /// - patchClass: normally not required 39 | /// - onEntry: closure called on entry 40 | /// - onExit: closure called on exit 41 | /// - replaceWith: optional replacement for function 42 | public class func interpose(aType: Any.Type, methodName: String? = nil, 43 | patchClass: Aspect.Type = Aspect.self, 44 | onEntry: EntryAspect? = nil, 45 | onExit: ExitAspect? = nil, 46 | replaceWith: nullImplementationType? = nil) -> Int { 47 | var bundlePath = searchBundleImages() 48 | if let isClass = aType as? AnyClass { 49 | bundlePath = class_getImageName(isClass) ?? searchBundleImages() 50 | if methodName == nil && onEntry == nil && onExit == nil { 51 | trace(aClass: isClass) // also update vtable 52 | } 53 | } 54 | 55 | return interpose(aBundle: bundlePath, 56 | methodName: methodName ?? "^"+_typeName(aType)+"\\.", 57 | patchClass: patchClass, onEntry: onEntry, onExit: onExit, 58 | replaceWith: replaceWith) 59 | } 60 | 61 | /// "interpose" aspects onto Swift function name. 62 | /// If the symbol is not in a different framework 63 | /// requires the linker flags -Xlinker -interposable. 64 | /// - Parameters: 65 | /// - aBundle: Patch to framework containing function 66 | /// - methodName: Regex to match against function 67 | /// - patchClass: normally not required 68 | /// - onEntry: closure called on entry 69 | /// - onExit: closure called on exit 70 | /// - replaceWith: optional replacement for function 71 | public class func interpose(aBundle: UnsafePointer?, methodName: String, 72 | patchClass: Aspect.Type = Aspect.self, 73 | onEntry: EntryAspect? = nil, 74 | onExit: ExitAspect? = nil, 75 | replaceWith: nullImplementationType? = nil) -> Int { 76 | let methodRegex = NSRegularExpression(regexp: methodName) 77 | var interposes = [dyld_interpose_tuple]() 78 | var symbols = [UnsafePointer]() 79 | 80 | for suffix in traceableFunctionSuffixes { 81 | findSwiftSymbols(aBundle, suffix) { symval, symname, _, _ in 82 | if let theMethod = SwiftMeta.demangle(symbol: symname), 83 | methodRegex.matches(theMethod), 84 | interposeEclusions?.matches(theMethod) != true, 85 | let current = interposed(replacee: symval), 86 | let interpose = patchClass.init(name: theMethod, 87 | original: OpaquePointer(current), 88 | onEntry: onEntry, onExit: onExit, 89 | replaceWith: replaceWith) { 90 | interposes.append(dyld_interpose_tuple( 91 | replacement: autoBitCast(interpose.forwardingImplementation), 92 | replacee: current)) 93 | symbols.append(symname) 94 | } 95 | } 96 | } 97 | 98 | return apply(interposes: interposes, symbols: symbols) 99 | } 100 | 101 | /// Has symbol already been interposed? 102 | /// - Parameter replacee: original function 103 | /// - Returns: pointer to end of chain of any interposes that have been aplied 104 | public class func interposed(replacee: UnsafeRawPointer) -> UnsafeRawPointer? { 105 | let interposed = NSObject.swiftTraceInterposed.bindMemory(to: 106 | [UnsafeRawPointer : UnsafeRawPointer].self, capacity: 1) 107 | var current = replacee 108 | while let replacement = interposed.pointee[current] { 109 | current = replacement 110 | } 111 | return current 112 | } 113 | 114 | /// Use interposing to trace all methods in a bundle 115 | /// Requires "Other Linker Flags" -Xlinker -interposable 116 | /// Filters using method include/exlxusion class vars. 117 | /// - Parameters: 118 | /// - inBundlePath: path to bundle to interpose 119 | /// - packageName: include only methods with prefix 120 | /// - subLevels: not currently used 121 | @objc public class func interposeMethods(inBundlePath: UnsafePointer, 122 | packageName: String? = nil, 123 | subLevels: Int = 0) -> Int { 124 | startNewTrace(subLevels: subLevels) 125 | var interposes = [dyld_interpose_tuple]() 126 | var symbols = [UnsafePointer]() 127 | 128 | for suffix in traceableFunctionSuffixes { 129 | findSwiftSymbols(inBundlePath, suffix) { 130 | symval, symname, _, _ in 131 | if let methodName = SwiftMeta.demangle(symbol: symname), 132 | packageName == nil || 133 | methodName.hasPrefix(packageName!+".") || 134 | methodName.hasPrefix("(extension in \(packageName!))"), 135 | interposeEclusions?.matches(methodName) != true, 136 | let factory = methodFilter(methodName), 137 | let current = interposed(replacee: symval), 138 | let method = factory.init(name: methodName, 139 | original: OpaquePointer(current)) { 140 | // print(interposes.count, methodName, String(cString: symname)) 141 | interposes.append(dyld_interpose_tuple( 142 | replacement: autoBitCast(method.forwardingImplementation), 143 | replacee: current)) 144 | symbols.append(symname) 145 | } 146 | } 147 | } 148 | 149 | bundlesInterposed.insert(String(cString: inBundlePath)) 150 | return apply(interposes: interposes, symbols: symbols) 151 | } 152 | 153 | /// Use interposing to trace all methods in main bundle 154 | @objc public class func traceMainBundleMethods() -> Int { 155 | return interposeMethods(inBundlePath: Bundle.main.executablePath!) 156 | } 157 | 158 | /// Use interposing to trace all methods in a framework 159 | /// Doesn't actually require -Xlinker -interposable 160 | /// - Parameters: 161 | /// - aClass: Class which the framework contains 162 | @objc public class func traceMethods(inFrameworkContaining aClass: AnyClass) -> Int { 163 | return interposeMethods(inBundlePath: class_getImageName(aClass)!) 164 | } 165 | 166 | /// Apply a trace to all methods in framesworks in app bundle 167 | @objc public class func traceFrameworkMethods() -> Int { 168 | var replaced = 0 169 | appBundleImages { imageName, _, _ in 170 | if strstr(imageName, ".framework") != nil { 171 | replaced += interposeMethods(inBundlePath: imageName) 172 | trace(bundlePath: imageName) 173 | } 174 | } 175 | return replaced 176 | } 177 | 178 | /// Legacy entry point that can use either fishhook or "dyld_dynamic_interpose" 179 | public class func apply(interposes: [dyld_interpose_tuple], 180 | symbols: [UnsafePointer], onInjection: 181 | ((UnsafePointer, intptr_t) -> Void)? = nil) 182 | -> Int { 183 | var rebindings = record(interposes: interposes, symbols: symbols) 184 | #if true // use fishhook now 185 | return apply(rebindings: &rebindings, onInjection: onInjection).count 186 | #else // Original way using dyld_dynamic_interpose 187 | interposes.withUnsafeBufferPointer { interposes in 188 | let debugInterpose = getenv("DEBUG_INTERPOSE") != nil 189 | var lastLoaded = true 190 | 191 | appBundleImages { (imageName, header, _) in 192 | if lastLoaded { 193 | onInjection?(header) 194 | lastLoaded = false 195 | } 196 | 197 | if debugInterpose { 198 | for symno in 0 ..< interposes.count { 199 | print("Interposing: \(SwiftMeta.demangle(symbol: symbols[symno]) ?? String(cString: symbols[symno]))") 200 | dyld_dynamic_interpose(header, 201 | interposes.baseAddress!+symno, 1) 202 | } 203 | } else { 204 | dyld_dynamic_interpose(header, 205 | interposes.baseAddress!, interposes.count) 206 | } 207 | } 208 | } 209 | #endif 210 | } 211 | 212 | /// record interposed so they can be untraced and combine with symbols to create rebindings 213 | public class func record(interposes: [dyld_interpose_tuple], 214 | symbols: [UnsafePointer]) -> [rebinding] { 215 | let interposed = NSObject.swiftTraceInterposed.bindMemory(to: 216 | [UnsafeRawPointer : UnsafeRawPointer].self, capacity: 1) 217 | for toapply in interposes 218 | where toapply.replacee != toapply.replacement { 219 | interposed.pointee[toapply.replacee] = toapply.replacement 220 | } 221 | var rebindings = [rebinding]() 222 | for i in 0.., intptr_t) -> Void)? = nil) 233 | -> [UnsafePointer] { 234 | var interposed = [UnsafePointer]() 235 | rebindings.withUnsafeMutableBufferPointer { 236 | let buffer = $0.baseAddress!, count = $0.count 237 | var lastLoaded = true 238 | appBundleImages { path, header, slide in 239 | if lastLoaded { 240 | onInjection?(header, slide) 241 | lastLoaded = false 242 | } 243 | 244 | interposed += apply(rebindings: buffer, count: count, 245 | header: header, slide: slide) 246 | } 247 | } 248 | return interposed 249 | } 250 | 251 | /// Use fishhook to apply interposes in an image returning an array of symbols that were patched 252 | public class func apply(rebindings: UnsafeMutablePointer, count: Int, 253 | header: UnsafePointer, slide: intptr_t) 254 | -> [UnsafePointer] { 255 | for i in 0..]() 263 | for i in 0..? = nil, objcMethod: Method? = nil, objcClass: AnyClass? = nil, original: OpaquePointer? = nil, replaceWith: nullImplementationType? = nil) { 65 | fatalError("SwiftTrace.Call.init(name:vtableSlot:objcMethod:objcClass:replaceWith:) must not be used") 66 | } 67 | 68 | public func reset(target: AnyObject) { 69 | self.target = target 70 | } 71 | 72 | public var intArgNumber = 0 73 | public var floatArgNumber = 0 74 | 75 | public func resetArgs() { 76 | intArgNumber = 0 77 | floatArgNumber = 0 78 | } 79 | 80 | public func add(arg: T) { 81 | if arg is SwiftTraceFloatArg { 82 | let registers = (MemoryLayout.size + 83 | MemoryLayout.size - 1) / MemoryLayout.size 84 | if floatArgNumber + registers > EntryStack.maxFloatSlots { 85 | fatalError("Too many float args for SwiftTrace.Call") 86 | } 87 | withUnsafeMutablePointer(to: &input.floatArg1) { 88 | rebind($0.advanced(by: floatArgNumber), to: T.self) 89 | .pointee = arg 90 | } 91 | floatArgNumber += registers 92 | return 93 | } 94 | else { 95 | let registers = (MemoryLayout.size + 96 | MemoryLayout.size - 1) / MemoryLayout.size 97 | if intArgNumber + registers > EntryStack.maxIntSlots { 98 | fatalError("Too many int args for SwiftTrace.Call") 99 | } 100 | withUnsafeMutablePointer(to: &input.intArg1) { 101 | rebind($0.advanced(by: intArgNumber), to: T.self) 102 | .pointee = arg 103 | } 104 | intArgNumber += registers 105 | } 106 | } 107 | 108 | public func invoke() { 109 | caller!() 110 | resetArgs() 111 | } 112 | 113 | public override func onEntry(stack: inout EntryStack, invocation: Invocation) { 114 | input.frame = stack.frame 115 | backup = stack 116 | stack = input 117 | } 118 | 119 | public override func onExit(stack: inout ExitStack, invocation: Invocation) { 120 | output = stack 121 | rebind(&stack.floatReturn1).pointee = backup 122 | } 123 | 124 | public func getReturn() -> T { 125 | return output.genericReturn(swizzle: self).pointee 126 | } 127 | 128 | public func invokeStret(args: Any...) -> T { 129 | for arg in args { 130 | if let arg = arg as? SwiftTraceArg { 131 | arg.add(toCall: self) 132 | } 133 | else if type(of: arg) is AnyObject.Type { 134 | self.add(arg: unsafeBitCast(arg, to: Int.self)) 135 | } 136 | else { 137 | fatalError("Unsupported argument type \(type(of: arg))") 138 | } 139 | } 140 | let ptr = UnsafeMutablePointer.allocate(capacity: 1) 141 | input.structReturn = autoBitCast(ptr) 142 | caller!() 143 | resetArgs() 144 | let out = ptr.pointee 145 | ptr.deinitialize(count: 1) 146 | ptr.deallocate() 147 | return out 148 | } 149 | } 150 | 151 | /** 152 | Basic Swift method invocation api 153 | - parameter self: instance to message 154 | - parameter methodName: de-mangled method name to invoke 155 | - parameter args: list of values to use as arguments 156 | */ 157 | public class func invoke(target: AnyObject, methodName: String, args: Any...) -> T { 158 | guard let call = Call(target: target, methodName: methodName) else { 159 | fatalError("Unknown method \(methodName) on class \(target)") 160 | } 161 | 162 | for arg in args { 163 | if let arg = arg as? SwiftTraceArg { 164 | arg.add(toCall: call) 165 | } 166 | else if type(of: arg) is AnyObject.Type { 167 | call.add(arg: unsafeBitCast(arg, to: Int.self)) 168 | } 169 | else { 170 | fatalError("Unsupported argument type \(type(of: arg))") 171 | } 172 | } 173 | 174 | call.invoke() 175 | 176 | return call.getReturn() 177 | } 178 | } 179 | #endif 180 | -------------------------------------------------------------------------------- /SwiftTrace/SwiftLifetime.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftLifetime.swift 3 | // SwiftTrace 4 | // 5 | // Created by John Holdsworth on 23/09/2020. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | // $Id: //depot/SwiftTrace/SwiftTrace/SwiftLifetime.swift#26 $ 9 | // 10 | // Trace instance life cycle for tracking down reference cycles. 11 | // ============================================================= 12 | // 13 | // Needs "Other Linker Flags" -Xlinker -interposable and to have 14 | // called both traceMainBundleMethods() and traceMainBundle(). 15 | // 16 | // "allocating_init" initialiazers of Swift classes are intercepted 17 | // and init* methods of Objective-C classe are swizzled to record new 18 | // objects in a "live" Set on a per-class basis. Swift classes with 19 | // non-trivial initialisers that inherit from NSObject have an Objective-C 20 | // method ".cxx_destruct" which is swizzled to remove the instances of the 21 | // live Set as they are dealloctaed. For classes that don't inherit from 22 | // NSObject an associated reference is added to a "Reaper" instance that 23 | // removes the instance from the live Set using a method _cxx_destruct 24 | // added to the tracked class. 25 | // 26 | 27 | #if DEBUG || !DEBUG_ONLY 28 | import Foundation 29 | 30 | extension SwiftTrace { 31 | 32 | public static var liveObjects = [UnsafeRawPointer: Set]() 33 | public static var liveObjectsLock = OS_SPINLOCK_INIT 34 | private static var reaperKey = strdup("_reaper_")! 35 | 36 | open class LifetimeTracker: Decorated { 37 | 38 | /// Tracker to detect deallocations for 39 | /// classes not inheriting from NSObject 40 | open class Reaper: NSObject { 41 | let owner: UnsafeRawPointer 42 | init(owner: UnsafeRawPointer) { 43 | self.owner = owner 44 | } 45 | @objc func _cxx_destruct() { 46 | } 47 | deinit { 48 | (autoBitCast(owner) as AnyObject)._cxx_destruct?() 49 | } 50 | } 51 | 52 | public let isAllocator: Bool 53 | public let isDeallocator: Bool 54 | override var isLifetime: Bool { return isAllocator || isDeallocator } 55 | 56 | public required init?(name signature: String, 57 | vtableSlot: UnsafeMutablePointer? = nil, 58 | objcMethod: Method? = nil, objcClass: AnyClass? = nil, 59 | original: OpaquePointer? = nil, 60 | replaceWith: nullImplementationType? = nil) { 61 | isAllocator = signature.contains(".__allocating_init(") || 62 | !class_isMetaClass(objcClass) && signature.contains(" init") 63 | && !signature.contains(" initial") 64 | 65 | isDeallocator = signature.contains("cxx_destruct") || 66 | signature.contains(".__deallocating_deinit") // not used 67 | super.init(name: signature, vtableSlot: vtableSlot, 68 | objcMethod: objcMethod, objcClass: objcClass, 69 | original: original, replaceWith: replaceWith) 70 | } 71 | 72 | /** 73 | Update instances for each class 74 | */ 75 | open func update(instance: AnyObject) -> UnsafeRawPointer { 76 | let metaType: UnsafeRawPointer = autoBitCast(type(of: instance)) 77 | OSSpinLockLock(&liveObjectsLock) 78 | if liveObjects.index(forKey: metaType) == nil { 79 | liveObjects[metaType] = Set() 80 | trackGenerics(metaType, register: metaType) 81 | } 82 | if isAllocator { 83 | liveObjects[metaType]! 84 | .insert(autoBitCast(instance)) 85 | } else { 86 | liveObjects[metaType]! 87 | .remove(autoBitCast(instance)) 88 | } 89 | invocation()?.numberLive = liveObjects[metaType]!.count 90 | OSSpinLockUnlock(&liveObjectsLock) 91 | return metaType 92 | } 93 | 94 | /// Make sure deallocations of generic classes are tracked. 95 | /// - Parameter metaType: pointer to type info 96 | /// - Parameter register: generic to actually register 97 | open func trackGenerics(_ metaType: UnsafeRawPointer, 98 | register: UnsafeRawPointer) { 99 | if !tracksDeallocs.contains(register), 100 | let generic = autoBitCast(metaType) as Any.Type as? AnyClass { 101 | var methodCount: UInt32 = 0 102 | if let methods = class_copyMethodList(generic, &methodCount) { 103 | for method in (0.. \(String(cString: type))", 110 | objcMethod: method, objcClass: generic) { 111 | class_replaceMethod(generic, sel, 112 | autoBitCast(swizzle.forwardingImplementation), type) 113 | tracksDeallocs.insert(register) 114 | } 115 | } 116 | free(methods) 117 | } 118 | if !tracksDeallocs.contains(register) { 119 | if let superClass = class_getSuperclass(generic) { 120 | trackGenerics(autoBitCast(superClass), register: register) 121 | } else { 122 | // fallback where class doesn't inherit from NSObject 123 | if let tracker = class_getInstanceMethod(Reaper.self, 124 | #selector(Reaper._cxx_destruct)), 125 | let type = method_getTypeEncoding(tracker), 126 | let originalClass = autoBitCast(register) as Any.Type as? AnyClass, 127 | let swizzle = swizzleFactory.init(name: 128 | "-[\(generic) _cxx_destruct] -> \(String(cString: type))", 129 | objcMethod: tracker, objcClass: originalClass) { 130 | class_replaceMethod(originalClass, 131 | #selector(Reaper._cxx_destruct), 132 | autoBitCast(swizzle.forwardingImplementation), 133 | type) 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | /** 141 | Increment live instances for initialisers 142 | */ 143 | open override func exitDecorate(stack: inout ExitStack, 144 | invocation: Invocation) -> String? { 145 | var info = "", live = "live" 146 | if isAllocator || isDeallocator { 147 | if isAllocator { 148 | let instance = returnAsAny as AnyObject 149 | let metaType = update(instance: instance) 150 | if !tracksDeallocs.contains(metaType) { 151 | objc_setAssociatedObject(instance, reaperKey, 152 | Reaper(owner: autoBitCast(instance)), 153 | .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 154 | live = "allocated" 155 | } 156 | } 157 | info = " [\(invocation.numberLive) \(live)]" 158 | } 159 | return super.exitDecorate(stack: &stack, invocation: invocation)! + info 160 | } 161 | 162 | /** 163 | Decrement live instances on deallocations 164 | */ 165 | open override func entryDecorate(stack: inout EntryStack, 166 | invocation: Invocation) -> String? { 167 | if isDeallocator { 168 | _ = update(instance: getSelf()) 169 | } 170 | return super.entryDecorate(stack: &stack, invocation: invocation) 171 | } 172 | } 173 | } 174 | #endif 175 | -------------------------------------------------------------------------------- /SwiftTrace/SwiftStack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftStack.swift 3 | // SwiftTrace 4 | // 5 | // Created by John Holdsworth on 20/04/2020. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | // $Id: //depot/SwiftTrace/SwiftTrace/SwiftStack.swift#23 $ 9 | // 10 | // Stack layout used by assembly trampolines 11 | // ========================================= 12 | // 13 | // See: https://github.com/apple/swift/blob/main/docs/ABI/RegisterUsage.md 14 | // https://github.com/apple/swift/blob/main/docs/ABI/CallingConvention.rst 15 | // 16 | 17 | #if DEBUG || !DEBUG_ONLY 18 | import Foundation 19 | 20 | extension SwiftTrace { 21 | 22 | public struct StackFrame { 23 | public var fp: UnsafePointer? = nil 24 | public var lr: UnsafeRawPointer? = nil 25 | } 26 | 27 | #if arch(arm64) 28 | /** 29 | Stack layout on entry from xt_forwarding_trampoline_arm64.s 30 | */ 31 | public struct EntryStack { 32 | static let maxFloatSlots = 8 33 | static let maxIntSlots = 6 // Conservative here, actually it's 8 34 | 35 | public var floatArg1: Double = 0.0 36 | public var floatArg2: Double = 0.0 37 | public var floatArg3: Double = 0.0 38 | public var floatArg4: Double = 0.0 39 | public var floatArg5: Double = 0.0 40 | public var floatArg6: Double = 0.0 41 | public var floatArg7: Double = 0.0 42 | public var floatArg8: Double = 0.0 43 | public var intArg1: intptr_t = 0 44 | public var intArg2: intptr_t = 0 45 | public var intArg3: intptr_t = 0 46 | public var intArg4: intptr_t = 0 47 | public var intArg5: intptr_t = 0 48 | public var intArg6: intptr_t = 0 49 | public var intArg7: intptr_t = 0 50 | public var intArg8: intptr_t = 0 51 | public var structReturn: intptr_t = 0 // x8 52 | public var framePointer: intptr_t = 0 53 | public var swiftSelf: intptr_t = 0 // x20 54 | public var thrownError: intptr_t = 0 // x21 55 | public var frame = StackFrame() 56 | } 57 | 58 | /** 59 | Stack layout on exit from xt_forwarding_trampoline_arm64.s 60 | */ 61 | public struct ExitStack { 62 | static let returnRegs = 4 63 | 64 | public var floatReturn1: Double = 0.0 65 | public var floatReturn2: Double = 0.0 66 | public var floatReturn3: Double = 0.0 67 | public var floatReturn4: Double = 0.0 68 | public var d4: Double = 0.0 69 | public var d5: Double = 0.0 70 | public var d6: Double = 0.0 71 | public var d7: Double = 0.0 72 | public var intReturn1: intptr_t = 0 73 | public var intReturn2: intptr_t = 0 74 | public var intReturn3: intptr_t = 0 75 | public var intReturn4: intptr_t = 0 76 | public var x4: intptr_t = 0 77 | public var x5: intptr_t = 0 78 | public var x6: intptr_t = 0 79 | public var x7: intptr_t = 0 80 | public var structReturn: intptr_t = 0 // x8 81 | public var framePointer: intptr_t = 0 82 | public var swiftSelf: intptr_t = 0 // x20 83 | public var thrownError: intptr_t = 0 // x21 84 | public var frame = StackFrame() 85 | 86 | mutating func resyncStructReturn() { 87 | structReturn = autoBitCast(invocation.structReturn) 88 | } 89 | } 90 | 91 | #else // x86_64 92 | /** 93 | Stack layout on entry from xt_forwarding_trampoline_x64.s 94 | */ 95 | public struct EntryStack { 96 | static let maxFloatSlots = 8 97 | static let maxIntSlots = 6 98 | 99 | public var floatArg1: Double = 0.0 100 | public var floatArg2: Double = 0.0 101 | public var floatArg3: Double = 0.0 102 | public var floatArg4: Double = 0.0 103 | public var floatArg5: Double = 0.0 104 | public var floatArg6: Double = 0.0 105 | public var floatArg7: Double = 0.0 106 | public var floatArg8: Double = 0.0 107 | public var r12: intptr_t = 0 108 | public var swiftSelf: intptr_t = 0 // r13 109 | public var r14: intptr_t = 0 110 | public var r15: intptr_t = 0 111 | public var intArg1: intptr_t = 0 // rdi 112 | public var intArg2: intptr_t = 0 // rsi 113 | public var intArg3: intptr_t = 0 // rcx 114 | public var intArg4: intptr_t = 0 // rdx 115 | public var intArg5: intptr_t = 0 // r8 116 | public var intArg6: intptr_t = 0 // r9 117 | public var r10: intptr_t = 0 118 | public var structReturn: intptr_t = 0 // rax 119 | public var rbx: intptr_t = 0 120 | public var rbp: intptr_t = 0 // for alignment 121 | public var frame = StackFrame() 122 | } 123 | 124 | /** 125 | Stack layout on exit from xt_forwarding_trampoline_x64.s 126 | */ 127 | public struct ExitStack { 128 | static let returnRegs = 4 129 | 130 | public var floatReturn1: Double = 0.0 // xmm0 131 | public var floatReturn2: Double = 0.0 // xmm1 132 | public var floatReturn3: Double = 0.0 // xmm2 133 | public var floatReturn4: Double = 0.0 // xmm3 134 | public var xmm4: Double = 0.0 135 | public var xmm5: Double = 0.0 136 | public var xmm6: Double = 0.0 137 | public var xmm7: Double = 0.0 138 | public var thrownError: intptr_t = 0 // r12 139 | public var swiftSelf: intptr_t = 0 // r13 140 | public var r14: intptr_t = 0 141 | public var r15: intptr_t = 0 142 | public var rdi: intptr_t = 0 143 | public var rsi: intptr_t = 0 144 | public var intReturn1: intptr_t = 0 // rax (also struct Return) 145 | public var intReturn2: intptr_t = 0 // rdx 146 | public var intReturn3: intptr_t = 0 // rcx 147 | public var intReturn4: intptr_t = 0 // r8 148 | public var r9: intptr_t = 0 149 | public var r10: intptr_t = 0 150 | public var rbx: intptr_t = 0 151 | public var rbp: intptr_t = 0 152 | public var frame = StackFrame() 153 | public var structReturn: intptr_t { 154 | return intReturn1 155 | } 156 | mutating func resyncStructReturn() { 157 | intReturn1 = autoBitCast(invocation.structReturn) 158 | } 159 | } 160 | #endif 161 | } 162 | 163 | extension SwiftTrace.EntryStack { 164 | public var invocation: SwiftTrace.Swizzle.Invocation! { 165 | return SwiftTrace.Swizzle.Invocation.current 166 | } 167 | } 168 | 169 | extension SwiftTrace.ExitStack { 170 | public var invocation: SwiftTrace.Swizzle.Invocation! { 171 | return SwiftTrace.Swizzle.Invocation.current 172 | } 173 | public mutating func genericReturn(swizzle: SwiftTrace.Swizzle? = nil, 174 | to: Any.Type = T.self) -> UnsafeMutablePointer { 175 | if MemoryLayout.size > MemoryLayout.size * SwiftTrace.ExitStack.returnRegs { 176 | resyncStructReturn() 177 | return UnsafeMutablePointer(cast: invocation.structReturn!) 178 | } 179 | else { 180 | let swizzle = swizzle ?? invocation!.swizzle 181 | if T.self is SwiftTraceFloatArg.Type { 182 | return swizzle.rebind(&floatReturn1) 183 | } 184 | else { 185 | return swizzle.rebind(&intReturn1) 186 | } 187 | } 188 | } 189 | mutating public func getReturn() -> T { 190 | return genericReturn().pointee 191 | } 192 | mutating public func setReturn(value: T) { 193 | intReturn1 = 0 194 | intReturn2 = 0 195 | intReturn3 = 0 196 | intReturn4 = 0 197 | return genericReturn().pointee = value 198 | } 199 | mutating public func stringReturn() -> String { 200 | return getReturn() 201 | } 202 | } 203 | #endif 204 | -------------------------------------------------------------------------------- /SwiftTrace/SwiftStats.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftStats.swift 3 | // SwiftTrace 4 | // 5 | // Created by John Holdsworth on 23/09/2020. 6 | // Copyright © 2020 John Holdsworth. All rights reserved. 7 | // 8 | // Obtaining invocation statistics 9 | // =============================== 10 | // 11 | // $Id: //depot/SwiftTrace/SwiftTrace/SwiftStats.swift#7 $ 12 | // 13 | 14 | #if DEBUG || !DEBUG_ONLY 15 | import Foundation 16 | 17 | extension SwiftTrace { 18 | 19 | func populate(elapsedTimes: inout [String: Double]) { 20 | previousSwiftTrace?.populate(elapsedTimes: &elapsedTimes) 21 | for (_, swizzle) in activeSwizzles { 22 | elapsedTimes[swizzle.signature] = swizzle.totalElapsed 23 | } 24 | } 25 | 26 | /** 27 | Accumulated amount of time spent in each swizzled method. 28 | */ 29 | public static func elapsedTimes() -> [String: Double] { 30 | var elapsedTimes = [String: Double]() 31 | lastSwiftTrace.populate(elapsedTimes: &elapsedTimes) 32 | return elapsedTimes 33 | } 34 | 35 | /** 36 | Sorted descending accumulated amount of time spent in each swizzled method. 37 | */ 38 | public static func sortedElapsedTimes(onlyFirst: Int? = nil) -> [(key: String, value: TimeInterval)] { 39 | let sorted = elapsedTimes().sorted { $1.value < $0.value } 40 | return onlyFirst != nil ? Array(sorted.prefix(onlyFirst!)) : sorted 41 | } 42 | 43 | func populate(invocationCounts: inout [String: Int]) { 44 | previousSwiftTrace?.populate(invocationCounts: &invocationCounts) 45 | for (_, swizzle) in activeSwizzles { 46 | invocationCounts[swizzle.signature] = swizzle.invocationCount 47 | } 48 | } 49 | 50 | /** 51 | Numbers of times each swizzled method has been invoked. 52 | */ 53 | public static func invocationCounts() -> [String: Int] { 54 | var invocationCounts = [String: Int]() 55 | lastSwiftTrace.populate(invocationCounts: &invocationCounts) 56 | return invocationCounts 57 | } 58 | 59 | /** 60 | Sorted descending numbers of times each swizzled method has been invoked. 61 | */ 62 | public static func sortedInvocationCounts(onlyFirst: Int? = nil) -> [(key: String, value: Int)] { 63 | let sorted = invocationCounts().sorted { $1.value < $0.value } 64 | return onlyFirst != nil ? Array(sorted.prefix(onlyFirst!)) : sorted 65 | } 66 | 67 | public static func callOrder() -> [Swizzle] { 68 | var calls = [Swizzle]() 69 | var call = firstCalled 70 | while call != nil { 71 | calls.append(call!) 72 | call = call!.nextCalled 73 | } 74 | return calls 75 | } 76 | } 77 | #endif 78 | -------------------------------------------------------------------------------- /SwiftTrace/SwiftTrace.h: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/include/SwiftTrace.h -------------------------------------------------------------------------------- /SwiftTraceApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftTraceApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftTraceApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftTraceApp 4 | // 5 | // Created by John Holdsworth on 10/06/2016. 6 | // Copyright © 2016 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftTrace 11 | 12 | public struct TestStruct: Equatable { 13 | // let a = 1.0, b = 2, c = 3.0 14 | var i = 111, j = 222, k = "333" 15 | } 16 | extension TestStruct: SwiftTraceArg {} 17 | 18 | public func ==(lhs: TestStruct, rhs: TestStruct) -> Bool { 19 | 20 | return //lhs.a == lhs.a && lhs.b == lhs.b && lhs.c == lhs.c && 21 | lhs.i == lhs.i && lhs.j == lhs.j && lhs.k == lhs.k 22 | } 23 | 24 | public struct Strings: SwiftTraceArg { 25 | var s1 = "ONE", s2 = "TWO"//, s3 = "THREE" 26 | } 27 | 28 | public protocol P { 29 | var i: Int { get set } 30 | func x() -> TestClass? 31 | func y() -> CGRect 32 | @discardableResult 33 | func zzz( _ d: Int, f: Double, g: Float, h: String, f1: CGFloat, g1: Float, h1: Double, f2: Double, g2: Float, h2: Double, e: Int, ff: Int, o: TestClass ) throws -> String 34 | func ssssss( a: TestStruct ) -> TestStruct 35 | func str() -> NSString 36 | func rect(r: CGRect) -> CGRect 37 | } 38 | 39 | public class TestClass: NSObject, P, SwiftTraceArg { 40 | 41 | public var i = 999 42 | 43 | public func x() -> TestClass? { 44 | print( "TestClass.x() self.i: \(i)" ) 45 | return self 46 | } 47 | 48 | public func y() -> CGRect { 49 | print( "TestClass.y()" ) 50 | return CGRect(x: 1.0, y: 2.0, width: 3.0, height: 4.0) 51 | } 52 | 53 | @discardableResult 54 | public func zzz( _ d: Int, f: Double, g: Float, h: String, f1: CGFloat, g1: Float, h1: Double, f2: Double, g2: Float, h2: Double, e: Int, ff: Int, o: TestClass ) throws -> String { 55 | print( "TestClass.zzz(_ d: \(d), f: \(f), g: \(g), h: \(h), f1: \(f1), g1: \(g1), h1: \(h1), f2: \(f2), g2: \(g2), h2: \(h2), e: \(e), ff: \(ff), self.i: \(i), o.i: \(o.i))") 56 | throw NSError(domain: "HOLLOO", code: 123, userInfo: ["john": "error"]) 57 | //return "4-4-4" 58 | } 59 | 60 | public func ssssss( a: TestStruct ) -> TestStruct { 61 | return a 62 | } 63 | 64 | static var c = 0 65 | 66 | public func str() -> NSString { 67 | return "NO" as NSString 68 | } 69 | 70 | public func str2(strs: inout Strings) -> Strings { 71 | return strs 72 | } 73 | 74 | public func str3(strs: Strings) -> Strings { 75 | return strs 76 | } 77 | 78 | public func rect(r: CGRect) -> CGRect { 79 | return r 80 | } 81 | } 82 | 83 | class MyTracer: SwiftTrace.Decorated { 84 | 85 | override func onEntry(stack: inout SwiftTrace.EntryStack) { 86 | //print(stack) 87 | print("MyTracer.onEntry, arguments: ", arguments) 88 | if signature == "SwiftTwaceApp.TestClass.zzz(_: Swift.Int, f: Swift.Double, g: Swift.Float, h: Swift.String, f1: CoreGraphics.CGFloat, g1: Swift.Float, h1: Swift.Double, f2: Swift.Double, g2: Swift.Float, h2: Swift.Double, e: Swift.Int, ff: Swift.Int, o: SwiftTwaceApp.TestClass) throws -> Swift.String" { 89 | print("MyTracer.onEntry, zzz: \(stack.intArg1) \(rebind(&stack.intArg2, to: String.self).pointee) \(stack.floatArg1) \(rebind(&stack.floatArg5, to: Float.self).pointee) \(rebind(&stack.intArg6, to: TestClass.self).pointee.i) \((getSelf() as TestClass).i)") 90 | } 91 | } 92 | 93 | override func onExit(stack: inout SwiftTrace.ExitStack) { 94 | //print(stack) 95 | print("\(getSelf() as AnyObject)") 96 | if signature == "SwiftTwaceApp.TestClass.ssssss(a: SwiftTwaceApp.TestStruct) -> SwiftTwaceApp.TestStruct" { 97 | print("MyTracer.onExit: ", stack.getReturn() as TestStruct) 98 | } 99 | if signature == "SwiftTwaceApp.TestClass.zzz(_: Swift.Int, f: Swift.Double, g: Swift.Float, h: Swift.String, f1: CoreGraphics.CGFloat, g1: Swift.Float, h1: Swift.Double, f2: Swift.Double, g2: Swift.Float, h2: Swift.Double, e: Swift.Int, ff: Swift.Int, o: SwiftTwaceApp.TestClass) throws -> Swift.String" { 100 | if stack.thrownError != 0 { 101 | print(rebind(&stack.thrownError, to: NSError.self).pointee) 102 | } 103 | stack.thrownError = 0 104 | // stack.invocation.patch.argument(&intReturn1, as: String.self) = "5-5-5" 105 | stack.setReturn(value: "5-5-5") 106 | } 107 | if signature == "SwiftTwaceApp.TestClass.y() -> __C.CGRect" { 108 | rebind(&stack.floatReturn1).pointee = CGRect(x: 11.0, y: 22.0, width: 33.0, height: 44.0) 109 | } 110 | if signature == "SwiftTwaceApp.TestClass.str2(strs: inout SwiftTwaceApp.Strings) -> SwiftTwaceApp.Strings" { 111 | rebind(&stack.intReturn1, to: Strings.self).pointee.s2 += "!" 112 | } 113 | } 114 | 115 | func jjjjj(a: TestClass) { 116 | do { 117 | try a.zzz( 88, f: 66, g: 55, h: "44", f1: 66, g1: 55, h1: 44, f2: 66, g2: 55, h2: 44, e: 77, ff: 11, o: TestClass() ) 118 | } 119 | catch { 120 | print(error) 121 | } 122 | } 123 | } 124 | 125 | class Benchmark { 126 | func x() { 127 | } 128 | } 129 | 130 | @UIApplicationMain 131 | class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate { 132 | 133 | var window: UIWindow? 134 | 135 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?) -> Bool { 136 | // Override point for customization after application launch. 137 | let splitViewController = self.window!.rootViewController as! UISplitViewController 138 | let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController 139 | navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem 140 | splitViewController.delegate = self 141 | print(objc_classArray().count) 142 | 143 | // any inclusions or exlusions need to come before trace enabled 144 | 145 | // SwiftTrace.methodExclusionPattern = "ObjcTraceTester |"+SwiftTrace.defaultMethodExclusions 146 | 147 | SwiftTrace.interpose(aType: TestClass.self, methodName: "SwiftTwaceApp.TestClass.x() -> Swift.Optional", 148 | onEntry: { swizzle, stack in print("entry... \(stack)") }, 149 | onExit: { swizzle, stack in print("exit \(stack)") }) 150 | SwiftTrace.interpose(aType: TestClass.self, methodName: "SwiftTwaceApp.TestClass.x() -> Swift.Optional", 151 | onEntry: { swizzle, stack in print("entry2... \(stack)") }, 152 | onExit: { swizzle, stack in print("exit2 \(stack)") }) 153 | SwiftTrace.interpose(aType: TestClass.self, methodName: "SwiftTwaceApp.TestClass.x() -> Swift.Optional", 154 | onEntry: { swizzle, stack in print("entry3... \(stack)") }, 155 | onExit: { swizzle, stack in print("exit3 \(stack)") }) 156 | 157 | _ = TestClass().x() 158 | 159 | SwiftTrace.swiftTraceBundle() 160 | ObjcTraceTester().a(44, i:45, b: 55, c: "66", o: self, s: Selector(("jjj:"))) 161 | 162 | SwiftTrace.swizzleFactory = MyTracer.self 163 | type(of: self).swiftTraceBundle() 164 | SwiftTrace.trace(aClass: type(of: self)) 165 | 166 | print(SwiftTrace.swiftClassList(bundlePath: Bundle.main.executablePath!)) 167 | print(SwiftTrace.methodNames(ofClass: TestClass.self)) 168 | print(SwiftTrace.swiftClassList(bundlePath: class_getImageName(TestClass.self))) 169 | 170 | print(SwiftTrace.addAspect(aClass: TestClass.self, methodName: "SwiftTwaceApp.TestClass.x() -> ()", 171 | onEntry: { (patch: SwiftTrace.Swizzle, stack: inout SwiftTrace.EntryStack) in 172 | // patch.rebind(&stack.intArg2).pointee = "Grief" 173 | print("SwiftTwaceApp.TestClass.x() enter") }, 174 | onExit: { (patch: SwiftTrace.Swizzle, stack: inout SwiftTrace.ExitStack) in 175 | // stack.setReturn(value: "Phew") 176 | print("SwiftTwaceApp.TestClass.x() exit") })) 177 | print(SwiftTrace.addAspect(methodName: "SwiftTwaceApp.TestClass.y() -> __C.CGRect", onExit: { (_, _) in print("SwiftTwaceApp.TestClass.y() exit!") })) 178 | print(SwiftTrace.addAspect(methodName: "SwiftTwaceApp.TestClass.str() -> __C.NSString", replaceWith: { 179 | TestClass.c += 1 180 | return "YES #\(TestClass.c)" as NSString 181 | })) 182 | print(SwiftTrace.addAspect(methodName: "SwiftTwaceApp.TestClass.ssssss(a: SwiftTwaceApp.TestStruct) -> SwiftTwaceApp.TestStruct", onExit: { (patch, stack) in 183 | // patch.structReturn(as: TestStruct.self).pointee.k = "8-8-8" 184 | (stack.genericReturn() as UnsafeMutablePointer).pointee.k = "8-8-8" 185 | })) 186 | 187 | var a: P = TestClass() 188 | _ = a.x() 189 | 190 | print( a.y() ) 191 | print(SwiftTrace.removeAspect(aClass: TestClass.self, methodName: "SwiftTwaceApp.TestClass.y() -> __C.CGRect")) 192 | print( a.y() ) 193 | 194 | _ = a.x() 195 | a.i = 888 196 | 197 | print(try! a.zzz( 123, f: 66, g: 55, h: "4-4", f1: 66, g1: 55, h1: 44, f2: 66, g2: 55, h2: 44, e: 77, ff: 11, o: TestClass())) 198 | print(a.ssssss( a: TestStruct())) 199 | 200 | print(a.rect(r: CGRect(x: 111.0, y: 222.0, width: 333.0, height: 444.0))) 201 | 202 | print(">>>> AH \(a.str())") 203 | print(">>>> AH \(a.str())") 204 | print(">>>> AH \(a.str())") 205 | 206 | let b = TestClass() 207 | 208 | let call = SwiftTrace.Call(target: b, methodName: "SwiftTwaceApp.TestClass.zzz(_: Swift.Int, f: Swift.Double, g: Swift.Float, h: Swift.String, f1: CoreGraphics.CGFloat, g1: Swift.Float, h1: Swift.Double, f2: Swift.Double, g2: Swift.Float, h2: Swift.Double, e: Swift.Int, ff: Swift.Int, o: SwiftTwaceApp.TestClass) throws -> Swift.String")! 209 | 210 | for _ in 0..<10 { 211 | call.add(arg: 777) 212 | call.add(arg: 101.0) 213 | call.add(arg: Float(102.0)) 214 | call.add(arg: "2-2") 215 | call.add(arg: CGFloat(103.0)) 216 | call.add(arg: Float(104.0)) 217 | call.add(arg: 105.0) 218 | call.add(arg: 106.0) 219 | call.add(arg: Float(107.0)) 220 | call.add(arg: 108.0) 221 | call.add(arg: 888) 222 | call.add(arg: 999) 223 | call.add(arg: b) 224 | 225 | call.invoke() 226 | 227 | print("!!!!!! "+call.getReturn()) 228 | } 229 | 230 | SwiftTrace.swizzleFactory = SwiftTrace.Decorated.self 231 | SwiftTrace.trace(anInstance: b) 232 | 233 | print("SwiftTwaceApp.TestClass.zzz: "+SwiftTrace.invoke(target: b, methodName: "SwiftTwaceApp.TestClass.zzz(_: Swift.Int, f: Swift.Double, g: Swift.Float, h: Swift.String, f1: CoreGraphics.CGFloat, g1: Swift.Float, h1: Swift.Double, f2: Swift.Double, g2: Swift.Float, h2: Swift.Double, e: Swift.Int, ff: Swift.Int, o: SwiftTwaceApp.TestClass) throws -> Swift.String", args: 777, 101.0, Float(102.0), "2-2", CGFloat(103.0), Float(104.0), 105.0, 106.0, Float(107.0), 108.0, 888, 999, b)) 234 | print("SwiftTwaceApp.TestClass.zzz: "+SwiftTrace.invoke(target: b, methodName: "SwiftTwaceApp.TestClass.zzz(_: Swift.Int, f: Swift.Double, g: Swift.Float, h: Swift.String, f1: CoreGraphics.CGFloat, g1: Swift.Float, h1: Swift.Double, f2: Swift.Double, g2: Swift.Float, h2: Swift.Double, e: Swift.Int, ff: Swift.Int, o: SwiftTwaceApp.TestClass) throws -> Swift.String", args: 777, 101.0, Float(102.0), "2-2", CGFloat(103.0), Float(104.0), 105.0, 106.0, Float(107.0), 108.0, 888, 999, b)) 235 | 236 | SwiftTrace.Decorated.swiftTypeHandlers["SwiftTwaceApp.TestStruct"] = { 237 | SwiftTrace.Decorated.handleArg(invocation: $0, isReturn: $1, type: TestStruct.self) 238 | } 239 | print("SwiftTwaceApp.TestClass.ssssss: \(SwiftTrace.invoke(target: b, methodName: "SwiftTwaceApp.TestClass.ssssss(a: SwiftTwaceApp.TestStruct) -> SwiftTwaceApp.TestStruct", args: TestStruct()) as TestStruct)") 240 | 241 | print(SwiftTrace.invoke(target: b, methodName: "SwiftTwaceApp.TestClass.rect(r: __C.CGRect) -> __C.CGRect", args: CGRect(x: 1111.0, y: 2222.0, width: 3333.0, height: 4444.0)) as CGRect) 242 | 243 | var strings = Strings() 244 | print(SwiftTrace.invoke(target: b, methodName: "SwiftTwaceApp.TestClass.str2(strs: inout SwiftTwaceApp.Strings) -> SwiftTwaceApp.Strings", args: call.rebind(&strings, to: Strings.self)) as Strings) 245 | print("invoke TestClass.str3: ", SwiftTrace.invoke(target: b, methodName: "SwiftTwaceApp.TestClass.str3(strs: SwiftTwaceApp.Strings) -> SwiftTwaceApp.Strings", args: strings) as Strings) 246 | print("invoke TestClass.x: ", SwiftTrace.invoke(target: b, methodName: "SwiftTwaceApp.TestClass.x() -> Swift.Optional", args: strings) as TestClass? ?? "nil") 247 | 248 | SwiftTrace.removeAllTraces() 249 | 250 | let x = Benchmark() 251 | 252 | print(SwiftTrace.methodNames(ofClass: Benchmark.self)) 253 | 254 | let start1 = Date.timeIntervalSinceReferenceDate 255 | for _ in 0..<10_000 { 256 | x.x(); x.x(); x.x(); x.x(); x.x() 257 | x.x(); x.x(); x.x(); x.x(); x.x() 258 | } 259 | print(Date.timeIntervalSinceReferenceDate - start1) 260 | 261 | print(SwiftTrace.addAspect(aClass: Benchmark.self, methodName: "SwiftTwaceApp.Benchmark.x() -> ()", onEntry: { (_, _) in})) 262 | 263 | let start2 = Date.timeIntervalSinceReferenceDate 264 | for _ in 0..<10_000 { 265 | x.x(); x.x(); x.x(); x.x(); x.x() 266 | x.x(); x.x(); x.x(); x.x(); x.x() 267 | } 268 | print(Date.timeIntervalSinceReferenceDate - start2) 269 | 270 | // SwiftTrace.methodExclusionPattern = " allocWithZone:| colorSpaceName|"+SwiftTrace.defaultMethodExclusions 271 | 272 | for call in SwiftTrace.callOrder() { 273 | print(call.signature) 274 | } 275 | 276 | let originalFilter = SwiftTrace.methodFilter 277 | SwiftTrace.methodFilter = { 278 | (symbol) in 279 | if let factory = originalFilter(symbol) { 280 | return factory 281 | } 282 | // print("Excluding:", symbol) 283 | return nil 284 | } 285 | 286 | UIColor.swiftTraceBundle() 287 | print(UIColor.systemBlue) 288 | 289 | 290 | #if !arch(arm64) 291 | TestClass.swiftTraceProtocolsInBundle() 292 | #endif 293 | type(of: self).swiftTraceMainBundleMethods() 294 | return true 295 | } 296 | 297 | func applicationWillResignActive(_ application: UIApplication) { 298 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 299 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 300 | } 301 | 302 | func applicationDidEnterBackground(_ application: UIApplication) { 303 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 304 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 305 | } 306 | 307 | func applicationWillEnterForeground(_ application: UIApplication) { 308 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 309 | } 310 | 311 | func applicationDidBecomeActive(_ application: UIApplication) { 312 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 313 | } 314 | 315 | func applicationWillTerminate(_ application: UIApplication) { 316 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 317 | } 318 | 319 | // MARK: - Split view 320 | 321 | func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool { 322 | guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false } 323 | guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false } 324 | if topAsDetailController.detailItem == nil { 325 | // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded. 326 | return true 327 | } 328 | return false 329 | } 330 | 331 | } 332 | -------------------------------------------------------------------------------- /SwiftTraceApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /SwiftTraceApp/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SwiftTraceApp/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /SwiftTraceApp/DetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailViewController.swift 3 | // SwiftTraceApp 4 | // 5 | // Created by John Holdsworth on 10/06/2016. 6 | // Copyright © 2016 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftTrace 11 | 12 | class DetailViewController: UIViewController { 13 | 14 | @IBOutlet weak var detailDescriptionLabel: UILabel! 15 | 16 | 17 | var detailItem: AnyObject? { 18 | didSet { 19 | // Update the view. 20 | self.configureView() 21 | } 22 | } 23 | 24 | func configureView() { 25 | // Update the user interface for the detail item. 26 | if let detail = self.detailItem { 27 | if let label = self.detailDescriptionLabel { 28 | label.text = detail.description 29 | } 30 | } 31 | print(SwiftTrace.sortedElapsedTimes(onlyFirst: 10)) 32 | print(SwiftTrace.sortedInvocationCounts(onlyFirst: 10)) 33 | } 34 | 35 | override func viewDidLoad() { 36 | super.viewDidLoad() 37 | // Do any additional setup after loading the view, typically from a nib. 38 | self.configureView() 39 | } 40 | 41 | override func didReceiveMemoryWarning() { 42 | super.didReceiveMemoryWarning() 43 | // Dispose of any resources that can be recreated. 44 | } 45 | 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /SwiftTraceApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarTintParameters 34 | 35 | UINavigationBar 36 | 37 | Style 38 | UIBarStyleDefault 39 | Translucent 40 | 41 | 42 | 43 | UISupportedInterfaceOrientations 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | UISupportedInterfaceOrientations~ipad 50 | 51 | UIInterfaceOrientationPortrait 52 | UIInterfaceOrientationPortraitUpsideDown 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /SwiftTraceApp/MasterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MasterViewController.swift 3 | // z50 4 | // 5 | // Created by John Holdsworth on 14/09/2016. 6 | // Copyright © 2016 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MasterViewController: UITableViewController { 12 | 13 | var detailViewController: DetailViewController? = nil 14 | var objects = [Any]() 15 | 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | // Do any additional setup after loading the view, typically from a nib. 20 | self.navigationItem.leftBarButtonItem = self.editButtonItem 21 | 22 | let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:))) 23 | self.navigationItem.rightBarButtonItem = addButton 24 | if let split = self.splitViewController { 25 | let controllers = split.viewControllers 26 | self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController 27 | } 28 | } 29 | 30 | override func viewWillAppear(_ animated: Bool) { 31 | self.clearsSelectionOnViewWillAppear = self.splitViewController!.isCollapsed 32 | super.viewWillAppear(animated) 33 | } 34 | 35 | override func didReceiveMemoryWarning() { 36 | super.didReceiveMemoryWarning() 37 | // Dispose of any resources that can be recreated. 38 | } 39 | 40 | @objc 41 | func insertNewObject(_ sender: Any) { 42 | objects.insert(NSDate(), at: 0) 43 | let indexPath = IndexPath(row: 0, section: 0) 44 | self.tableView.insertRows(at: [indexPath], with: .automatic) 45 | } 46 | 47 | // MARK: - Segues 48 | 49 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 50 | if segue.identifier == "showDetail" { 51 | if let indexPath = self.tableView.indexPathForSelectedRow { 52 | let object = objects[indexPath.row] as! NSDate 53 | let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController 54 | controller.detailItem = object 55 | controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem 56 | controller.navigationItem.leftItemsSupplementBackButton = true 57 | } 58 | } 59 | } 60 | 61 | // MARK: - Table View 62 | 63 | override func numberOfSections(in tableView: UITableView) -> Int { 64 | return 1 65 | } 66 | 67 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 68 | return objects.count 69 | } 70 | 71 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 72 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) 73 | 74 | let object = objects[indexPath.row] as! NSDate 75 | cell.textLabel!.text = object.description 76 | return cell 77 | } 78 | 79 | override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { 80 | // Return false if you do not want the specified item to be editable. 81 | return true 82 | } 83 | 84 | override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { 85 | if editingStyle == .delete { 86 | objects.remove(at: indexPath.row) 87 | tableView.deleteRows(at: [indexPath], with: .fade) 88 | } else if editingStyle == .insert { 89 | // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view. 90 | } 91 | } 92 | 93 | 94 | } 95 | 96 | -------------------------------------------------------------------------------- /SwiftTraceD: -------------------------------------------------------------------------------- 1 | SwiftTrace -------------------------------------------------------------------------------- /SwiftTraceGuts/ObjCBridge.mm: -------------------------------------------------------------------------------- 1 | // 2 | // ObjCBridge.mm 3 | // SwiftTrace 4 | // 5 | // Created by John Holdsworth on 21/01/2022. 6 | // Repo: https://github.com/johnno1962/SwiftTrace 7 | // $Id: //depot/SwiftTrace/SwiftTraceGuts/ObjCBridge.mm#3 $ 8 | // 9 | 10 | #if DEBUG || !DEBUG_ONLY 11 | #import "include/SwiftTrace.h" 12 | 13 | #ifndef SWIFTUISUPPORT 14 | // Bridge via NSObject for when SwiftTrace is dynamically loaded 15 | #import "SwiftTrace-Swift.h" 16 | 17 | @implementation NSObject(SwiftTrace) 18 | + (NSString *)swiftTraceDefaultMethodExclusions { 19 | return [SwiftTrace defaultMethodExclusions]; 20 | } 21 | + (NSString *)swiftTraceMethodExclusionPattern { 22 | return [SwiftTrace methodExclusionPattern]; 23 | } 24 | + (void)setSwiftTraceMethodExclusionPattern:(NSString *)pattern { 25 | [SwiftTrace setMethodExclusionPattern:pattern]; 26 | } 27 | + (NSString *)swiftTraceMethodInclusionPattern { 28 | return [SwiftTrace methodInclusionPattern]; 29 | } 30 | + (void)setSwiftTraceMethodInclusionPattern:(NSString *)pattern { 31 | [SwiftTrace setMethodInclusionPattern:pattern]; 32 | } 33 | + (NSArray * _Nonnull)swiftTraceFunctionSuffixes { 34 | return [SwiftTrace swiftFunctionSuffixes]; 35 | } 36 | + (void)setSwiftTraceFunctionSuffixes:(NSArray * _Nonnull)value { 37 | [SwiftTrace setSwiftFunctionSuffixes:value]; 38 | } 39 | + (BOOL)swiftTracing { 40 | return [SwiftTrace isTracing]; 41 | } 42 | + (void *)swiftTraceInterposed { 43 | return [SwiftTrace interposedPointer]; 44 | } 45 | + (BOOL)swiftTraceTypeLookup { 46 | return [SwiftTrace typeLookup]; 47 | } 48 | + (void)setSwiftTraceTypeLookup:(BOOL)enabled { 49 | [SwiftTrace setTypeLookup:enabled]; 50 | [SwiftTrace setDecorateAny:enabled]; 51 | } 52 | + (void)swiftTrace { 53 | [SwiftTrace traceWithAClass:self]; 54 | } 55 | + (void)swiftTraceBundle { 56 | [self swiftTraceBundleWithSubLevels:0]; 57 | } 58 | + (void)swiftTraceBundleWithSubLevels:(int)subLevels { 59 | [SwiftTrace traceBundleWithContaining:self subLevels:subLevels]; 60 | } 61 | + (void)swiftTraceMainBundle { 62 | [self swiftTraceMainBundleWithSubLevels:0]; 63 | } 64 | + (void)swiftTraceMainBundleWithSubLevels:(int)subLevels { 65 | [SwiftTrace traceMainBundleWithSubLevels:subLevels]; 66 | } 67 | + (void)swiftTraceClassesMatchingPattern:(NSString *)pattern { 68 | [self swiftTraceClassesMatchingPattern:pattern subLevels:0]; 69 | } 70 | + (void)swiftTraceClassesMatchingPattern:(NSString *)pattern subLevels:(intptr_t)subLevels { 71 | [SwiftTrace traceClassesMatchingPattern:pattern subLevels:subLevels]; 72 | } 73 | + (NSArray *)swiftTraceMethodNames { 74 | return [SwiftTrace methodNamesOfClass:self]; 75 | } 76 | + (NSArray *)switTraceMethodsNamesOfClass:(Class)aClass { 77 | return [SwiftTrace methodNamesOfClass:aClass]; 78 | } 79 | + (BOOL)swiftTraceUndoLastTrace { 80 | return [SwiftTrace undoLastTrace]; 81 | } 82 | + (void)swiftTraceRemoveAllTraces { 83 | [SwiftTrace removeAllTraces]; 84 | } 85 | + (void)swiftTraceRevertAllInterposes { 86 | [SwiftTrace revertInterposes]; 87 | } 88 | + (void)swiftTraceInstances { 89 | [self swiftTraceInstancesWithSubLevels:0]; 90 | } 91 | + (void)swiftTraceInstancesWithSubLevels:(int)subLevels { 92 | [SwiftTrace traceInstancesOfClass:self subLevels:subLevels]; 93 | } 94 | - (void)swiftTraceInstance { 95 | [self swiftTraceInstanceWithSubLevels:0]; 96 | } 97 | - (void)swiftTraceInstanceWithSubLevels:(int)subLevels { 98 | [SwiftTrace traceWithAnInstance:self subLevels:subLevels]; 99 | } 100 | + (void)swiftTraceProtocolsInBundle { 101 | [self swiftTraceProtocolsInBundleWithMatchingPattern:nil subLevels:0]; 102 | } 103 | + (void)swiftTraceProtocolsInBundleWithMatchingPattern:(NSString * _Nullable)pattern { 104 | [self swiftTraceProtocolsInBundleWithMatchingPattern:pattern subLevels:0]; 105 | } 106 | + (void)swiftTraceProtocolsInBundleWithSubLevels:(int)subLevels { 107 | [self swiftTraceProtocolsInBundleWithMatchingPattern:nil subLevels:subLevels]; 108 | } 109 | + (void)swiftTraceProtocolsInBundleWithMatchingPattern:(NSString *)pattern subLevels:(int)subLevels { 110 | [SwiftTrace traceProtocolsInBundleWithContaining:self matchingPattern:pattern subLevels:subLevels]; 111 | } 112 | + (NSInteger)swiftTraceMethodsInFrameworkContaining:(Class _Nonnull)aClass { 113 | return [SwiftTrace traceMethodsInFrameworkContaining:aClass]; 114 | } 115 | + (NSInteger)swiftTraceMainBundleMethods { 116 | return [SwiftTrace traceMainBundleMethods]; 117 | } 118 | + (NSInteger)swiftTraceFrameworkMethods { 119 | return [SwiftTrace traceFrameworkMethods]; 120 | } 121 | + (NSInteger)swiftTraceMethodsInBundle:(const char * _Nonnull)bundlePath 122 | packageName:(NSString * _Nullable)packageName { 123 | return [SwiftTrace interposeMethodsInBundlePath:(const int8_t *)bundlePath 124 | packageName:packageName subLevels:0]; 125 | } 126 | + (void)swiftTraceBundlePath:(const char * _Nonnull)bundlePath { 127 | [SwiftTrace traceWithBundlePath:(const int8_t *)bundlePath subLevels:0]; 128 | } 129 | + (NSString * _Nullable)swiftTraceFilterInclude { 130 | return [SwiftTrace traceFilterInclude]; 131 | } 132 | + (void)setSwiftTraceFilterInclude:(NSString * _Nullable)include { 133 | [SwiftTrace setTraceFilterInclude:include]; 134 | } 135 | + (NSString * _Nullable)swiftTraceFilterExclude { 136 | return [SwiftTrace traceFilterExclude]; 137 | } 138 | + (void)setSwiftTraceFilterExclude:(NSString * _Nullable)exclude { 139 | [SwiftTrace setTraceFilterExclude:exclude]; 140 | } 141 | + (STSymbolFilter _Nonnull)swiftTraceSymbolFilter { 142 | return [SwiftTrace injectableSymbol]; 143 | } 144 | + (void)setSwiftTraceSymbolFilter:(STSymbolFilter _Nonnull)filter { 145 | [SwiftTrace setInjectableSymbol:filter]; 146 | } 147 | + (NSDictionary * _Nonnull)swiftTraceElapsedTimes { 148 | return [SwiftTrace elapsedTimes]; 149 | } 150 | + (NSDictionary * _Nonnull)swiftTraceInvocationCounts { 151 | return [SwiftTrace invocationCounts]; 152 | } 153 | + (NSString * _Nullable)swiftTraceDemangle:(char const * _Nonnull)symbol { 154 | return [SwiftMeta demangleWithSymbol:(const int8_t *)symbol]; 155 | } 156 | @end 157 | #endif 158 | 159 | #ifdef OBJC_TRACE_TESTER 160 | @implementation ObjcTraceTester: NSObject 161 | 162 | - (OSRect)a:(float)a i:(int)i b:(double)b c:(NSString *)c o:o s:(SEL)s { 163 | return OSMakeRect(1, 2, 3, 4); 164 | } 165 | @end 166 | #endif 167 | #endif 168 | -------------------------------------------------------------------------------- /SwiftTraceGuts/SwiftTrace.mm: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftTrace.m 3 | // SwiftTrace 4 | // 5 | // Repo: https://github.com/johnno1962/SwiftTrace 6 | // $Id: //depot/SwiftTrace/SwiftTraceGuts/SwiftTrace.mm#119 $ 7 | // 8 | 9 | #if DEBUG || !DEBUG_ONLY 10 | #import "include/SwiftTrace.h" 11 | #import 12 | #import 13 | 14 | NSArray *objc_classArray() { 15 | unsigned nc; 16 | NSMutableArray *array = [NSMutableArray new]; 17 | if (Class *classes = objc_copyClassList(&nc)) 18 | for (int i=0; i= 0 ; i--) { 137 | const char *imageName = _dyld_get_image_name(i); 138 | if (!(imageName && (bundlePath == searchAllImages() || 139 | imageName == bundlePath || 140 | strstr(imageName, bundlePath)) && 141 | !strstr(imageName, "InjectionScratch.framework"))) 142 | continue; 143 | 144 | filterImageSymbols(i, visibility, 145 | swiftSymbolsWithSuffixOrObjcClass, callback); 146 | if (bundlePath != searchAllImages() && 147 | bundlePath != mainBundlePath) 148 | break; 149 | } 150 | } 151 | 152 | void filterImageSymbols(int32_t imageNumber, STVisibility visibility, STSymbolFilter filter, 153 | void (^ _Nonnull callback)(const void * _Nonnull address, const char * _Nonnull symname, 154 | void * _Nonnull typeref, void * _Nonnull typeend)) { 155 | const struct mach_header *header = nullptr; 156 | if (imageNumber < 0) { 157 | imageNumber = lastLoadedIndex ?: _dyld_image_count()+imageNumber; 158 | header = lastPseudoImage(); 159 | } 160 | if (!header) 161 | header = _dyld_get_image_header(imageNumber); 162 | filterHeaderSymbols(header, visibility, filter, callback); 163 | } 164 | 165 | #import 166 | static const char *findingSwiftSymbol; 167 | 168 | void filterHeaderSymbols(const struct mach_header *header, STVisibility visibility, STSymbolFilter filter, 169 | void (^ _Nonnull callback)(const void * _Nonnull address, const char * _Nonnull symname, 170 | void * _Nonnull typeref, void * _Nonnull typeend)) { 171 | if (!findingSwiftSymbol) 172 | fast_dlscan(header, visibility, filter, callback); 173 | else { // For performance looking for a single symbol. 174 | uint64_t typeref_size = 0; 175 | char *typeref = getsectdatafromheader_64((const mach_header_64 *)header, 176 | SEG_TEXT, "__swift5_typeref", &typeref_size); 177 | if (void *value = fast_dlsym(header, findingSwiftSymbol)) 178 | callback(value, findingSwiftSymbol, typeref, typeref+typeref_size); 179 | } 180 | } 181 | 182 | void *findSwiftSymbol(const char *path, const char *suffix, STVisibility visibility) { 183 | __block void *found = nullptr; 184 | if (!fullSwiftPrefix(suffix)) 185 | findingSwiftSymbol = suffix; 186 | findHiddenSwiftSymbols(path, suffix, visibility, 187 | ^(const void * _Nonnull address, const char * _Nonnull symname, 188 | void * _Nonnull typeref, void * _Nonnull typeend) { 189 | #if DEBUG && 0 190 | if (found && found != address) 191 | NSLog(@"SwiftTrace: Contradicting values for %s: %@ %p != %@ %p", suffix, 192 | describeImagePointer(found), found, 193 | describeImagePointer(address), address); 194 | // else 195 | #endif 196 | found = (void *)address; 197 | }); 198 | findingSwiftSymbol = nullptr; 199 | return found; 200 | } 201 | 202 | void appBundleImages(void (^callback)(const char *imageName, const struct mach_header *, intptr_t slide)) { 203 | for (ssize_t i = getLoadedPseudoImages().size()-1; i>=0 ; i--) 204 | callback(getLoadedPseudoImages()[i].first, (struct mach_header *) 205 | getLoadedPseudoImages()[i].second, 0); 206 | 207 | NSBundle *mainBundle = [NSBundle mainBundle]; 208 | const char *mainExecutable = mainBundle.executablePath.UTF8String; 209 | const char *bundleFrameworks = mainBundle.privateFrameworksPath.UTF8String; 210 | size_t frameworkPathLength = strlen(bundleFrameworks); 211 | 212 | for (int32_t i = _dyld_image_count()-1; i >= 0 ; i--) { 213 | const char *imageName = _dyld_get_image_name(i); 214 | // NSLog(@"findImages: %s", imageName); 215 | if (strcmp(imageName, mainExecutable) == 0 || 216 | strncmp(imageName, bundleFrameworks, frameworkPathLength) == 0 || 217 | (strstr(imageName, "/DerivedData/") && 218 | strstr(imageName, ".framework/")) || 219 | strstr(imageName, ".debug.dylib") || // Xcode16 220 | strstr(imageName, ".xctest/") || 221 | strstr(imageName, "/eval")) 222 | callback(imageName, _dyld_get_image_header(i), 223 | _dyld_get_image_vmaddr_slide(i)); 224 | } 225 | } 226 | 227 | const char *callerBundle() { 228 | void *returnAddress = __builtin_return_address(1); 229 | Dl_info info; 230 | if (dladdr(returnAddress, &info)) 231 | return info.dli_fname; 232 | return nullptr; 233 | } 234 | 235 | const char *_Nonnull swiftTrace_path() { 236 | return __FILE__; 237 | } 238 | #endif 239 | -------------------------------------------------------------------------------- /SwiftTraceGuts/Trampolines.mm: -------------------------------------------------------------------------------- 1 | // 2 | // Trampolines.mm 3 | // SwiftTrace 4 | // 5 | // Created by John Holdsworth on 21/01/2022. 6 | // Repo: https://github.com/johnno1962/SwiftTrace 7 | // $Id: //depot/SwiftTrace/SwiftTraceGuts/Trampolines.mm#6 $ 8 | // 9 | 10 | #if DEBUG || !DEBUG_ONLY 11 | #import "include/SwiftTrace.h" 12 | 13 | // 14 | // Trampoline code thanks to: 15 | // https://github.com/OliverLetterer/imp_implementationForwardingToSelector 16 | // 17 | // imp_implementationForwardingToSelector.m 18 | // imp_implementationForwardingToSelector 19 | // 20 | // Created by Oliver Letterer on 22.03.14. 21 | // Copyright (c) 2014 Oliver Letterer 22 | // 23 | // Permission is hereby granted, free of charge, to any person obtaining a copy 24 | // of this software and associated documentation files (the "Software"), to deal 25 | // in the Software without restriction, including without limitation the rights 26 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 27 | // copies of the Software, and to permit persons to whom the Software is 28 | // furnished to do so, subject to the following conditions: 29 | // 30 | // The above copyright notice and this permission notice shall be included in 31 | // all copies or substantial portions of the Software. 32 | // 33 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 34 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 35 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 36 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 37 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 38 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 39 | // THE SOFTWARE. 40 | // 41 | 42 | #import 43 | #import 44 | 45 | #import 46 | #import 47 | #import 48 | #import 49 | #import 50 | 51 | extern char xt_forwarding_trampoline_page, xt_forwarding_trampolines_start, 52 | xt_forwarding_trampolines_next, xt_forwarding_trampolines_end; 53 | 54 | // trampoline implementation specific stuff... 55 | typedef struct { 56 | #if !defined(__LP64__) 57 | IMP tracer; 58 | #endif 59 | void *patch; // Pointer to SwiftTrace.Patch instance retained elsewhere 60 | } SwiftTraceTrampolineDataBlock; 61 | 62 | typedef int32_t SPLForwardingTrampolineEntryPointBlock[2]; 63 | #if defined(__i386__) 64 | static const int32_t SPLForwardingTrampolineInstructionCount = 8; 65 | #elif defined(_ARM_ARCH_7) 66 | static const int32_t SPLForwardingTrampolineInstructionCount = 12; 67 | #undef PAGE_SIZE 68 | #define PAGE_SIZE (1<<14) 69 | #elif defined(__arm64__) 70 | static const int32_t SPLForwardingTrampolineInstructionCount = 62; 71 | #undef PAGE_SIZE 72 | #define PAGE_SIZE (1<<14) 73 | #elif defined(__LP64__) // x86_64 74 | static const int32_t SPLForwardingTrampolineInstructionCount = 92; 75 | #else 76 | #error SwiftTrace is not supported on this platform 77 | #endif 78 | 79 | static const size_t numberOfTrampolinesPerPage = (PAGE_SIZE - SPLForwardingTrampolineInstructionCount * sizeof(int32_t)) / sizeof(SPLForwardingTrampolineEntryPointBlock); 80 | 81 | typedef struct { 82 | union { 83 | struct { 84 | #if defined(__LP64__) 85 | IMP onEntry; 86 | IMP onExit; 87 | #endif 88 | int32_t nextAvailableTrampolineIndex; 89 | }; 90 | int32_t trampolineSize[SPLForwardingTrampolineInstructionCount]; 91 | }; 92 | 93 | SwiftTraceTrampolineDataBlock trampolineData[numberOfTrampolinesPerPage]; 94 | 95 | int32_t trampolineInstructions[SPLForwardingTrampolineInstructionCount]; 96 | SPLForwardingTrampolineEntryPointBlock trampolineEntryPoints[numberOfTrampolinesPerPage]; 97 | } SPLForwardingTrampolinePage; 98 | 99 | static_assert(sizeof(SPLForwardingTrampolineEntryPointBlock) == sizeof(SwiftTraceTrampolineDataBlock), 100 | "Inconsistent entry point/data block sizes"); 101 | static_assert(sizeof(SPLForwardingTrampolinePage) == 2 * PAGE_SIZE, 102 | "Incorrect trampoline pages size"); 103 | static_assert(offsetof(SPLForwardingTrampolinePage, trampolineInstructions) == PAGE_SIZE, 104 | "Incorrect trampoline page offset"); 105 | 106 | static SPLForwardingTrampolinePage *SPLForwardingTrampolinePageAlloc() 107 | { 108 | vm_address_t trampolineTemplatePage = (vm_address_t)&xt_forwarding_trampoline_page; 109 | 110 | vm_address_t newTrampolinePage = 0; 111 | kern_return_t kernReturn = KERN_SUCCESS; 112 | 113 | //printf( "%d %d %d %d %d\n", vm_page_size, &xt_forwarding_trampolines_start - &xt_forwarding_trampoline_page, SPLForwardingTrampolineInstructionCount*4, &xt_forwarding_trampolines_end - &xt_forwarding_trampoline_page, &xt_forwarding_trampolines_next - &xt_forwarding_trampolines_start ); 114 | 115 | assert( &xt_forwarding_trampolines_start - &xt_forwarding_trampoline_page == 116 | SPLForwardingTrampolineInstructionCount * sizeof(int32_t) ); 117 | assert( &xt_forwarding_trampolines_end - &xt_forwarding_trampoline_page == PAGE_SIZE ); 118 | assert( &xt_forwarding_trampolines_next - &xt_forwarding_trampolines_start == sizeof(SwiftTraceTrampolineDataBlock) ); 119 | 120 | // allocate two consequent memory pages 121 | kernReturn = vm_allocate(mach_task_self(), &newTrampolinePage, PAGE_SIZE * 2, VM_FLAGS_ANYWHERE); 122 | NSCAssert1(kernReturn == KERN_SUCCESS, @"vm_allocate failed", kernReturn); 123 | 124 | // deallocate second page where we will store our trampoline 125 | vm_address_t trampoline_page = newTrampolinePage + PAGE_SIZE; 126 | kernReturn = vm_deallocate(mach_task_self(), trampoline_page, PAGE_SIZE); 127 | NSCAssert1(kernReturn == KERN_SUCCESS, @"vm_deallocate failed", kernReturn); 128 | 129 | // trampoline page will be remapped with implementation of spl_objc_forwarding_trampoline 130 | vm_prot_t cur_protection, max_protection; 131 | kernReturn = vm_remap(mach_task_self(), &trampoline_page, PAGE_SIZE, 0, 0, mach_task_self(), trampolineTemplatePage, FALSE, &cur_protection, &max_protection, VM_INHERIT_SHARE); 132 | NSCAssert1(kernReturn == KERN_SUCCESS, @"vm_remap failed", kernReturn); 133 | 134 | return (SPLForwardingTrampolinePage *)newTrampolinePage; 135 | } 136 | 137 | static NSMutableArray *normalTrampolinePages = nil; 138 | 139 | static SPLForwardingTrampolinePage *nextTrampolinePage() 140 | { 141 | static dispatch_once_t onceToken; 142 | dispatch_once(&onceToken, ^{ 143 | normalTrampolinePages = [NSMutableArray array]; 144 | }); 145 | 146 | NSMutableArray *thisArray = normalTrampolinePages; 147 | 148 | SPLForwardingTrampolinePage *trampolinePage = (SPLForwardingTrampolinePage *)[thisArray.lastObject pointerValue]; 149 | 150 | if (!trampolinePage || (trampolinePage->nextAvailableTrampolineIndex == numberOfTrampolinesPerPage) ) { 151 | trampolinePage = SPLForwardingTrampolinePageAlloc(); 152 | [thisArray addObject:[NSValue valueWithPointer:trampolinePage]]; 153 | } 154 | 155 | return trampolinePage; 156 | } 157 | 158 | #if 00 159 | /// Fix for libMainThreadCheck when using trampolines 160 | typedef const char * (*image_path_func)(const void *ptr); 161 | static image_path_func orig_path_func; 162 | 163 | static const char *myld_image_path_containing_address(const void* addr) { 164 | return orig_path_func(addr) ?: "/trampoline"; 165 | } 166 | #endif 167 | 168 | IMP imp_implementationForwardingToTracer(void *patch, IMP onEntry, IMP onExit) 169 | { 170 | #if 00 171 | static dispatch_once_t once; 172 | dispatch_once(&once, ^{ 173 | struct rebinding path_rebinding = {"dyld_image_path_containing_address", 174 | (void *)myld_image_path_containing_address, (void **)&orig_path_func}; 175 | rebind_symbols(&path_rebinding, 1); 176 | }); 177 | #endif 178 | 179 | static os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; 180 | os_unfair_lock_lock(&lock); 181 | 182 | SPLForwardingTrampolinePage *dataPageLayout = nextTrampolinePage(); 183 | 184 | int32_t nextAvailableTrampolineIndex = dataPageLayout->nextAvailableTrampolineIndex; 185 | 186 | #if !defined(__LP64__) 187 | dataPageLayout->trampolineData[nextAvailableTrampolineIndex].tracer = onEntry; 188 | #else 189 | dataPageLayout->onEntry = onEntry; 190 | dataPageLayout->onExit = onExit; 191 | #endif 192 | dataPageLayout->trampolineData[nextAvailableTrampolineIndex].patch = patch; 193 | dataPageLayout->nextAvailableTrampolineIndex++; 194 | 195 | IMP implementation = (IMP)&dataPageLayout->trampolineEntryPoints[nextAvailableTrampolineIndex]; 196 | 197 | os_unfair_lock_unlock(&lock); 198 | 199 | return implementation; 200 | } 201 | 202 | id findSwizzleOf(void * _Nonnull trampoline) { 203 | for (NSValue *allocated in normalTrampolinePages) { 204 | SPLForwardingTrampolinePage *trampolinePage = 205 | (SPLForwardingTrampolinePage *)allocated.pointerValue; 206 | if (trampoline >= trampolinePage->trampolineInstructions && trampoline < 207 | trampolinePage->trampolineInstructions + numberOfTrampolinesPerPage) 208 | return *(id const *)(void *)((char *)trampoline - PAGE_SIZE); 209 | } 210 | return nil; 211 | } 212 | #endif 213 | -------------------------------------------------------------------------------- /SwiftTraceGuts/fishhook.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, Facebook, Inc. 2 | // All rights reserved. 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // * Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // * Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // * Neither the name Facebook nor the names of its contributors may be used to 11 | // endorse or promote products derived from this software without specific 12 | // prior written permission. 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | #if DEBUG || !DEBUG_ONLY 25 | #include "fishhook.h" 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | // Modified to rebind to the value provided by dlsym for rebindings_nel == -1. 42 | 43 | #ifdef __LP64__ 44 | typedef struct mach_header_64 mach_header_t; 45 | typedef struct segment_command_64 segment_command_t; 46 | typedef struct section_64 section_t; 47 | typedef struct nlist_64 nlist_t; 48 | #define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64 49 | #else 50 | typedef struct mach_header mach_header_t; 51 | typedef struct segment_command segment_command_t; 52 | typedef struct section section_t; 53 | typedef struct nlist nlist_t; 54 | #define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT 55 | #endif 56 | 57 | #ifndef SEG_DATA_CONST 58 | #define SEG_DATA_CONST "__DATA_CONST" 59 | #endif 60 | 61 | struct rebindings_entry { 62 | struct rebinding *rebindings; 63 | size_t rebindings_nel; 64 | struct rebindings_entry *next; 65 | }; 66 | 67 | static struct rebindings_entry *_rebindings_head; 68 | 69 | static int prepend_rebindings(struct rebindings_entry **rebindings_head, 70 | struct rebinding rebindings[], 71 | size_t nel) { 72 | struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry)); 73 | if (!new_entry) { 74 | return -1; 75 | } 76 | new_entry->rebindings = (struct rebinding *) malloc(sizeof(struct rebinding) * nel); 77 | if (!new_entry->rebindings) { 78 | free(new_entry); 79 | return -1; 80 | } 81 | memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel); 82 | new_entry->rebindings_nel = nel; 83 | new_entry->next = *rebindings_head; 84 | *rebindings_head = new_entry; 85 | return 0; 86 | } 87 | 88 | vm_prot_t get_protection(void *sectionStart) { 89 | mach_port_t task = mach_task_self(); 90 | vm_size_t size = 0; 91 | vm_address_t address = (vm_address_t)sectionStart; 92 | memory_object_name_t object; 93 | #if __LP64__ 94 | mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; 95 | vm_region_basic_info_data_64_t info; 96 | kern_return_t info_ret = vm_region_64( 97 | task, &address, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_64_t)&info, &count, &object); 98 | #else 99 | mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT; 100 | vm_region_basic_info_data_t info; 101 | kern_return_t info_ret = vm_region(task, &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &count, &object); 102 | #endif 103 | if (info_ret == KERN_SUCCESS) { 104 | return info.protection; 105 | } else { 106 | return VM_PROT_READ; 107 | } 108 | } 109 | 110 | // SwiftTrace additions here 111 | static STTracer stTracer; 112 | int rebind_symbols_trace(void * _Nonnull header, 113 | intptr_t slide, 114 | STTracer tracer) { 115 | stTracer = tracer; 116 | #pragma clang diagnostic push 117 | #pragma clang diagnostic ignored "-Wnonnull" 118 | int rval = rebind_symbols_image(header, slide, NULL, -1); 119 | #pragma clang diagnostic pop 120 | stTracer = NULL; 121 | return rval; 122 | }// SwiftTrace additions end 123 | 124 | static void perform_rebinding_with_section(struct rebindings_entry *rebindings, 125 | section_t *section, 126 | intptr_t slide, 127 | nlist_t *symtab, 128 | char *strtab, 129 | uint32_t *indirect_symtab) { 130 | const bool isDataConst = strcmp(section->segname, SEG_DATA_CONST) == 0; 131 | uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1; 132 | void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr); 133 | vm_prot_t oldProtection = VM_PROT_READ; 134 | if (isDataConst) { 135 | oldProtection = get_protection(rebindings); 136 | mprotect(indirect_symbol_bindings, section->size, PROT_READ | PROT_WRITE); 137 | } 138 | for (uint i = 0; i < section->size / sizeof(void *); i++) { 139 | uint32_t symtab_index = indirect_symbol_indices[i]; 140 | if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL || 141 | symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) { 142 | continue; 143 | } 144 | uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx; 145 | char *symbol_name = strtab + strtab_offset; 146 | bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1]; 147 | struct rebindings_entry *cur = rebindings; 148 | if (!cur) { // SwiftTrace additions here 149 | void *value = !stTracer ? NULL : 150 | stTracer(indirect_symbol_bindings[i], symbol_name+1); 151 | if (value) { 152 | indirect_symbol_bindings[i] = value; 153 | continue; 154 | } 155 | void *fast_dlsym(const void *ptr, const char *symname); 156 | value = dlsym(RTLD_DEFAULT, symbol_name+1) ?: 157 | dlsym(RTLD_DEFAULT, symbol_name) ?: 158 | fast_dlsym(section, symbol_name+1); // Symbol can be unhidden fileprivate 159 | #if DEBUG && 01 160 | if (!indirect_symbol_bindings[i] && !value && 161 | strcmp(symbol_name, "dyld_stub_binder") != 0) 162 | printf("SwiftTrace: Cannot bind symbol %p %p %s %p\n", 163 | section, indirect_symbol_bindings[i], symbol_name, value); 164 | #endif 165 | if (value) 166 | indirect_symbol_bindings[i] = value; 167 | continue; 168 | } // SwiftTrace additions end 169 | while (cur) { 170 | for (uint j = 0; j < cur->rebindings_nel; j++) { 171 | if (symbol_name_longer_than_1 && 172 | strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) { 173 | if (cur->rebindings[j].replaced != NULL && 174 | indirect_symbol_bindings[i] != cur->rebindings[j].replacement) { 175 | *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i]; 176 | } 177 | indirect_symbol_bindings[i] = cur->rebindings[j].replacement; 178 | goto symbol_loop; 179 | } 180 | } 181 | cur = cur->next; 182 | } 183 | symbol_loop:; 184 | } 185 | if (isDataConst) { 186 | int protection = 0; 187 | if (oldProtection & VM_PROT_READ) { 188 | protection |= PROT_READ; 189 | } 190 | if (oldProtection & VM_PROT_WRITE) { 191 | protection |= PROT_WRITE; 192 | } 193 | if (oldProtection & VM_PROT_EXECUTE) { 194 | protection |= PROT_EXEC; 195 | } 196 | mprotect(indirect_symbol_bindings, section->size, protection); 197 | } 198 | } 199 | 200 | static void rebind_symbols_for_image(struct rebindings_entry *rebindings, 201 | const struct mach_header *header, 202 | intptr_t slide) { 203 | Dl_info info; 204 | if (dladdr(header, &info) == 0) { 205 | return; 206 | } 207 | 208 | segment_command_t *cur_seg_cmd; 209 | segment_command_t *linkedit_segment = NULL; 210 | struct symtab_command* symtab_cmd = NULL; 211 | struct dysymtab_command* dysymtab_cmd = NULL; 212 | 213 | uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t); 214 | for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { 215 | cur_seg_cmd = (segment_command_t *)cur; 216 | if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { 217 | if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) { 218 | linkedit_segment = cur_seg_cmd; 219 | } 220 | } else if (cur_seg_cmd->cmd == LC_SYMTAB) { 221 | symtab_cmd = (struct symtab_command*)cur_seg_cmd; 222 | } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) { 223 | dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd; 224 | } 225 | } 226 | 227 | if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment || 228 | !dysymtab_cmd->nindirectsyms) { 229 | return; 230 | } 231 | 232 | // Find base symbol/string table addresses 233 | uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff; 234 | nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff); 235 | char *strtab = (char *)(linkedit_base + symtab_cmd->stroff); 236 | 237 | // Get indirect symbol table (array of uint32_t indices into symbol table) 238 | uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff); 239 | 240 | cur = (uintptr_t)header + sizeof(mach_header_t); 241 | for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { 242 | cur_seg_cmd = (segment_command_t *)cur; 243 | if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { 244 | if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 && 245 | strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) { 246 | continue; 247 | } 248 | for (uint j = 0; j < cur_seg_cmd->nsects; j++) { 249 | section_t *sect = 250 | (section_t *)(cur + sizeof(segment_command_t)) + j; 251 | if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) { 252 | perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab); 253 | } 254 | if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) { 255 | perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab); 256 | } 257 | } 258 | } 259 | } 260 | } 261 | 262 | static void _rebind_symbols_for_image(const struct mach_header *header, 263 | intptr_t slide) { 264 | rebind_symbols_for_image(_rebindings_head, header, slide); 265 | } 266 | 267 | int rebind_symbols_image(void *header, 268 | intptr_t slide, 269 | struct rebinding rebindings[], 270 | size_t rebindings_nel) { 271 | struct rebindings_entry *rebindings_head = NULL; 272 | int retval = prepend_rebindings(&rebindings_head, rebindings, rebindings_nel); 273 | rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide); 274 | if (rebindings_head) { 275 | free(rebindings_head->rebindings); 276 | } 277 | free(rebindings_head); 278 | return retval; 279 | } 280 | 281 | int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) { 282 | int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel); 283 | if (retval < 0) { 284 | return retval; 285 | } 286 | // If this was the first call, register callback for image additions (which is also invoked for 287 | // existing images, otherwise, just run on existing images 288 | if (!_rebindings_head->next) { 289 | _dyld_register_func_for_add_image(_rebind_symbols_for_image); 290 | } else { 291 | uint32_t c = _dyld_image_count(); 292 | for (uint32_t i = 0; i < c; i++) { 293 | _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i)); 294 | } 295 | } 296 | return retval; 297 | } 298 | #endif 299 | -------------------------------------------------------------------------------- /SwiftTraceGuts/include/SwiftTrace.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftTrace.h 3 | // SwiftTrace 4 | // 5 | // Created by John Holdsworth on 10/06/2016. 6 | // Copyright © 2016 John Holdsworth. All rights reserved. 7 | // 8 | // Repo: https://github.com/johnno1962/SwiftTrace 9 | // $Id: //depot/SwiftTrace/SwiftTraceGuts/include/SwiftTrace.h#69 $ 10 | // 11 | 12 | #if DEBUG || !DEBUG_ONLY 13 | #ifndef SWIFTTRACE_H 14 | #define SWIFTTRACE_H 15 | 16 | #import 17 | #import "fishhook.h" 18 | 19 | //! Project version number for SwiftTrace. 20 | FOUNDATION_EXPORT double SwiftTraceVersionNumber; 21 | 22 | //! Project version string for SwiftTrace. 23 | FOUNDATION_EXPORT const unsigned char SwiftTraceVersionString[]; 24 | 25 | // In this header, you should import all the public headers of your framework using statements like #import 26 | 27 | /** 28 | Objective-C inteface to SwftTrace as a category on NSObject 29 | as a summary of the functionality available. Intended to be 30 | used from Swift where SwifTrace has been provided from a 31 | dynamically loaded bundle, for example, from InjectionIII. 32 | 33 | Each trace superceeds any previous traces when they where 34 | not explicit about the class or instance being traced 35 | (see swiftTraceIntances and swiftTraceInstance). For 36 | example, the following code: 37 | 38 | UIView.swiftTraceBundle() 39 | UITouch.traceInstances(withSubLevels: 3) 40 | 41 | Will put a trace on all of the UIKit frameowrk which is then 42 | refined by the specific trace for only instances of class 43 | UITouch to be printed and any calls to UIKit made by those 44 | methods up to three levels deep. 45 | */ 46 | 47 | /** 48 | Signature of function used to select symbols to inject. 49 | */ 50 | typedef BOOL (^ _Nonnull STSymbolFilter)(const char *_Nonnull symname); 51 | /** 52 | Callback on selecting symbol. 53 | */ 54 | typedef void (^ _Nonnull STSymbolCallback)(const void *_Nonnull address, const char *_Nonnull symname, 55 | void *_Nonnull typeref, void *_Nonnull typeend); 56 | 57 | @interface NSObject(SwiftTrace) 58 | /** 59 | The default regexp used to exclude certain methods from tracing. 60 | */ 61 | + (NSString *_Nonnull)swiftTraceDefaultMethodExclusions; 62 | /** 63 | Optional filter of methods to be included in subsequent traces. 64 | */ 65 | @property (nonatomic, class, copy) NSString *_Nullable swiftTraceMethodInclusionPattern; 66 | /** 67 | Provide a regular expression to exclude methods. 68 | */ 69 | @property (nonatomic, class, copy) NSString *_Nullable swiftTraceMethodExclusionPattern; 70 | /** 71 | Real time control over methods to be traced (regular expressions) 72 | */ 73 | @property (nonatomic, class, copy) NSString *_Nullable swiftTraceFilterInclude; 74 | @property (nonatomic, class, copy) NSString *_Nullable swiftTraceFilterExclude; 75 | /** 76 | Filter of symbols that will be patched/interposed. 77 | */ 78 | @property (nonatomic, class, copy) STSymbolFilter _Nonnull swiftTraceSymbolFilter; 79 | /** 80 | Function type suffixes at end of mangled symbol name. 81 | */ 82 | @property (nonatomic, class, copy) NSArray *_Nonnull swiftTraceFunctionSuffixes; 83 | /** Are we tracing? */ 84 | @property (readonly, class) BOOL swiftTracing; 85 | /** Pointer to common interposed state dictionary */ 86 | @property (readonly, class) void *_Nonnull swiftTraceInterposed; 87 | /** lookup unknown types */ 88 | @property (class) BOOL swiftTraceTypeLookup; 89 | /** 90 | Class will be traced (as opposed to swiftTraceInstances which 91 | will trace methods declared in super classes as well and only 92 | for instances of that particular class not any subclasses.) 93 | */ 94 | + (void)swiftTrace; 95 | /** 96 | Trace all methods defined in classes contained in the main 97 | executable of the application. 98 | */ 99 | + (void)swiftTraceMainBundle; 100 | /** 101 | Trace all methods of classes in the main bundle but also 102 | up to subLevels of calls made by those methods if a more 103 | general trace has already been placed on them. 104 | */ 105 | + (void)swiftTraceMainBundleWithSubLevels:(int)subLevels; 106 | /** 107 | Add a trace to all methods of all classes defined in the 108 | bundle or framework that contains the receiving class. 109 | */ 110 | + (void)swiftTraceBundle; 111 | /** 112 | Add a trace to all methods of all classes defined in the 113 | all frameworks in the app bundle. 114 | */ 115 | + (NSInteger)swiftTraceFrameworkMethods; 116 | /** 117 | Output a trace of methods defined in the bundle containing 118 | the reciever and up to subLevels of calls made by them. 119 | */ 120 | + (void)swiftTraceBundleWithSubLevels:(int)subLevels; 121 | /** 122 | Trace classes in the application that have names matching 123 | the regular expression. 124 | */ 125 | + (void)swiftTraceClassesMatchingPattern:(NSString *_Nonnull)pattern; 126 | /** 127 | Trace classes in the application that have names matching 128 | the regular expression and subLevels of cals they make to 129 | classes that have already been traced. 130 | */ 131 | + (void)swiftTraceClassesMatchingPattern:(NSString *_Nonnull)pattern subLevels:(intptr_t)subLevels; 132 | /** 133 | Return an array of the demangled names of methods declared 134 | in the reciving Swift class that can be traced. 135 | */ 136 | + (NSArray *_Nonnull)swiftTraceMethodNames; 137 | /** 138 | Return an array of the demangled names of methods declared 139 | in the Swift class provided. 140 | */ 141 | + (NSArray *_Nonnull)switTraceMethodsNamesOfClass:(Class _Nonnull)aClass; 142 | /** 143 | Trace instances of the specific receiving class (including 144 | the methods of its superclasses.) 145 | */ 146 | + (void)swiftTraceInstances; 147 | /** 148 | Trace instances of the specific receiving class (including 149 | the methods of its superclasses and subLevels of previously 150 | traced methods called by those methods.) 151 | */ 152 | + (void)swiftTraceInstancesWithSubLevels:(int)subLevels; 153 | /** 154 | Trace a methods (including those of all superclasses) for 155 | a particular instance only. 156 | */ 157 | - (void)swiftTraceInstance; 158 | /** 159 | Trace methods including those of all superclasses for a 160 | particular instance only and subLevels of calls they make. 161 | */ 162 | - (void)swiftTraceInstanceWithSubLevels:(int)subLevels; 163 | /** 164 | Trace all protocols contained in the bundle declaring the receiver class 165 | */ 166 | + (void)swiftTraceProtocolsInBundle; 167 | /** 168 | Trace protocols in bundle with qualifications 169 | */ 170 | + (void)swiftTraceProtocolsInBundleWithMatchingPattern:(NSString *_Nullable)pattern; 171 | + (void)swiftTraceProtocolsInBundleWithSubLevels:(int)subLevels; 172 | + (void)swiftTraceProtocolsInBundleWithMatchingPattern:(NSString *_Nullable)pattern subLevels:(int)subLevels; 173 | /** 174 | Use interposing to trace all methods in main bundle 175 | Use swiftTraceInclusionPattern, swiftTraceExclusionPattern to filter 176 | */ 177 | + (NSInteger)swiftTraceMethodsInFrameworkContaining:(Class _Nonnull)aClass; 178 | + (NSInteger)swiftTraceMainBundleMethods; 179 | + (NSInteger)swiftTraceMethodsInBundle:(const char *_Nonnull)bundlePath 180 | packageName:(NSString *_Nullable)packageName; 181 | + (void)swiftTraceBundlePath:(const char *_Nonnull)bundlePath; 182 | /** 183 | Remove most recent trace 184 | */ 185 | + (BOOL)swiftTraceUndoLastTrace; 186 | /** 187 | Remove all tracing swizles. 188 | */ 189 | + (void)swiftTraceRemoveAllTraces; 190 | /** 191 | Remove all interposes from tracing. 192 | */ 193 | + (void)swiftTraceRevertAllInterposes; 194 | /** 195 | Total elapsed time by traced method. 196 | */ 197 | + (NSDictionary *_Nonnull)swiftTraceElapsedTimes; 198 | /** 199 | Invocation counts by traced method. 200 | */ 201 | + (NSDictionary *_Nonnull)swiftTraceInvocationCounts; 202 | /** 203 | Demangle Swift symbol. 204 | */ 205 | + (NSString *_Nullable)swiftTraceDemangle:(char const *_Nonnull)symbol; 206 | @end 207 | 208 | #import 209 | @interface ObjcDYLookup: NSObject { 210 | void *dyLookup; 211 | } 212 | - (instancetype _Nonnull)init; 213 | - (int)dladdr:(void *_Nonnull)pointer info:(Dl_info *_Nonnull)info;// SWIFT_NAME(dladdr(_:_)); 214 | @end 215 | 216 | #import 217 | #import 218 | 219 | #define ST_LAST_IMAGE -1 220 | #define ST_ANY_VISIBILITY 0 221 | #define ST_GLOBAL_VISIBILITY 0xf 222 | #define ST_HIDDEN_VISIBILITY 0x1e 223 | #define ST_LOCAL_VISIBILITY 0xe 224 | 225 | typedef NS_ENUM(uint8_t, STVisibility) { 226 | STVisibilityAny = ST_ANY_VISIBILITY, 227 | STVisibilityGlobal = ST_GLOBAL_VISIBILITY, 228 | STVisibilityHidden = ST_HIDDEN_VISIBILITY, 229 | STVisibilityLocal = ST_LOCAL_VISIBILITY, 230 | }; 231 | 232 | #ifdef __cplusplus 233 | extern "C" { 234 | #endif 235 | IMP _Nonnull imp_implementationForwardingToTracer(void *_Nonnull patch, 236 | IMP _Nonnull onEntry, IMP _Nonnull onExit); 237 | NSArray *_Nonnull objc_classArray(void); 238 | NSMethodSignature *_Nullable method_getSignature(Method _Nonnull Method); 239 | const char *_Nonnull sig_argumentType(id _Nonnull signature, NSUInteger index); 240 | const char *_Nonnull sig_returnType(id _Nonnull signature); 241 | const char *_Nonnull searchMainImage(void); 242 | const char *_Nonnull searchLastLoaded(void); 243 | const char *_Nullable searchAllImages(void); 244 | const char *_Nonnull searchBundleImages(void); 245 | const char *_Nonnull classesIncludingObjc(void); 246 | void findSwiftSymbols(const char *_Nullable path, const char *_Nonnull suffix, 247 | STSymbolCallback callback); 248 | void findHiddenSwiftSymbols(const char *_Nullable path, const char *_Nonnull suffix, STVisibility visibility, 249 | STSymbolCallback callback); 250 | void *_Nullable findSwiftSymbol(const char *_Nullable path, const char *_Nonnull suffix, STVisibility visibility); 251 | void filterImageSymbols(int32_t imageNumber, STVisibility visibility, 252 | STSymbolFilter filter, STSymbolCallback callback); 253 | void filterHeaderSymbols(const struct mach_header *_Nonnull header, 254 | STVisibility visibility, STSymbolFilter filter, STSymbolCallback callback); 255 | void appBundleImages(void (^ _Nonnull callback)(const char *_Nonnull imageName, 256 | const struct mach_header *_Nonnull header, intptr_t slide)); 257 | id _Nullable findSwizzleOf(void *_Nonnull trampoline); 258 | const char *_Nullable swiftUIBundlePath(void); 259 | const char *_Nullable callerBundle(void); 260 | 261 | void pushPseudoImage(const char *_Nonnull path, 262 | const void *_Nonnull header); 263 | const struct mach_header *_Nullable lastPseudoImage(void); 264 | const struct mach_header *_Nonnull lastLoadedImage(void); 265 | NSString *_Nonnull describeImageSymbol(const char *_Nonnull symname); 266 | NSString *_Nonnull describeImageInfo(const Dl_info *_Nonnull info); 267 | NSString *_Nonnull describeImagePointer(const void *_Nonnull pointer); 268 | void injection_stack(void); 269 | 270 | void *_Nullable fast_dlopen(const char * _Nonnull __path, int __mode); 271 | void *_Nullable fast_dlsym(const void *_Nonnull ptr, const char *_Nonnull symname); 272 | int fast_dladdr(const void *_Nonnull, Dl_info *_Nonnull); 273 | void fast_dlscan(const void *_Nonnull ptr, STVisibility visibility, 274 | STSymbolFilter filter, STSymbolCallback callback); 275 | vm_prot_t get_protection(void *_Nonnull sectionStart); 276 | const char *_Nonnull swiftTrace_path(); 277 | #ifdef __cplusplus 278 | } 279 | #import 280 | typedef std::pair PseudoImage; 281 | extern const std::vector &getLoadedPseudoImages(void); 282 | #endif 283 | 284 | struct dyld_interpose_tuple { 285 | const void *_Nonnull replacement; 286 | const void *_Nonnull replacee; 287 | }; 288 | 289 | /// Very handy albeit private API on dynamic loader. 290 | /// Replaced by fishhook to remain in the App Store. 291 | //void dyld_dynamic_interpose( 292 | // const struct mach_header * _Nonnull mh, 293 | // const struct dyld_interpose_tuple array[_Nonnull], 294 | // size_t count) __attribute__((weak_import)); 295 | 296 | #ifdef __IPHONE_OS_VERSION_MIN_REQUIRED 297 | #import 298 | #define OSRect CGRect 299 | #define OSMakeRect CGRectMake 300 | #else 301 | #define OSRect NSRect 302 | #define OSMakeRect NSMakeRect 303 | #endif 304 | 305 | @interface ObjcTraceTester: NSObject 306 | 307 | - (OSRect)a:(float)a i:(int)i b:(double)b c:(NSString *_Nullable)c o:o s:(SEL _Nullable)s; 308 | 309 | @end 310 | #endif 311 | #endif 312 | -------------------------------------------------------------------------------- /SwiftTraceGuts/include/fishhook.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, Facebook, Inc. 2 | // All rights reserved. 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // * Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // * Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // * Neither the name Facebook nor the names of its contributors may be used to 11 | // endorse or promote products derived from this software without specific 12 | // prior written permission. 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | #ifndef fishhook_h 25 | #define fishhook_h 26 | 27 | #include 28 | #include 29 | 30 | #if defined(FISHHOOK_HIDDEN) 31 | #define FISHHOOK_VISIBILITY __attribute__((visibility("hidden"))) 32 | #else 33 | #define FISHHOOK_VISIBILITY __attribute__((visibility("default"))) 34 | #endif 35 | 36 | #ifdef __cplusplus 37 | extern "C" { 38 | #endif //__cplusplus 39 | 40 | /* 41 | * A structure representing a particular intended rebinding from a symbol 42 | * name to its replacement 43 | */ 44 | struct rebinding { 45 | const char * _Nonnull name; 46 | void * _Nonnull replacement; 47 | void * _Nonnull * _Nullable replaced; 48 | }; 49 | 50 | /* 51 | * For each rebinding in rebindings, rebinds references to external, indirect 52 | * symbols with the specified name to instead point at replacement for each 53 | * image in the calling process as well as for all future images that are loaded 54 | * by the process. If rebind_functions is called more than once, the symbols to 55 | * rebind are added to the existing list of rebindings, and if a given symbol 56 | * is rebound more than once, the later rebinding will take precedence. 57 | */ 58 | FISHHOOK_VISIBILITY 59 | int rebind_symbols(struct rebinding rebindings[_Nonnull], size_t rebindings_nel); 60 | 61 | /* 62 | * Rebinds as above, but only in the specified image. The header should point 63 | * to the mach-o header, the slide should be the slide offset. Others as above. 64 | */ 65 | FISHHOOK_VISIBILITY 66 | int rebind_symbols_image(void * _Nonnull header, 67 | intptr_t slide, 68 | struct rebinding rebindings[_Nonnull], 69 | size_t rebindings_nel); 70 | 71 | // SwiftTrace additions here 72 | typedef void * _Nullable(* _Nullable STTracer)(void * _Nonnull existing, 73 | const char * _Nonnull symname); 74 | int rebind_symbols_trace(void * _Nonnull header, 75 | intptr_t slide, 76 | STTracer interposer); 77 | // SwiftTrace additions end 78 | #ifdef __cplusplus 79 | } 80 | #endif //__cplusplus 81 | 82 | #endif //fishhook_h 83 | 84 | -------------------------------------------------------------------------------- /SwiftTraceGuts/xt_forwarding_trampoline_arm64.s: -------------------------------------------------------------------------------- 1 | 2 | // $Id: //depot/SwiftTrace/SwiftTraceGuts/xt_forwarding_trampoline_arm64.s#9 $ 3 | 4 | // for ARM64 abi see http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf 5 | // Layout shadowed in SwiftStack.swift 6 | 7 | #if DEBUG || !DEBUG_ONLY 8 | #if defined(__arm64__) 9 | .text 10 | .align 14 11 | onEntry: 12 | .quad 0 // pointer to function to trace call extry 13 | onExit: 14 | .quad 0 // pointer to function to trace call exit 15 | // SwiftTrace.Swizzle instance pointer at trampoline offset 16 | 17 | .align 14 18 | .globl _xt_forwarding_trampoline_page 19 | .globl _xt_forwarding_trampolines_start 20 | .globl _xt_forwarding_trampolines_next 21 | .globl _xt_forwarding_trampolines_end 22 | 23 | _xt_forwarding_trampoline_page: 24 | _xt_forwarding_trampoline: 25 | sub x16, lr, #0x8 // x16 = lr - 8, that is the address of the corresponding `mov x17, lr` instruction of the current trampoline 26 | sub x16, x16, #0x4000 // x16 = x16 - 16384, that is where the data for this trampoline is stored 27 | mov lr, x17 // restore the link register to that to be used when calling the original implementation 28 | stp fp, lr, [sp, #-16]! // set up frame pointers 29 | mov fp, sp 30 | stp x20, x21, [sp, #-16]! // save error return and context reg (self) 31 | stp x8, fp, [sp, #-16]! // x20 "context" (self), r8 for return of structs 32 | stp x6, x7, [sp, #-16]! // save all regs used in parameter passing 33 | stp x4, x5, [sp, #-16]! 34 | stp x2, x3, [sp, #-16]! 35 | stp x0, x1, [sp, #-16]! 36 | stp d6, d7, [sp, #-16]! 37 | stp d4, d5, [sp, #-16]! 38 | stp d2, d3, [sp, #-16]! 39 | stp d0, d1, [sp, #-16]! 40 | ldr x0, [x16] // first argument is pointer to Swizzle instance 41 | mov x1, lr // second argument is return address 42 | mov x2, sp // third argument is pointer to stack 43 | ldr x16, onEntry 44 | blr x16 // call tracing entry routine (saves return address) 45 | mov x16, x0 // original implementation to call is returned 46 | ldp d0, d1, [sp], #16 47 | ldp d2, d3, [sp], #16 48 | ldp d4, d5, [sp], #16 49 | ldp d6, d7, [sp], #16 50 | ldp x0, x1, [sp], #16 51 | ldp x2, x3, [sp], #16 52 | ldp x4, x5, [sp], #16 53 | ldp x6, x7, [sp], #16 54 | ldp x8, fp, [sp], #16 55 | ldp x20, x21, [sp], #16 56 | ldp fp, lr, [sp], #16 57 | bl getpc 58 | getpc: 59 | add lr, lr, #8 60 | br x16 // continue onto original implemntation 61 | 62 | returning: 63 | stp fp, lr, [sp, #-16]! // set up frame pointers 64 | mov fp, sp 65 | stp x20, x21, [sp, #-16]! 66 | stp x8, fp, [sp, #-16]!// save frame pointer and struct return 67 | stp x6, x7, [sp, #-16]! // save all regs used in parameter passing 68 | stp x4, x5, [sp, #-16]! 69 | stp x2, x3, [sp, #-16]! 70 | stp x0, x1, [sp, #-16]! 71 | stp d6, d7, [sp, #-16]! 72 | stp d4, d5, [sp, #-16]! 73 | stp d2, d3, [sp, #-16]! 74 | stp d0, d1, [sp, #-16]! 75 | ldr x16, onExit 76 | blr x16 // call tracing exit routine 77 | ldp d0, d1, [sp], #16 78 | ldp d2, d3, [sp], #16 79 | ldp d4, d5, [sp], #16 80 | ldp d6, d7, [sp], #16 81 | ldp x0, x1, [sp], #16 82 | ldp x2, x3, [sp], #16 83 | ldp x4, x5, [sp], #16 84 | ldp x6, x7, [sp], #16 85 | ldp x8, fp, [sp], #16 86 | ldp x20, x21, [sp], #16 87 | ldp fp, lr, [sp], #16 88 | ret // return to caller - reset by "onExit()" 89 | nop 90 | 91 | _xt_forwarding_trampolines_start: 92 | # Save lr, which contains the address to where we need to branch back after function returns, then jump to the actual trampoline implementation 93 | mov x17, lr 94 | bl _xt_forwarding_trampoline; 95 | 96 | _xt_forwarding_trampolines_next: 97 | .rept 2016 98 | # Next trampoline entry point 99 | mov x17, lr 100 | bl _xt_forwarding_trampoline; 101 | .endr 102 | 103 | _xt_forwarding_trampolines_end: 104 | nop 105 | 106 | #endif 107 | #endif 108 | -------------------------------------------------------------------------------- /SwiftTraceGuts/xt_forwarding_trampoline_arm7.s: -------------------------------------------------------------------------------- 1 | 2 | // $Id: //depot/SwiftTrace/SwiftTraceGuts/xt_forwarding_trampoline_arm7.s#3 $ 3 | 4 | // *** This architecture is no longer supported *** // 5 | 6 | #if DEBUG || !DEBUG_ONLY 7 | #ifdef __arm__ 8 | 9 | #include 10 | #if defined(_ARM_ARCH_7) 11 | 12 | // for abi see: http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042f/IHI0042F_aapcs.pdf 13 | // instrux ref: http://infocenter.arm.com/help/topic/com.arm.doc.qrc0001m/QRC0001_UAL.pdf 14 | 15 | # Write out the trampoline table, aligned to the page boundary 16 | .text 17 | .align 14 18 | .globl _xt_forwarding_trampoline_page 19 | .globl _xt_forwarding_trampolines_start 20 | .globl _xt_forwarding_trampolines_next 21 | .globl _xt_forwarding_trampolines_end 22 | 23 | _xt_forwarding_trampoline_page: 24 | _xt_forwarding_trampoline: 25 | push {r7, lr} // save frame pointer and return addresss 26 | mov r7, sp // set up new frame 27 | push {r0, r1, r2, r3, r9} // save first four args on stack 28 | sub r12, #0x4000 // r12 = r12 - pagesize, that is where the data for this trampoline is stored 29 | ldr r0, [r12, #-4] // first arg is user data ptr 30 | ldr r12, [r12] // get pointer to tracer func 31 | blx r12 // call it 32 | mov r12, r0 // return value is original implementation 33 | pop {r0, r1, r2, r3, r9} 34 | mov sp, r7 // unwind stack 35 | pop {r7, lr} 36 | mov pc, r12 // pass control to original imp. 37 | 38 | _xt_forwarding_trampolines_start: 39 | # Save pc+8 into r12, then jump to the actual trampoline implementation 40 | mov r12, pc 41 | b _xt_forwarding_trampoline; 42 | 43 | _xt_forwarding_trampolines_next: 44 | .rept 2041 45 | # Next trampoline entry point 46 | mov r12, pc 47 | b _xt_forwarding_trampoline; 48 | .endr 49 | 50 | _xt_forwarding_trampolines_end: 51 | #endif 52 | #endif 53 | #endif 54 | -------------------------------------------------------------------------------- /SwiftTraceGuts/xt_forwarding_trampoline_x64.s: -------------------------------------------------------------------------------- 1 | 2 | // $Id: //depot/SwiftTrace/SwiftTraceGuts/xt_forwarding_trampoline_x64.s#12 $ 3 | 4 | // https://en.wikipedia.org/wiki/X86_calling_conventions 5 | // Layout shadowed in SwiftStack.swift 6 | 7 | #if DEBUG || !DEBUG_ONLY 8 | #if defined(__LP64__) && !defined(__arm64__) 9 | .text 10 | .align 12 11 | onEntry: 12 | .quad 0 // pointer to function to trace call extry 13 | onExit: 14 | .quad 0 // pointer to function to trace call exit 15 | // SwiftTrace.Swizzle instance pointer at trampoline offset 16 | 17 | .align 12 18 | .globl _xt_forwarding_trampoline_page 19 | .globl _xt_forwarding_trampolines_start 20 | .globl _xt_forwarding_trampolines_next 21 | .globl _xt_forwarding_trampolines_end 22 | 23 | _xt_forwarding_trampoline_page: 24 | _xt_forwarding_trampoline: 25 | popq %r11 // recover trampoline return address 26 | pushq %rbp // save frame pointer 27 | movq %rsp, %rbp 28 | pushq %rbp // align stack to 16 bytes 29 | pushq %rbx 30 | pushq %rax // pointer for return of struct 31 | pushq %r10 32 | pushq %r9 // push the 6 registers for int parameters 33 | pushq %r8 34 | pushq %rcx 35 | pushq %rdx 36 | pushq %rsi 37 | pushq %rdi 38 | pushq %r15 39 | pushq %r14 40 | pushq %r13 // Swift "call context" register for self 41 | pushq %r12 42 | subq $64, %rsp // make space for floating point registers and save 43 | movsd %xmm0, (%rsp) 44 | movsd %xmm1, 8(%rsp) 45 | movsd %xmm2, 16(%rsp) 46 | movsd %xmm3, 24(%rsp) 47 | movsd %xmm4, 32(%rsp) 48 | movsd %xmm5, 40(%rsp) 49 | movsd %xmm6, 48(%rsp) 50 | movsd %xmm7, 56(%rsp) 51 | subq $4096+5, %r11 // find trampoline info relative to return address 52 | movq (%r11), %rdi // first argument is pointer to Swizzle instance 53 | movq 184(%rsp), %rsi // second argument is original return address 54 | movq %rsp, %rdx // third argument is stack pointer 55 | leaq onEntry(%rip), %r11 56 | callq *(%r11) // call tracing entry routine (saves return address) 57 | leaq returning(%rip), %r11 58 | movq %r11, 184(%rsp) // patch return address to "returning" code 59 | movq %rax, %r11 // pointer to original implementation returned 60 | movsd (%rsp), %xmm0 // restore all registers 61 | movsd 8(%rsp), %xmm1 62 | movsd 16(%rsp), %xmm2 63 | movsd 24(%rsp), %xmm3 64 | movsd 32(%rsp), %xmm4 65 | movsd 40(%rsp), %xmm5 66 | movsd 48(%rsp), %xmm6 67 | movsd 56(%rsp), %xmm7 68 | addq $64, %rsp 69 | popq %r12 70 | popq %r13 71 | popq %r14 72 | popq %r15 73 | popq %rdi 74 | popq %rsi 75 | popq %rdx 76 | popq %rcx 77 | popq %r8 78 | popq %r9 79 | popq %r10 80 | popq %rax 81 | popq %rbx 82 | popq %rbp 83 | popq %rbp // restore frame pointer 84 | jmpq *%r11 // forward onto original implementation 85 | 86 | returning: 87 | pushq %rbp // make space for real return address 88 | pushq %rbp // bump frame 89 | movq %rsp, %rbp 90 | pushq %rbp // align stack to 16 bytes 91 | pushq %rbx 92 | pushq %r10 93 | pushq %r9 94 | pushq %r8 // push the 4 regs used for int returns 95 | pushq %rcx 96 | pushq %rdx 97 | pushq %rax 98 | pushq %rsi 99 | pushq %rdi 100 | pushq %r15 101 | pushq %r14 102 | pushq %r13 // Swift "call context" register for self 103 | pushq %r12 104 | subq $64, %rsp // make space for floating point regeisters and save 105 | movsd %xmm0, (%rsp) 106 | movsd %xmm1, 8(%rsp) 107 | movsd %xmm2, 16(%rsp) 108 | movsd %xmm3, 24(%rsp) 109 | movsd %xmm4, 32(%rsp) 110 | movsd %xmm5, 40(%rsp) 111 | movsd %xmm6, 48(%rsp) 112 | movsd %xmm7, 56(%rsp) 113 | leaq onExit(%rip), %r11 114 | callq *(%r11) // call tracing exit routine 115 | movsd (%rsp), %xmm0 // restore all registers 116 | movsd 8(%rsp), %xmm1 117 | movsd 16(%rsp), %xmm2 118 | movsd 24(%rsp), %xmm3 119 | movsd 32(%rsp), %xmm4 120 | movsd 40(%rsp), %xmm5 121 | movsd 48(%rsp), %xmm6 122 | movsd 56(%rsp), %xmm7 123 | addq $64, %rsp 124 | popq %r12 125 | popq %r13 126 | popq %r14 127 | popq %r15 128 | popq %rdi 129 | popq %rsi 130 | popq %rax 131 | popq %rdx 132 | popq %rcx 133 | popq %r8 134 | popq %r9 135 | popq %r10 136 | popq %rbx 137 | popq %rbp 138 | popq %rbp 139 | ret // return to original caller - reset by "onExit()" 140 | nop 141 | nop 142 | nop 143 | nop 144 | 145 | _xt_forwarding_trampolines_start: 146 | callq _xt_forwarding_trampoline 147 | nop 148 | nop 149 | nop 150 | 151 | // another 465 trampoline entry points 152 | _xt_forwarding_trampolines_next: 153 | .rept 465 154 | callq _xt_forwarding_trampoline 155 | nop 156 | nop 157 | nop 158 | .endr 159 | 160 | _xt_forwarding_trampolines_end: 161 | nop 162 | 163 | #endif 164 | #endif 165 | -------------------------------------------------------------------------------- /SwiftTraceGuts/xt_forwarding_trampoline_x86.s: -------------------------------------------------------------------------------- 1 | 2 | // $Id: //depot/SwiftTrace/SwiftTraceGuts/xt_forwarding_trampoline_x86.s#4 $ 3 | 4 | // *** This architecture is no longer supported *** // 5 | 6 | #if DEBUG || !DEBUG_ONLY 7 | #if defined(__i386__) 8 | .text 9 | .align 12 10 | .globl _xt_forwarding_trampoline_page 11 | .globl _xt_forwarding_trampolines_start 12 | .globl _xt_forwarding_trampolines_next 13 | .globl _xt_forwarding_trampolines_end 14 | 15 | _xt_forwarding_trampoline_page: 16 | _xt_forwarding_trampoline: 17 | popl %eax // pop saved pc (address of first of the three nops) 18 | pushl %ebp // save frame pointer 19 | movl %esp, %ebp // set up new frame 20 | subl $4096+5, %eax // offset address by one page and the length of the call instrux 21 | pushl %eax // save pointer to trampoline data (func+data) 22 | movl 4(%eax), %eax 23 | pushl %eax // save pointer to user data 24 | movl 4(%esp), %eax // fetch pointer to C aspect handler 25 | call *(%eax) // call trace handler 26 | popl %ebp 27 | popl %ebp 28 | popl %ebp // restore frame pointer 29 | jmpl *%eax // pass on to original implementation 30 | nop 31 | nop 32 | nop 33 | nop 34 | nop 35 | nop 36 | nop 37 | 38 | // 508 trampoline entry points 39 | _xt_forwarding_trampolines_start: 40 | call _xt_forwarding_trampoline 41 | nop 42 | nop 43 | nop 44 | 45 | _xt_forwarding_trampolines_next: 46 | .rept 507 47 | call _xt_forwarding_trampoline 48 | nop 49 | nop 50 | nop 51 | .endr 52 | 53 | _xt_forwarding_trampolines_end: 54 | nop 55 | #endif 56 | #endif 57 | -------------------------------------------------------------------------------- /SwiftTraceGutsD/ObjCBridge.mm: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/ObjCBridge.mm -------------------------------------------------------------------------------- /SwiftTraceGutsD/SwiftTrace-Swift.h: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/SwiftTrace-Swift.h -------------------------------------------------------------------------------- /SwiftTraceGutsD/SwiftTrace.mm: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/SwiftTrace.mm -------------------------------------------------------------------------------- /SwiftTraceGutsD/Trampolines.mm: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/Trampolines.mm -------------------------------------------------------------------------------- /SwiftTraceGutsD/fast_dladdr.mm: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/fast_dladdr.mm -------------------------------------------------------------------------------- /SwiftTraceGutsD/fishhook.c: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/fishhook.c -------------------------------------------------------------------------------- /SwiftTraceGutsD/include/SwiftTrace.h: -------------------------------------------------------------------------------- 1 | ../../SwiftTraceGuts/include/SwiftTrace.h -------------------------------------------------------------------------------- /SwiftTraceGutsD/include/fishhook.h: -------------------------------------------------------------------------------- 1 | ../../SwiftTraceGuts/include/fishhook.h -------------------------------------------------------------------------------- /SwiftTraceGutsD/xt_forwarding_trampoline_arm64.s: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/xt_forwarding_trampoline_arm64.s -------------------------------------------------------------------------------- /SwiftTraceGutsD/xt_forwarding_trampoline_arm7.s: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/xt_forwarding_trampoline_arm7.s -------------------------------------------------------------------------------- /SwiftTraceGutsD/xt_forwarding_trampoline_x64.s: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/xt_forwarding_trampoline_x64.s -------------------------------------------------------------------------------- /SwiftTraceGutsD/xt_forwarding_trampoline_x86.s: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/xt_forwarding_trampoline_x86.s -------------------------------------------------------------------------------- /SwiftTraceOSX/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftTraceOSX 4 | // 5 | // Created by John Holdsworth on 13/06/2016. 6 | // Copyright © 2016 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | public typealias XInt = UInt16 12 | 13 | public struct Stret: SwiftTraceFloatArg { 14 | let r1: CGRect, r2: CGRect, r3: CGRect 15 | } 16 | 17 | public struct Str3 { 18 | var s1 = "s1", s2 = "s2", s3 = "s3" 19 | } 20 | 21 | public typealias uuid_t = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) 22 | public typealias uuid_string_t = (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8) 23 | 24 | public struct STR: Hashable { 25 | let s: String 26 | // let a = 1 27 | // let u = URL(string: "https://google.com") 28 | let u = URL(string: "") 29 | // let b = 2 30 | public init(s: String) { 31 | self.s = s 32 | // u = UUID() 33 | // print(u) 34 | } 35 | public func hash(into hasher: inout Hasher) { 36 | // s.hash(into: &hasher) 37 | } 38 | public static func ==(lhs: STR, rhs: STR) -> Bool { 39 | return lhs.s == rhs.s && lhs.u == rhs.u 40 | } 41 | } 42 | 43 | public protocol P2 { 44 | } 45 | extension String: P2 { 46 | } 47 | 48 | public protocol P { 49 | associatedtype myType 50 | associatedtype myType2 51 | var i: Int { get set } 52 | func x() 53 | func y() -> Float 54 | func z( _ d: XInt, f: Double, s: String?, g: Float, h: Double, f1: Double?, g1: Float, h1: Double, f2: Double, g2: Float, h2: myType?, e: myType2? ) 55 | func rect(r1: NSRect, r2: NSRect) -> NSRect 56 | func rect2(r1: NSRect, r2: NSRect) -> Stret 57 | func arr(a: [String?], b: [Int]) -> ArraySlice 58 | func arr2(a: [String?], b: [Int]) -> Set 59 | func str(i: Int, s: STR, j: Int) -> STR 60 | func dict(d: [String: Set]?) -> [String: Set]? 61 | func c(c: @escaping (_ a: String) -> ()) -> (_ a: String) -> () 62 | func u(i: Int, u: URL, j: Int) -> URL 63 | func p(p: P2) -> P2 64 | func c2(c: TestClass) -> TestClass 65 | func any(a: Any) -> Any 66 | } 67 | 68 | open class TestClass: P { 69 | 70 | public var i = 999 71 | public var s = "8" 72 | public var tc: TestClass? 73 | 74 | open func x() { 75 | print( "open func x() \(i)" ) 76 | } 77 | 78 | open func y() -> Float { 79 | print( "open func y()" ) 80 | return -9.0 81 | } 82 | 83 | open func z( _ d: XInt, f: Double, s: String?, g: Float, h: Double, f1: Double?, g1: Float, h1: Double, f2: Double, g2: Float, h2: Double?, e: CGFloat? ) { 84 | print( "open func z( \(i) \(d) \(String(describing: e)) \(f) \(String(describing: s)) \(g) \(h) \(String(describing: f1)) \(g1) \(h1) \(f2) \(g2) \(String(describing: h2)) )" ) 85 | } 86 | 87 | public func rect(r1: NSRect, r2: NSRect) -> NSRect { 88 | return r1 89 | } 90 | 91 | public func rect2(r1: NSRect, r2: NSRect) -> Stret { 92 | return Stret(r1: r1, r2: r2, r3: r2) 93 | } 94 | 95 | public func arr(a: [String?], b: [Int]) -> ArraySlice { 96 | return a[1...] 97 | } 98 | 99 | public func arr2(a: [String?], b: [Int]) -> Set { 100 | return Set(a.map {STR(s: $0!)}) 101 | } 102 | 103 | public func str(i: Int, s: STR, j: Int) -> STR { 104 | return s 105 | } 106 | 107 | public func dict(d: [String: Set]?) -> [String: Set]? { 108 | return d 109 | } 110 | 111 | public func c(c: @escaping (_ a: String) -> ()) -> (_ a: String) -> () { 112 | return c 113 | } 114 | 115 | public func u(i: Int, u: URL, j: Int) -> URL { 116 | return u 117 | } 118 | 119 | public func p(p: P2) -> P2 { 120 | return p 121 | } 122 | 123 | public func c2(c: TestClass) -> TestClass { 124 | return c 125 | } 126 | 127 | public func any(a: Any) -> Any { 128 | return a 129 | } 130 | 131 | public func str3(s1: String, s2: String, s3: String) -> Str3 { 132 | return Str3(s1: s1, s2: s2, s3: s3) 133 | } 134 | } 135 | 136 | struct TestStruct: P { 137 | 138 | public var i = 999 139 | 140 | public func x() { 141 | print( "open func x() \(i)" ) 142 | } 143 | 144 | public func y() -> Float { 145 | print( "open func y()" ) 146 | return -9.0 147 | } 148 | 149 | public func z( _ d: XInt, f: Double, s: String?, g: Float, h: Double, f1: Double?, g1: Float, h1: Double, f2: Double, g2: Float, h2: CGFloat?, e: Int? ) { 150 | print( "open func z( \(i) \(d) \(String(describing: e)) \(f) \(String(describing: s)) \(g) \(h) \(String(describing: f1)) \(g1) \(h1) \(f2) \(g2) \(String(describing: h2)) )" ) 151 | } 152 | 153 | public func rect(r1: NSRect, r2: NSRect) -> NSRect { 154 | return r1 155 | } 156 | 157 | public func rect2(r1: NSRect, r2: NSRect) -> Stret { 158 | return Stret(r1: r1, r2: r2, r3: r2) 159 | } 160 | 161 | public func arr(a: [String?], b: [Int]) -> ArraySlice { 162 | return a[1...] 163 | } 164 | 165 | public func arr2(a: [String?], b: [Int]) -> Set { 166 | return Set(a.map {STR(s: $0!)}) 167 | } 168 | 169 | public func str(i: Int, s: STR, j: Int) -> STR { 170 | return s 171 | } 172 | 173 | public func dict(d: [String: Set]?) -> [String: Set]? { 174 | return d 175 | } 176 | 177 | public func c(c: @escaping (_ a: String) -> ()) -> (_ a: String) -> () { 178 | return c 179 | } 180 | 181 | public func u(i: Int, u: URL, j: Int) -> URL { 182 | return u 183 | } 184 | 185 | public func p(p: P2) -> P2 { 186 | return p 187 | } 188 | 189 | public func c2(c: TestClass) -> TestClass { 190 | return c 191 | } 192 | 193 | public func any(a: Any) -> Any { 194 | return a 195 | } 196 | } 197 | 198 | @NSApplicationMain 199 | class AppDelegate: NSObject, NSApplicationDelegate { 200 | 201 | @IBOutlet weak var window: NSWindow! 202 | 203 | func applicationDidFinishLaunching(_ aNotification: Notification) { 204 | #if true 205 | // Insert code here to initialize your application 206 | // print(objc_classArray().count) 207 | 208 | // any inclusions or exlusiona need to come before trace enabled 209 | //SwiftTrace.include( "Swift.Optiona|TestClass" ) 210 | SwiftTrace.typeLookup = true 211 | SwiftTrace.decorateAny = true 212 | 213 | class MyTracer: SwiftTrace.Swizzle { 214 | 215 | override func onEntry(stack: inout SwiftTrace.EntryStack) { 216 | print( ">> "+signature ) 217 | } 218 | } 219 | 220 | // Self.swiftTraceSetExclusionPattern(NSObject.swiftTraceDefaultMethodExclusions()) 221 | // NSObject.swiftTraceSetInclusionPattern(".") 222 | // SwiftTrace.patchFactory = MyTracer.self 223 | 224 | 225 | let objcTester = ObjcTraceTester() 226 | 227 | objcTester.swiftTraceInstance(withSubLevels: 2) 228 | objcTester.a(44, i:45, b: 55, c: "66", o: self, s: Selector(("jjj:"))) 229 | 230 | NSObject.swiftTraceClasses(matchingPattern: "Test", subLevels: 2) 231 | 232 | objcTester.a(44, i:45, b: 55, c: "66", o: self, s: Selector(("jjj:"))) 233 | 234 | // SwiftTrace.excludeFunction = NSRegularExpression(regexp: 235 | // "^\\w+\\.\\w+\\(|extension in S|SwiftTrace|out: inout|autoBitCast") 236 | _ = SwiftTrace.traceMainBundleMethods() 237 | 238 | var a/*: P*/ = TestClass() 239 | // print(SwiftTrace.invoke(target: a as AnyObject, methodName: "SwiftTwaceOSX.TestClass.rect(r1: __C.CGRect, r2: __C.CGRect) -> __C.CGRect", args: NSRect(x: 1111.0, y: 2222.0, width: 3333.0, height: 4444.0), NSRect(x: 11111.0, y: 22222.0, width: 33333.0, height: 44444.0)) as NSRect) 240 | 241 | print(a.rect2(r1: NSRect(x: 1111.0, y: 2222.0, width: 3333.0, height: 4444.0), r2:NSRect(x: 11111.0, y: 22222.0, width: 33333.0, height: 44444.0))) 242 | 243 | print(SwiftTrace.methodNames(ofClass: TestClass.self)) 244 | print(SwiftTrace.swiftClassList(bundlePath: class_getImageName(TestClass.self))) 245 | 246 | let d = Optional.some(["test": Set([STR(s: "value")])]) 247 | let any: Any = d 248 | print(a.any(a: any)) 249 | 250 | print(a.u(i: 99, u: URL(string: "http://google.com")!, j: 89)) 251 | 252 | a.i = 888 253 | print(a.i) 254 | a.x() 255 | print( a.y() ) 256 | a.x() 257 | a.z( 88, f: 66, s: "$%^", g: 55, h: 44, f1: 66, g1: 55, h1: 44, f2: 66, g2: 55, h2: 44, e: 77 ) 258 | print(a.arr(a: ["a", "b", "c"], b: [1, 2, 3])) 259 | print(a.arr2(a: ["a", "b", "c"], b: [1, 2, 3])) 260 | print(a.str(i: 77, s: STR(s: "value"), j: 88)) 261 | print(SwiftMeta.sizeof(anyType: type(of: d))) 262 | print(a.dict(d: d)!) 263 | print(a.c(c: { _ in })) 264 | // a.tc = a 265 | // print(a.c2(c: a)) 266 | print(a.p(p: "s")) 267 | print(MemoryLayout.size) 268 | print(SwiftTrace.invoke(target: a, methodName: "SwiftTwaceOSX.TestClass.rect(r1: __C.CGRect, r2: __C.CGRect) -> __C.CGRect", args: NSRect(x: 1111.0, y: 2222.0, width: 3333.0, height: 4444.0), NSRect(x: 11111.0, y: 22222.0, width: 33333.0, height: 44444.0)) as NSRect) 269 | 270 | print(SwiftTrace.invoke(target: a, methodName: "SwiftTwaceOSX.TestClass.arr(a: Swift.Array>, b: Swift.Array) -> Swift.ArraySlice>", args: ["a", "b", "c"] as [String?], [1, 2, 3]) as ArraySlice) 271 | 272 | print("invokeStret", SwiftTrace.Call(target: a, methodName: "SwiftTwaceOSX.TestClass.rect2(r1: __C.CGRect, r2: __C.CGRect) -> SwiftTwaceOSX.Stret")!.invokeStret(args: NSRect(x: 1111.0, y: 2222.0, width: 3333.0, height: 4444.0), NSRect(x: 1111.0, y: 2222.0, width: 3333.0, height: 4444.0)) as Stret) 273 | 274 | print("invokeStr3", SwiftTrace.Call(target: a as AnyObject, methodName: "SwiftTwaceOSX.TestClass.str3(s1: Swift.String, s2: Swift.String, s3: Swift.String) -> SwiftTwaceOSX.Str3")!.invokeStret(args: "a", "b", "c") as Str3) 275 | 276 | print(SwiftTrace.invoke(target: a as AnyObject, methodName: "SwiftTwaceOSX.TestClass.dict(d: Swift.Optional>>) -> Swift.Optional>>", args: d) as [String: Set]? as Any) 277 | 278 | NSObject.swiftTraceRemoveAllTraces() 279 | 280 | print(SwiftTrace.invoke(target: a as AnyObject, methodName: "SwiftTwaceOSX.TestClass.rect(r1: __C.CGRect, r2: __C.CGRect) -> __C.CGRect", args: NSRect(x: 1111.0, y: 2222.0, width: 3333.0, height: 4444.0), NSRect(x: 11111.0, y: 22222.0, width: 33333.0, height: 44444.0)) as NSRect) 281 | 282 | SwiftTrace.swizzleFactory = SwiftTrace.Decorated.self 283 | #endif 284 | // SwiftTrace.trace(aClass: TestClass.self) 285 | 286 | // ptest(p: TestClass()) 287 | 288 | #if !arch(arm64) 289 | // TestClass.swiftTraceProtocolsInBundle() 290 | #endif 291 | 292 | ptest(p: TestStruct()) 293 | ptest(p: TestClass()) 294 | 295 | for call in SwiftTrace.callOrder() { 296 | print(call.signature) 297 | } 298 | 299 | findSwiftSymbols(Bundle.main.executablePath, classesIncludingObjc()) { 300 | cls,_,_,_ in 301 | print(unsafeBitCast(cls, to: AnyClass.self)) 302 | } 303 | } 304 | 305 | func ptest(p: T) { 306 | p.z( 88, f: 66, s: "$%^", g: 55, h: 44, f1: 66, g1: 55, h1: 44, f2: 66, g2: 55, h2: 44 as? T.myType, e: 77 as? T.myType2) 307 | print(p.rect(r1: NSRect(x: 1111.0, y: 2222.0, width: 3333.0, height: 4444.0), r2: NSRect(x: 1111.0, y: 2222.0, width: 3333.0, height: 4444.0))) 308 | } 309 | 310 | func applicationWillTerminate(_ aNotification: Notification) { 311 | // Insert code here to tear down your application 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /SwiftTraceOSX/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /SwiftTraceOSX/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2016 John Holdsworth. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /SwiftTraceOSX/SwiftTwaceOSX-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "SwiftTrace.h" 6 | -------------------------------------------------------------------------------- /SwiftTraceTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftTraceTests/SwiftTraceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftTraceTests.swift 3 | // SwiftTraceTests 4 | // 5 | // Created by John Holdsworth on 13/06/2016. 6 | // Copyright © 2016 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import SwiftTrace 11 | 12 | struct TestStruct: Equatable { 13 | 14 | let a = 1.0, b = 2.0, c = 3.0 15 | let i = 111, j = 222, k = 333 16 | 17 | } 18 | 19 | func ==(lhs: TestStruct, rhs: TestStruct) -> Bool { 20 | 21 | return lhs.a == lhs.a && lhs.b == lhs.b && lhs.c == lhs.c && lhs.i == lhs.i && lhs.j == lhs.j && lhs.k == lhs.k 22 | } 23 | 24 | protocol P { 25 | 26 | func x() 27 | func y() -> Float 28 | func z( d: Int, f: Double, g: Float, h: Double, f1: Double, g1: Float, h1: Double, f2: Double, g2: Float, h2: Double, e: Int ) 29 | func s( a: TestStruct ) -> TestStruct 30 | 31 | } 32 | 33 | var got = "" 34 | var args = "" 35 | 36 | class SwiftTwaceTests: XCTestCase { 37 | 38 | class TestClass: P { 39 | 40 | let i = 111 41 | 42 | func x() { 43 | got = "\(i)" 44 | } 45 | 46 | func y() -> Float { 47 | got = "\(i)" 48 | return -222.0 49 | } 50 | 51 | func z(d: Int, f: Double, g: Float, h: Double, f1: Double, g1: Float, h1: Double, f2: Double, g2: Float, h2: Double, e: Int) { 52 | got = "\(i) \(d) \(e) \(f) \(g) \(h) \(f1) \(g1) \(h1) \(f2) \(g2) \(h2)" 53 | } 54 | 55 | func s(a: TestStruct) -> TestStruct { 56 | return a 57 | } 58 | 59 | class TestSwizzle: SwiftTrace.Swizzle { 60 | 61 | override func onEntry(stack: inout SwiftTrace.EntryStack) { 62 | args = "\(stack.intArg1) \(getSelf(as: TestClass.self).i) \(stack.floatArg1)" 63 | } 64 | } 65 | } 66 | 67 | let p: P = TestClass() 68 | 69 | override func setUp() { 70 | super.setUp() 71 | // Put setup code here. This method is called before the invocation of each test method in the class. 72 | SwiftTrace.swizzleFactory = TestClass.TestSwizzle.self 73 | SwiftTrace.trace(aClass: TestClass.self) 74 | } 75 | 76 | override func tearDown() { 77 | // Put teardown code here. This method is called after the invocation of each test method in the class. 78 | super.tearDown() 79 | } 80 | 81 | func testExample() { 82 | // This is an example of a functional test case. 83 | // Use XCTAssert and related functions to verify your tests produce the correct results. 84 | 85 | p.x() 86 | XCTAssertEqual(got, "111") 87 | 88 | XCTAssertEqual(p.y(), -222.0) 89 | XCTAssertEqual(got, "111") 90 | 91 | p.z( d: 88, f: 66, g: 55, h: 44, f1: 66, g1: 55, h1: 44, f2: 66, g2: 55, h2: 44, e: 77 ) 92 | XCTAssertEqual(got, "111 88 77 66.0 55.0 44.0 66.0 55.0 44.0 66.0 55.0 44.0" ) 93 | XCTAssertEqual(args, "88 111 66.0") 94 | 95 | XCTAssertEqual(p.s( a: TestStruct() ), TestStruct()) 96 | } 97 | 98 | func testPerformanceExample() { 99 | // This is an example of a performance test case. 100 | self.measure { 101 | // Put the code you want to measure the time of here. 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /SwiftTraceXTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftTraceXTests/SwiftTraceXTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftTraceXTests.swift 3 | // SwiftTraceXTests 4 | // 5 | // Created by John Holdsworth on 13/06/2016. 6 | // Copyright © 2016 John Holdsworth. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | struct TestStruct: Equatable { 12 | 13 | let a = 1.0, b = 2.0, c = 3.0 14 | let i = 111, j = 222, k = 333 15 | 16 | } 17 | 18 | func ==(lhs: TestStruct, rhs: TestStruct) -> Bool { 19 | 20 | return lhs.a == lhs.a && lhs.b == lhs.b && lhs.c == lhs.c && lhs.i == lhs.i && lhs.j == lhs.j && lhs.k == lhs.k 21 | } 22 | 23 | protocol P { 24 | 25 | func x() 26 | func y() -> Float 27 | func z( d: Int, f: Double, g: Float, h: Double, f1: Double, g1: Float, h1: Double, f2: Double, g2: Float, h2: Double, e: Int ) 28 | func s( a: TestStruct ) -> TestStruct 29 | 30 | } 31 | 32 | var got = "" 33 | 34 | class SwiftTraceTests: XCTestCase { 35 | 36 | class TestClass: P { 37 | 38 | let i = 111 39 | 40 | func x() { 41 | got = "\(i)" 42 | } 43 | 44 | func y() -> Float { 45 | got = "\(i)" 46 | return -222.0 47 | } 48 | 49 | func z( d: Int, f: Double, g: Float, h: Double, f1: Double, g1: Float, h1: Double, f2: Double, g2: Float, h2: Double, e: Int ) { 50 | got = "\(i) \(d) \(e) \(f) \(g) \(h) \(f1) \(g1) \(h1) \(f2) \(g2) \(h2)" 51 | } 52 | 53 | func s( a: TestStruct ) -> TestStruct { 54 | return a 55 | } 56 | 57 | } 58 | 59 | let p: P = TestClass() 60 | 61 | override func setUp() { 62 | super.setUp() 63 | // Put setup code here. This method is called before the invocation of each test method in the class. 64 | SwiftTrace.trace( aClass: TestClass.self ) 65 | } 66 | 67 | override func tearDown() { 68 | // Put teardown code here. This method is called after the invocation of each test method in the class. 69 | super.tearDown() 70 | } 71 | 72 | func testExample() { 73 | // This is an example of a functional test case. 74 | // Use XCTAssert and related functions to verify your tests produce the correct results. 75 | 76 | p.x() 77 | XCTAssertEqual( got, "111" ) 78 | 79 | XCTAssertEqual( p.y(), -222.0 ) 80 | XCTAssertEqual( got, "111" ) 81 | 82 | p.z( d: 88, f: 66, g: 55, h: 44, f1: 66, g1: 55, h1: 44, f2: 66, g2: 55, h2: 44, e: 77 ) 83 | XCTAssertEqual( got, "111 88 77 66.0 55.0 44.0 66.0 55.0 44.0 66.0 55.0 44.0" ) 84 | 85 | XCTAssertEqual( p.s( a: TestStruct() ), TestStruct() ) 86 | } 87 | 88 | func testPerformanceExample() { 89 | // This is an example of a performance test case. 90 | self.measure { 91 | // Put the code you want to measure the time of here. 92 | } 93 | } 94 | 95 | } 96 | --------------------------------------------------------------------------------