Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

您这个和Aspects的instance hook有什么不同吗😂 #1

Open
Assuner-Lee opened this issue Feb 10, 2020 · 7 comments
Open

您这个和Aspects的instance hook有什么不同吗😂 #1

Assuner-Lee opened this issue Feb 10, 2020 · 7 comments

Comments

@Assuner-Lee
Copy link

No description provided.

@gsdios
Copy link
Collaborator

gsdios commented Feb 10, 2020

在开源这份代码之前我并未了解和使用过aspect,发表完https://mp.weixin.qq.com/s/wxigL1Clem1dR8Nkt8LLMw 这篇技术文章之后看到有些留言提到了aspect然后我去github大概看了一下基本思路都是基于类似kvo的isa替换,但是从api设计以及实现上也有明确的区别。具体区别或者异同我最近先研究下aspect源码,随后会给出答案。

@Assuner-Lee
Copy link
Author

Assuner-Lee commented Feb 11, 2020

以及

还包括了统一hook时替换imp为objc_msgForward,hook forwardInvocation: 这种细节;

@gsdios
Copy link
Collaborator

gsdios commented Feb 12, 2020

在开源这份代码之前我并未了解和使用过aspects,发表完https://mp.weixin.qq.com/s/wxigL1Clem1dR8Nkt8LLMw 这篇技术文章之后看到有些留言提到了aspects然后我去github大概看了一下基本思路都是基于类似kvo的isa替换,但是从api设计以及实现上也有明确的区别,我们通过以下示例简要介绍下:

假设有这样一个自定义类Test,在其内部定义了一个求和的方法,接收四个int类型的参数。

@implementation Test

- (int)sumWithA:(int)a b:(int)b c:(int)c d:(int)d {
    return a + b + c + d;
}

@end

现在要求将四个参数分别平方然后再求和。

使用aspects实现如下:

    Test *testObj = [Test new];
    [testObj aspect_hookSelector:@selector(sumWithA:b:c:d:) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> info, int a, int b, int c, int d) {
        int aa = a * a;
        int bb = b * b;
        int cc = c * c;
        int dd = d * d;
        [info.originalInvocation setArgument:&aa atIndex:2];
        [info.originalInvocation setArgument:&bb atIndex:3];
        [info.originalInvocation setArgument:&cc atIndex:4];
        [info.originalInvocation setArgument:&dd atIndex:5];
    } error:NULL];

    int sum = [testObj sumWithA:1 b:2 c:3 d:4];
    NSLog(@">>>> %d", sum); // >>>> 30

使用SDMagicHook实现如下:

    Test *testObj = [Test new];
    [testObj hookMethod:@selector(sumWithA:b:c:d:) impBlock:^(typeof(testObj) this, int a, int b, int c, int d) {
        __block int res;
        [this callOriginalMethodInBlock:^{
            res = [this sumWithA:a * a b:b * b c:c * c d:d * d];
        }];
        return res;
    }];

    int sum = [testObj sumWithA:1 b:2 c:3 d:4];
    NSLog(@">>>> %d", sum); // >>>> 30

由以上demo可以看出:
1.aspects使用AspectOptions来决定自定义方法和原始方法的执行顺序;SDMagicHook使用callOriginalMethodInBlock来调用原始方法,可以将原始方法放在自定义逻辑的前、中、后任意位置执行,更加灵活方便。

2.aspects将原始方法封装在NSInvocation里面,如果想要修改sumWithA:b:c:d:的参数值需要调用setArgument:atIndex:方法来实现,api不够简洁友好;SDMagicHook只需在callOriginalMethodInBlock的block参数内部直接调用原始的sumWithA:b:c:d:方法传参即可,直观简便。

@yulingtianxia
Copy link

yulingtianxia commented Feb 21, 2020

其实触发方法转发走 forwardInvocation、替换 isa 指针、对象级别 Hook 等等这些都是老生常谈的Hook 技术,很多组件都基于这套方案来 Hook,在长时间使用后也暴露了很多已知的缺陷。我之前写的节流限频组件也是用到了这些技术,文章大部分都是介绍 Aspects 没有处理好的场景:
http://yulingtianxia.com/blog/2018/07/31/MessageThrottle-Safety/

不过凡是基于方法转发的 Hook 方案都有一个无法解决的致命问题,就是父子类都 Hook 了同一个方法,然后子类调用父类 super 的时候会死循环。这也是为何这个方案在多年之后 Aspects 的作者终于声明它不再适用于生产环境。

而此问题需要吴子奇基于桥的 Hook 在汇编层面来解决。

想问下除了原理基本相同,API 略有不同之外,有没有解决 Aspects 相关技术带来的这些通病呢?

@gsdios
Copy link
Collaborator

gsdios commented Feb 21, 2020

其实触发方法转发走 forwardInvocation、替换 isa 指针、对象级别 Hook 等等这些都是老生常谈的Hook 技术,很多组件都基于这套方案来 Hook,在长时间使用后也暴露了很多已知的缺陷。我之前写的节流限频组件也是用到了这些技术,文章大部分都是介绍 Aspects 没有处理好的场景:
http://yulingtianxia.com/blog/2018/07/31/MessageThrottle-Safety/

不过凡是基于方法转发的 Hook 方案都有一个无法解决的致命问题,就是父子类都 Hook 了同一个方法,然后子类调用父类 super 的时候会死循环。这也是为何这个方案在多年之后 Aspects 的作者终于声明它不再适用于生产环境。

而此问题需要吴子奇基于桥的 Hook 在汇编层面来解决。

想问下除了原理基本相同,API 略有不同之外,有没有解决 Aspects 相关技术带来的这些通病呢?

关于你提到的第一个问题:
“父子类都 Hook 了同一个方法,然后子类调用父类 super 的时候会死循环”应该是在所有同一类型的对象共享同一个类kvo类指针造成的问题。SDMagicHook是每个对象单独享有一个自定义的新类,对象之间相互隔离互不影响。也就是说假如有这样一个继承关系A->B->C,现在C类有个实例c1(0xa0),B类有个实例b1(0xb0),c1和b1都hook了自己的test方法,那么会生成两个新类SDC_0xa0和SDB_0xb0,其继承关系如下A->B->C->SDC_0xa0、A->B->SDB_0xb0,具体的hook操作发生在SDC_0xa0和SDB_0xb0这两个新类上,原始的A类和B类不受任何影响SDC_0xa0和SDB_0xb0之间也相互隔离所以避开了你提到的这个问题。

关于你提到的Aspects本身遇到但是尚未解决的问题:在aspects的readme中看到了他们介绍到有“KVO works if observers are created after your calls aspect_hookSelector: It most likely will crash the other way around. Still looking for workarounds here - any help appreciated.”这样一个问题。SDMagicHook测试了一下同样遇到了,但是我们已经有了相关解决方案,大概会在下周一前后同步到github上。

还有其他问题的话欢迎持续交流反馈 @yulingtianxia

@gsdios gsdios closed this as completed Feb 21, 2020
@gsdios gsdios reopened this Feb 21, 2020
@gsdios
Copy link
Collaborator

gsdios commented Mar 8, 2020

兼容系统KVO已经实现,欢迎更新&反馈

@623637646
Copy link

623637646 commented Jun 19, 2020

Hi @yulingtianxia

父子类都 Hook 了同一个方法,然后子类调用父类 super 的时候会死循环

关于这个问题,我相信 https://github.com/623637646/SwiftHook 很好的解决了这个问题。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants