執(zhí)行hook事件
在Aspects源碼分析的第一篇文章中主要分析了為hook做的準(zhǔn)備工作,接下來(lái)分析一下,當(dāng) selector執(zhí)行時(shí)是如何執(zhí)行你自己添加的自定義hook事件的。
通過(guò)hook準(zhǔn)備工作的處理后 ,外界調(diào)用的hook selector 會(huì)直接進(jìn)入消息轉(zhuǎn)發(fā)執(zhí)行到方法forwardInvocation: ,然后此時(shí)forwardInvocation:方法的IMP是指向處理hook的函數(shù) __ASPECTS_ARE_BEING_CALLED__,這個(gè)函數(shù)也是整個(gè)hook事件的核心函數(shù)。代碼實(shí)現(xiàn)如下
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
NSCParameterAssert(self);
NSCParameterAssert(invocation);
SEL originalSelector = invocation.selector;
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
invocation.selector = aliasSelector;
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
NSArray *aspectsToRemove = nil;
// Before hooks.
aspect_invoke(classContainer.beforeAspects, info);
aspect_invoke(objectContainer.beforeAspects, info);
// Instead hooks.
BOOL respondsToAlias = YES;
if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
aspect_invoke(classContainer.insteadAspects, info);
aspect_invoke(objectContainer.insteadAspects, info);
}else {
Class klass = object_getClass(invocation.target);
do {
if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
[invocation invoke]; //aliasSelector 已經(jīng)在 aspect_prepareClassAndHookSelector 函數(shù)中替換為原來(lái)selector的實(shí)現(xiàn) , 這里就是調(diào)回原方法的實(shí)現(xiàn)代碼
break;
}
}while (!respondsToAlias && (klass = class_getSuperclass(klass)));
}
// After hooks.
aspect_invoke(classContainer.afterAspects, info);
aspect_invoke(objectContainer.afterAspects, info);
// If no hooks are installed, call original implementation (usually to throw an exception)
if (!respondsToAlias) {
invocation.selector = originalSelector;
SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
if ([self respondsToSelector:originalForwardInvocationSEL]) {
((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
}else {
[self doesNotRecognizeSelector:invocation.selector];
}
}
// Remove any hooks that are queued for deregistration.
[aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}
這個(gè)函數(shù)首先把傳進(jìn)來(lái)的NSInvocation對(duì)象的selector 賦值為 IMP指向調(diào)用方法的原IMP的aliasSelector , 這樣可以方便調(diào)用會(huì)原方法的IMP的實(shí)現(xiàn)。
獲取hook事件容器
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
這里是通過(guò)aliasSelector分別取出綁定在 hook對(duì)象 以及 hook class (hook對(duì)象的isa指針指向的Class)中對(duì)應(yīng)的容器對(duì)象AspectsContainer , 并生成一個(gè) AspectInfo對(duì)象,用于封裝執(zhí)行方法及hook事件是所需的實(shí)參。接下來(lái)分別是遍歷兩個(gè)容器對(duì)象中的三個(gè)數(shù)組(beforeAspects 、insteadAspects 、afterAspects)是否有 hook的標(biāo)識(shí)對(duì)象AspectIdentifier , 如果有的話就執(zhí)行相應(yīng)的hook事件。insteadAspects如果這個(gè)數(shù)組有對(duì)象存放,就說(shuō)明原方法的實(shí)現(xiàn)被替換為執(zhí)行 insteadAspects里的hook事件了。
hook執(zhí)行
//執(zhí)行hook
aspect_invoke(classContainer.beforeAspects, info);
//hook執(zhí)行的宏代碼
#define aspect_invoke(aspects, info) \
for (AspectIdentifier *aspect in aspects) {\
[aspect invokeWithInfo:info];\
if (aspect.options & AspectOptionAutomaticRemoval) { \
aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
} \
}
- (BOOL)invokeWithInfo:(id<AspectInfo>)info {
//根據(jù)block得簽名字符串 , 生成對(duì)應(yīng)的消息調(diào)用對(duì)象。用來(lái)在設(shè)置完參數(shù)后調(diào)用block
NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
//取出外界調(diào)用方法時(shí),系統(tǒng)封裝的消息調(diào)用對(duì)象,用來(lái)獲取實(shí)參的值
NSInvocation *originalInvocation = info.originalInvocation;
NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
// Be extra paranoid. We already check that on hook registration.
if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
AspectLogError(@"Block has too many arguments. Not calling %@", info);
return NO;
}
// The `self` of the block will be the AspectInfo. Optional.
//這里設(shè)置Block的 第一個(gè)參數(shù)為傳進(jìn)來(lái)的AspectInfo對(duì)象 , 第0位置的參數(shù)是Block本身
if (numberOfArguments > 1) { //有參數(shù)的話就吧第一個(gè)參數(shù) 設(shè)置為 AspectInfo , 第0位置是block本身。
/**
官方文檔解析 : When the argument value is an object, pass a pointer to the variable (or memory) from which the object should be copied
&info : info對(duì)象指針的地址
這樣傳參的目的是保證了,參數(shù)無(wú)論是普通類型參數(shù)還是對(duì)象都可以通過(guò)你傳進(jìn)來(lái)的指針,通過(guò)拷貝指針指向的內(nèi)容來(lái)獲取到 普通類型數(shù)據(jù) 或者 對(duì)象指針。
*/
[blockInvocation setArgument:&info atIndex:1];
}
void *argBuf = NULL;
//遍歷參數(shù)類型typeStr , 為blockInvocation對(duì)應(yīng)的參數(shù)創(chuàng)建所需空間 , 賦值數(shù)據(jù) , 設(shè)置blockInvocation參數(shù)
for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
NSUInteger argSize; //實(shí)參多需要的空間大小
NSGetSizeAndAlignment(type, &argSize, NULL); //根據(jù)encodeType 字符串 創(chuàng)建對(duì)應(yīng)空間存放block的參數(shù)數(shù)據(jù)所屬要的size
if (!(argBuf = reallocf(argBuf, argSize))) { //創(chuàng)建size大小的空間
AspectLogError(@"Failed to allocate memory for block invocation.");
return NO;
}
[originalInvocation getArgument:argBuf atIndex:idx]; //獲取到指向?qū)?yīng)參數(shù)的指針
[blockInvocation setArgument:argBuf atIndex:idx]; //把指向?qū)?yīng)實(shí)參指針的地址(相當(dāng)于指向?qū)崊⒅羔樀闹羔槪﹤鹘oinvocation 進(jìn)行拷貝,得到的就是指向?qū)崊?duì)象的指針
}
[blockInvocation invokeWithTarget:self.block]; //設(shè)置完實(shí)參執(zhí)行block
if (argBuf != NULL) {
free(argBuf); //c語(yǔ)言的創(chuàng)建空間 ,用完后需要釋放,關(guān)于c語(yǔ)言的動(dòng)態(tài)內(nèi)存相關(guān)資料可以看 https://blog.csdn.net/qq_29924041/article/details/54897204
}
return YES;
}
可以看出 AspectIdentifier的-invokeWithInfo是執(zhí)行hook事件最終的方法。該方法主要處理的事情是:根據(jù)傳進(jìn)來(lái)的AspectInfo對(duì)象為最初定義hook事件的Block設(shè)置相應(yīng)的參數(shù)。并執(zhí)行Block(hook事件)
blockInvocation設(shè)置參數(shù)解析
設(shè)置了
block的第一個(gè)位置的參數(shù)為AspectInfo * info, 這樣做及未來(lái)方便內(nèi)部遍歷設(shè)置參數(shù) (與selector保持一致,自定義參數(shù)從 索引為2的位置開(kāi)始),又方便了外界在定義hook的事件是獲取到實(shí)例對(duì)象 -[info instance]getArgument:atIndex:返回的是對(duì)應(yīng)索引參數(shù)的指針(地址)。假如參數(shù)是一個(gè)對(duì)象指針的話,會(huì)返回對(duì)象的指針地址。而setArgument:atIndex:會(huì)把傳進(jìn)來(lái)的參數(shù)(指針)拷貝其指向的內(nèi)容到相應(yīng)的索引位置中。所以argBuf在整個(gè)for循環(huán)中可以不斷地使用同一個(gè)指針并不斷的reallocf返回指向一定堆空間的指針。argBuf指針只是作為一個(gè)設(shè)置參數(shù)的中介,每一個(gè)for循環(huán)后setArgument :atIndex:都會(huì)把argBuf指向的內(nèi)容拷貝到invocation中。
hook的移除
AspectIdentifier的remove方法,會(huì)調(diào)用到下面的函數(shù)
static BOOL aspect_remove(AspectIdentifier *aspect, NSError **error) {
NSCAssert([aspect isKindOfClass:AspectIdentifier.class], @"Must have correct type.");
__block BOOL success = NO;
aspect_performLocked(^{
id self = aspect.object; // strongify
if (self) {
AspectsContainer *aspectContainer = aspect_getContainerForObject(self, aspect.selector);
success = [aspectContainer removeAspect:aspect]; //重container的 三個(gè)數(shù)組中移除aspect
aspect_cleanupHookedClassAndSelector(self, aspect.selector);
// destroy token
aspect.object = nil;
aspect.block = nil;
aspect.selector = NULL;
}else {
NSString *errrorDesc = [NSString stringWithFormat:@"Unable to deregister hook. Object already deallocated: %@", aspect];
AspectError(AspectErrorRemoveObjectAlreadyDeallocated, errrorDesc);
}
});
return success;
}
1. 移除AspectContainer中的AspectIdentifier
首先獲取被hook的對(duì)象中通過(guò)runtime綁定的關(guān)聯(lián)屬性 ---AspectsContainer *aspectContainer ,并分別移除數(shù)組的hook標(biāo)識(shí)對(duì)象 - AsepctIdentifier * aspect,實(shí)現(xiàn)代碼如下:
//AspectContainer的實(shí)例方法
- (BOOL)removeAspect:(id)aspect {
for (NSString *aspectArrayName in @[NSStringFromSelector(@selector(beforeAspects)),
NSStringFromSelector(@selector(insteadAspects)),
NSStringFromSelector(@selector(afterAspects))]) {
NSArray *array = [self valueForKey:aspectArrayName];
NSUInteger index = [array indexOfObjectIdenticalTo:aspect];
if (array && index != NSNotFound) {
NSMutableArray *newArray = [NSMutableArray arrayWithArray:array];
[newArray removeObjectAtIndex:index];
[self setValue:newArray forKey:aspectArrayName];
return YES;
}
}
return NO;
}
2.還原selector指向的IMP
// Check if the method is marked as forwarded and undo that.
Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
if (aspect_isMsgForwardIMP(targetMethodIMP)) {
// Restore the original method implementation.
const char *typeEncoding = method_getTypeEncoding(targetMethod);
SEL aliasSelector = aspect_aliasForSelector(selector);
Method originalMethod = class_getInstanceMethod(klass, aliasSelector);
IMP originalIMP = method_getImplementation(originalMethod);
NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
class_replaceMethod(klass, selector, originalIMP, typeEncoding);
AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}
在進(jìn)行hook準(zhǔn)備工作室,把selector的IMP修改成立進(jìn)入消息轉(zhuǎn)發(fā)的,并且添加了一個(gè)新的selector(asepct__selector)指向原selector的IMP這里是還原selector的指向。
3.移除AspectTracker對(duì)應(yīng)的記錄
static void aspect_deregisterTrackedSelector(id self, SEL selector) {
if (!class_isMetaClass(object_getClass(self))) return;
NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
NSString *selectorName = NSStringFromSelector(selector);
Class currentClass = [self class];
do {
AspectTracker *tracker = swizzledClassesDict[currentClass];
if (tracker) {
[tracker.selectorNames removeObject:selectorName];
if (tracker.selectorNames.count == 0) {
[swizzledClassesDict removeObjectForKey:tracker];
}
}
}while ((currentClass = class_getSuperclass(currentClass)));
}
如果被hook的是類(調(diào)用的是類方法添加hook)。在全局對(duì)象(NSMutableDictionary *swizzledClassesDict)中移除hook class的整個(gè)向上繼承關(guān)系鏈上的AspectTracker中的selectors數(shù)組中的selectorName字符串。
4.還原被hook的實(shí)例對(duì)象的isa的指向 + 還原被hook Class的forwardInvocation:方法的IMP指向
// Get the aspect container and check if there are any hooks remaining. Clean up if there are not.
AspectsContainer *container = aspect_getContainerForObject(self, selector);
if (!container.hasAspects) {
// Destroy the container
aspect_destroyContainerForObject(self, selector);
// Figure out how the class was modified to undo the changes.
NSString *className = NSStringFromClass(klass);
if ([className hasSuffix:AspectsSubclassSuffix]) {
Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:AspectsSubclassSuffix withString:@""]);
NSCAssert(originalClass != nil, @"Original class must exist");
object_setClass(self, originalClass); //把hook的類對(duì)象isa 從_Aspects_class -> 原來(lái)的類
AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(originalClass));
// We can only dispose the class pair if we can ensure that no instances exist using our subclass.
// Since we don't globally track this, we can't ensure this - but there's also not much overhead in keeping it around.
//objc_disposeClassPair(object.class);
}else {
// Class is most likely swizzled in place. Undo that.
if (isMetaClass) {
aspect_undoSwizzleClassInPlace((Class)self);
}
}
}
這里首先判斷hook對(duì)象的AspectContainer屬性數(shù)組中是否還有 AspectIndetafier對(duì)象(hook標(biāo)識(shí))。如果沒(méi)有的話就清除掉該對(duì)象的關(guān)聯(lián)屬性容器。接下來(lái)分兩種情況進(jìn)行還原處理
情況1. 被hook的是普通實(shí)例對(duì)象 : 此時(shí)需要把對(duì)象的isa指向還原會(huì)為原來(lái)的Class --> object_setClass(self, originalClass);
情況2. 被hook的是類: 還原forwardInvocation:方法的IMP指向?yàn)樵瓉?lái)的進(jìn)入消息轉(zhuǎn)發(fā)的IMP,并且從全局變量swizzledClasses中移除類名字符串的記錄。