diff --git a/InjectionBundle/InjectionClient.h b/InjectionBundle/InjectionClient.h index 418603be..c2ec3efd 100644 --- a/InjectionBundle/InjectionClient.h +++ b/InjectionBundle/InjectionClient.h @@ -58,7 +58,6 @@ typedef NS_ENUM(int, InjectionResponse) { InjectionError, InjectionFrameworkList, InjectionCallOrderList, - InjectionCallReorderList, InjectionExit = ~0 }; diff --git a/InjectionBundle/InjectionClient.mm b/InjectionBundle/InjectionClient.mm index 82d7e15a..fbcaeb3c 100644 --- a/InjectionBundle/InjectionClient.mm +++ b/InjectionBundle/InjectionClient.mm @@ -5,7 +5,7 @@ // Created by John Holdsworth on 06/11/2017. // Copyright Ā© 2017 John Holdsworth. All rights reserved. // -// $Id: //depot/ResidentEval/InjectionBundle/InjectionClient.mm#133 $ +// $Id: //depot/ResidentEval/InjectionBundle/InjectionClient.mm#135 $ // #import "InjectionClient.h" @@ -301,7 +301,10 @@ - (void)runInBackground { [self filteringChanged]; break; case InjectionStats: - [SwiftInjection dumpStats]; + static int top = 200; + printf("\nšŸ’‰ Sorted top %d elapsed time/invocations by method\n" + "šŸ’‰ =================================================\n", top); + [SwiftInjection dumpStatsWithTop:top]; [self needsTracing]; break; case InjectionCallOrder: @@ -315,13 +318,11 @@ - (void)runInBackground { printf("\nšŸ’‰ Source files in the order they were first referenced:\n" "šŸ’‰ =====================================================\n" "šŸ’‰ (Order the source files should be compiled in target)\n"); - [self writeCommand:InjectionCallOrderList - withString:[[SwiftInjection callOrder] - componentsJoinedByString:CALLORDER_DELIMITER]]; + [SwiftInjection fileOrder]; [self needsTracing]; break; case InjectionFileReorder: - [self writeCommand:InjectionCallReorderList + [self writeCommand:InjectionCallOrderList withString:[[SwiftInjection callOrder] componentsJoinedByString:CALLORDER_DELIMITER]]; [self needsTracing]; diff --git a/InjectionBundle/SwiftEval.swift b/InjectionBundle/SwiftEval.swift index bb353450..fe4e4943 100644 --- a/InjectionBundle/SwiftEval.swift +++ b/InjectionBundle/SwiftEval.swift @@ -5,7 +5,7 @@ // Created by John Holdsworth on 02/11/2017. // Copyright Ā© 2017 John Holdsworth. All rights reserved. // -// $Id: //depot/ResidentEval/InjectionBundle/SwiftEval.swift#146 $ +// $Id: //depot/ResidentEval/InjectionBundle/SwiftEval.swift#149 $ // // Basic implementation of a Swift "eval()" including the // mechanics of recompiling a class and loading the new @@ -724,6 +724,21 @@ public class SwiftEval: NSObject { .first } + class func uniqueTypeNames(signatures: [String], exec: (String) -> Void) { + var typesSearched = Set() + + for signature in signatures { + let parts = signature.components(separatedBy: ".") + if parts.count < 3 { + continue + } + let typeName = parts[1] + if typesSearched.insert(typeName).inserted { + exec(typeName) + } + } + } + func shell(command: String) -> Bool { let commandFile = "\(tmpDir)/command.sh" try! command.write(toFile: commandFile, atomically: false, encoding: .utf8) @@ -732,7 +747,7 @@ public class SwiftEval: NSObject { #if os(macOS) let task = Process() task.launchPath = "/bin/bash" - task.arguments = ["-c", command] + task.arguments = [commandFile] task.launch() task.waitUntilExit() return task.terminationStatus == EXIT_SUCCESS @@ -758,7 +773,7 @@ public class SwiftEval: NSObject { if fork() == 0 { let commandsIn = fdopen(commandsPipe[ForReading], "r") let statusesOut = fdopen(statusesPipe[ForWriting], "w") - var buffer = [Int8](repeating: 0, count: 4096) + var buffer = [Int8](repeating: 0, count: Int(MAXPATHLEN)) close(commandsPipe[ForWriting]) close(statusesPipe[ForReading]) diff --git a/InjectionBundle/SwiftInjection.swift b/InjectionBundle/SwiftInjection.swift index 633532d3..790b6f44 100644 --- a/InjectionBundle/SwiftInjection.swift +++ b/InjectionBundle/SwiftInjection.swift @@ -5,7 +5,7 @@ // Created by John Holdsworth on 05/11/2017. // Copyright Ā© 2017 John Holdsworth. All rights reserved. // -// $Id: //depot/ResidentEval/InjectionBundle/SwiftInjection.swift#89 $ +// $Id: //depot/ResidentEval/InjectionBundle/SwiftInjection.swift#96 $ // // Cut-down version of code injection in Swift. Uses code // from SwiftEval.swift to recompile and reload class. @@ -205,29 +205,30 @@ public class SwiftInjection: NSObject { public class func interpose(functionsIn dylib: String) { let main = dlopen(nil, RTLD_NOW) + let debug = getenv("INJECTION_DETAIL") != nil var interposes = Array() // Find all definitions of Swift functions and ... // SwiftUI body properties defined in the new dylib. for suffix in SwiftTrace.swiftFunctionSuffixes { - findSwiftSymbols(dylib, suffix) { - (loadedFunc, symbol, _, _) in - guard let existing = dlsym(main, symbol) else { return } - // has this symbol already been interposed? - let current = SwiftTrace.interposed[existing] ?? existing - let tuple = dyld_interpose_tuple( - replacement: loadedFunc, replacee: current) - interposes.append(tuple) - // record functions that have beeen interposed + findSwiftSymbols(dylib, suffix) { (loadedFunc, symbol, _, _) in + guard let existing = dlsym(main, symbol), + let current = SwiftTrace.interposed(replacee: existing) else { + return + } + if debug { + let method = SwiftTrace.demangle(symbol: symbol) + print("šŸ’‰ Replacing \(method ?? String(cString: symbol))") + } + interposes.append(dyld_interpose_tuple( + replacement: loadedFunc, replacee: current)) SwiftTrace.interposed[existing] = loadedFunc SwiftTrace.interposed[current] = loadedFunc -// print("šŸ’‰ Replacing \(demangle(symbol))") } } // Using array of new interpose structs - interposes.withUnsafeBufferPointer { - interps in + interposes.withUnsafeBufferPointer { interps in var mostRecentlyLoaded = true // Apply interposes to all images in the app bundle @@ -248,6 +249,7 @@ public class SwiftInjection: NSObject { } mostRecentlyLoaded = false } + // patch out symbols defined by new dylib. dyld_dynamic_interpose(header, interps.baseAddress!, interps.count) @@ -347,13 +349,7 @@ public class SwiftInjection: NSObject { } } - @objc class func dumpStats() { - let top = 200 - print(""" - - šŸ’‰ Sorted top \(top) elapsed time/invocations by method - šŸ’‰ ================================================= - """) + @objc class func dumpStats(top: Int) { let invocationCounts = SwiftTrace.invocationCounts() for (method, elapsed) in SwiftTrace.sortedElapsedTimes(onlyFirst: top) { print("\(String(format: "%.1f", elapsed*1000.0))ms/\(invocationCounts[method] ?? 0)\t\(method)") @@ -363,6 +359,31 @@ public class SwiftInjection: NSObject { @objc class func callOrder() -> [String] { return SwiftTrace.callOrder().map { $0.signature } } + + @objc class func fileOrder() { + let builder = SwiftEval.sharedInstance() + let signatures = callOrder() + + guard let projectRoot = builder.projectFile.flatMap({ + URL(fileURLWithPath: $0).deletingLastPathComponent().path+"/" + }), + let (_, logsDir) = + try? builder.determineEnvironment(classNameOrFile: "") else { + print("šŸ’‰ File ordering not available.") + return + } + + let tmpfile = builder.tmpDir+"/eval101" + + SwiftEval.uniqueTypeNames(signatures: signatures) { typeName in + if !typeName.contains("("), let (_, foundSourceFile) = + try? builder.findCompileCommand(logsDir: logsDir, + classNameOrFile: typeName, tmpfile: tmpfile) { + print(foundSourceFile + .replacingOccurrences(of: projectRoot, with: "")) + } + } + } } class SwiftSweeper { diff --git a/InjectionIII.xcodeproj/project.pbxproj b/InjectionIII.xcodeproj/project.pbxproj index 3c1cfbfb..db6eadff 100644 --- a/InjectionIII.xcodeproj/project.pbxproj +++ b/InjectionIII.xcodeproj/project.pbxproj @@ -83,6 +83,7 @@ BBE64E5D2524D1B50049B6D4 /* SwiftEval.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB439B8A1FABA65D00B4F50B /* SwiftEval.swift */; }; BBEB704C1FD28C6F00127711 /* XcodeHash.m in Sources */ = {isa = PBXBuildFile; fileRef = BBEB704B1FD28C6F00127711 /* XcodeHash.m */; }; BD35949E21A6C5DE0020EB94 /* Vaccine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD35949D21A6C5DE0020EB94 /* Vaccine.swift */; }; + CEBE11CD25418CF900B468FE /* SwiftTrace-Swift.h in Headers */ = {isa = PBXBuildFile; fileRef = CEBE11CC25418CF900B468FE /* SwiftTrace-Swift.h */; }; CEC1702A253ED117002E823F /* Experimental.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC17029253ED117002E823F /* Experimental.swift */; }; CEC17035253ED472002E823F /* SwiftRegex in Frameworks */ = {isa = PBXBuildFile; productRef = CEC17034253ED472002E823F /* SwiftRegex */; }; /* End PBXBuildFile section */ @@ -263,6 +264,7 @@ BBEB704B1FD28C6F00127711 /* XcodeHash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XcodeHash.m; sourceTree = ""; }; BD35949D21A6C5DE0020EB94 /* Vaccine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vaccine.swift; sourceTree = ""; }; BDB6A7CE21824C800001CF95 /* UserDefaults.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserDefaults.h; sourceTree = ""; }; + CEBE11CC25418CF900B468FE /* SwiftTrace-Swift.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SwiftTrace-Swift.h"; path = "SwiftTraceGuts/SwiftTrace-Swift.h"; sourceTree = ""; }; CEC17029253ED117002E823F /* Experimental.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Experimental.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -441,6 +443,7 @@ children = ( BB6C87F72520D2C3005AFCFC /* xt_forwarding_trampoline_arm64.s */, BB6C87F82520D2C3005AFCFC /* xt_forwarding_trampoline_x64.s */, + CEBE11CC25418CF900B468FE /* SwiftTrace-Swift.h */, BB6C87C62520D0D2005AFCFC /* SwiftTrace.h */, BB6C87F62520D2C3005AFCFC /* SwiftTrace.mm */, BB6C87EA2520D152005AFCFC /* SwiftTrace.swift */, @@ -585,6 +588,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + CEBE11CD25418CF900B468FE /* SwiftTrace-Swift.h in Headers */, BB6C87D62520D0D2005AFCFC /* SwiftTrace.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1385,7 +1389,7 @@ INFOPLIST_FILE = InjectionIII/Info.plist; LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.12; - MARKETING_VERSION = 2.3.5; + MARKETING_VERSION = 2.4.0; PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.InjectionIII; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1408,7 +1412,7 @@ INFOPLIST_FILE = InjectionIII/Info.plist; LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.12; - MARKETING_VERSION = 2.3.5; + MARKETING_VERSION = 2.4.0; PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.InjectionIII; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/InjectionIII.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/InjectionIII.xcodeproj/project.xcworkspace/contents.xcworkspacedata index fac171f4..919434a6 100644 --- a/InjectionIII.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/InjectionIII.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/InjectionIII/Experimental.swift b/InjectionIII/Experimental.swift index 3f45a606..a345d70b 100644 --- a/InjectionIII/Experimental.swift +++ b/InjectionIII/Experimental.swift @@ -5,7 +5,7 @@ // Created by User on 20/10/2020. // Copyright Ā© 2020 John Holdsworth. All rights reserved. // -// $Id: //depot/ResidentEval/InjectionIII/Experimental.swift#21 $ +// $Id: //depot/ResidentEval/InjectionIII/Experimental.swift#22 $ // import Cocoa @@ -40,33 +40,6 @@ extension AppDelegate { lastConnection?.sendCommand(.fileReorder, with: nil) } - func fileOrder(signatures: [String]) { - let builder = SwiftEval() - builder.projectFile = selectedProject - - guard let projectRoot = selectedProject.flatMap({ - URL(fileURLWithPath: $0).deletingLastPathComponent().path+"/" - }), - let (_, logsDir) = - try? builder.determineEnvironment(classNameOrFile: "") else { - lastConnection?.sendCommand(.log, with: - "šŸ’‰ File ordering not available.") - return - } - - let tmpfile = NSTemporaryDirectory()+"/eval101" - - uniqueTypeNames(signatures: signatures) { typeName in - if let (_, foundSourceFile) = - try? builder.findCompileCommand(logsDir: logsDir, - classNameOrFile: typeName, tmpfile: tmpfile) { - let relativePath = foundSourceFile - .replacingOccurrences(of: projectRoot, with: "") - lastConnection?.sendCommand(.log, with: relativePath) - } - } - } - func fileReorder(signatures: [String]) { var projectEncoding: String.Encoding = .utf8 let projectURL = selectedProject.flatMap { @@ -85,7 +58,7 @@ extension AppDelegate { var orders = ["AppDelegate.swift": 0] var order = 1 - uniqueTypeNames(signatures: signatures) { typeName in + SwiftEval.uniqueTypeNames(signatures: signatures) { typeName in orders[typeName+".swift"] = order order += 1 } @@ -136,27 +109,11 @@ extension AppDelegate { } } - func uniqueTypeNames(signatures: [String], exec: (String) -> Void) { - var typesSearched = Set() - - for signature in signatures { - let parts = signature.components(separatedBy: ".") - if parts.count < 3 { - continue - } - let typeName = parts[1] - if typesSearched.insert(typeName).inserted { - exec(typeName) - } - } - } - - /// Entry point for "Injection Goto" service /// - Parameters: /// - pboard: NSPasteboard containing selected type [+method) name /// - userData: N/A - /// - error: N/A + /// - errorPtr: NSString describing error on error @objc func injectionGoto(_ pboard: NSPasteboard, userData: NSString, error errorPtr: UnsafeMutablePointer) { guard pboard.canReadObject(forClasses: [NSString.self], options:nil), diff --git a/InjectionIII/Info.plist b/InjectionIII/Info.plist index bc81b4dc..348e5937 100644 --- a/InjectionIII/Info.plist +++ b/InjectionIII/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 4681 + 4778 LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/InjectionIII/InjectionServer.swift b/InjectionIII/InjectionServer.swift index 112dab89..01c68395 100644 --- a/InjectionIII/InjectionServer.swift +++ b/InjectionIII/InjectionServer.swift @@ -5,7 +5,7 @@ // Created by John Holdsworth on 06/11/2017. // Copyright Ā© 2017 John Holdsworth. All rights reserved. // -// $Id: //depot/ResidentEval/InjectionIII/InjectionServer.swift#57 $ +// $Id: //depot/ResidentEval/InjectionIII/InjectionServer.swift#58 $ // let commandQueue = DispatchQueue(label: "InjectionCommand") @@ -220,15 +220,9 @@ public class InjectionServer: SimpleSocket { sendCommand(.signed, with: signedOK ? "1": "0") break case .callOrderList: - fallthrough - case .callReorderList: if let calls = readString()? .components(separatedBy: CALLORDER_DELIMITER) { - if command == .callOrderList { - appDelegate.fileOrder(signatures: calls) - } else { - appDelegate.fileReorder(signatures: calls) - } + appDelegate.fileReorder(signatures: calls) } break case .error: diff --git a/InjectionIII/build_bundles.sh b/InjectionIII/build_bundles.sh index ad12b5e2..9b830ad1 100755 --- a/InjectionIII/build_bundles.sh +++ b/InjectionIII/build_bundles.sh @@ -6,7 +6,7 @@ # Created by John Holdsworth on 04/10/2019. # Copyright Ā© 2019 John Holdsworth. All rights reserved. # -# $Id: //depot/ResidentEval/InjectionIII/build_bundles.sh#43 $ +# $Id: //depot/ResidentEval/InjectionIII/build_bundles.sh#44 $ # # Injection has to assume a fixed path for Xcode.app as it uses @@ -29,6 +29,10 @@ function build_bundle () { "$DEVELOPER_BIN_DIR"/xcodebuild SYMROOT=$SYMROOT ARCHS="$ARCHS" -sdk $SDK -config $CONFIGURATION -target SwiftUISupport } +if [ -w "$SRCROOT/SwiftTrace/SwiftTraceGuts/SwiftTrace-Swift.h" ]; then + rsync -au "$BUILT_PRODUCTS_DIR/SwiftTrace.framework/Versions/A/Headers/SwiftTrace-Swift.h" "$SRCROOT/SwiftTrace/SwiftTraceGuts" +fi && + #build_bundle macOS MacOSX macosx && build_bundle iOS iPhoneSimulator iphonesimulator && build_bundle tvOS AppleTVSimulator appletvsimulator && diff --git a/SwiftTrace b/SwiftTrace index ef268bc0..d0bf02ed 160000 --- a/SwiftTrace +++ b/SwiftTrace @@ -1 +1 @@ -Subproject commit ef268bc07647e3f01d6ca5de31e45075f610146f +Subproject commit d0bf02ed9caa2e345e4ebf0fb8d0b2c432c92941