3

I need to replace some methods’ implementations of specific Objective-C classes. A set of functions from objc/runtime library is capable of doing that. To simplify the issue I just write a simplest sample code as following:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface MyClass : NSObject
- (void) func;
@end

@implementation MyClass

- (void) func {
    NSLog(@"Hello from MyClass!");
}

@end

//Original implementation of the method
static IMP gOriginalFunc = nil;

//Hook function that will take place of the original method
void HookFunc(id self, SEL _cmd)
{
    NSLog(@"[MyClass func] is hooked!");
    gOriginalFunc(self, _cmd);//EXC_BAD_ACCESS occurs here when ARC is enabled!!
}

int main(int argc, char *argv[])
{
    Class clsMyClass = objc_getClass("MyClass");
    // Restore the original method implementation:
    gOriginalFunc = class_getMethodImplementation(clsMyClass, @selector(func));
    Method mtdFunc = class_getInstanceMethod(clsMyClass, @selector(func));
    // Replace implementaion of the method of my own:
    class_replaceMethod(clsMyClass, @selector(func), IMP(HookFunc), method_getTypeEncoding(mtdFunc));
    objc_registerClassPair(clsMyClass);

    MyClass* obj = [[MyClass alloc] init];
    [obj func];

    return 0;
}

I just replace the original implementation of [MyClass func]. When compiler flag -fno-objc-arc is set, this code works just fine:

2014-12-18 11:59:17.524 Hooker[749:72783] [MyClass func] is hooked!
2014-12-18 11:59:20.361 Hooker[749:72783] Hello from MyClass!

But problem occurs when ARC is enabled(by setting compiler flag as -fobjc-arc). A EXC_BAD_ACCESS signal is thrown while invoking gOriginalFunc(as comments say). I don’t know the reason, could anyone tell?

pkamb
  • 33,281
  • 23
  • 160
  • 191
godspeed1024
  • 159
  • 1
  • 13

1 Answers1

1

Your function prototype is inaccurate, and the compiler sets up the call incorrectly. With ARC, that includes reference count operations. Either is undefined behavior.

To correct the program, inform the compiler of the actual function signature so it doesn't screw up the call:

typedef void (*HookFunc_Signature)(id, SEL); // << != IMP's signature
static HookFunc_Signature gOriginalFunc = nil;

...later:

gOriginalFunc = (HookFunc_Signature)class_getMethodImplementation(clsMyClass, @selector(func));
justin
  • 104,054
  • 14
  • 179
  • 226
  • Thanks a lot! It helps much.Though I'm not quite clear about why the return type matters. I found this problem occurs on project with target SDK version earlier than 7.0, because IMP was defined as: typedef id (*IMP)(id, SEL). It only differs in return type from your HookFunc_Signature. – godspeed1024 Dec 18 '14 at 14:32
  • @user1224028 it depends on the ABI and ARC (which can introduce objc variances). the compiler needs to know how to set up the function call, otherwise it will read and/or write memory it should not, and possibly interpret it differently. ARC adds complexity because the call can also result in retain/release calls introduced by the compiler. as an example, the compiler may be telling the runtime to perform a reference count operation where the return value is stored (e.g. a register value). with a void function, that will be a garbage value (=undefined behavior to treat as an object). – justin Dec 19 '14 at 05:28
  • I suggest you read https://blog.newrelic.com/2014/04/16/right-way-to-swizzle/ (and especially the second footnote). It magically fixed this EXC_BAD_ACCESS for me and I could understand why! Basically, the compiler (and ARC) tends to retain the result of swizzled methods, which causes a crash when these methods do not return an NSObject instance. Properly casting the result of these functions prevents the compiler from retaining too much – Daladim Jul 12 '16 at 23:19