preface

The other morning, as I was about to embark on the happiest thing in the world — typing — I received a message from the little sister of the test. At that moment, I had a bad forecast, open the message to see, sure enough, it is you, bug monster.

Problem description

Test sister said, this version, the entire App all share, suddenly can not share. When I received the message, I was confused. The App’s native sharing function component had been there from the beginning, and there had been n versions of the sharing function component without modification. How could it suddenly not be shared? Test little elder sister is not to download wrong version, oneself ran below development version debugging directly, HMMM, that is to test little elder sister to download wrong version…….. However, the development version is not shared. When you click the share wechat button, it will prompt “Please install the wechat client first”.

Next, it is the exciting part of the fight against small monsters.

The problem to find

Analyze the type of monsters, which species they belong to, and whether humans have reproductive isolation

First, according to the prompt, I searched for “Please install the wechat client first”. It was found that when I called UMSocialManager isInstall:] of Umeng to determine whether to install wechat, the interface of Umeng returned false. Therefore, the service judged that wechat was not installed, and a relevant prompt was displayed. I tried to determine whether to install wechat or not, and directly called the sharing interface of UmENG to share, but it could not be shared normally. Because it is related to the ALLIANCE SDK, I have done some troubleshooting.

  • Open the Log printing of the UmENG SDK and view the internal logs of the SDK as shown in the following figure

From the log, it can be seen that the initialization of each sub-platform of The Alliance failed. In the article about the problems related to the Alliance, two kinds of inspection schemes were also given, but the problems were not solved after trying.

  • Check the relevant version of the Alliance to see if the version caused the problem.

The SDK version of the problem is 6.9.6, and as of April 3, the latest SDK version on the official website of The Alliance is 6.9.8. Check the update document, the main solution is wechat for the required configuration of the Universal Link. It doesn’t feel like it’s going to matter, but just to try it out, UPDATE the SDK to the latest version 6.9.8. After testing, the problem still couldn’t be solved.

  • Roll back to the previous version and compare the code differences between the two versions.

After rolling back to the last available version, it is found that the sharing feature (SDK version 6.9.6) is working properly. Therefore, it is due to the relevant code added in the latest version that causes the sharing error of the UmENG SDK.

After checking the above three aspects, it was finally determined that the latest version of the code introduced, resulting in the failure of alliance sharing. Since the latest version did not change the code related to Alliances, we had to narrow down code access through dichotomy. Finally, it was determined that an internal performance monitoring library introduced in the latest version was responsible for the failure of alliances sharing. After locating the functions in the performance monitoring library in detail, it was found that the performance monitoring library counted all the time-consuming code of the +load method when the App was started, which led to the failure of alliance sharing.

Returning to find

Then, qiao Mimi’s search small monster’s weakness, the pursuit of a lead to victory. So ordinary people will think you are very handsome, after all handsome is a lifetime thing.

After narrowing down the scope of code modification, it was finally determined that it was related to the time consuming of the statistic +load method, which led to the invalidation of alliance sharing. At this point, I’m still very confused, because these two parts of the code, basically have nothing to do with each other, why would they affect each other. I have asked related colleagues who were responsible for this part of the code before, and learned that the implementation of counting the time of +load method is based on the time of calculating +load method. Detailed implementation can be linked, the general implementation idea is as follows:

Hook all class +load methods before all dynamic libraries are loaded, and insert relevant time statistics before and after the +hookLoad method. Finally, all +load methods are added up to get the total time.

Since we hook all the +load methods in the project, we can only guess whether there is a class within the SDK that implements the +load method and does something in the method. . In accordance with this idea, I did some operations.

  • Check to see if the Amun SDK is implemented+loadMethod, which class implements it+loadMethods.

In the related code of hook +load method, hook classes are printed out. Since the class names in the Alliance SDK are prefixed with UM, it is easy to recognize. Finally, we found that the following related classes implement the +load method

  • Is it due to the Hook alliance+loadMethods that lead to the original alliance internal+loadThe method was not executed, resulting in a share exception

How to determine if the amU’s original +load method is being implemented? Take the [UMSocialManager Load] method as an example, add a related Symbolic Breakpoint in Xcode, and then re-run to see the execution of the Breakpoint, the result is as shown in the figure below:

You can see that the [UMSocialManager Load] breakpoint can be broken, and that the associated assembly code has been executed. And from the call stack on the left, you can see that the [UMSocialManager Load] method is called by our hookLoad method. Therefore, it can be inferred that the existing +load method in the alliance is implemented (I also checked the other classes one by one).

  • Is it a Hook ally+loadMethod leads to original+loadThe execution logic of the relevant code in the method is different, resulting in the failure of alliance sharing.

In the previous step to verify that the amun +load method has been executed, I have already briefly scanned the implementation of each method, which involves not many related assembly commands, so it is easy to read. Debug the breakpoint step by step, compare the logic executed by the +load method before and after hook, and finally find the inconsistencies. As shown in the figure below:

From the two pictures above, we can easily see the difference. So why are the values of register X19 inconsistent? What do the values in registers X0 and X19 represent? We analyze <+40> CMP x0, x19 before the instruction, can easily conclude:

  1. Register X19 stores aselfIn the<+16>: mov x19, x0When the assignment
  2. Register X0 contains[UMSocialHandler class]Method, the associated assembly instruction is< 24 > + < + 32 > < + > 36

So you can see that the logic to execute here is self == [UMSocialHandler class], so, Why is the value that self points to before hook is UMSocialQQHandler and the value that hook points to after hook is actually UMSocialHandler. By looking at the call stack on the left, we can see that the [UMSocialHandler Load] is not executed by the default system call behavior, but the subclass UMSocialQQHandler is executed by calling [Super Load]. As shown in the figure below:

Since the call is made through super, self points to UMSocialQQHandler instead of UMSocialHandler in the + Load method of the superclass. See iOS: About super keyword (runtime analysis) for details. When hook +load, self points to a UMSocialHandler. This is why self points to a UMSocialHandler.


static void swizzleLoadMethod(Class cls, Method method, LMLoadInfo *info) {
retry:
    do {
        SEL hookSel = getRandomLoadSelector();
        Class metaCls = object_getClass(cls);
        IMP hookImp = imp_implementationWithBlock(^(){
            info->_start = CFAbsoluteTimeGetCurrent(a); ((void (*)(Class, SEL))objc_msgSend)(cls, hookSel);
            info->_end = CFAbsoluteTimeGetCurrent(a);if(! --LMAllLoadNumber) printLoadInfoWappers(); });BOOL didAddMethod = class_addMethod(metaCls, hookSel, hookImp, method_getTypeEncoding(method));
        if(! didAddMethod)goto retry;
        
        info->_nSEL = hookSel;
        Method hookMethod = class_getInstanceMethod(metaCls, hookSel);
        method_exchangeImplementations(method, hookMethod);
    } while(0);
}
Copy the code

((void (*)(Class, SEL))objc_msgSend)(CLS, hookSel); You can see that the first argument to objc_msgSend, which is the value of self that we eventually get in the +load method, is the class that was written to the hook. For example, when hook UMSocialHandler’s +load method, CLS refers to UMSocialHandler, and CLS is assigned to the first argument of objc_msgSend, So self in the UMSocialHandler is always pointing to the UMSocialHandler. This is the root cause of sharing failure. Since self always points to the UMSocialHandler, part of the logic in the UMSocialHandler’s +load is never executed, resulting in a failure to initialize the sharing platform. This is the error message associated with ally log printing.

The solution

Finally, I took out my treasured sword for many years, and gently stabbed. Matter of flick clothes go, do not take a cloud.

Find the problem and the solution is easy to come up with. The root cause of the problem is self pointing to an error due to hook. Therefore, we can solve this problem by simply assigning the original self back, as follows:

static void swizzleLoadMethod(Class cls, Method method, LMLoadInfo *info) {
retry:
    do {
        SEL hookSel = getRandomLoadSelector();
        Class metaCls = object_getClass(cls);
        IMP hookImp = imp_implementationWithBlock(^(id originSelf){
            info->_start = CFAbsoluteTimeGetCurrent(a); ((void (*)(Class, SEL))objc_msgSend)(originSelf, hookSel);
            info->_end = CFAbsoluteTimeGetCurrent(a);if(! --LMAllLoadNumber) printLoadInfoWappers()`; });BOOL didAddMethod = class_addMethod(metaCls, hookSel, hookImp, method_getTypeEncoding(method));
        if(! didAddMethod)goto retry;
        
        info->_nSEL = hookSel;
        Method hookMethod = class_getInstanceMethod(metaCls, hookSel);
        method_exchangeImplementations(method, hookMethod);
    } while(0);
}
Copy the code

As you can see, the change is really very small, just take the first argument of hookBlock and assign it to the first argument of objc_msgSend, and it works perfectly.

The resources

Calculate the time of the +load method. IOS: About super keywords (Using Runtime analysis)