├── SwiftTraceD ├── .gitignore ├── SwiftTraceGutsD ├── fishhook.c ├── ObjCBridge.mm ├── SwiftTrace.mm ├── Trampolines.mm ├── fast_dladdr.mm ├── SwiftTrace-Swift.h ├── include │ ├── fishhook.h │ └── SwiftTrace.h ├── xt_forwarding_trampoline_arm64.s ├── xt_forwarding_trampoline_arm7.s ├── xt_forwarding_trampoline_x64.s └── xt_forwarding_trampoline_x86.s ├── SwiftTrace ├── SwiftTrace.h ├── Info.plist ├── SwiftStats.swift ├── EasyPointer.swift ├── SwiftAspects.swift ├── SwiftInvoke.swift ├── SwiftStack.swift ├── SwiftLifetime.swift ├── StringIndex.swift ├── SwiftInterpose.swift └── SwiftRefs.swift ├── SwiftTrace.gif ├── .github └── FUNDING.yml ├── SwiftTraceOSX ├── SwiftTwaceOSX-Bridging-Header.h ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist └── AppDelegate.swift ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── SwiftTraceApp.xcodeproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── SwiftTrace.podspec ├── SwiftTraceTests ├── Info.plist └── SwiftTraceTests.swift ├── SwiftTraceXTests ├── Info.plist └── SwiftTraceXTests.swift ├── SwiftTraceApp ├── DetailViewController.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist └── MasterViewController.swift ├── Package.swift ├── LICENSE ├── SwiftTraceGuts ├── xt_forwarding_trampoline_x86.s ├── xt_forwarding_trampoline_arm7.s ├── include │ ├── fishhook.h │ └── SwiftTrace.h ├── xt_forwarding_trampoline_arm64.s ├── xt_forwarding_trampoline_x64.s ├── ObjCBridge.mm ├── Trampolines.mm ├── SwiftTrace.mm └── fishhook.c └── README.md /SwiftTraceD: -------------------------------------------------------------------------------- 1 | SwiftTrace -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | *Library/* 3 | *xcuserdata* 4 | -------------------------------------------------------------------------------- /SwiftTraceGutsD/fishhook.c: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/fishhook.c -------------------------------------------------------------------------------- /SwiftTrace/SwiftTrace.h: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/include/SwiftTrace.h -------------------------------------------------------------------------------- /SwiftTraceGutsD/ObjCBridge.mm: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/ObjCBridge.mm -------------------------------------------------------------------------------- /SwiftTraceGutsD/SwiftTrace.mm: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/SwiftTrace.mm -------------------------------------------------------------------------------- /SwiftTraceGutsD/Trampolines.mm: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/Trampolines.mm -------------------------------------------------------------------------------- /SwiftTraceGutsD/fast_dladdr.mm: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/fast_dladdr.mm -------------------------------------------------------------------------------- /SwiftTraceGutsD/SwiftTrace-Swift.h: -------------------------------------------------------------------------------- 1 | ../SwiftTraceGuts/SwiftTrace-Swift.h -------------------------------------------------------------------------------- /SwiftTraceGutsD/include/fishhook.h: -------------------------------------------------------------------------------- 1 | ../../SwiftTraceGuts/include/fishhook.h -------------------------------------------------------------------------------- /SwiftTraceGutsD/include/SwiftTrace.h: -------------------------------------------------------------------------------- 1 | ../../SwiftTraceGuts/include/SwiftTrace.h -------------------------------------------------------------------------------- /SwiftTrace.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnno1962/SwiftTrace/HEAD/SwiftTrace.gif -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /SwiftTrace/SwiftInvoke.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftInvoke.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/SwiftInvoke.swift#31 $ 10 | // 11 | // Invocation interface for Swift 12 | // ============================== 13 | // 14 | 15 | #if DEBUG || !DEBUG_ONLY 16 | extension SwiftTraceArg { 17 | public func add(toCall call: SwiftTrace.Call) { 18 | call.add(arg: self) 19 | } 20 | } 21 | 22 | extension Array: SwiftTraceArg { 23 | } 24 | extension Optional: SwiftTraceArg { 25 | } 26 | extension Dictionary: SwiftTraceArg { 27 | } 28 | 29 | extension SwiftTrace { 30 | 31 | /** 32 | Implementation of invocation api 33 | */ 34 | public class Call: Swizzle { 35 | 36 | public var input = EntryStack() 37 | public var output = ExitStack() 38 | var backup = EntryStack() 39 | var target: AnyObject 40 | var caller: SIMP? = nil 41 | 42 | public init?(target: AnyObject, methodName: String) { 43 | self.target = target 44 | var sigh: SIMP = { } 45 | super.init(name: methodName, vtableSlot: &sigh) 46 | 47 | guard iterateMethods(ofClass: type(of: target), callback: { 48 | (name, slotIndex, vtableSlot, stop) in 49 | if name == methodName { 50 | self.vtableSlot = vtableSlot 51 | implementation = rebind(vtableSlot).pointee 52 | stop = true 53 | } 54 | }) else { 55 | return nil 56 | } 57 | 58 | input.swiftSelf = autoBitCast(target) 59 | 60 | caller = autoBitCast(imp_implementationForwardingToTracer(autoBitCast(self), 61 | autoBitCast(Swizzle.onEntry), autoBitCast(Swizzle.onExit))) 62 | } 63 | 64 | public required init?(name signature: String, vtableSlot: UnsafeMutablePointer? = 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/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#24 $ 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 = 8 // Was 6 for Intel 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/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 | -------------------------------------------------------------------------------- /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#7 $ 8 | // 9 | 10 | #if DEBUG || !DEBUG_ONLY 11 | #import "include/SwiftTrace.h" 12 | 13 | // 14 | // Trampoline code adapted from: 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 std::vector normalTrampolinePages; 142 | 143 | auto &thisArray = normalTrampolinePages; 144 | auto trampolinePage = thisArray.empty() ? nullptr : thisArray.back(); 145 | 146 | if (!trampolinePage || (trampolinePage->nextAvailableTrampolineIndex == numberOfTrampolinesPerPage) ) { 147 | trampolinePage = SPLForwardingTrampolinePageAlloc(); 148 | thisArray.push_back(trampolinePage); 149 | } 150 | 151 | return trampolinePage; 152 | } 153 | 154 | #if 00 155 | /// Fix for libMainThreadCheck when using trampolines 156 | typedef const char * (*image_path_func)(const void *ptr); 157 | static image_path_func orig_path_func; 158 | 159 | static const char *myld_image_path_containing_address(const void* addr) { 160 | return orig_path_func(addr) ?: "/trampoline"; 161 | } 162 | #endif 163 | 164 | IMP imp_implementationForwardingToTracer(void *patch, IMP onEntry, IMP onExit) 165 | { 166 | #if 00 167 | static dispatch_once_t once; 168 | dispatch_once(&once, ^{ 169 | struct rebinding path_rebinding = {"dyld_image_path_containing_address", 170 | (void *)myld_image_path_containing_address, (void **)&orig_path_func}; 171 | rebind_symbols(&path_rebinding, 1); 172 | }); 173 | #endif 174 | 175 | static os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; 176 | os_unfair_lock_lock(&lock); 177 | 178 | SPLForwardingTrampolinePage *dataPageLayout = nextTrampolinePage(); 179 | 180 | int32_t nextAvailableTrampolineIndex = dataPageLayout->nextAvailableTrampolineIndex; 181 | 182 | #if !defined(__LP64__) 183 | dataPageLayout->trampolineData[nextAvailableTrampolineIndex].tracer = onEntry; 184 | #else 185 | dataPageLayout->onEntry = onEntry; 186 | dataPageLayout->onExit = onExit; 187 | #endif 188 | dataPageLayout->trampolineData[nextAvailableTrampolineIndex].patch = patch; 189 | dataPageLayout->nextAvailableTrampolineIndex++; 190 | 191 | IMP implementation = (IMP)&dataPageLayout->trampolineEntryPoints[nextAvailableTrampolineIndex]; 192 | 193 | os_unfair_lock_unlock(&lock); 194 | 195 | return implementation; 196 | } 197 | 198 | id findSwizzleOf(void * _Nonnull trampoline) { 199 | for (NSValue *allocated in normalTrampolinePages) { 200 | SPLForwardingTrampolinePage *trampolinePage = 201 | (SPLForwardingTrampolinePage *)allocated.pointerValue; 202 | if (trampoline >= trampolinePage->trampolineInstructions && trampoline < 203 | trampolinePage->trampolineInstructions + numberOfTrampolinesPerPage) 204 | return *(id const *)(void *)((char *)trampoline - PAGE_SIZE); 205 | } 206 | return nil; 207 | } 208 | #endif 209 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /SwiftTraceGuts/SwiftTrace.mm: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftTrace.mm 3 | // SwiftTrace 4 | // 5 | // Repo: https://github.com/johnno1962/SwiftTrace 6 | // $Id: //depot/SwiftTrace/SwiftTraceGuts/SwiftTrace.mm#120 $ 7 | // 8 | 9 | #if DEBUG || !DEBUG_ONLY 10 | #import "include/SwiftTrace.h" 11 | #import 12 | #import 13 | #import 14 | #import 15 | 16 | NSArray *objc_classArray() { 17 | unsigned nc; 18 | NSMutableArray *array = [NSMutableArray new]; 19 | if (Class *classes = objc_copyClassList(&nc)) 20 | for (int i=0; i= 0 ; i--) { 139 | const char *imageName = _dyld_get_image_name(i); 140 | if (!(imageName && (bundlePath == searchAllImages() || 141 | imageName == bundlePath || 142 | strstr(imageName, bundlePath)) && 143 | !strstr(imageName, "InjectionScratch.framework"))) 144 | continue; 145 | 146 | filterImageSymbols(i, visibility, 147 | swiftSymbolsWithSuffixOrObjcClass, callback); 148 | if (bundlePath != searchAllImages() && 149 | bundlePath != mainBundlePath) 150 | break; 151 | } 152 | } 153 | 154 | void filterImageSymbols(int32_t imageNumber, STVisibility visibility, STSymbolFilter filter, 155 | void (^ _Nonnull callback)(const void * _Nonnull address, const char * _Nonnull symname, 156 | void * _Nonnull typeref, void * _Nonnull typeend)) { 157 | const struct mach_header *header = nullptr; 158 | if (imageNumber < 0) { 159 | imageNumber = lastLoadedIndex ?: _dyld_image_count()+imageNumber; 160 | header = lastPseudoImage(); 161 | } 162 | if (!header) 163 | header = _dyld_get_image_header(imageNumber); 164 | filterHeaderSymbols(header, visibility, filter, callback); 165 | } 166 | 167 | #import 168 | static const char *findingSwiftSymbol; 169 | 170 | void filterHeaderSymbols(const struct mach_header *header, STVisibility visibility, STSymbolFilter filter, 171 | void (^ _Nonnull callback)(const void * _Nonnull address, const char * _Nonnull symname, 172 | void * _Nonnull typeref, void * _Nonnull typeend)) { 173 | if (!findingSwiftSymbol) 174 | fast_dlscan(header, visibility, filter, callback); 175 | else { // For performance looking for a single symbol. 176 | uint64_t typeref_size = 0; 177 | char *typeref = getsectdatafromheader_64((const mach_header_64 *)header, 178 | SEG_TEXT, "__swift5_typeref", &typeref_size); 179 | if (void *value = fast_dlsym(header, findingSwiftSymbol)) 180 | callback(value, findingSwiftSymbol, typeref, typeref+typeref_size); 181 | } 182 | } 183 | 184 | void *findSwiftSymbol(const char *path, const char *suffix, STVisibility visibility) { 185 | __block void *found = nullptr; 186 | if (!fullSwiftPrefix(suffix)) 187 | findingSwiftSymbol = suffix; 188 | findHiddenSwiftSymbols(path, suffix, visibility, 189 | ^(const void * _Nonnull address, const char * _Nonnull symname, 190 | void * _Nonnull typeref, void * _Nonnull typeend) { 191 | #if DEBUG && 0 192 | if (found && found != address) 193 | NSLog(@"SwiftTrace: Contradicting values for %s: %@ %p != %@ %p", suffix, 194 | describeImagePointer(found), found, 195 | describeImagePointer(address), address); 196 | // else 197 | #endif 198 | found = (void *)address; 199 | }); 200 | findingSwiftSymbol = nullptr; 201 | return found; 202 | } 203 | 204 | void appBundleImages(void (^callback)(const char *imageName, const struct mach_header *, intptr_t slide)) { 205 | for (ssize_t i = getLoadedPseudoImages().size()-1; i>=0 ; i--) 206 | callback(getLoadedPseudoImages()[i].first, (struct mach_header *) 207 | getLoadedPseudoImages()[i].second, 0); 208 | 209 | NSBundle *mainBundle = [NSBundle mainBundle]; 210 | const char *mainExecutable = mainBundle.executablePath.UTF8String; 211 | const char *bundleFrameworks = mainBundle.privateFrameworksPath.UTF8String; 212 | size_t frameworkPathLength = strlen(bundleFrameworks); 213 | std::map seen; 214 | 215 | for (int32_t i = _dyld_image_count()-1; i >= 0 ; i--) { 216 | const char *imageName = _dyld_get_image_name(i); 217 | // NSLog(@"findImages: %s", imageName); 218 | std::string image = imageName; 219 | seen[image] = true; 220 | if (seen[image+".debug.dylib"]) 221 | continue; 222 | if (strcmp(imageName, mainExecutable) == 0 || 223 | strncmp(imageName, bundleFrameworks, frameworkPathLength) == 0 || 224 | (strstr(imageName, "/DerivedData/") && 225 | strstr(imageName, ".framework/")) || 226 | strstr(imageName, ".debug.dylib") || // Xcode16 227 | strstr(imageName, ".xctest/") || 228 | strstr(imageName, "/eval")) 229 | callback(imageName, _dyld_get_image_header(i), 230 | _dyld_get_image_vmaddr_slide(i)); 231 | } 232 | } 233 | 234 | const char *callerBundle() { 235 | void *returnAddress = __builtin_return_address(1); 236 | Dl_info info; 237 | if (dladdr(returnAddress, &info)) 238 | return info.dli_fname; 239 | return nullptr; 240 | } 241 | 242 | const char *_Nonnull swiftTrace_path() { 243 | return __FILE__; 244 | } 245 | #endif 246 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | invocation: SwiftTrace.Swizzle.Invocation) { 217 | print( ">> "+signature ) 218 | } 219 | } 220 | 221 | // Self.swiftTraceSetExclusionPattern(NSObject.swiftTraceDefaultMethodExclusions()) 222 | // NSObject.swiftTraceSetInclusionPattern(".") 223 | // SwiftTrace.patchFactory = MyTracer.self 224 | 225 | 226 | let objcTester = ObjcTraceTester() 227 | 228 | objcTester.swiftTraceInstance(withSubLevels: 2) 229 | objcTester.a(44, i:45, b: 55, c: "66", o: self, s: Selector(("jjj:"))) 230 | 231 | NSObject.swiftTraceClasses(matchingPattern: "Test", subLevels: 2) 232 | 233 | objcTester.a(44, i:45, b: 55, c: "66", o: self, s: Selector(("jjj:"))) 234 | 235 | // SwiftTrace.excludeFunction = NSRegularExpression(regexp: 236 | // "^\\w+\\.\\w+\\(|extension in S|SwiftTrace|out: inout|autoBitCast") 237 | _ = SwiftTrace.traceMainBundleMethods() 238 | 239 | var a/*: P*/ = TestClass() 240 | // 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) 241 | 242 | 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))) 243 | 244 | print(SwiftTrace.methodNames(ofClass: TestClass.self)) 245 | print(SwiftTrace.swiftClassList(bundlePath: class_getImageName(TestClass.self))) 246 | 247 | let d = Optional.some(["test": Set([STR(s: "value")])]) 248 | let any: Any = d 249 | print(a.any(a: any)) 250 | 251 | print(a.u(i: 99, u: URL(string: "http://google.com")!, j: 89)) 252 | 253 | a.i = 888 254 | print(a.i) 255 | a.x() 256 | print( a.y() ) 257 | a.x() 258 | a.z( 88, f: 66, s: "$%^", g: 55, h: 44, f1: 66, g1: 55, h1: 44, f2: 66, g2: 55, h2: 44, e: 77 ) 259 | print(a.arr(a: ["a", "b", "c"], b: [1, 2, 3])) 260 | print(a.arr2(a: ["a", "b", "c"], b: [1, 2, 3])) 261 | print(a.str(i: 77, s: STR(s: "value"), j: 88)) 262 | print(SwiftMeta.sizeof(anyType: type(of: d))) 263 | print(a.dict(d: d)!) 264 | print(a.c(c: { _ in })) 265 | // a.tc = a 266 | // print(a.c2(c: a)) 267 | print(a.p(p: "s")) 268 | print(MemoryLayout.size) 269 | 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) 270 | 271 | 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) 272 | 273 | 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) 274 | 275 | 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) 276 | 277 | print(SwiftTrace.invoke(target: a as AnyObject, methodName: "SwiftTwaceOSX.TestClass.dict(d: Swift.Optional>>) -> Swift.Optional>>", args: d) as [String: Set]? as Any) 278 | 279 | NSObject.swiftTraceRemoveAllTraces() 280 | 281 | 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) 282 | 283 | SwiftTrace.swizzleFactory = SwiftTrace.Decorated.self 284 | #endif 285 | // SwiftTrace.trace(aClass: TestClass.self) 286 | 287 | // ptest(p: TestClass()) 288 | 289 | #if !arch(arm64) 290 | // TestClass.swiftTraceProtocolsInBundle() 291 | #endif 292 | 293 | ptest(p: TestStruct()) 294 | ptest(p: TestClass()) 295 | 296 | for call in SwiftTrace.callOrder() { 297 | print(call.signature) 298 | } 299 | 300 | findSwiftSymbols(Bundle.main.executablePath, classesIncludingObjc()) { 301 | cls,_,_,_ in 302 | print(unsafeBitCast(cls, to: AnyClass.self)) 303 | } 304 | } 305 | 306 | func ptest(p: T) { 307 | 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) 308 | 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))) 309 | } 310 | 311 | func applicationWillTerminate(_ aNotification: Notification) { 312 | // Insert code here to tear down your application 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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.. Bool { 79 | SwiftTrace.traceProtocolsInBundle(containing: UIHostingController.self) 80 | return true 81 | } 82 | ``` 83 | 84 | Which traces are applied can be filtered using method name inclusion and exclusion regexps. 85 | ```swift 86 | SwiftTrace.methodInclusionPattern = "TestClass" 87 | SwiftTrace.methodExclusionPattern = "init|"+SwiftTrace.defaultMethodExclusions 88 | ``` 89 | These methods must be called before you start the trace as they are applied during the "Swizzle" phase. 90 | There is a default set of exclusions setup as a result of testing by tracing UIKit. 91 | 92 | open class var defaultMethodExclusions: String { 93 | return """ 94 | \\.getter| (?:retain|_tryRetain|release|_isDeallocating|.cxx_destruct|dealloc|description| debugDescription)]|initWithCoder|\ 95 | ^\\+\\[(?:Reader_Base64|UI(?:NibStringIDTable|NibDecoder|CollectionViewData|WebTouchEventsGestureRecognizer)) |\ 96 | ^.\\[(?:UIView|RemoteCapture) |UIDeviceWhiteColor initWithWhite:alpha:|UIButton _defaultBackgroundImageForType:andState:|\ 97 | UIImage _initWithCompositedSymbolImageLayers:name:alignUsingBaselines:|\ 98 | _UIWindowSceneDeviceOrientationSettingsDiffAction _updateDeviceOrientationWithSettingObserverContext:windowScene:transitionContext:|\ 99 | UIColorEffect colorEffectSaturate:|UIWindow _windowWithContextId:|RxSwift.ScheduledDisposable.dispose| ns(?:li|is)_ 100 | """ 101 | } 102 | 103 | If you want to further process output you can define your own custom tracing sub class: 104 | ```swift 105 | class MyTracer: SwiftTrace.Decorated { 106 | 107 | override func onEntry(stack: inout SwiftTrace.EntryStack) { 108 | print( ">> "+stack ) 109 | } 110 | } 111 | 112 | SwiftTrace.swizzleFactory = MyTracer.self 113 | ``` 114 | As the amount of of data logged can quickly get out of hand you can control what is 115 | logged by combing traces with the optional `subLevels` parameter to the above functions. 116 | For example, the following puts a trace on all of UIKit but will only log calls to methods 117 | of the target instance and up to three levels of calls those method make: 118 | ```Swift 119 | SwiftTrace.traceBundle(containing: UIView.self) 120 | SwiftTrace.trace(anInstance: anObject, subLevels: 3) 121 | ``` 122 | Or, the following will log methods of the application and calls to RxSwift they make: 123 | ```Swift 124 | SwiftTrace.traceBundle(containing: RxSwift.DisposeBase.self) 125 | SwiftTrace.traceMainBundle(subLevels: 3) 126 | ``` 127 | If this seems arbitrary the rules are reasonably simple. When you add a trace with a 128 | non-zero subLevels parameter all previous traces are inhibited unless they are being 129 | made up to subLevels inside a method in the most recent trace or if they where filtered 130 | anyway by a class or instance (traceInstances(ofClass:) and trace(anInstance:)). 131 | 132 | If you would like to extend SwiftTrace to be able to log one of your app's types 133 | there are two steps. First, you may need to extend the type to conform to 134 | SwiftTraceFloatArg if it contains only float only float types for example SwiftUI.EdgeInsets. 135 | ```Swift 136 | extension SwiftUI.EdgeInsets: SwiftTraceFloatArg {} 137 | ``` 138 | Then, add a handler for the type using the following api: 139 | ```Swift 140 | SwiftTrace.addFormattedType(SwiftUI.EdgeInsets.self, prefix: "SwiftUI") 141 | ``` 142 | Many of these API's are also available as a extension of NSObject which is useful 143 | when SwiftTrace is made available by dynamically loading bundle as in 144 | (InjectionIII)[https://github.com/johnno1962/InjectionIII]. 145 | ```Swift 146 | SwiftTrace.traceBundle(containing: UIView.class) 147 | // becomes 148 | UIView.traceBundle() 149 | 150 | SwiftTrace.trace(inInstance: anObject) 151 | // becomes 152 | anObject.swiftTraceInstance() 153 | ``` 154 | This is useful when SwiftTrace is made available by dynamically loading a bundle 155 | such as when using (InjectionIII)[https://github.com/johnno1962/InjectionIII]. Rather 156 | than having to include a CocoaPod, all you need to do is add SwiftTrace.h in the 157 | InjectionIII application's bundle to your bridging header and dynamically load the bundle. 158 | ```Swift 159 | Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load() 160 | ``` 161 | #### Benchmarking 162 | 163 | To benchmark an app or framework, trace it's methods then you can use one of the following: 164 | ``` 165 | SwiftTrace.sortedElapsedTimes(onlyFirst: 10)) 166 | SwiftTrace.sortedInvocationCounts(onlyFirst: 10)) 167 | ``` 168 | #### Object lifetime tracking 169 | 170 | You can track the allocations an deallocations of Swift and 171 | Objective-C classes using the [SwiftTrace.LifetimeTracker](https://github.com/johnno1962/SwiftTrace/blob/main/SwiftTrace/SwiftLifetime.swift) class: 172 | 173 | ```Swift 174 | SwiftTrace.swizzleFactory = SwiftTrace.LifetimeTracker.self 175 | SwiftTrace.traceMainBundleMethods() == 0 { 176 | print("⚠️ Tracing Swift methods can only work if you have -Xlinker -interposable to your project's \"Other Linker Flags\"") 177 | } 178 | SwiftTrace.traceMainBundle() 179 | ``` 180 | Each time an object is allocated you will see a `.__allocating_init` message 181 | followed by the result and the resulting count of live objects allocated 182 | since tracing was started. Each time an object is deallocated you will 183 | see a `cxx_destruct` message followed by the number of objects 184 | oustanding for that class. 185 | 186 | If you would like to track the lifecycle of Swift structs, create a marker 187 | class and add a property to the struct initialised to an instance of it. 188 | 189 | ```Swift 190 | class Marker {} 191 | 192 | struct MyView: SwiftUI.View { 193 | var marker = Marker() 194 | } 195 | ``` 196 | This idea is based on the [LifetimeTracker](https://github.com/krzysztofzablocki/LifetimeTracker) 197 | project by [Krzysztof Zabłocki](https://github.com/krzysztofzablocki). 198 | #### Aspects 199 | 200 | You can add an aspect to a particular method using the method's de-mangled name: 201 | ```swift 202 | print(SwiftTrace.addAspect(aClass: TestClass.self, 203 | methodName: "SwiftTwaceApp.TestClass.x() -> ()", 204 | onEntry: { (_, _) in print("ONE") }, 205 | onExit: { (_, _) in print("TWO") })) 206 | ``` 207 | This will print "ONE" when method "x" of TextClass is called and "TWO when it has exited. The 208 | two arguments are the Swizzle which is an object representing the "Swizzle" and the entry or 209 | exit stack. The full signature for the entry closure is: 210 | ```swift 211 | onEntry: { (swizzle: SwiftTrace.Swizzle, stack: inout SwiftTrace.EntryStack) in 212 | ``` 213 | 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 214 | stack to modify the incoming arguments and, for the exit aspect closure you can replace 215 | the return value and on a good day log (and prevent) an error being thrown. 216 | 217 | Replacing an input argument in the closure is relatively simple: 218 | ```swift 219 | stack.intArg1 = 99 220 | stack.floatArg3 = 77.3 221 | ``` 222 | Other types of argument a little more involved. They must be cast and String 223 | takes up two integer registers. 224 | ```swift 225 | swizzle.rebind(&stack.intArg2).pointee = "Grief" 226 | swizzle.rebind(&stack.intArg4).pointee = TestClass() 227 | ``` 228 | In an exit aspect closure, setting the return type is easier as it is generic: 229 | ```swift 230 | stack.setReturn(value: "Phew") 231 | ``` 232 | When a function throws you can access NSError objects. 233 | ```swift 234 | print(swizzle.rebind(&stack.thrownError, to: NSError.self).pointee) 235 | ``` 236 | It is possible to set `stack.thrownError` to zero to cancel the throw but you will need to set 237 | the return value. 238 | 239 | If this seems complicated there is a property `swizzle.arguments` which can be used 240 | `onEntry` which contains the arguments as an `Array` containing elements of type `Any` 241 | which can be cast to the expected type. Element 0 is `self`. 242 | 243 | #### Invocation interface 244 | 245 | Now we have a trampoline infrastructure, it is possible to implement an invocation api for Swift: 246 | ```swift 247 | print("Result: "+SwiftTrace.invoke(target: b, 248 | 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", 249 | 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())) 250 | ``` 251 | In order to determine the mangled name of a method you can get the full list for a class 252 | using this function: 253 | ```swift 254 | print(SwiftTrace.methodNames(ofClass: TestClass.self)) 255 | ``` 256 | There are limitations to this abbreviated interface in that it only supports Double, Float, 257 | String, Int, Object, CGRect, CGSize and CGPoint arguments. For other struct types that 258 | do not contain floating point values you can conform them to protocol `SwiftTraceArg` 259 | to be able to pass them on the argument list or `SwiftTraceFloatArg` if they contain 260 | only floats. These values and return values must fit into 32 bytes and not contain floats. 261 | 262 | #### How it works 263 | 264 | A Swift `AnyClass` instance has a layout similar to an Objective-C class with some 265 | additional data documented in the `ClassMetadataSwift` in SwiftMeta.swift. After this data 266 | there is the vtable of pointers to the class and instance member functions of the class up to 267 | the size of the class instance. SwiftTrace replaces these function pointers with a pointer 268 | to a unique assembly language "trampoline" entry point which has destination function and 269 | data pointers associated with it. Registers are saved and this function is called passing 270 | the data pointer to log the method name. The method name is determined by de-mangling the 271 | symbol name associated the function address of the implementing method. The registers are 272 | then restored and control is passed to the original function implementing the method. 273 | 274 | Please file an issue if you encounter a project that doesn't work while tracing. It should 275 | be far more reliable as it uses assembly language trampolines rather than Swizzling like 276 | Xtrace did. Otherwise, the author can be contacted on Twitter [@Injection4Xcode](https://twitter.com/@Injection4Xcode). 277 | 278 | Thanks to Oliver Letterer for the [imp_implementationForwardingToSelector](https://github.com/OliverLetterer/imp_implementationForwardingToSelector) project adapted to set up the 279 | trampolines, included under an MIT license. 280 | 281 | The repo includes a very slightly modified version of the very handy 282 | [https://github.com/facebook/fishhook](https://github.com/facebook/fishhook). 283 | See the source and header files for their licensing details. 284 | 285 | Thanks also to [@twostraws](https://twitter.com/twostraws)' 286 | [Unwrap](https://github.com/twostraws/Unwrap) and [@artsy](https://twitter.com/ArtsyOpenSource)'s 287 | [eidolon](https://github.com/artsy/eidolon) used extensively during testing. 288 | 289 | Enjoy! 290 | 291 | $Date: 2022/01/22 $ 292 | 293 | -------------------------------------------------------------------------------- /SwiftTrace/SwiftRefs.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftMeta.swift 3 | // SwiftTwaceApp 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/SwiftRefs.swift#4 $ 10 | // 11 | // Requires https://github.com/johnno1962/StringIndex.git 12 | // 13 | // Code to estimate if a tye is passed by reference 14 | // ================================================ 15 | // 16 | 17 | #if DEBUG || !DEBUG_ONLY 18 | import Foundation 19 | 20 | extension SwiftMeta { 21 | /** 22 | Information about a field of a struct or class 23 | */ 24 | public struct FieldInfo { 25 | let name: String 26 | let type: Any.Type 27 | let offset: size_t 28 | } 29 | 30 | /** 31 | Get approximate nformation about the fields of a type 32 | */ 33 | open class func fieldInfo(forAnyType: Any.Type) -> [FieldInfo]? { 34 | _ = structsPassedByReference 35 | return approximateFieldInfoByTypeName[_typeName(forAnyType)] 36 | } 37 | 38 | static var approximateFieldInfoByTypeName = [String: [FieldInfo]]() 39 | static var doesntHaveStorage = Set() 40 | 41 | /** 42 | Structs that have only fields that conform to .SwiftTraceFloatArg 43 | */ 44 | static var structsAllFloats = Set() 45 | static var enumTypes = Set() 46 | 47 | public static var usePrecalculated = 01 == 1 48 | public static var swiftUIPassedByReference = """ 49 | SwiftUI.AnimationCompletionCriteria 50 | SwiftUI.ArchivedViewCore.Metadata 51 | SwiftUI.DiffResult 52 | SwiftUI.DisplayList.ArchiveIDs 53 | SwiftUI.ImageResolutionContext 54 | SwiftUI.LinkDestination 55 | SwiftUI.LinkDestination.Configuration 56 | SwiftUI.OpenURLAction.SystemHandlerInput 57 | SwiftUI.ReferenceDateModifier 58 | SwiftUI.ResolvableAbsoluteDate 59 | SwiftUI.ResolvableStringResolutionContext 60 | SwiftUI.SystemFormatStyle.DateOffset 61 | SwiftUI.SystemFormatStyle.Timer 62 | SwiftUI.ScrollTargetBehavior 63 | SwiftUI.ScrollIndicatorVisibility 64 | SwiftUI.ScenePhase 65 | """ 66 | public static var swiftUIStructsAllFloats = """ 67 | SwiftUI.AngularGradient._Paint 68 | SwiftUI.Capsule._Inset 69 | SwiftUI.Circle._Inset 70 | SwiftUI.ColorMatrix 71 | SwiftUI.ConcentricRectangle.AnimatableData 72 | SwiftUI.ContainerRelativeShape._Inset 73 | SwiftUI.CoreBaselineOffsetPair 74 | SwiftUI.DistanceGesture 75 | SwiftUI.EdgeInsets 76 | SwiftUI.Ellipse._Inset 77 | SwiftUI.EllipticalGradient._Paint 78 | SwiftUI.EmptyAnimatableData 79 | SwiftUI.FocusableFillerBounds.Metrics 80 | SwiftUI.Font.ResolvedTraits 81 | SwiftUI.GlassContainer.AppearanceSettings 82 | SwiftUI.GlassContainer.Entry.ShapeBoundsResult 83 | SwiftUI.GlassContainer.TranslationKick 84 | SwiftUI.GraphicsFilter.DisplacementMap 85 | SwiftUI.GraphicsFilter.GlassBackgroundStyle 86 | SwiftUI.LayoutPositionQuery 87 | SwiftUI.LayoutPriorityLayout 88 | SwiftUI.LinearGradient._Paint 89 | SwiftUI.NamedImage.DecodedInfo 90 | SwiftUI.OffsetTransition 91 | SwiftUI.OpacityRendererEffect 92 | SwiftUI.RadialGradient._Paint 93 | SwiftUI.Rectangle._Inset 94 | SwiftUI.RectangleCornerRadii 95 | SwiftUI.ResolvedGradientVector 96 | SwiftUI.ResolvedSafeAreaInsets 97 | SwiftUI.RootSizeInfo 98 | SwiftUI.RoundedRectangle 99 | SwiftUI.RoundedRectangle._Inset 100 | SwiftUI.RoundedRectangularShapeCorners.AnimatableData 101 | SwiftUI.RoundedSize 102 | SwiftUI.SDFStyle.Group 103 | SwiftUI.ScrollStateRequestKind.UpdateValueConfig 104 | SwiftUI.ShaderVectorData 105 | SwiftUI.ShaderVectorData.Element 106 | SwiftUI.Spacing.TextMetrics 107 | SwiftUI.SystemHoverEffectStyleMetrics 108 | SwiftUI.SystemShadowStyleMetrics.Grounding 109 | SwiftUI.SystemShadowStyleMetrics.Separated 110 | SwiftUI.Text.Layout.TypographicBounds 111 | SwiftUI.TextProxy 112 | SwiftUI.UnevenRoundedRectangle 113 | SwiftUI.UnevenRoundedRectangle._Inset 114 | SwiftUI.ViewFrame 115 | SwiftUI.ViewListSublistSlice 116 | SwiftUI.ViewSize 117 | SwiftUI._BrightnessEffect 118 | SwiftUI._ColorMatrix 119 | SwiftUI._ContrastEffect 120 | SwiftUI._GrayscaleEffect 121 | SwiftUI._LayoutTraits 122 | SwiftUI._LayoutTraits.Dimension 123 | SwiftUI._OffsetEffect 124 | SwiftUI._OpacityEffect 125 | SwiftUI._PositionLayout 126 | SwiftUI._SaturationEffect 127 | SwiftUI._ScaledValue 128 | SwiftUI._ScrollLayout 129 | SwiftUI._ShapeStyle_Pack.Effect 130 | SwiftUI._ShapeStyle_Pack.Effect.Kind.AnimatableData 131 | SwiftUI._ShapeStyle_Pack.Fill.AnimatableData 132 | SwiftUI._ShapeStyle_RenderedShape 133 | SwiftUI._ViewList_Group 134 | SwiftUI.UnitPoint 135 | __C.CGSize 136 | """ 137 | 138 | public static var structsPassedByReference: Set = { 139 | var problemTypes = Set() 140 | func passedByReference(_ type: Any.Type) { 141 | problemTypes.insert(autoBitCast(type)) 142 | if let type = convert(type: type, handler: getOptionalTypeFptr) { 143 | problemTypes.insert(autoBitCast(type)) 144 | } 145 | } 146 | 147 | for type: Any.Type in [URL.self, UUID.self, Date.self, 148 | IndexPath.self, IndexSet.self, URLRequest.self] { 149 | passedByReference(type) 150 | } 151 | 152 | for iOS15ResilientTypeName in ["Foundation.AttributedString", 153 | "Foundation.AttributedString.Index"] { 154 | if let resilientType = lookupType(named: iOS15ResilientTypeName) { 155 | passedByReference(resilientType) 156 | } 157 | } 158 | 159 | #if true // Attempts to determine which getters have storage 160 | // properties that have key path getters are not stored?? 161 | let appImages = searchBundleImages() 162 | findHiddenSwiftSymbols(appImages, "pACTK", 163 | .hidden) { (_, symbol, _, _) in 164 | doesntHaveStorage.insert(String(cString: symbol) 165 | .replacingOccurrences(of: "pACTK", with: "g")) 166 | } 167 | // ...unless they have a field offset 168 | // ...or property wrapper backing initializer ?? 169 | for suffix in ["pWvd", "pfP"] { 170 | findSwiftSymbols(appImages, suffix) { 171 | (_, symbol, _, _) in 172 | doesntHaveStorage.remove(String(cString: symbol) 173 | .replacingOccurrences(of: suffix, with: "g")) 174 | } 175 | } 176 | // print(doesntHaveStorage) 177 | #endif 178 | 179 | structsAllFloats.insert(autoBitCast(CGFloat.self)) 180 | for typ: Any.Type in [CGFloat.self, OSPoint.self, OSSize.self, 181 | OSRect.self, OSEdgeInsets.self] { 182 | structsAllFloats.insert(autoBitCast(typ)) 183 | } 184 | if let swiftUIFramework = swiftUIBundlePath() { 185 | let saveProblemTypes = problemTypes 186 | let saveFloatStructs = structsAllFloats 187 | process(bundlePath: swiftUIFramework, skip: usePrecalculated, 188 | problemTypes: &problemTypes) 189 | if !usePrecalculated { 190 | for typ in problemTypes.subtracting(saveProblemTypes) 191 | .map({ _typeName(autoBitCast($0)) }).sorted() { 192 | print(typ) 193 | } 194 | print("--") 195 | for typ in structsAllFloats.subtracting(saveFloatStructs) 196 | .map({ _typeName(autoBitCast($0)) }).sorted() { 197 | print(typ) 198 | } 199 | } 200 | } 201 | if usePrecalculated { 202 | for byReference in swiftUIPassedByReference.components(separatedBy: "\n") { 203 | if let type = SwiftMeta.lookupType(named: byReference) { 204 | passedByReference(autoBitCast(type)) 205 | } 206 | } 207 | for allFloats in swiftUIStructsAllFloats.components(separatedBy: "\n") { 208 | if let typ = lookupType(named: allFloats) { 209 | structsAllFloats.insert(autoBitCast(typ)) 210 | } 211 | } 212 | } 213 | 214 | appBundleImages { bundlePath, _, _ in 215 | process(bundlePath: bundlePath, problemTypes: &problemTypes) 216 | } 217 | 218 | passedByReference(Any.self) 219 | // print(problemTypes.map {unsafeBitCast($0, to: Any.Type.self)}, approximateFieldInfoByTypeName) 220 | return problemTypes 221 | }() 222 | 223 | /** 224 | Performs a one time scan of all property getters at a bundlePath to 225 | look out for structs that are or contain bridged(?) values such as URL 226 | or UUID and are passed by reference by the compiler for some reason. 227 | */ 228 | open class func process(bundlePath: UnsafePointer, 229 | skip: Bool = !SwiftTrace.typeLookup, problemTypes: 230 | UnsafeMutablePointer>) { 231 | // let start = Date.timeIntervalSinceReferenceDate 232 | // defer { print("Took \(Date.timeIntervalSinceReferenceDate-start) " + 233 | // "to process \(String(cString: bundlePath))") } 234 | findHiddenSwiftSymbols(bundlePath, "ON", .any) { (symval, symbol, _, _) in 235 | enumTypes.insert(symval) 236 | } 237 | 238 | if skip { return } 239 | 240 | var offset = 0 241 | var currentType = "" 242 | var wasFloatType = false 243 | 244 | var symbols = [(symval: UnsafeRawPointer, symname: UnsafePointer)]() 245 | findHiddenSwiftSymbols(bundlePath, "g", .any) { (symval, symbol, _, _) in 246 | symbols.append((symval, symbol)) 247 | } 248 | 249 | // Need to process symbols in emitted order if we are 250 | // to have any hope of recovering type memory layout. 251 | let debugPassedByReference = getenv("DEBUG_BYREFERENCE") != nil 252 | for (_, symbol) in symbols.sorted(by: { $0.symval < $1.symval }) { 253 | guard let demangled = SwiftMeta.demangle(symbol: symbol) else { 254 | print("Could not demangle: \(String(cString: symbol))") 255 | continue 256 | } 257 | if demangled.hasPrefix("(extension in ") { 258 | continue 259 | } 260 | func debug(_ str: @autoclosure () -> String) { 261 | if !debugPassedByReference { return } 262 | print(demangled) 263 | print(str()) 264 | } 265 | guard let fieldStart = demangled.index(of: .first(of: " : ")+3), 266 | let nameEnd = demangled.index(of: fieldStart + .last(of: ".")), 267 | let typeEnd = demangled.index(of: nameEnd + .last(of: ".")), 268 | let typeName = demangled[..