0

I have a project with a lot of classes. I want to log (e.g. to stderr) invocations of each selector in runtime.
My main requirement is not to change the existing code, so I can't just log the function's params at the start of each call.

If some method is invoked during program execution, e.g.

@implementation Class1

// ...

- (int)someFunc:(Class2*) a andClass3:(Class3*)b
{

}

// ...

@end 

I want to replace it with something like:

- (int)someFuncWrapper:(Class2*) a andClass3:(Class3*)b
{
     NSLog(@"- (int)someFuncWrapper:a andClass3:b <= a=%@, ab=%@", a, b);
     return [someFunc: a andClass3:b];
}

Is it possible?

I've read of method swizzling, KVO, forward messaging.

My current approach with method swizzling causes infinite recursion:

- (int)funcToSwizzle:(int)a andB:(int)b
{
    int r = a+b;
    NSLog(@"funcToSwizzle: %d", r);
    return r;
}

- (void)doSimpleSwizzling
{
    NSLog(@"r1 = %d", [self funcToSwizzle:10 andB:20]);

    Class curClass = NSClassFromString(@"HPTracer");

    unsigned int methodCount = 0;
    Method *methods = class_copyMethodList( curClass, &methodCount);

    for (int i=0; i<methodCount; ++i)
    {
        SEL originalSelector = method_getName(methods[i]);
        if ( strcmp("funcToSwizzle:andB:", sel_getName(originalSelector)) == 0 )
        {
            Method m1 = class_getInstanceMethod(curClass, originalSelector);

            id block3 = ^(id self, int a, int b) {
                NSLog(@"My block: %d", a*b);
                // get current implementation of "funcToSwizzle".
                // copy it. store that "IMP"/"void *" etc
                return [self funcToSwizzle:a andB:b];
            };

            IMP imp3 = imp_implementationWithBlock(block3);
            method_setImplementation(m1, imp3);
        }
    }

    NSLog(@"r2 = %d", [self funcToSwizzle:10 andB:20]);
}

And I'm afraid it's impossible to generate a block3 or some method in runtime. There's NSSelectorFromString but no ImplementationFromString.

UPD
I looked at DTrace util, it seems very powerful, but doesn't fit my needs. It requires disabling SIP on Mac OS, and is either impossible on iOS or possible on jailbreaked device.

What I need from methods interceptions is creating a stable custom "framework" for both Debug and production build modes.

olha
  • 2,132
  • 1
  • 18
  • 39
  • 1
    Possible duplicate of [Track all ObjC method calls?](https://stackoverflow.com/questions/7223555/track-all-objc-method-calls) – Willeke Apr 02 '19 at 09:31
  • @Willeke Thanks, it's possible! I'll check if that DTrace can get values of parameters. If yes, I'll close this question. – olha Apr 02 '19 at 09:42
  • 1
    @OlhaPavliuk There's some techniques you could use to figure the parameters, but basically all of them involve hand-rolling assembly. Fact is, due to how objc uses c-style calling conventions, there's just no way for you to know (at the call site) with 100% certainty the ABI of the invocation (especially if you mix complex types, or C++ objects in there too, which will certainly happen at some point in the stack). You can make best-effort attempts, and you may even consider hot-patching objc_msgSend, but this seems like an attempt in futility. – Richard J. Ross III Apr 10 '19 at 21:18
  • 1
    Your 'current approach' is actually fairly close to correct (albeit without respect to the correct calling convention of the function), you should instead of invoking the function again (with `[self foo:arg1 bar:arg2]`, get the old `IMP`, cast it to the right type, and invoke that. – Richard J. Ross III Apr 10 '19 at 21:20
  • 1
    If you want to try and solve the argument types without using assembly, you could look at class swizzling & NSInvocation, I did an interesting approach here a long time ago, that worked very well: https://github.com/parse-community/Parse-SDK-iOS-OSX/blob/master/Parse/Parse/Internal/Object/Subclassing/PFObjectSubclassingController.m There's a lot of drawbacks to using imp_implementationWithBlock, off the top of my head, there's actually a limit (something like 32k) of how many you can actually create on iOS (true up to at least iOS 7). – Richard J. Ross III Apr 10 '19 at 21:25
  • @RichardJ.RossIII Thanks a lot!!! I'll look at your repo, but it'll take few hours to understand your approach, so I comment later. "get the old IMP, cast it to the right type, and invoke that" - thanks, I'll try that. – olha Apr 11 '19 at 05:55
  • @OlhaPavliuk no problem. Mention me again in this thread if you need more pointers, this stuff can be pretty hard to grok. – Richard J. Ross III Apr 11 '19 at 22:58

0 Answers0