使 NSInitation 调用特定的 IMP

发布于 2024-10-18 10:05:17 字数 1238 浏览 9 评论 0原文

我正在寻找一种方法来使 NSInspiration 调用特定的 IMP 。默认情况下,它调用它能找到的“最低”IMP(即最近重写的版本),但我正在寻找一种方法让它调用 IMP< /code> 来自继承链的更高层。我想要调用的 IMP 是动态确定的,否则我可以使用 super 关键字或类似的东西。

我的想法是使用 -forwardInspiration: 机制来捕获消息(简单且已经可以工作),然后更改 IMP 以便它转到一个既不是 code>super 实现也不是最远后代的实现。 (难)

我发现唯一接近的是 AspectObjectiveC,但这需要 libffi,它使其不兼容 iOS。理想情况下,我希望这是跨平台的。

有什么想法吗?

免责声明:我只是在试验


尝试@bbum关于蹦床功能的想法

所以我想我已经把事情基本设置好了;我已经通过 class_addMethod() 正确添加了以下蹦床,并且它确实被输入:

id dd_trampolineFunction(id self, SEL _cmd, ...) {
    IMP imp = [self retrieveTheProperIMP];
    self = [self retrieveTheProperSelfObject];
    asm(
        "jmp %0\n"
        :
        : "r" (imp)
        );
    return nil; //to shut up the compiler
}

我已经验证了正确的 self 和正确的 IMP 在 JMP 之前都是正确的,并且 _cmd 参数也正确传入。 (换句话说,我正确地添加了这个方法)。

然而,有些事情正在发生。有时我发现自己跳转到一个带有 nil self_cmd 的方法(通常不是正确的方法)。其他时候我会因为 EXC_BAD_ACCESS 而在不知名的地方崩溃。有想法吗? (我已经很长时间没有在汇编中做过任何事情了......)我正在 x86_64 上测试它。

I'm looking for a way to make an NSInvocation invoke a specific IMP. By default, it invokes the "lowest" IMP it can find (ie, the most-recently-overridden version), but I'm looking for a way to make it invoke an IMP from higher up in the inheritance chain. The IMP I want to invoke is determined dynamically, or else I'd be able to use the super keyword or something like that.

My thought was to use the -forwardInvocation: mechanism to capture a message (easy and already working) and then alter the IMP so it goes to a method that is neither the super implementation nor the furthest descendent's implementation. (hard)

The only thing I've found that comes remotely close is AspectObjectiveC, but that requires libffi, which makes it non-iOS compatible. Ideally I'd like this to be cross platform.

Any ideas?

disclaimer: i'm just experimenting


Trying out @bbum's idea of a trampoline function

So I think I've got things mostly set up; I've got the following trampoline that gets correctly added via class_addMethod(), and it does get entered:

id dd_trampolineFunction(id self, SEL _cmd, ...) {
    IMP imp = [self retrieveTheProperIMP];
    self = [self retrieveTheProperSelfObject];
    asm(
        "jmp %0\n"
        :
        : "r" (imp)
        );
    return nil; //to shut up the compiler
}

I've verified that both the proper self and the proper IMP are the right things prior to the JMP, and the _cmd parameter is also coming in properly. (in other words, I correctly added this method).

However, something is going on. I sometimes find myself jumping to a method (usually not the right one) with a nil self and _cmd. Other times I'll just crash in the middle of nowhere with an EXC_BAD_ACCESS. Ideas? (it's been a long time since I've done anything in assembly...) I'm testing this on x86_64.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

゛清羽墨安 2024-10-25 10:05:17

NSIncation 只是消息发送的对象表示。因此,它无法像普通消息发送那样调用特定的 IMP。为了让调用调用特定的 IMP,您要么需要编写一个自定义 NSInitation 类来执行 IMP 调用例程,要么必须编写一个实现该行为的蹦床,然后创建一个表示的调用发送给蹦床的消息(即您基本上不会使用 NSInitation 来做任何事情)。

NSInvocation is just an object representation of a message send. As such, it can't invoke a specific IMP any more than a normal message send could. In order to have an invocation call a specific IMP, you'd either need to write a custom NSInvocation class that goes through the IMP-calling routine or you'd have to write a trampoline that implements the behavior and then create an invocation that represents a message to the trampoline (i.e. you basically wouldn't be using NSInvocation for much of anything).

瞎闹 2024-10-25 10:05:17

事后很久添加,供参考:

您可以使用私有 API 来做到这一点。将此类别放在方便的地方:

@interface NSInvocation (naughty)
-(void)invokeUsingIMP:(IMP)imp;
@end

瞧,它完全符合您的期望。我从 Mike Ash 的一篇旧博客文章中挖出了这颗宝石。

像这样的私有 API 技巧非常适合研究或内部代码。只需记住将其从应用程序商店绑定的版本中删除即可。

Added long after the fact, for reference:

You can do it with private API. Put this category somewhere convenient:

@interface NSInvocation (naughty)
-(void)invokeUsingIMP:(IMP)imp;
@end

and voila, it does exactly what you'd expect. I dug up this gem from one of Mike Ash's old blog posts.

Private API tricks like this are great for research or in-house code. Just remember to excise it from your appstore-bound builds.

像极了他 2024-10-25 10:05:17

鉴于您已经有了 IMP,您只需要一种方法来对所述 IMP 进行非常原始的方法调用。如果您愿意使用类似 NSInitation 的解决方案,那么您也可以构建一个类似的代理类。

如果我遇到这种情况,我会创建一个简单的代理类,其中包含要调用的 IMP 和目标对象(您需要设置 self 参数)。然后,我会在汇编中编写一个蹦床函数,它接受第一个参数,假设它是代理类的实例,获取 self,将其填充到保存参数 0 的寄存器中,获取 IMP 并*将其作为尾调用进行 JMP。

有了 Trampoline,您就可以将该 Trampoline 添加为您想要转发到特定 IMP 的代理类上任何选择器的 IMP...

要实现像这样的任何类型的通用机制,关键是 避免任何与重写堆栈帧有关的事情。避免 C ABI。避免争论。

Given that you already have the IMP, you simply need a way to do a very raw forward of the method call to said IMP. And given that you are willing to use an NSInvocation like solution, then you could also build a similar proxy class.

If I were faced with this, I would create a simple proxying class that contained the IMP to be called and the target object (you'll need to set the self parameter). Then, I would write a trampoline function in assembly that takes the first argument, assumes it is an instance of the proxying class, grabs the self, stuffs it into the register holding argument 0, grabs the IMP and *JMPs to it as a tail call.

With trampoline in hand, you would then add that trampoline as an IMP for any selector on the proxying class that you want forwarded to a particular IMP....

To achieve any kind of generic mechanism like this, the key is to avoid anything having to do with rewriting the stack frame. Avoid the C ABI. Avoid moving arguments about.

天涯离梦残月幽梦 2024-10-25 10:05:17

一个未经测试的想法:

您可以使用 object_setClass() 强制选择您想要的 IMP 吗?那是…

- (void)forwardInvocation:(NSInvocation *)invocation {
    id target = [invocation target];
    Class targetClass = classWithTheImpIWant();
    Class originalClass = objc_setClass(target, targetClass);
    [invocation invoke];
    objc_setClass(target, originalClass);
}

An untested idea:

Could you use object_setClass() to force the selection of the IMP that you want? That is…

- (void)forwardInvocation:(NSInvocation *)invocation {
    id target = [invocation target];
    Class targetClass = classWithTheImpIWant();
    Class originalClass = objc_setClass(target, targetClass);
    [invocation invoke];
    objc_setClass(target, originalClass);
}
国际总奸 2024-10-25 10:05:17

我认为你最好的选择是使用 libffi。您是否在 https://github.com/landonf/libffi-ios 看到了 iOS 的移植?我还没有尝试过该端口,但我已在 Mac 上使用任意参数成功调用了 IMP。

看看 JSCocoa https://github.com/parmanoir/jscocoa 它包含帮助您准备的代码来自 Methodffi_cif 结构,它还包含应在 iOS 上编译的 libffi 版本。 (也没有测试过)

I think your best choice is to use libffi. Have you seen the port to iOS at https://github.com/landonf/libffi-ios? I haven't tried the port, but i have successfully invoked IMP with arbitrary arguments on the Mac.

Have a look at JSCocoa https://github.com/parmanoir/jscocoa it includes code to help you prepare a ffi_cif structure from a Method and it also contains a version of libffi that should compile on iOS. (Haven't tested either)

你与清晨阳光 2024-10-25 10:05:17

您可能应该看看我们如何在 https:// 中的对象实例上混合特定方法的实现。 github.com/tuenti/TMInstanceMethodSwizzler

基本上,您为类的所有对象混合方法,因此当调用它时,它会在字典中查找必须为目标对象调用的实现是什么,然后返回到如果没有找到,则使用原始实现。

您还可以使用私有 invokeWithImp: 方法,但如果您打算将应用程序提交到商店,则不鼓励这样做。

You should probably have a look at how we swizzle the implementation of a certain method on an instance of an object in https://github.com/tuenti/TMInstanceMethodSwizzler

Basically, you swizzle the method for all object of a class so when its called it look up in a dictionary whats is the implementation which has to be called for the target object, falling back to the original implementation if not found.

You can also use the private invokeWithImp: method, but this is discouraged if you intent to submit the app to the store.

有深☉意 2024-10-25 10:05:17

您可以使用新选择器下的 class_addMethod 将 IMP 添加到类中并调用该选择器。

但临时方法无法删除。

you could add the IMP to the class using class_addMethod under a new selector and invoke that selector.

the temporary method can't be removed though.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文