In the previous article, we explored a process dynamic method resolution in which message sending could not find a method. This article explores dynamic method resolution in which THE IMP’s next step, message forwarding, could not be found.
Unlike the previous process, the source code for message forwarding is not in the LibobJC library, but in the CFFoundtion framework. The content of message forwarding is not found in the open source code of CFFoundtion box, so we have to find other ways to explore this process.
Through instrumentObjcMessageSends print a message log
The origin of the instrumentObjcMessageSends
LookUpImpOrForward –> log_and_fill_cache –> LogMessageSend, we find instrumentObjcMessageSends logMessageSend method source code below:
void instrumentObjcMessageSends(BOOL flag)
{
bool enable = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get some traces
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if(objcMsgLogFD ! =- 1)
fsync (objcMsgLogFD);
// Specify whether to print the message log
objcMsgLogEnabled = enable;
}
Copy the code
The path to the log file can be seen in the logMessageSend method, which is in the/TMP /msgSends directory:
bool logMessageSend(bool isClassMethod,
const char *objectsClass,
const char *implementingClass,
SEL selector)
{
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (- 1))
{
/// Log file print path
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
if (objcMsgLogFD < 0) {
// no log file - disable logging
objcMsgLogEnabled = false;
objcMsgLogFD = - 1;
return true; }}// Make the log entry
snprintf(buf, sizeof(buf), "%c %s %s %s\n",
isClassMethod ? '+' : The '-',
objectsClass,
implementingClass,
sel_getName(selector));
objcMsgLogLock.lock(a);write (objcMsgLogFD, buf, strlen(buf));
objcMsgLogLock.unlock(a);// Tell caller to not cache the method
return false;
}
Copy the code
The use of instrumentObjcMessageSends
- in
main.m
In the file, approvedextern
The statementinstrumentObjcMessageSends
Methods. - Log is turned on before calling the method and closed after calling the method. The code is as follows:
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
instrumentObjcMessageSends(YES);
JSPerson *person = [JSPerson alloc];
[person saySomething];
instrumentObjcMessageSends(NO);
}
return 0;
}
Copy the code
- Run the code and view the log file
The log file clearly shows which methods were executed:
resolveInstanceMethod
Methods, i.e.,Dynamic method resolution
forwardingTargetForSelector
Methods, i.e.,Quick message forwarding
methodSignatureForSelector
Methods, i.e.,Slow message forwarding
resolveInstanceMethod
Methods. The second timeDynamic method resolution
- The last execution
doesNotRecognizeSelector
, throw an exception.
This way we can clearly see the method invocation process, let’s verify it in another way.
Exploration of decompilation methods
Hopper is a tool that helps us statically analyze executables. There are two things we need to explore with tools:
-
After decompilation, the code is searched for keywords
Let’s run the following code. Note that the sayNB method is not implemented
int main(int argc, const char * argv[]) { @autoreleasepool { JSPerson *person = [JSPerson alloc]; [person sayNB]; } return 0; } Copy the code
After running, it will crash directly. We use the BT command to view the call stack information
You can see that the starting point for the method call here is CoreFoundation_forwarding_prep_0__ + 120 ‘, so the keyword we search for decompiling code is forwarding_prep_0
-
How do I get the executable we need to decompile
In step 1, we know that __forwarding_prep_0___ in the CoreFoundation framework, we use the LLDB image list command to find the location of the CoreFoundation executable file.
Use the directory in the figure above to find the CoreFoundation file.
-
After The first two necessary conditions are clear, we open Hooper software and choose Try The Demo(mainly because The software is too expensive, The local rich please directly buy The genuine version), and then drag The executable file of The previous step into Hooper for decompile, and select x86(64-bit).
The diagram of the toolbar we used for Hooper software is as follows:
-
Search for __forwarding_prep_0___ in the search box location, select the pseudo-code view, and find that it matches the stack information we printed, calling the ____forwarding___ method
int ___forwarding_prep_0___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) { var_20 = rax; var_30 = zero_extend_64(xmm7); var_40 = zero_extend_64(xmm6); var_50 = zero_extend_64(xmm5); var_60 = zero_extend_64(xmm4); var_70 = zero_extend_64(xmm3); var_80 = zero_extend_64(xmm2); var_90 = zero_extend_64(xmm1); var_A0 = zero_extend_64(xmm0); var_A8 = arg5; var_B0 = arg4; var_B8 = arg3; var_C0 = arg2; var_C8 = arg1; /// is the same as the call stack we printed rax = ____forwarding___(&var_D0, 0x0); if(rax ! =0x0) { rax = *rax; } else { rax = objc_msgSend(var_D0, var_C8); } return rax; } Copy the code
-
Continue to see ____forwarding___ method of pseudo code, first is determine whether implement forwardingTargetForSelector fast forwarding method, if not to jump to forward loc_64a67 go slow process
- goto
loc_64a67
To viewSlowly forward
- If no implementation, jump, direct error.
We also verify the process of message forwarding by using decompilation.
Message forwarding instance
In the previous example, let’s add the message forwarding section.
Quick message forwarding
- Let’s define one
JSProxy
Class, it implementssayNB
methods
@interface JSPerson : NSObject
- (void)sayNB;
@end
@implementation JSProxy
- (void)sayNB{
NSLog(@"%@ - %s",self , __func__);
}
@end
Copy the code
- in
JSPerson
Class to addforwardingTargetForSelector
methods
#import "JSProxy.h"
@implementation JSPerson
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"forwardingTargetForSelector :%@-%@",self,NSStringFromSelector(aSelector));
return [JSProxy alloc];
}
@end
Copy the code
- Run the code and the program will run, print
log
As follows:
202107 -- 04 22:24:49.089260+0800 ResolveMethodTest[7682:442095] resolveInstanceMethod :JSPerson-sayNB
202107 -- 04 22:24:49.090863+0800 ResolveMethodTest[7682:442095] resolveInstanceMethod :JSPerson-encodeWithOSLogCoder:options:maxLength:
202107 -- 04 22:24:49.091431+0800 ResolveMethodTest[7682:442095] forwardingTargetForSelector :<JSPerson: 0x600000010160>-sayNB
202107 -- 04 22:24:49.091571+0800 ResolveMethodTest[7682:442095] <JSProxy: 0x6000000080d0> - -[JSProxy sayNB]
Copy the code
Slow message forwarding
On the basis of the above example, we implement methodSignatureForSelector in JSPerson class and forwardInvocation forwardingTargetForSelector method returns nil marks not to fast forward.
@implementation JSPerson
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"forwardingTargetForSelector :%@-%@",self,NSStringFromSelector(aSelector));
return nil;
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"methodSignatureForSelector :%@-%@",self,NSStringFromSelector(aSelector));
if (aSelector == @selector(sayNB)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%@ - %s",self , __func__);
}
@end
Copy the code
We run the code and find that it works properly. The print result is as follows:
202107 -- 04 22:34:32.163137+0800 ResolveMethodTest[7780:447311] resolveInstanceMethod :JSPerson-sayNB
202107 -- 04 22:34:32.163803+0800 ResolveMethodTest[7780:447311] resolveInstanceMethod :JSPerson-encodeWithOSLogCoder:options:maxLength:
202107 -- 04 22:34:32.164673+0800 ResolveMethodTest[7780:447311] forwardingTargetForSelector :<JSPerson: 0x600000008070>-sayNB
202107 -- 04 22:34:32.165289+0800 ResolveMethodTest[7780:447311] resolveInstanceMethod :JSPerson-encodeWithOSLogCoder:options:maxLength:
202107 -- 04 22:34:32.165436+0800 ResolveMethodTest[7780:447311] methodSignatureForSelector :<JSPerson: 0x600000008070>-sayNB
202107 -- 04 22:34:32.165551+0800 ResolveMethodTest[7780:447311] resolveInstanceMethod :JSPerson-_forwardStackInvocation:
202107 -- 04 22:34:32.165634+0800 ResolveMethodTest[7780:447311] resolveInstanceMethod :JSPerson-encodeWithOSLogCoder:options:maxLength:
202107 -- 04 22:34:32.165986+0800 ResolveMethodTest[7780:447311] <JSPerson: 0x600000008070> - -[JSPerson forwardInvocation:]
Copy the code
Some of you may have added the forwardInvocation method here because you can check the Official Apple documentation to see why
conclusion
At this point we have explored the entire process of objc_msgSend.
- Assembly code quickly finds the cache
loopUpImpForward
Slowly recursively find a list of methods for the class and its parent class (including caching)- Dynamic methods parse processing messages
- Fast message forwarding process
- Slow message forwarding process