Skip to content

Commit

Permalink
Include Xprobe
Browse files Browse the repository at this point in the history
  • Loading branch information
johnno1962 committed Dec 6, 2017
1 parent 885b178 commit 8e930eb
Show file tree
Hide file tree
Showing 11 changed files with 370 additions and 61 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "XprobePlugin"]
path = XprobePlugin
url = https://github.com/johnno1962/XprobePlugin
67 changes: 66 additions & 1 deletion InjectionBundle/InjectionClient.mm
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,39 @@
#import "macOSInjection-Swift.h"
#endif

#ifdef XPROBE_PORT
#import "../XprobePlugin/Classes/Xtrace.mm"
#import "../XprobePlugin/Classes/Xprobe.mm"
#import "../XprobePlugin/Classes/Xprobe+Service.mm"

@interface BundleInjection: NSObject
@end
@implementation BundleInjection
+ (const char *)connectedAddress {
return "127.0.0.1";
}
@end

@implementation Xprobe(Seeding)
#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED
+ (NSArray *)xprobeSeeds {
UIApplication *app = [UIApplication sharedApplication];
NSMutableArray *seeds = [[app windows] mutableCopy];
[seeds insertObject:app atIndex:0];
return seeds;
}
#else
+ (NSArray *)xprobeSeeds {
NSApplication *app = [NSApplication sharedApplication];
NSMutableArray *seeds = [[app windows] mutableCopy];
if ( app.delegate )
[seeds insertObject:app.delegate atIndex:0];
return seeds;
}
#endif
@end
#endif

@implementation InjectionClient

+ (void)load {
Expand All @@ -31,7 +64,9 @@ + (void)load {
}

- (void)runInBackground {
printf("Injection connected, watching %s/...\n", [self readString].UTF8String);
NSString *projectFile = [self readString];
printf("Injection connected, watching %s/...\n",
projectFile.stringByDeletingLastPathComponent.UTF8String);
[self writeString:[NSBundle mainBundle].privateFrameworksPath];
#ifdef __LP64__
[self writeString:@"x86_64"];
Expand All @@ -40,15 +75,45 @@ - (void)runInBackground {
#endif
[self writeString:[NSBundle mainBundle].executablePath];

[SwiftEval sharedInstance].projectFile = projectFile;
[SwiftEval sharedInstance].injectionNumber = 100;

int codesignStatusPipe[2];
pipe(codesignStatusPipe);
SimpleSocket *reader = [[SimpleSocket alloc] initSocket:codesignStatusPipe[0]];
SimpleSocket *writer = [[SimpleSocket alloc] initSocket:codesignStatusPipe[1]];

// make available implementation of signing delegated to macOS app
[SwiftEval sharedInstance].signer = ^BOOL(NSString *_Nonnull dylib) {
[self writeString:dylib];
return [reader readString].boolValue;
};

// As tmp file names come in, inject them
while (NSString *swiftSource = [self readString])
if ([swiftSource hasPrefix:@"LOG "])
printf("%s\n", [swiftSource substringFromIndex:@"LOG ".length].UTF8String);
else if ([swiftSource hasPrefix:@"SIGNED "])
[writer writeString:[swiftSource substringFromIndex:@"SIGNED ".length]];
else
dispatch_async(dispatch_get_main_queue(), ^{
NSError *err;
if ([swiftSource hasPrefix:@"INJECT "])
[SwiftInjection injectWithTmpfile:[swiftSource substringFromIndex:@"INJECT ".length] error:&err];
#ifdef XPROBE_PORT
else if ([swiftSource hasPrefix:@"XPROBE"]) {
[Xprobe connectTo:NULL retainObjects:YES];
[Xprobe search:@""];
}
else if ([swiftSource hasPrefix:@"EVAL "]) {
NSString *args = [swiftSource substringFromIndex:@"EVAL ".length];
NSArray<NSString *> *parts = [args componentsSeparatedByString:@"^"];
int pathID = parts[0].intValue;
[self writeString:@"PAUSE 5"];
[xprobePaths[pathID].object evalSwift:parts[3].stringByRemovingPercentEncoding];
[Xprobe writeString:[NSString stringWithFormat:@"$('BUSY%d').hidden = true; ", pathID]];
}
#endif
[self writeString:err ? [@"ERROR " stringByAppendingString:err.localizedDescription] : @"COMPLETE"];
});
}
Expand Down
73 changes: 56 additions & 17 deletions InjectionBundle/SwiftEval.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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#86 $
// $Id: //depot/ResidentEval/InjectionBundle/SwiftEval.swift#89 $
//
// Basic implementation of a Swift "eval()" including the
// mechanics of recompiling a class and loading the new
Expand All @@ -20,13 +20,21 @@ private func debug(_ str: String) {
// print(str)
}

@objc protocol SwiftEvalImpl {
@objc optional func evalImpl(_ptr: UnsafeMutableRawPointer)
}

extension NSObject {

private static var lastEvalByClass = [String: String]()

@objc public func evalSwift(_ expression: String) {
eval("{\n\(expression)\n}", (() -> ())?.self)?()
}

/// eval() for String value
public func eval(_ expression: String) -> String {
return eval("\"" + expression + "\"", String.self)
return eval("\"\(expression)\"", String.self)
}

/// eval() for value of any type
Expand All @@ -37,11 +45,37 @@ extension NSObject {
extension \(className) {
@objc dynamic override func evalImpl(_ptr: UnsafeMutableRawPointer) {
let _ptr = _ptr.assumingMemoryBound(to: \(type).self)
@objc func evalImpl(_ptr: UnsafeMutableRawPointer) {
func xprint<T>(_ str: T) {
if let xprobe = NSClassFromString("Xprobe") {
#if swift(>=4.0)
_ = (xprobe as AnyObject).perform(Selector(("xlog:")), with: "\\(str)")
#elseif swift(>=3.0)
Thread.detachNewThreadSelector(Selector(("xlog:")), toTarget:xprobe, with:"\\(str)" as NSString)
#else
NSThread.detachNewThreadSelector(Selector("xlog:"), toTarget:xprobe, withObject:"\\(str)" as NSString)
#endif
}
}
#if swift(>=3.0)
struct XprobeOutputStream: TextOutputStream {
var out = ""
mutating func write(_ string: String) {
out += string
}
}
func xdump<T>(_ arg: T) {
var stream = XprobeOutputStream()
dump(arg, to: &stream)
xprint(stream.out)
}
#endif
let _ptr = _ptr.assumingMemoryBound(to: (\(type)).self)
_ptr.pointee = \(expression)
}
}
"""
Expand All @@ -58,11 +92,11 @@ extension NSObject {

// swizzle new version of evalImpl onto class

if let newMethod = class_getInstanceMethod(newClass, #selector(evalImpl(_ptr:))) {
class_replaceMethod(oldClass, #selector(evalImpl(_ptr:)),
let selector = #selector(SwiftEvalImpl.evalImpl(_ptr:))
if let newMethod = class_getInstanceMethod(newClass, selector) {
class_replaceMethod(oldClass, selector,
method_getImplementation(newMethod),
method_getTypeEncoding(newMethod))

NSObject.lastEvalByClass[className] = expression
}
}
Expand All @@ -76,16 +110,12 @@ extension NSObject {
let ptr = UnsafeMutablePointer<T>.allocate(capacity: 1)
bzero(ptr, MemoryLayout<T>.size)
if NSObject.lastEvalByClass[className] == expression {
evalImpl(_ptr: ptr)
unsafeBitCast(self, to: SwiftEvalImpl.self).evalImpl?(_ptr: ptr)
}
let out = ptr.pointee
ptr.deallocate(capacity: 1)
return out
}

@objc dynamic func evalImpl(_ptr _: UnsafeMutableRawPointer) {
print("NSObject.evalImpl() called - no subclass implementation loaded")
}
}

fileprivate extension String {
Expand All @@ -103,6 +133,10 @@ public class SwiftEval: NSObject {

static var instance = SwiftEval()

@objc public class func sharedInstance() -> SwiftEval {
return instance
}

@objc public var signer: ((_: String) -> Bool)?

// client specific info
Expand Down Expand Up @@ -135,8 +169,12 @@ public class SwiftEval: NSObject {
findDerivedData(url: sourceURL) else {
throw evalError("Could not locate derived data. Is the project under you home directory?")
}
guard let (projectFile, logsDir) = derivedLogs.flatMap({
(URL(fileURLWithPath: self.projectFile!), URL(fileURLWithPath: $0)) }) ??
guard let (projectFile, logsDir) =
self.derivedLogs
.flatMap({ (URL(fileURLWithPath: self.projectFile!), URL(fileURLWithPath: $0)) }) ??
self.projectFile
.flatMap({ logsDir(project: URL(fileURLWithPath: $0), derivedData: derivedData) })
.flatMap({ (URL(fileURLWithPath: self.projectFile!), $0) }) ??
findProject(for: sourceURL, derivedData: derivedData) else {
throw evalError("Could not locate containg project or it's logs.")
}
Expand Down Expand Up @@ -392,7 +430,7 @@ public class SwiftEval: NSObject {
throw evalError("Could not locate source file \(compileCommand) -- \(regexp)")
}

return (compileCommand, sourceFile)
return (compileCommand, sourceFile.replacingOccurrences(of: "\\$", with: "$"))
}

lazy var mainHandle = dlopen(nil, RTLD_NOLOAD)
Expand Down Expand Up @@ -508,7 +546,8 @@ public class SwiftEval: NSObject {
func logsDir(project: URL, derivedData: URL) -> URL? {
let filemgr = FileManager.default
let projectPrefix = project.deletingPathExtension()
.lastPathComponent.replacingOccurrences(of: " ", with: "_")
.lastPathComponent.replacingOccurrences(of: "\\s+", with: "_",
options: .regularExpression, range: nil)
let relativeDerivedData = project.deletingLastPathComponent()
.appendingPathComponent("DerivedData/\(projectPrefix)/Logs/Build")

Expand Down
35 changes: 34 additions & 1 deletion InjectionBundle/SwiftInjection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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#38 $
// $Id: //depot/ResidentEval/InjectionBundle/SwiftInjection.swift#40 $
//
// Cut-down version of code injection in Swift. Uses code
// from SwiftEval.swift to recompile and reload class.
Expand Down Expand Up @@ -377,4 +377,37 @@ public struct ClassMetadataSwift {

/** pointer to a function implementing a Swift method */
public typealias SIMP = @convention(c) (_: AnyObject) -> Void

#if swift(>=3.0)
// not public in Swift3
@_silgen_name("swift_demangle")
private
func _stdlib_demangleImpl(
mangledName: UnsafePointer<CChar>?,
mangledNameLength: UInt,
outputBuffer: UnsafeMutablePointer<UInt8>?,
outputBufferSize: UnsafeMutablePointer<UInt>?,
flags: UInt32
) -> UnsafeMutablePointer<CChar>?

public func _stdlib_demangleName(_ mangledName: String) -> String {
return mangledName.utf8CString.withUnsafeBufferPointer {
(mangledNameUTF8) in

let demangledNamePtr = _stdlib_demangleImpl(
mangledName: mangledNameUTF8.baseAddress,
mangledNameLength: UInt(mangledNameUTF8.count - 1),
outputBuffer: nil,
outputBufferSize: nil,
flags: 0)

if let demangledNamePtr = demangledNamePtr {
let demangledName = String(cString: demangledNamePtr)
free(demangledNamePtr)
return demangledName
}
return mangledName
}
}
#endif
#endif
10 changes: 10 additions & 0 deletions InjectionBundle/XprobeSwift-Bridging-Header.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//

#import "../XprobePlugin/Classes/Xtrace.h"
#import "../XprobePlugin/Classes/Xprobe.h"

@interface NSObject(InjectionSweep)
- (void)bsweep;
@end
Loading

0 comments on commit 8e930eb

Please sign in to comment.