background

IOS14 was officially released in early September, and the online version of the new Crash. There was a Crash,UICollectionView refresh logic loop, blocked the main thread.

Yang Difference Yang error, the “misunderstanding” of two Programmers in China and America caused the Crash.

App has a page with a custom XXCollectionView. The XXCollectionView is nested in the Cell, and the person writing the code is lazy and sets the delegate as himself. The Apple engineers also don’t talk about discipline, forwarding methods to a delegate that are not declared in the protocol (UICollectionViewDelegate). There is no spirit of contract.

Most of the Apple frameworks are not open source and have strange internal logic that we can only analyze by reading assembly instructions. At the beginning of the analysis of the source code, strange logic makes me doubt life, and finally through debugging assembly instructions, to understand the specific reason.

This case is quite classic. In the analysis process, oc, LLDB debugging, ARM and other common knowledge are used, as well as induction, logical reasoning, reverse thinking, abstract thinking and other thinking methods.

I summarized the analysis process in detail and communicated and discussed with everyone.

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

1.0. Feature analysis of Crash Log

1.1. Environmental characteristics

All crashes occurred in iOS14, which is inferred to be caused by changes in iOS14 system library logic.

1.2. Call stack

We have UICollectionview and CPXXProxy in the stack, so we can figure out which pages use both classes,

Swipe Click events... -[UICollectionView _diffableDataSourceImpl] ... _CF_forwarding_prep_0 ... -[CPXXProxy forwardInvocation] ... -[UICollectionView _diffableDataSourceImpl] (start loop)... _CF_forwarding_prep_0 ... -[CPXXProxy forwardInvocation]Copy the code

1.3. Scene repetition

Meet the conditions of a lot of pages, tried a few can not reproduce. Abort has many swipe events in the log. The page also uses UICollectionview. Clicking on various areas of page A finally freezes in the blank and reproduces the same stack.

2.0 source code debugging analysis

For the sake of simplicity, the following terms with the acronym said diffxx representative _diffableDataSourceImpl _CPCollectionViewFlowLayoutProxy CPXXProxy representativeCopy the code

2.1. What is the difference between Page A and Page B?

Although many pages also use UICollectionView and CPXXProxy, but will not appear this crash. Therefore, I found one of the pages, which is called page B below. Logically, there must be A logical difference between page A and page B, and this difference ultimately caused the Crash of page A. Next, analyze the differences between page A and page B.

Difference 1: UICollectionview is nested in page A

There is A difference between page A and page B. The UICollectionView of page A has multiple cells, one of which has A nested UICollectionView. The XXCollectionview is inherited from the UICollectionview of the system.

Difference 2: Page B does not invoke “-[CPXXProxy forwardInvocation]”

Find three methods of critical path and add symbolic breakpoints. Simulate the operation of Crash, and analyze the difference between page A and page B call stack.

-[UICollectionView _diffableDataSourceImpl]
_CF_forwarding_prep_0
-[CPXXProxy forwardInvocation]
Copy the code

The “-[CPXXProxy forwardInvocation]” invocation is generated on page A but not on page B. Page A goes to the forwardInvocation indicating that the system sent an unimplemented method to CPXXProxy. The method is “_diffableDataSourceImpl”.

2.2 where is the _diffableDataSourceImpl method defined?

IOS13 introduces DiffableDataSource to help make local refreshes easier for UITableView and UICollection. Foreign development of the class is NSDiffableDataSourceSnapshot, there is no diffxx method.

Use the Runtime interface to export all UICollectionView methods and find that the Diffxx method is not open to the public, it is a private method.

2.3 why page A calls “-[CPXXProxy _diffableDataSourceImpl]”

Diffxx is the UICollectionView’s own method, so why should it be forwarded to the delegate is a strange logic.

CPXXProxy is set as the delegate of UICollectionView. It receives methods declared by the UICollectionViewDelegate protocol, but UICollectionViewDelegate does not have diffxx methods. Theoretically this method should not trigger a call.

“-[CPXXProxy _diffableDataSourceImpl]” is triggered by UIKit internal logic, and UIKit source code is not open source, so next can only debug Arm assembly to continue analysis.

3.0. Assembly debugging analysis

Which approach should we take? Let me get my thoughts straight.

“Why is page A abnormal?” The exception logic must have A source point, and the logic of page A should be normal before that source point.

As A normal reference, we need to find where page B diverges logically from page A. Compare the call stacks of page A and page B. The last method they have in common is “-[UICollectionView _diffableDataSourceImpl]”. The logic fork is in this method, so we start with this method.

3.1. “-[UICollectionView _diffableDataSourceImpl]” which command appears logic fork?

Page A instruction

Register w0 has a value of 1, there is no jump hit TBZ instruction, and the next instruction continues in sequence. Run all the way to bl 0x196D04820 and jump to the function where 0x196D04820 is located.

Note 1: TBZ instructionCopy the code

After several hops in bl 0x196D04820, the objc_msgSend method is executed. According to the register value, the target in objc_msgSend is “CPXXProxy” and the selector is “diffxx”.

CPXXProxy does not implement diffxx function to forward messages _CF_forwarding_prep_0

Page B instruction

The value of register w0 is 0, and the jump hitting TBZ instruction jumps to 0x1991C12AC to continue executing the instruction, and CPXXProxy method is not called subsequently.

conclusion

The fork of page A and page B is in the TBZ instruction. TBZ is A conditional jump, where the test value of TBZ on page A is 1, and the test value of TBZ on page B is 0, leading to A different logic.

3.2 Where is the difference in W0 caused?

The key instruction we are looking for is “TBZ w0 #0x0”. Here’s where to change the value of w0 to 1.

The x0 register was an object before “BL 0x197157750” was executed, but after it was executed, register X0 became 1, indicating that the return value of this method call is 1, which is true.

Go to “BL 0x197157750″ and find that the final method called is” CPXXXProxy respondsToSelector “, which returns true. That is, when the “-[CPXXXProxy respondsToSelector]” method is called, the results of page A and page B are different.

3.3. Why is the return value of -[CPXXXProxy respondsToSelector] different

CPXXXProxy source code, direct analysis of the source logic.

The CPXXXProxy object has a target property, which is the XXCollectionView nested in the Cell. The delegate and datasource of the XXCollectionView are set to CPXXXProxy. The callback methods of the collectionView are sent to CPXXXProxy. CPXXXProxy takes over some of the callback methods. CPXXXProxy then forwards the information to Target for processing.Copy the code

According to the screenshots show, call “- [_target respondsToSelector: aSelector]”, the running result is true. The _target of page A implements the _diffableDataSourceImpl method, but the _target of page B does not.

On page A, XXCollectionView is nested in cell, and the target of CPXXXProxy is set to XXCollectionView. On page B, XXCollectionView is not nested, and the target of CPXXXProxy is set as the page controller of page B, XXViewController.

“_diffableDataSourceImpl” is itself a method of UICollectionView, and XXCollectionView inherits from UICollectionView, which of course results in true.

4.0,

Analysis here, has suddenly revealed, the following is the call link of the infinite loop.

  • =>”-[XXCollectionView _diffableDataSourceImpl”]”
  • =>”-[CPXXXProxy respondsToSelector:@”_diffableDataSourceImpl”]”
    • => “-[XXCollectionView respondsToSelector:@”_diffableDataSourceImpl”]
  • =>”-[CPXXXProxy _diffableDataSourceImpl”]”
  • =>”-[CPXXXProxy forwardInvocation”]”
  • =>”-[XXCollectionView _diffableDataSourceImpl”]”
  • = >… Infinite loop

reference

Note 1: TBZ instruction

When the test bit is 0, the IMM specifies a certain bit of the destination register, consisting of “B5: B40”, 0-63 or 0-31, depending on B5. Which destination register is specified by Rt, and label is the offset address.

Introduce TBNZ www.cnblogs.com/rongmouzhan…