IOS internal power series of articles

The basic knowledge of Crash analysis is not much, you can refer to some articles written in the original

[iOS internal functions] Crash analysis model

In-depth analysis of the memory layout of Crash call stack

[iOS internal functions] ARM black magic – stack frame on and off the stack

Use Hopper to locate difficult problems

【iOS internal work 】ARM assembly actual combat, parsing iOS14 UICollectionView dead loop problem

I. Overview of the problem

Apple updates iOS every year and may make logical changes to the library. We have some code in our own projects that you haven’t touched in years, but when the system is upgraded, it crashes in a weird way. In a case where the iOS13.3 upgrade caused a method signature in the project to cause an array overrun inside the NSInvocation. Because it could not be perfectly reproduced, it was finally repaired after many hypotheses and experiments.

There are two difficulties with this kind of problem. The first difficulty is that they are tasks evoked by the system, which are difficult to reproduce directly. We cannot observe the context information through the runtime. The second difficulty is that the stack of Crash is all in the system method, so we cannot directly see the logic of the system method, and there will be blind areas in the derivation process.

Second, fundamental analysis

Characteristics analysis

Since September this year, there have been online NSInvocation crashes. The Crash will only occur on devices running iOS13.3 or higher.

Crash call stack analysis

Crash exception description:

'NSInvalidArgumentException', reason: '-[NSInvocation getArgument:atIndex:]: index (0) out of bounds [-1, -1]'
Copy the code

Crash call stack can be simplified to six important methods:

1 _GSEventRunModal (main runloop wake-up) 2 __NSFireTimer 3 _CF_forwarding_prep_0 4 +[NSInvocation _invocationWithMethodSignature:frame:] 5 _NSIGetArgumentAtIndex 6 _objc_exception_throwCopy the code

3. Analysis of doubts

  • Suspect 1: Why does it go to message forwarding?

The first is that the method is not implemented. Checked all the timer codes in the project, and found none. Second, the new system has some logic that directly calls the message forwarding function of __NSFireTimer target. It’s probably the second one.” _CF_forwarding_prep_0 “rep_0” u.

  • Suspicion 2: Why is the Invocation out of bounds?

According to the exception log, the array was out of bounds when the 0th parameter in the invocation parameter list was picked up. The NSInvocation has an array arguments that stores all the incoming arguments for the method. According to abnormal description, “methodSignatureForSelector” method will be called to _NSIGetArgumentAtIndex after execution, then took out the first parameter to the arguments. The first parameter is the self, the arguments of drawing parameters directly crossing the line.

Holmes observes unusual details in his investigations. Suspect analysis is all about finding traces of the killer, details that are different than usual.

Four, put forward the hypothesis, simulate the scene

In the above analysis of Doubt 1, the conclusion is that there are two situations that will lead to message forwarding. The specific reason is not important, what is important is that we should restore the scene as far as possible, let the timer perform the task after the message forward. Therefore, we can actively trigger the message forwarding mechanism of the timer target to simulate the Crash scene.

The Demo simulation

To verify this quickly, I created a Demo project that started a timer and called an unimplemented method. Neither the simulator nor the real machine can be reproduced.

Source item simulation

It may be that the environment is not the same as the source engineering simulation test. Emulator running cannot reproduce. According to Crash features, I found an iPhone Xʀ iOS14.2, finally reappeared!!

Therefore, the conclusion is that there is a key logic in the engineering environment, which directly triggers Crash.

Think outside the box in simulation, just like in football we don’t have to pass the ball from the back. If the striker is clearly open, a long ball directly to him is more efficient.

5. Runtime analysis

Looking for returning

After a successful replay, runtime analysis can be performed along the call stack. Add a symbol breakpoint methodSignatureForSelector “, a breakpoint in a NSTimer Category, this Category implements “methodSignatureForSelector” method, the code is as follows.

/ / return the method signature - (NSMethodSignature *) methodSignatureForSelector: (SEL) the selector {NSMethodSignature * signature = [self. The target methodSignatureForSelector:selector]; if (! signature) { signature = [NSMethodSignature signatureWithObjCTypes:"v"]; } return signature; }Copy the code

The implementation logic is divided into two steps. The first step is to sign the method in target and return it if it exists. Second, if it does not exist, target does not implement the method and returns an empty method signature.

In our simulated scenario, the timer calls an unimplemented method, so it goes to the second step and returns the empty method signature.

Empty method signature returned is suspect!!

To fix the problem

Change the method signature to “v@” and run it again. The miracle time has arrived, the run result no longer crashes.

[NSMethodSignature signatureWithObjCTypes:"v@:"];
Copy the code

Let’s review the basics of OC. OC message sending takes two fixed arguments, self and selector. The method signature is self with the symbol ‘@’, and selector with the symbol ‘:’. The first two arguments in the NSInvocation argument are self and selector.

objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
Copy the code

While the empty method signature “v” does not describe these two parameters, a simplest OC method should at least be written as “v @: “. Therefore, we come to the conclusion. The invocation will be out of bounds if the method signature is missing the parameter symbol. That’s not easy to understand,

Logical rigor is important in debugging analysis. Cause and effect are sometimes hard to understand, but as long as the derivation is logical, it is true.

Six, summarized

To summarize the thinking of the investigation:

  • The first step is basic analysis. Collect valuable information from Crash logs.
  • The second step, doubt analysis. Analyze the information collected to find anything suspicious.
  • The third step is to simulate the scene. This stage requires constant hypothesis and verification to find a breakthrough.
  • Step 4, runtime analysis. Step by step, find the root cause and fix the solution.