Learn to read crashes and reports

An application doesn’t always run well, and it can crash. If access to some of the third party in the application of crash collection tool or self-built crash report collection platform will be very good to help developers to analyze and solve the problems of the application running on online, when the collapse of the problems can be timely solution and fast when the repair will greatly promote the application of the user experience.

Many of the most popular crash collection and analysis tools are based on the open source KSCrash code for encapsulation and improvement. Apple has built its own crash collection and analysis mechanism. You can view crash information from the online log of the real machine or from the developer account. There are also many articles on crash analysis on the web, as well as on the symbolization of crash stacks. It is assumed that you already know some methods and techniques for viewing crash reports, as well as some simple crash analysis techniques, as these are some of the skills you need to have as a developer.

An objc_msgSend+16 crash stack

Some of the crash exceptions that occur in the application can be easily analyzed and resolved. Usually, these crash exceptions have clear context information and function call hierarchy stack. However, not all crash exceptions can be easily resolved, especially in cases where there is no clear context in the call stack or where none of the functions or methods in the call stack can be directly located to the source code, such as the following crashed function call stack (partial information) :

Incident Identifier: 85BE3461-D7FD-4043-A4B9-1C0D9A33F63D CrashReporter Key: 9 ec5a1d3b8d5190024476c7068faa58d8db0371f Hardware Model: iPhone7, 2 Code Type: ARM - 64 the Parent Process:? [1] Date/Time: 2018-08-06 16:36:58.000 +0800 OS Version: iOS 10.3.3 (14G60) Report Version: 104 Exception Type: EXC_BAD_ACCESS (SIGBUS) Exception Codes: 0x00000000 at 0x00000005710bbeb8 Crashed Thread: 2 Thread 2 name: WebThread Thread 2 Crashed: 0 libobjc.A.dylib objc_msgSend + 16 1 UIKit -[UIWebDocumentView _updateSubviewCaches] + 40 2 UIKit -[UIWebDocumentView subviews] + 92 3 UIKit -[UIView(CALayerDelegate) _wantsReapplicationOfAutoLayoutWithLayoutDirtyOnEntry:] + 72 4 UIKit -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1256 5 QuartzCore -[CALayer layoutSublayers] + 148 6 QuartzCore CA::Layer::layout_if_needed(CA::Transaction*) + 292 7 QuartzCore CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 32 8 QuartzCore CA::Context::commit_transaction(CA::Transaction*) + 252 9 QuartzCore CA::Transaction::commit() + 504 10 QuartzCore CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 120 11 CoreFoundation __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32 12 CoreFoundation __CFRunLoopDoObservers + 372 13 CoreFoundation CFRunLoopRunSpecific + 456 14 WebCore RunWebThread(void*) + 456 15 libsystem_pthread.dylib _pthread_body + 240 16 libsystem_pthread.dylib _pthread_body + 0 Thread 2 crashed with ARM-64 Thread State: cpsr: 0x0000000020000000 fp: 0x000000016e18d7c0 lr: 0x000000018e2765fc     pc: 0x0000000186990150 
    sp: 0x000000016e18d7b0     x0: 0x0000000174859740     x1: 0x000000018eb89b7b    x10: 0x0000000102ffc000 
   x11: 0x00000198000003ff    x12: 0x0000000102ffc290    x13: 0xbadd8a65710bbead    x14: 0x0000000000000000 
   x15: 0x000000018caeb48c    x16: 0x00000005710bbea8    x17: 0x000000018e2765d4    x18: 0x0000000000000000 
   x19: 0x0000000103a52800     x2: 0x0000000000000000    x20: 0x00000000000002a0    x21: 0x0000000000000000 
   x22: 0x0000000000000000    x23: 0x0000000000000000    x24: 0x0000000000000098    x25: 0x0000000000000000 
   x26: 0x000000018ebade52    x27: 0x00000001ad018624    x28: 0x0000000000000000    x29: 0x000000016e18d7c0 
    x3: 0x000000017463db60     x4: 0x0000000000000000     x5: 0x0000000000000000     x6: 0x0000000000000000 
    x7: 0x0000000000000000     x8: 0x00000001acfb9000     x9: 0x000000018ebf8829 

Binary Images:
       0x100030000 -        0x1022cbfff +xxxx arm64  <6b98f446542b3de5818256a8f2dc9ebf> /var/containers/Bundle/Application/441619EF-BD56-4738-B6CF-854492CDFAC9/xxxx.app/xxxx
       0x1063f8000 -        0x106507fff  MacinTalk arm64  <0890ce05452130bb9af06c0a04633cbb> /System/Library/TTSPlugins/MacinTalk.speechbundle/MacinTalk
       0x107000000 -        0x1072e3fff  TTSSpeechBundle arm64  <d583808dd4b9361b99a911b40688ffd0> /System/Library/TTSPlugins/TTSSpeechBundle.speechbundle/TTSSpeechBundle
...
       0x18e03d000 -        0x18ede3fff  UIKit arm64  <314063bdf85f321d88d6e24a0de464a2> /System/Library/Frameworks/UIKit.framework/UIKit
       0x18ede4000 -        0x18ee0cfff  CoreBluetooth arm64  <ced176702d7c37e6a9027eeb3fbf7f66> /System/Library/Frameworks/CoreBluetooth.framework/CoreBluetooth

Copy the code

This is a fragment of information about a crash exception report on a 64-bit device with iOS10.3.3. Keep this information in mind to help locate the crash exception. From the collapsed function call stack, we can see that the exception occurred at the top function call objc_msgSend+16, that is, at the fifth instruction of objc_msgSend **(normally, arm architectures have 4 bytes for each instruction, The above information indicates that the crash occurred at the 16th byte offset of the function, which is the 5th instruction of the function. A crash exception type of EXC_BAD_ACCESS** indicates that an invalid read/write access to the address was generated, and there is no application context information in the entire crash function call stack. The objc_msgSend function is the core engine for runtime method execution and is called so frequently that it is unlikely to have bugs inside it. So why did it crash here?

When an exception occurs inside a function that has no source code, the only way is to look at the “source code” implementation inside

Since the problem is at the fifth instruction of the objc_msgSend function, take a look at the beginning of the assembly code instruction for this function implementation:

; Partial implementation of objc_msgSend after iOS10. _objc_msgSend: 00000001800bc140<+0> cmp x0,#0x0 ; The judgment object receiver is compared to 000000001800bc144<+4> b.le 0x1800bc1ac ; A special processing jump is performed if the object pointer is 0 or its high position is 1. 00000001800bc148<+8> ldr x13, [x0] ; X13 00000001800bc14c<+12> and x16, x13,#0xffffffff8 ; Gets the Class object pointer of the object and assigns it to x16
00000001800bc150<+16>	ldp	x10, x11, [x16, #0x10] ; The cache members of the Class object are fetched and stored in registers X10 and X11-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the above instruction is broken code. 00000001800bc154<+20> and w12, w1, w11Copy the code

XCODE allows you to view the assembly code implementation of any called function at run time, both on the real machine and in the emulator. You can view the assembly code implementation of a function by setting symbolic breakpoints or by entering assembly debug mode and jumping to a single instruction.

An invalid address access exception occurred while reading the data member cache of the object’s Class object pointer. However, the defined data of the Class object of the object is stored in the data section of the process memory and exists throughout the life cycle of the application. It cannot be released or destroyed. Therefore, it is not possible to have illegal memory address access exceptions under normal circumstances. This occurs when the OC object on which the method was called is destroyed, and more specifically when an instance method is called on an already released OC object. So when this type of crash occurs, the cause is the same whether there is a clear context or not. This chart makes it clear why:

In fact, isa does not store the Class object address of the object in the ARM64-bit system. The purpose of the above diagram is to show the cause of the problem more intuitively.

Before an OC object obj is destroyed, the ISA pointer will point to the correct memory address of the Class object. So calling objc_msgSend will work, but once the obj object is destroyed, its allocated heap memory will be reclaimed for other purposes, so it is possible that the data in this area will be overwritten. When an instance method is called on a freed OC object, the isa pointer to obj read inside the objc_msgSend function will return an unknown or possibly invalid pointer value. The exception of EXC_BAD_ACCESS crashes when accessing the memory at this unknown address.

CPU instructions that operate registers and constants, such as instructions 1, 2, 4 and 6 above, generally do not generate crash exceptions. Generally, the instructions that generate access exceptions are those that access memory addresses, such as 3 and 5.

You might be wondering why the crash occurs in the fifth directive of the objc_msgSend function, since the obj object has been released. The third directive accesses the isa data of the object. Why not crash here? The answer is very simple, because almost all the OC objects from the heap memory areas in the allocation of memory, so when a OC after the object is destroyed, the memory will still be back on the managed heap memory area, and the address of the heap memory area can be arbitrarily read-write access, so even if the object is destroyed the release, The data in the memory region to which the object points is still accessible.

In addition to the function call stack providing a reference for analysis when an application crashes, the value in the register can also be used for one-step analysis. According to the above function instruction implementation can be seen:

The pointer to the destroyed object is stored in the X0 register. The x1 register holds the address of the name of the method that produced the crashed object. The x13 register holds the value of the object’s ISA pointer. The x16 register holds the Class pointer object of the object.

Function crash instruction is:

ldp x10, x11, [x16, #0x10] 
Copy the code

X16 contains an invalid pointer to a Class object, so when you run the LDP command to read the data from memory at 0x10 offset from the address x16 points to, the error code crashes:

  Exception Codes: 0x00000000 at 0x00000005710bbeb8
Copy the code

The address value in the x16 register is exactly the same as the value in the X16 register. This means that the pointer to the Class object stored in X16 is an invalid and invalid memory address.

In all OC methods, if you set a symbolic breakpoint, the object stored in x0 at the start of the method is always the object that executed the method, which is the argument to the first method; X1 always holds the name string of the executed method, which is also an argument to the second method; Then x2 through x15 could be the other parameters of the method in turn. So in general you can type Po $x0 in the debug console to display the object information and p (char*)$x1 to display the method name. For details, please refer to my other article: Register Introduction

In the above crash call stack, all functions and methods are system functions without the source code of the program itself, so it is difficult to trace or find the cause of the problem, because at this time, it is impossible to know which class of object performs the method call and the crash is caused, and the only clue is the value in the x1 register. The value in this register holds the name of the method that was called, which is SEL data, so you can backtrace from the method name stored in X1, which is the class name of the object that produced the crash.

The memory address of the method stored in the x1 register is stored in the code segment of the loaded library Image, so the library Image information that defines the method name can be found in the Binary Images list of the crash log. Each library Image in the Binary Images list has the start and end address of the library load and the path name. It is easy to find out which library the method name in the X1 register belongs to from these interval lists. In the example above it is clear that the method address 0x18eb89b7b belongs to:

 0x18e03d000 -  0x18ede3fff  UIKit arm64  <314063bdf85f321d88d6e24a0de464a2> /System/Library/Frameworks/UIKit.framework/UIKit
Copy the code

That is, some object defined in the UIKit library is performing the method referred to by X1 and crashes. With this further information in hand, you can check in the source code to see what part of the code calls the object defined in the library that caused the crash (of course UIKit is not representative here, in practice the method name might be in another library when the crash occurred). This will narrow down the scope of the problem to some extent.

Common crash exception analysis and location methods

When a crash exception call stack occurs without context, it is not impossible to do anything about it. In addition to analyzing by exception type (the type of signal), you can also use search engines and FAQ sites to find the answers, and of course you can use several locating and analyzing methods listed below:

1. Open source method

This method is actually very simple, Apple actually open source a lot of the base library source code, so when the program crashes on these open source base library can download the corresponding base library source code to read. Then from the source code analysis of the problem, so as to find the cause of the abnormal crash. You can download it from https://opensource.apple.com to source the latest source code. The downside of this approach is that not all code is open source, and open source code doesn’t have to be the version of iOS running on your real device. Therefore, this method can only be an auxiliary method.

2. Method symbol breakpoint method

When using this approach, make sure you have a real machine on hand with the same version of the operating system that caused the crash exception, so you can debug and run it online. You can report the crash exception in:

OS Version:      iOS 10.3.3 (14G60)
Copy the code

The section shows the operating system version number that caused the exception, as in the example in this article, the operating system version number that caused the exception is iOS 10.3.3. Because the code in all libraries implements the same thing in the same OS version number. If there is no device with the corresponding version, you can try to find a device with the closest version. Identify the operating system version and the actual device and then check out the code repository for the same version of the application that you have online (this condition is not so strict if the crash call stack does not contain any of the function code we wrote). Open the project and add a symbolic breakpoint to the top function or method name of the function call stack that caused the crash. If you don’t know how to add symbols breakpoint please refer to the article: blog.csdn.net/xuhen/artic… Or look for the keyword “XCODE symbol breakpoint”.

The method or function name for setting a symbolic breakpoint has the following options:

  1. If the top of the stack where the crash occurred is an OC object method, you can use the class name and method name directly to set the symbol breakpoint.
  2. If the top of the stack that caused the crash is a generic C function such as objc_msgSend, free, or objc_release, consider setting a symbolic breakpoint with the second layer function and method name of the function call stack. Take the -[UIWebDocumentView _updateSubviewCaches] method in the text example.
  3. If the crash occurs at the top of the function call stack with an unexposed C function that is more difficult to set a symbolic breakpoint, it is often considered to use a function or method name at the second level of the function call stack as a symbolic breakpoint.

Symbolic breakpoints are set so that dynamic analysis can be performed at the breakpoint at run time when the crash function call stack reappears. When you set a symbolic breakpoint, if the program logic runs to the function or method, the system will stop at the first instruction of the set method or function. Can view at this time and whether the function call stack and result in collapse of the call stack, if consistent so that can reproduce logic problems may occur, if the breakpoint at the call stack and the collapse of the call stack is not the same, you may need to make the program continues to run, so that the next time in the same breakpoints are compared, and the call stack Because the name of the method that set the breakpoint doesn’t have to be called in just one place.

When the program stops at the start address of the function or method that set the symbolic breakpoint, a second breakpoint needs to be set within the method, at the offset of the function above the call in the crash call stack. This can be seen in the crash report:

0   libobjc.A.dylib                 objc_msgSend + 16
1   UIKit                          -[UIWebDocumentView _updateSubviewCaches] + 40
Copy the code

You need to add a breakpoint near the 11th instruction of the _updateSubviewCaches function or the 40th offset byte of the function. In this way, when the program moves to the breakpoint, the value of each register can be viewed before the function calls the upper function to locate and analyze the problem.

Usually collapse function stack report except the top function of each layer after the function name + figures show is the address of the corresponding in the current function near offset in the upper function calls, or the address of the corresponding offset generally exists a bl instruction or BLR near the two instructions, both the role of instruction is to perform function calls.

By setting a secondary breakpoint, the program runs to the breakpoint with the following instructions:

0x18c0248fc<+36>: bl 0x1893042dc ; The address 0x1893042dc is the function address of objc_msgSendCopy the code

The cause of the exception crash in this example is a crash caused by continuing to call methods on an object that has already been freed. So when the breakpoint stops at the instruction, we can print the instruction in the LLDB console in the lower right corner:

(lldb)po $x0<__NSArrayM 0x1c044c2a0>( <UIWebOverflowScrollView: 0x1281d7e00; frame = (0 0; 375, 603); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x1c0851190>; layer = <WebLayer: 0x1c4426ba0>; contentOffset: {0, 0}; contentSize: {375, 12810}; adjustedContentInset: {0, 0, 0, 0}> ) (lldb) p (char*)$x1
(char *) $6 = 0x000000018cb9dd70 "release"
(lldb) 
Copy the code

You can see that X0 is an array object, and x1 is release. This makes it clear that the exception crashed when the release method was called on a freed array object. What array x0 is and where it is stored can be further analyzed by using the x0 register in the assembly instruction to look up the instructions backwards. A closer look reveals that the thread that crashed is not in the main thread, but in a worker thread. View operations should be performed on the main thread, so when some subview array objects are released from the main thread and read from the helper thread, the above exception crash occurs.

After the breakpoint is set on the function call bl or BLR instruction, all non-floating point arguments are stored in x0, X1, and…., respectively, according to the ABI rules In these registers. So you can print out the values of each of these registers at the breakpoint to know the parameters passed before the function call. This method is very helpful for problem location and analysis.

3. Manual reproduction method

Sometimes, even if you set a symbolic breakpoint, the scene still cannot be reproduced, and a special method is required to perform the method call manually. This is done simply by making an artificial call to the top of the crash stack in some demo code. For example, when the [UIWebDocumentView_updatesubViewCaches] method is not being used, you can manually create a UIWebDocumentView object yourself. And manually call the corresponding method _updateSubviewCaches. The two problems here are that it is possible that the class is not declared, or that we do not know the parameter types of the method or the values to be passed. The solution to the first problem is to use NSClassFromString to get the class information and create the object. The second problem can be solved by using tools such as class-dump or other methods to determine the number and type of parameters of the method. Anyway, the goal is to be able to access the breakpoint of a function, and even to temporarily solve the problem by passing all the arguments to zero or nil if you don’t know how to pass them. Here is the implementation code for calling the simulated crash function:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for// Since neither the class name nor the method name was customization after application launch, we had some technical means of getting a particular method executed in order to be able to access the internal implementation of the method. Class cls = NSClassFromString(@"UIWebDocumentView");
    id obj = [[cls alloc] init];
    SEL sel = sel_registerName("_updateSubviewCaches"); [obj performSelector:sel]; / /... }Copy the code

The test code can be written anywhere, but in this case it is added at the start of the program for convenience. Once the code is written, you can set a symbolic breakpoint for the method. So when the program runs it must be able to get inside the function. Once a breakpoint occurs after the function has been executed, crash analysis can be performed as described in method 2.

In fact, the principle of the third method is that as long as the method that causes the crash exception is called, there are various ways to try to run objects and methods together.

4. Third-party tool static analysis

The first two are dynamic analysis, and sometimes you can also use decompilation tools to analyze the program code statically. Tools like Hopper or IDA. The downside is that these tools cost money and are not as effective as dynamic analysis. I think IDA analysis tools are more user-friendly and powerful.

When using third-party tools, you need to find the library of the crashed function, which can be found in the list of the crashed function call stack. If the crash function is defined in the application itself, then you need to decompress the IPA file uploaded to the Appstore and extract the executable program from it and open it with the tool. If the crash function is defined in a system library, the following path can be used:

~/Library/Developer/Xcode/iOS DeviceSupport/

The contents of the iOS DeviceSupport folder will show you a copy of all the libraries of various operating system versions that you have ever debugged online. If you have not debugged a crashed operating system version on a real machine, please find a real machine that has that operating system version installed and bring it online. Your folder will have copies of the system libraries for the operating system version.

Find the corresponding produce crash phone operating System version number of the Library files: 10.3.3 (14 g60 will be)/Symbols/System/Library/Frameworks/UIKit. Framework/UIKit

When you open the library file or executable file using IDA, you will see all the code and data in assembly form for the library file. So you can search the menu to find the name of the function or method that caused the crash. At this point you can further analyze the assembly code of the function that caused the problem. The disadvantage of using IDA for assembly code analysis is that static analysis cannot see the true values of individual registers at run time, so this approach may require more consideration of your ability to understand assembly code. Here is the assembly code for the [UIWebDocumentView _updateSubviewCaches] method in this example:

When using IDA tools for analysis, knowledge of such things as base addresses and code data offsets and address redirection is required. For security, The Apple system uses ASLR to load each library, which means that the base address of the library loaded is random at each run, so that when a crash occurs, we need to convert the address of the crash to the address we open through IDA. The conversion formula is:

The translated address = the original address stored in the register at crash time - the base address of the library where the address was at crash time + the base address set when the tool opens the library.Copy the code

In the case of the above crash exception, when we use IDA to see what the value in register X1 is, we just subtract the value of x1 (0x018eb89b7b) from the base address of the library UIKit (0x18E03d000). Add the base address when the IDA tool opened the library (to see the base address scroll to the beginning of the IDA view, this time the base address is 0x187769000). So the address value in register X1 should be converted to:

0x018eb89b7b -  0x18e03d000 + 0x187769000 = 0x1882B5B7B
Copy the code

In the IDA tool, jump the address to 0x1882B5B7B to see that the method that caused the crash in this example is called Release:

Of course, the IDA tool allows you to customize the base address manually, so you don’t need to do calculations to align it with the base address that crashed online.

If you have no third-party tools at hand, in fact, the system built-in OTools tools can also help us to locate the problem and assembly code view and analysis, the specific method of everyone to find the relevant tutorial on the use of OTools, here is not expanded.

conclusion

All the analysis methods listed above include both static and dynamic analysis. When there is a crash, in addition to the breakdown function call stack to analyze the problem, but also from the register, and loaded mirror list, as well as the top of the collapse of the function assembly code and so on comprehensive analysis and judgment. Of course, even this does not guarantee that all problems will be solved. The example in this paper is only a very common crash exception in practice. I hope that this example can play a role in introducing jade, after all, different crash exceptions are quite different. Problems need specific analysis, into the internal implementation of the function will be able to find the root cause of the problem.

👉 [Return to directory]


Welcome to myMaking the address