I. Introduction of message forwarding process
What if the method is still not found in the dynamic method resolution process? And then it returns nil or _objc_msgForward_impcache!
So is there no room to save it?
We can print by instrumentObjcMessageSends objc related log in at the bottom of the:
@interface HPerson : NSObject
- (void)sayNO;
@end
@implementation HPerson
@end
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
HPerson * p = [HPerson alloc];
instrumentObjcMessageSends(YES);
[p sayNO];
instrumentObjcMessageSends(NO);
}
return 0;
}
Copy the code
In the search instrumentObjcMessageSends objc source code:
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);
objcMsgLogEnabled = enable;
}
Copy the code
ObjcMsgLogEnabled (); objcMsgLogEnabled ();
/***********************************************************************
* log_and_fill_cache
* Log this method call. If the logger permits it, fill the method cache.
* cls is the method whose cache should be filled.
* implementer is the class that owns the implementation in question.
**********************************************************************/
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if(! cacheIt)return;
}
#endif
cls->cache.insert(sel, imp, receiver);
}
Copy the code
Then go to logMessageSend:
bool logMessageSend(bool isClassMethod,
const char *objectsClass,
const char *implementingClass,
SEL selector)
{
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (- 1))
{
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();
write (objcMsgLogFD, buf, strlen(buf));
objcMsgLogLock.unlock();
// Tell caller to not cache the method
return false;
}
Copy the code
/ TMP = / TMP = / TMP = / TMP = / TMP = / TMP
We found resolveInstanceMethod is a process of dynamic method resolution, but after forwardingTargetForSelector?
2. Message forwarding process
1. Fast forwarding process
We can command + shift + 0 to open the development document:
You can tell that this method is a redirection process!
Rewrite forwardingTargetForSelector method first in the class, because we call object method, so here is rewritten – method and running:
Found really into forwardingTargetForSelector way!
Then we can pass this method to another class for execution!
Create an HClass class that implements the sayNO method:
This completes message forwarding and is not as bloated as dynamic method resolutions!
That’s the fast forwarding process!
2. Slow forwarding process
What if the HClass class doesn’t implement the sayNO method?
So will enter methodSignatureForSelector method, namely forward slow process!
Once again, open the development document for review:
This is a return method signature process!
Rewrite methodSignatureForSelector method first in the class, because we call object method, so here is rewritten – method and running:
Found really into methodSignatureForSelector way!
The development documentation knows that this method needs to be accompanied by NSInvocation and return the appropriate method signature, which is NSMethodSignature:
Successful execution, but no implementation!
In iOS, there is the concept of transaction, which can be executed or not executed, so the method is saved in the signature and can be extracted when necessary:
Or follow the development document case:
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL aSelector = [invocation selector];
if ([friend respondsToSelector:aSelector])
[invocation invokeWithTarget:friend];
else
[super forwardInvocation:invocation];
}
Copy the code
3. Disassemble message forwarding process
1, LLDB view stack
Although the message forwarding process is understood, we do not see the process of calling in the source code, so how is it called?
First use LLDB to view the stack with BT:
You can see that three methods were executed before the method reported an error:
__forwarding_prep_0___
–> ___forwarding___
–> doesNotRecognizeSelector
And all three methods belong to the CoreFoundation dynamic library!
We can download it from Apple’s open source website, but there is no corresponding method found in the CoreFoundation dynamic library, indicating that Apple is not fully open source!
Then we have to reverse it!
2. Reverse with Hopper
To reverse the first need to have executable files!
Use the emulator to view the image list in LLDB:
Then pull the CoreFoundation into Hopper, open pseudo-code mode, and search for the __forwarding_prep_0___ function:
Just as we saw the process in BT, we then enter the ___forwarding___ function!
3, have forwardingTargetForSelector method
Enter the ___forwarding___ function:
To determine whether have forwardingTargetForSelector method.
If forwardingTargetForSelector methods, namely into the fast forwarding process, call forwardingTargetForSelector method.
Then determine the return value:
If the return value is null or the object, with no forwardingTargetForSelector method, into the loc_115baf!
If there is a return value or it is different from the current object, the result is returned after processing!
3, have methodSignatureForSelector method
If there is no forwardingTargetForSelector way!
Enter loc_115BAf:
1: Check whether the object is a zombie. If no, continue. If yes, jump to loc_115F34, that is, 14.
2: determine whether have methodSignatureForSelector: method, have continued to, don’t jump to loc_115f4a, namely, 13.
3: perform methodSignatureForSelector: method, namely the slow forward process, and the return value, continued to have value, the value is empty, jump to loc_115fc5, namely 10.
4: Check whether there is _forwardStackInvocation: Method, if yes, continue, if not, go to loc_115d65, that is 7.
5: Execute the _forwardStackInvocation: method.
6: Switch to loc_115EF5.
7: There is no _forwardStackInvocation, see if there is a forwardStackInvocation, continue if there is no invocation, go to loc_115F92, 9.
8: Execute the forwardInvocation: method, and go to loc_115DD2, same as the _forwardStackInvocation: Method.
9: No forwardInvocation: Method, print the error and continue.
10-12: determine whether have doesNotRecognizeSelector: method, and perform, this is the final can not find a method of error!
13: Print error and jump to loc_115FBE, i.e. 10.
14: Print error and jump to loc_115f4a, i.e. 13
You can see whether it’s the _forwardStackInvocation or the forwardStackInvocation, it’s always going to loc_115EF5:
loc_115ef5:
if (**___stack_chk_guard == **___stack_chk_guard) {
rax = r15;
}
else {
rax = __stack_chk_fail();
}
return rax;
Copy the code
That is, return the processed result directly.
All that do not find a corresponding method will eventually implement the doesNotRecognizeSelector method:
// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("+[%s %s]: unrecognized selector sent to instance %p",
class_getName(self), sel_getName(sel), self);
}
// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("-[%s %s]: unrecognized selector sent to instance %p",
object_getClassName(self), sel_getName(sel), self);
}
Copy the code
The method was not found error!
In the disassembly process, you can see that the _forwardStackInvocation method is invoked, which is not exposed, but we can also override it to see how it works:
Indeed, it’s the same flow we saw, when there’s the _forwardStackInvocation, there’s no forwardStackInvocation!
4. Summary of assembly process
4. Why the dynamic method resolution is called twice
Before the dynamic method resolution, we found that the dynamic method resolution is called twice, why is this?
Put a breakpoint in the object dynamic method resolution in the objc source code, bt view the stack:
Found that the second come in because in CoreFoundation methodSignatureForSelector repository method __methodDescriptionForSelector method called objc class_getInstanceMeth repository Od way!
Let’s look at objc methodSignatureForSelector method in the source code:
// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("+[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}
// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("-[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}
Copy the code
No CoreFoundation library is available!
Note that the real source code is in the CoreFoundation library, to disassemble the view:
Found that there are __methodDescriptionForSelector method, further follow up:
Class_getInstanceMethod: class_getInstanceMethod: class_getInstanceMethod
/*********************************************************************** * class_getInstanceMethod. Return the instance method for the * specified class and selector. **********************************************************************/
Method class_getInstanceMethod(Class cls, SEL sel)
{
if(! cls || ! sel)return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
Copy the code
Find that lookUpImpOrForward is indeed called!
So the dynamic method resolution is called twice!