October 25, 2014

Whose symbol is this?

Once in a while I found myself in need of finding who declared certain function or method. With Apple added support for iOS frameworks, the need in being sure where particular piece of code is coming is somewhat high.

#import <dlfcn.h>

Function

Dl_info info;
if (dladdr((void *)NSStringFromClass, &info)) {
    printf("%s", info.dli_fname);
}
…/Frameworks/Foundation.framework/Foundation

Method

IMP imp = [view methodForSelector:@selector(drawRect:)];
Dl_info info;
if (dladdr((void *)imp, &info)) {
    printf("%s", info.dli_fname);
}
…/Frameworks/UIKit.framework/UIKit

Keep in mind that methodForSelector: resolves implementation that’d be actually called at runtime. If you want to trace down an original implementation (e.g. before swizzling), it all boils down to finding (keeping) the right IMP:

Swizzled methods

// after swizzling, our implementation can be reached through orignial selector
// and original implementation can be reached through our swzld_ selector
IMP swizzledIMP = [self methodForSelector:@selector(viewDidLoad)];
IMP originalIMP = [self methodForSelector:@selector(swzld_viewDidLoad)];
Dl_info info;
if (dladdr((void *)swizzledIMP, &info)) {
    printf("%s\n", info.dli_fname);
}
if (dladdr((void *)originalIMP, &info)) {
    printf("%s", info.dli_fname);
}
…/Test.app/Test
…/Frameworks/UIKit.framework/UIKit

Sadly you can not trace the origins of declarations, e.g. I couldn’t figure out how to find out who declared -[UIApplicationDelegate applicationDidFinishLaunching:]

Keep in mind

If you mistype selector when obtaining implementation, you’ll get a confusing …/usr/lib/libobjc.A.dylib as an originating binary.

This is due to the fact that methodForSelector: short-circuits to -[NSObject doesNotRecognizeSelector], which, as you probably guessed by now, is declared in Objective-C runtime.

Blocks

Finally, much more rare case of getting a pointer to a block and tracing its origin back to the binary. As turns out, it’s no different from a function:

void(^block)(NSUInteger) = ^(NSUInteger a){
    NSLog(@"%tu", a);
};

Dl_info info;
if (dladdr((__bridge void *)block, &info)) {
    printf("%s", info.dli_fname);
}
…/Test.app/Test

Bonus

As a bonus though, here is a snippet on how to print names of all the loaded frameworks at runtime without:

unsigned int imagesCount;
const char **bundles = objc_copyImageNames(&imagesCount);
for (unsigned int i = 0; i < imagesCount; ++i) {
    printf("%s", bundles[i]);
}
…/usr/lib/system/introspection/libdispatch.dylib
…/usr/lib/system/libxpc.dylib
…/usr/lib/system/libsystem_network.dylib
…/usr/lib/libobjc.A.dylib
…/System/Library/Frameworks/Accelerate.framework/Frameworks/vecLib.framework/libLinearAlgebra.dylib
…/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
…/System/Library/PrivateFrameworks/FontServices.framework/libFontParser.dylib
…/usr/lib/libextension.dylib
…/System/Library/Frameworks/CFNetwork.framework/CFNetwork
…/System/Library/Frameworks/MobileCoreServices.framework/MobileCoreServices
…/System/Library/Frameworks/Foundation.framework/Foundation
…/System/Library/Frameworks/JavaScriptCore.framework/JavaScriptCore
…/System/Library/PrivateFrameworks/AggregateDictionary.framework/AggregateDictionary

It returns a full list of names that can be used with dlfcn functions making it useful for further investigations.