preface

If Crash stack, the last method is in their own engineering source code, problems are better solved. But if it ends up in the system library method, and the system library is not open source, it is very difficult to locate the cause. So we have to read assembly code, or disassemble pseudo-code. Here’s the Hopper Disassembler, one of the more commonly used disassembly tools

download

Download the Hopper tool from www.hopperapp.com/. You can use the trial version first, which can be opened for 30 minutes at a time.

The import

Method 1 top navigation bar, select File-read Executable to Disassemble… , select the binary file to analyze.

Method 2 directly drag the executable/dynamic library/static library /archive into Hopper

The default is AArch64(Arm64).

Take a look at the general analysis of a case

Crash Log Basic information

Date/Time:           2020-11-27 06:37:36 +0000
OS Version:          iPhone OS 14.2 (18B92)
Report Version:      104
Exception Type:  SIGSEGV
Exception Codes: SEGV_ACCERR at 0x0
Triggered by Thread:  63
Copy the code

The Crash occurred in thread 63, and the last stack frame was _objc_msgSend. The 44th instruction showed an illegal memory access SEGV_ACCERR, which accessed a pointer with a memory address of 0x0, and 0x0 was obviously an illegal memory address. Let’s try to analyze where 0x0 comes from by looking at libobJC’s compilation.

Import target file

Open the iOS DeviceSupport path, in the target system 14.2 (18B92) folder, locate the libobJC dynamic library, and drag libobjc to the decomcompiler Hopper Disassembler.

Libobjc path where/Users/XXX/Library/Developer/Xcode/iOS DeviceSupport / 14.2 (18 b92) arm64e/Symbols/usr/lib/libobjc. A. d. ylibCopy the code

Locate the Crash instruction line

Search for _objc_msgSend to locate the ARM assembly code for _objc_msgSend. The first address of the method is 00000001949a60e0, and the offset +44 is 00000001949a610C.

Reverse the search for the root cause of the exception

00000001949a610c add x13, x10, x12, lsl #4

The error log shows that illegal memory 0x0 was accessed. It simply means that an instruction is executed to read the value of a register whose address is 0x0. So this is a game of inference, where we find out where address 0x0 is coming from.

LSL is the logical left shift instruction and ADD is the addition instruction. This line represents shifting x12 four bits to the left, equivalent to multiplying by 16, then adding the value of x10, and finally assigning to register X13.

x13 <= x10 + (x12 * 16)

Here the values of two memory addresses x10 and x12 are read. Because an illegal memory read occurred, the address of either x10 or x12 is 0x0.

LSL is to move the logic left and add 0 to the right. 010101010[0] The LSR is moved to the right, and 0 is added to the left. Logical move right one bit: [0]101010101Copy the code

00000001949a6104 eor x12, x1, x1, lsr #7

Let’s look at where x12 comes from, and see if it could be 0x0

This means that x1 is shifted 7 bits to the right, which is equivalent to dividing by 128, and xor with x1, which is assigned to register X12, which implies that x12 is unlikely to be 0, so x10 is 0.

EOR Logical xor If the values of a and B are different, the xor result is 1. If a and B have the same value, the xOR result is 0.Copy the code

00000001949a6100 and x10, x11, #0xffffffffffff

Now let’s look at where x10 comes from. X10 is equal to 0, x11 and upper 0xffffFFFFFF is equal to 0, so x11 is equal to 0.

00000001949a60ec and x16, x13, #0x7ffffffffffff8

Struct objc_class * CLS = (struct objc_class *)(isa & 0x7ffffffff8);

Add isa to 0xffffff8 to get the Class object that the object belongs to, where x13 is isa pointer and x16 is Class object

00000001949a60f8 ldr x11, [x16, #0x10]

struct objc_class : objc_object { struct objc_class * superclass; // Base class information structure. cache_t cache; // method cache hash table //... Other data members are ignored. }; struct cache_t { struct bucket_t *buckets; // Cache method hash bucket array pointer, number of buckets = mask + 1 int mask; - 1 int occupied; // The number of methods that have been cached in the bucket. };Copy the code

Read the address offset in x16 by 16 bytes (0x10) and get the value 0. Takes a value offset by 16 bytes, usually a member variable of an object or structure. From the above line, x16 is the class object, offset by 16 bytes should be the cache object, so the cache object should be null.

conclusion

The use of disassembly tools is the basis for analyzing difficult problems. Today we introduced the use of the Hopper Disassembler tool. You can find some difficult problems to analyze, and gradually become familiar with disassembly tools and ARM64 assembly

reference

Deconstruct the implementation of the objc_msgSend function in depth

Hopper Disassembler decompiled export pseudocode