Skip to content

Commit

Permalink
Afterthoughts
Browse files Browse the repository at this point in the history
  • Loading branch information
johnno1962 committed Oct 23, 2020
1 parent ef268bc commit 0934cbe
Show file tree
Hide file tree
Showing 8 changed files with 527 additions and 230 deletions.
37 changes: 34 additions & 3 deletions SwiftTrace/SwiftArgs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// Copyright © 2020 John Holdsworth. All rights reserved.
//
// Repo: https://github.com/johnno1962/SwiftTrace
// $Id: //depot/SwiftTrace/SwiftTrace/SwiftArgs.swift#88 $
// $Id: //depot/SwiftTrace/SwiftTrace/SwiftArgs.swift#93 $
//
// Decorate trace with argument/return values
// ==========================================
Expand Down Expand Up @@ -205,8 +205,8 @@ extension SwiftTrace {
isReturn: isReturn,
type: AnyObject?.self)
} else if type.hasPrefix("Swift.Optional<") {
let optional = type[type.index(type.startIndex, offsetBy: 15) ..<
type.index(type.endIndex, offsetBy: -1)]
let optional = type[type.startIndex+15 ..<
type.endIndex-1]
.replacingOccurrences(of: "^__C\\.", with: "",
options: .regularExpression)
if NSClassFromString(optional) != nil {
Expand Down Expand Up @@ -482,3 +482,34 @@ extension Optional: OptionalTyping {
return "nil"
}
}

extension String.Index {
public struct OffsetIndex: Comparable {
let index: String.Index, offset: Int
public static func < (lhs: OffsetIndex, rhs: OffsetIndex) -> Bool {
return false
}
public static func + (index: OffsetIndex, offset: Int) -> OffsetIndex {
return OffsetIndex(index: index.index, offset: index.offset + offset)
}
public static func - (index: OffsetIndex, offset: Int) -> OffsetIndex {
return index + -offset
}
}
public static func + (index: String.Index, offset: Int) -> OffsetIndex {
return OffsetIndex(index: index, offset: offset)
}
public static func - (index: String.Index, offset: Int) -> OffsetIndex {
return index + -offset
}
}
extension StringProtocol {
public subscript (offset: String.Index.OffsetIndex) -> Character {
return self[index(offset.index, offsetBy: offset.offset)]
}
public subscript (range: Range<String.Index.OffsetIndex>) -> String {
let from = range.lowerBound, to = range.upperBound
return String(self[index(from.index, offsetBy: from.offset) ..<
index(to.index, offsetBy: to.offset)])
}
}
70 changes: 41 additions & 29 deletions SwiftTrace/SwiftInterpose.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Created by John Holdsworth on 23/09/2020.
// Copyright © 2020 John Holdsworth. All rights reserved.
//
// $Id: //depot/SwiftTrace/SwiftTrace/SwiftInterpose.swift#21 $
// $Id: //depot/SwiftTrace/SwiftTrace/SwiftInterpose.swift#25 $
//
// Extensions to SwiftTrace using dyld_dynamic_interpose
// =====================================================
Expand All @@ -23,8 +23,8 @@ extension SwiftTrace {
public static var swiftFunctionSuffixes = ["fC", "yF", "lF", "tF", "Qrvg"]

/// Regexp pattern for functions to exclude from interposing
public static var excludeFunction = NSRegularExpression(regexp: "^(\\w+\\.\\w+\\()")

public static var excludeFunction = NSRegularExpression(regexp:
"^\\w+\\.\\w+\\(|SwiftTrace|\\.getter : (?!some)")

/// "interpose" aspects onto Swift function name.
/// If the symbol is not in a different framework
Expand Down Expand Up @@ -66,26 +66,20 @@ extension SwiftTrace {
var interposes = [dyld_interpose_tuple]()

for suffix in swiftFunctionSuffixes {
findSwiftSymbols(aBundle, suffix, { symval, symname, _, _ in
findSwiftSymbols(aBundle, suffix, { symval, symname, _, _ in
if demangle(symbol: symname) == methodName,
let method = patchClass.init(name: methodName,
original: OpaquePointer(symval),
onEntry: onEntry, onExit: onExit,
replaceWith: replaceWith) {
let hook = method.forwardingImplementation()
interposes.append(dyld_interpose_tuple(
replacement: unsafeBitCast(hook, to: UnsafeRawPointer.self),
replacement: autoBitCast(method.forwardingImplementation()),
replacee: symval))
}
})
}

interposes.withUnsafeBufferPointer { interposes in
appBundleImages { (imageName, header) in
dyld_dynamic_interpose(header, interposes.baseAddress!,
interposes.count)
}
}
apply(interposes: interposes, symbols: [methodName])
}

/// Use interposing to trace all methods in a bundle
Expand All @@ -104,9 +98,7 @@ extension SwiftTrace {
symval, symname, _, _ in
if let methodName = demangle(symbol: symname),
excludeFunction.firstMatch(in: methodName, options: [],
range: NSMakeRange(0, methodName.utf16.count)) == nil &&
!methodName.contains("SwiftTrace") &&
!(methodName.contains(".getter :") && !methodName.hasSuffix("some")),
range: NSMakeRange(0, methodName.utf16.count)) == nil,
let factory = methodFilter(methodName),
let method = factory.init(name: methodName,
original: OpaquePointer(symval)) {
Expand All @@ -117,24 +109,12 @@ extension SwiftTrace {
// print(interposes.count, methodName)
interposes.append(dyld_interpose_tuple(
replacement: hook, replacee: current))
interposed[current] = hook
symbols.append(methodName)
}
}
}

interposes.withUnsafeBufferPointer { interposes in
let debugInterpose = getenv("DEBUG_INTERPOSE") != nil
appBundleImages { (imageName, header) in
for symno in 0..<interposes.count {
if debugInterpose {
print("Interposing: \(symbols[symno])")
}
dyld_dynamic_interpose(header,
interposes.baseAddress!+symno, 1)
}
}
}
apply(interposes: interposes, symbols: symbols)

bundlesInterposed.insert(String(cString: inBundlePath))
}
Expand All @@ -154,13 +134,45 @@ extension SwiftTrace {

/// Apply a trace to all methods in framesworks in app bundle
/// - Parameter subLevels: levels of unqualified traces to show
@objc class func traceFrameworkMethods() {
@objc open class func traceFrameworkMethods() {
appBundleImages { imageName, _ in
if strstr(imageName, ".framework") != nil {
interposeMethods(inBundlePath: imageName)
trace(bundlePath: imageName)
}
}
}

open class func apply(interposes: [dyld_interpose_tuple],
symbols: [String]? = nil) {
interposes.withUnsafeBufferPointer { interposes in
let debugInterpose = getenv("DEBUG_INTERPOSE") != nil
appBundleImages { (imageName, header) in
for symno in 0 ..< interposes.count {
if debugInterpose, let symbols = symbols {
print("Interposing: \(symbols[symno])")
}
dyld_dynamic_interpose(header,
interposes.baseAddress!+symno, 1)
let interpose = interposes.baseAddress![symno]
interposed[interpose.replacee] = interpose.replacement
}
}
}
}

@objc open class func revertInterposes() {
var interposes = [dyld_interpose_tuple]()
for (replacee, replacement) in interposed {
interposes.append(dyld_interpose_tuple(
replacement: replacee, replacee: replacement))
}
interposes.withUnsafeBufferPointer { interposes in
appBundleImages { (imageName, header) in
dyld_dynamic_interpose(header,
interposes.baseAddress!, interposes.count)
}
}
}
}
#endif
8 changes: 3 additions & 5 deletions SwiftTrace/SwiftStats.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// Obtaining invocation statistics
// ===============================
//
// $Id: //depot/SwiftTrace/SwiftTrace/SwiftStats.swift#5 $
// $Id: //depot/SwiftTrace/SwiftTrace/SwiftStats.swift#6 $
//

import Foundation
Expand Down Expand Up @@ -36,8 +36,7 @@ extension SwiftTrace {
*/
public static func sortedElapsedTimes(onlyFirst: Int? = nil) -> [(key: String, value: TimeInterval)] {
let sorted = elapsedTimes().sorted { $1.value < $0.value }
return onlyFirst != nil && onlyFirst! < sorted.count ?
Array(sorted[0 ... onlyFirst!]) : sorted
return onlyFirst != nil ? Array(sorted.prefix(onlyFirst!)) : sorted
}

func populate(invocationCounts: inout [String: Int]) {
Expand All @@ -61,8 +60,7 @@ extension SwiftTrace {
*/
public static func sortedInvocationCounts(onlyFirst: Int? = nil) -> [(key: String, value: Int)] {
let sorted = invocationCounts().sorted { $1.value < $0.value }
return onlyFirst != nil && onlyFirst! < sorted.count ?
Array(sorted[0 ... onlyFirst!]) : sorted
return onlyFirst != nil ? Array(sorted.prefix(onlyFirst!)) : sorted
}

public static func callOrder() -> [Swizzle] {
Expand Down
9 changes: 4 additions & 5 deletions SwiftTrace/SwiftSwizzle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// Copyright © 2020 John Holdsworth. All rights reserved.
//
// Repo: https://github.com/johnno1962/SwiftTrace
// $Id: //depot/SwiftTrace/SwiftTrace/SwiftSwizzle.swift#35 $
// $Id: //depot/SwiftTrace/SwiftTrace/SwiftSwizzle.swift#38 $
//
// Mechanics of Swizzling Swift
// ============================
Expand Down Expand Up @@ -269,10 +269,9 @@ extension SwiftTrace {
guard objcMethod != nil && !isReturn else { return false }
let returnType = methodSignature == nil ? "UNDECODABLE" :
String(cString: sig_returnType(methodSignature!))
let isStret = returnType.hasPrefix("{") &&
!returnType.hasSuffix("=ff}") &&
!returnType.hasSuffix("=dd}") &&
!returnType.hasSuffix("=QQ}")
let isStret =
returnType.hasPrefix("{") && returnType.hasSuffix("}") &&
returnType[returnType.endIndex-4] != "="
if isStret && !isReturn {
invocation.swiftSelf = intArgs[1]
}
Expand Down
44 changes: 17 additions & 27 deletions SwiftTrace/SwiftTrace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// Copyright © 2016 John Holdsworth. All rights reserved.
//
// Repo: https://github.com/johnno1962/SwiftTrace
// $Id: //depot/SwiftTrace/SwiftTrace/SwiftTrace.swift#247 $
// $Id: //depot/SwiftTrace/SwiftTrace/SwiftTrace.swift#249 $
//

import Foundation
Expand Down Expand Up @@ -61,11 +61,11 @@ open class SwiftTrace: NSObject {
public static var lastSwiftTrace = SwiftTrace(previous: nil, subLevels: 0)

/// Previous interposes need to be tracked
public static var interposed = [UnsafeMutableRawPointer: UnsafeMutableRawPointer]()
public static var interposed = [UnsafeRawPointer: UnsafeRawPointer]()

static var bundlesInterposed = Set<String>()

@objc class var isTracing: Bool {
public class var isTracing: Bool {
return lastSwiftTrace.previousSwiftTrace != nil
}

Expand Down Expand Up @@ -112,27 +112,16 @@ open class SwiftTrace: NSObject {
Default pattern of common/problematic symbols to be excluded from tracing
*/
open class var defaultMethodExclusions: String {
#if !os(macOS)
return """
\\.getter| (?:retain|_tryRetain|release|autorelease|_isDeallocating|.cxx_destruct|_?dealloc|description| debugDescription|contextID)]|initWithCoder|\
\\.getter : (?!some)| (?:retain(?:Count)?|_tryRetain|release|autorelease|_isDeallocating|.cxx_destruct|_?dealloc|class|description|\
debugDescription|contextID|undoManager|_animatorClassForTargetClass|cursorUpdate|_isTrackingAreaObject)]|initWithCoder|\
^\\+\\[(?:Reader_Base64|UI(?:NibStringIDTable|NibDecoder|CollectionViewData|WebTouchEventsGestureRecognizer)) |\
^.\\[(?:__NSAtom|NSView|UIView|RemoteCapture|BCEvent) |UIDeviceWhiteColor initWithWhite:alpha:|UIButton _defaultBackgroundImageForType:andState:|\
UIImage _initWithCompositedSymbolImageLayers:name:alignUsingBaselines:|\
_UIWindowSceneDeviceOrientationSettingsDiffAction _updateDeviceOrientationWithSettingObserverContext:windowScene:transitionContext:|\
UIColorEffect colorEffectSaturate:|UIWindow _windowWithContextId:|RxSwift.ScheduledDisposable.dispose| ns(?:li|is)_
"""
#else
return """
\\.getter| (?:retain(?:Count)?|_tryRetain|release|autorelease|_isDeallocating|.cxx_destruct|_?dealloc|class|description| debugDescription|\
contextID!undoManager|_animatorClassForTargetClass|cursorUpdate|_isTrackingAreaObject)]|initWithCoder|\
^\\+\\[(?:Reader_Base64|UI(?:NibStringIDTable|NibDecoder|CollectionViewData|WebTouchEventsGestureRecognizer)) |\
^.\\[(?:__NSAtom|NS(?:View|Appearance|AnimationContext|Segment|KVONotifying__)|_NSViewAnimator|UIView|RemoteCapture|BCEvent) |\
^.\\[(?:__NSAtom|NS(?:View|Appearance|AnimationContext|Segment|KVONotifying_\\S+)|_NSViewAnimator|UIView|RemoteCapture|BCEvent) |\
_TtGC7SwiftUI|NSTheme|NSTracking|UIDeviceWhiteColor initWithWhite:alpha:|UIButton _defaultBackgroundImageForType:andState:|\
UIImage _initWithCompositedSymbolImageLayers:name:alignUsingBaselines:|\
_UIWindowSceneDeviceOrientationSettingsDiffAction _updateDeviceOrientationWithSettingObserverContext:windowScene:transitionContext:|\
UIColorEffect colorEffectSaturate:|UIWindow _windowWithContextId:|RxSwift.ScheduledDisposable.dispose| ns(?:li|is)_
"""
#endif
}

static var exclusionRegexp: NSRegularExpression? =
Expand Down Expand Up @@ -254,7 +243,7 @@ open class SwiftTrace: NSObject {
- parameter bundlePath: Path to bundle to trace
- parameter subLevels: levels of unqualified traces to show
*/
@objc class func trace(bundlePath: UnsafePointer<Int8>?, subLevels: Int = 0) {
@objc open class func trace(bundlePath: UnsafePointer<Int8>?, subLevels: Int = 0) {
startNewTrace(subLevels: subLevels)
forAllClasses(bundlePath: bundlePath) {
(aClass, stop) in
Expand All @@ -265,7 +254,7 @@ open class SwiftTrace: NSObject {
/**
Lists Swift classes not inheriting from NSObject in an app or framework.
*/
open class func swiftClassList(bundlePath: UnsafePointer<Int8>? = nil) -> [AnyClass] {
@objc open class func swiftClassList(bundlePath: UnsafePointer<Int8>? = nil) -> [AnyClass] {
var classes = [AnyClass]()
findSwiftSymbols(bundlePath, "CN", { aClass, _, _, _ in
classes.append(autoBitCast(aClass))
Expand All @@ -278,7 +267,7 @@ open class SwiftTrace: NSObject {
- parameter pattern: regexp patten to specify classes to trace
- parameter subLevels: levels of unqualified traces to show
*/
open class func traceClasses(matchingPattern pattern: String, subLevels: Int = 0) {
@objc open class func traceClasses(matchingPattern pattern: String, subLevels: Int = 0) {
startNewTrace(subLevels: subLevels)
let regexp = NSRegularExpression(regexp: pattern)
forAllClasses {
Expand All @@ -297,7 +286,7 @@ open class SwiftTrace: NSObject {
Underlying implementation of tracing an individual classs.
- parameter aClass: the class, the methods of which to trace
*/
open class func trace(aClass: AnyClass) {
@objc open class func trace(aClass: AnyClass) {
let className = NSStringFromClass(aClass)
if className.hasPrefix("Swift.") || className.hasPrefix("__") {
return
Expand Down Expand Up @@ -335,7 +324,7 @@ open class SwiftTrace: NSObject {
- parameter aClass: the class, the methods of which to trace
- parameter subLevels: levels of unqualified traces to show
*/
open class func traceInstances(ofClass aClass: AnyClass, subLevels: Int = 0) {
@objc open class func traceInstances(ofClass aClass: AnyClass, subLevels: Int = 0) {
startNewTrace(subLevels: subLevels).classFilter = aClass
var tClass: AnyClass? = aClass
while tClass != NSObject.self && tClass != nil {
Expand All @@ -349,7 +338,7 @@ open class SwiftTrace: NSObject {
- parameter anInstance: the class, the methods of which to trace
- parameter subLevels: levels of unqualified traces to show
*/
open class func trace(anInstance: AnyObject, subLevels: Int = 0) {
@objc open class func trace(anInstance: AnyObject, subLevels: Int = 0) {
traceInstances(ofClass: object_getClass(anInstance)!, subLevels: subLevels)
lastSwiftTrace.instanceFilter = unsafeBitCast(anInstance, to: intptr_t.self)
lastSwiftTrace.classFilter = nil
Expand Down Expand Up @@ -416,14 +405,15 @@ open class SwiftTrace: NSObject {
- parameter subLevels: subLevels to log of previous traces to trace
*/
#if swift(>=5.0)
open class func traceProtocolsInBundle(containing aClass: AnyClass? = nil, matchingPattern: String? = nil, subLevels: Int = 0) {
@objc open class func traceProtocolsInBundle(containing aClass: AnyClass? = nil, matchingPattern: String? = nil, subLevels: Int = 0) {
startNewTrace(subLevels: subLevels)
let regex = matchingPattern != nil ?
NSRegularExpression(regexp: matchingPattern!) : nil
findSwiftSymbols(aClass == nil ? callerBundle() :
aClass == NSObject.self ? nil : class_getImageName(aClass), "WP") {
(address: UnsafeMutableRawPointer, _, typeref, typeend) in
let witnessTable = address.assumingMemoryBound(to: SIMP.self)
(address: UnsafeRawPointer, _, typeref, typeend) in
let witnessTable = UnsafeMutableRawPointer(mutating: address)
.assumingMemoryBound(to: SIMP.self)
var info = Dl_info()
// The start of a witness table is always the protocol descriptor
// then the associated types (always in section `__swift5_typeref`)
Expand All @@ -449,7 +439,7 @@ open class SwiftTrace: NSObject {
regex == nil || regex!.matches(demangled) {
if let factory = methodFilter(demangled),
let swizzle = factory.init(name: demangled,
vtableSlot: &witnessTable[slot]) {
vtableSlot: &witnessTable[slot]) {
witnessTable[slot] = swizzle.forwardingImplementation()
}
continue
Expand Down
Loading

0 comments on commit 0934cbe

Please sign in to comment.