Aspects源码分析

Aspects是一个用来切片编程的开源框架,提供了丰富接口,可以Hook类和单个对象的方法,并提供了原实现前Hook,替换原实现,原实现后Hook等选项。

1 补充知识点

Objective-C里面的方法调用,我们看到的第1个参数,实际上是第3个参数,前2个参数是隐形参数,第1个是self(encode后是@),即实例对象或者类对象,第2个是Selector(encode后是#),即方法名。
Block有些类似,我们看到的第1个参数,实际上是第2个参数,前1个参数是隐形参数,即Block自身,encode后是@?。

2 简单流程

不管是Hook类的还是Hook某个对象的,基本流程都是将原SEL指向objc_msgForward,使其走消息转发的流程,然后将原实现保存在aliseSEL中。然后将forwardInnovation的实现设为自定义的函数,如果原来有forwardInnovation的实现,则将其保存在AspectsForwardInvocationSelectorName这个SEL中。

Aspects做了很多其他的操作来保证安全性和稳定性。

1 先判断方法是否能被Hook

这部分位于aspect_isSelectorAllowedAndTrack中

static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, AspectOptions options, NSError **error) {static NSSet *disallowedSelectorList;static dispatch_once_t pred;dispatch_once(&pred, ^{disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];});// Check against the blacklist.NSString *selectorName = NSStringFromSelector(selector);if ([disallowedSelectorList containsObject:selectorName]) {NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];AspectError(AspectErrorSelectorBlacklisted, errorDescription);return NO;}// Additional checks.AspectOptions position = options&AspectPositionFilter;if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) {NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc.";AspectError(AspectErrorSelectorDeallocPosition, errorDesc);return NO;}if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) {NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName];AspectError(AspectErrorDoesNotRespondToSelector, errorDesc);return NO;}// Search for the current class and the class hierarchy IF we are modifying a class objectif (class_isMetaClass(object_getClass(self))) {Class klass = [self class];NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();Class currentClass = [self class];do {AspectTracker *tracker = swizzledClassesDict[currentClass];if ([tracker.selectorNames containsObject:selectorName]) {// Find the topmost class for the log.if (tracker.parentEntry) {AspectTracker *topmostEntry = tracker.parentEntry;while (topmostEntry.parentEntry) {topmostEntry = topmostEntry.parentEntry;}NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(topmostEntry.trackedClass)];AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);return NO;}else if (klass == currentClass) {// Already modified and topmost!return YES;}}}while ((currentClass = class_getSuperclass(currentClass)));// Add the selector as being modified.currentClass = klass;AspectTracker *parentTracker = nil;do {AspectTracker *tracker = swizzledClassesDict[currentClass];if (!tracker) {tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass parent:parentTracker];swizzledClassesDict[(id)currentClass] = tracker;}[tracker.selectorNames addObject:selectorName];// All superclasses get marked as having a subclass that is modified.parentTracker = tracker;}while ((currentClass = class_getSuperclass(currentClass)));}return YES;
}
首先就是判断retain、release、autorelease等等这些禁止hook的方法。
对于dealloc则只能替换或后Hook。
对于不响应的方法不Hook。
最后是判断是否已经Hook过了该方法。但个人认为这里有点瑕疵,会产生一个问题,当2个分别继承自NSObject的类有着相同的方法名,Hook了其中一个类的方法后,另一个类的就不能Hook了。
2 替换后的Block和原实现的参数是否匹配

这部分判断位于aspect_isCompatibleBlockSignature中,
首先Block是一种特殊的结构体,首个变量是和其他对象一样都是isa,这使得Block也可以响应方法的调用。在Block结构体的最后保存着Block的签名,这个签名和方法的签名是一样的。参数的匹配就是比较2个签名的参数个数和参数类型的匹配。

static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {BOOL signaturesMatch = YES;NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {signaturesMatch = NO;}else {if (blockSignature.numberOfArguments > 1) {const char *blockType = [blockSignature getArgumentTypeAtIndex:1];if (blockType[0] != '@') {signaturesMatch = NO;}}// Argument 0 is self/block, argument 1 is SEL or id. We start comparing at argument 2.// The block can have less arguments than the method, that's ok.if (signaturesMatch) {for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) {const char *methodType = [methodSignature getArgumentTypeAtIndex:idx];const char *blockType = [blockSignature getArgumentTypeAtIndex:idx];// Only compare parameter, not the optional type data.if (!methodType || !blockType || methodType[0] != blockType[0]) {signaturesMatch = NO; break;}}}}if (!signaturesMatch) {NSString *description = [NSString stringWithFormat:@"Blog signature %@ doesn't match %@.", blockSignature, methodSignature];AspectError(AspectErrorIncompatibleBlockSignature, description);return NO;}return YES;
}
3 进行Hook

对于类的Hook走的是简单流程,但对于对象的Hook,因为为了不影响其他的对象,处理流程上有些不同。
以要Hook的实例对象的类为父类,新生成一个子类,然后将这个子类的forwardInvocation实现改为自定义的函数,同时替换这个子类的-class和+class方法,让其都返回要Hook的实例对象的类。然后修改将要Hook的实例对象的isa,将其isa指向这个子类。然后再将这个子类的原方法的实现改为objc_msgSend,并将原实现保存在aliseSEL中。总结起来就是Hook类和Hook某个对象的方法类似,不同的是,Hook对象需要额外生成一个子类,而后所有的修改操作都在这个子类上进行。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部