Aspects源码分析
Posted FarmGuo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 object
if (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<NSCopying>)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<AspectInfo>. 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对象需要额外生成一个子类,而后所有的修改操作都在这个子类上进行。
以上是关于Aspects源码分析的主要内容,如果未能解决你的问题,请参考以下文章