He found thousands of baidu, suddenly look back, that person was in the lights dim. — Green Jade Case · Yuan Xi

Learn to watch crashes and reports

An application doesn’t always run well, it always crashes. 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 popular crash collection and analysis tools are encapsulated and improved based on the open source KSCrash code. Apple itself has also built a crash collection and analysis mechanism, you can view the corresponding crash information from the online log of the real machine or from the developer account. There are also many articles on the web about crash analysis, as well as the symbolic handling of the Crash stack. It is assumed that you already know some tips and techniques for viewing crash reports, as well as some simple crash analysis techniques, because these are some of the skills you need to have as a developer.

An objc_msgSend+16 crash stack

Some crash exceptions in application programs can be simply analyzed and solved. Usually, these crash exceptions have clear context information and function call hierarchy stack. However, not all crash exceptions can be solved simply, especially those where there is no clear context information in the function call stack or where no function or method in the call stack can be directly located to the source code, such as the following crash 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] 2018-08-06 16:36:58.000 +0800 OS Version: iOS 10.3.3 (14G60) Report Version: 104 Exception Type: 2018-08-06 16:36:58.000 +0800 OS Version: iOS 10.3.3 (14G60) 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/CoreBluetoothCopy the code

This is a snippet of information from a crash exception report on 64-bit iOS10.3.3 devices. Keep this information in mind and it will be very helpful to locate crash exceptions. As you can see from the crashed function call stack, the exception occurred at the top level of the function call objc_msgSend+16, which is the 5th instruction of the objc_msgSend function. (Normally, each instruction in the ARM architecture takes 4 bytes, The above information indicates a crash at the 16th byte offset of the function, which is the 5th instruction of the function). The crash exception type displayed as EXC_BAD_ACCESS indicates that invalid read/write access to the address was generated, and the entire crash function call stack contains no context information in the application. The objc_msgSend function is the core engine for runtime execution and is called so frequently that it is impossible to have bugs inside it. So why did it crash here?

When an exception occurs inside a function without source code, the only way is to look at its internal “source” implementation

Since the problem is in the 5th instruction of the objc_msgSend function, we can look at the opening fragment of the assembly code instruction implemented by this function:

; IOS10 after objc_msgSend part of the implementation code. _objc_msgSend: 00000001800bc140<+0> cmp x0, #0x0 ; Compare the object receiver with 0 00000001800BC144 <+4> B.lee 0x1800BC1AC; A special processing jump is performed if the object pointer is 0 or high 1. 00000001800bc148<+8> ldr x13, [x0] ; X13 00000001800BC14c <+12> and x16, x13, # 0xffffff8; Assign the Class object pointer to x16 00000001800BC150 <+16> LDP x10, x11, [x16, #0x10]; Remove Class object cache members respectively save to x10, x11 register -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 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 runtime, whether on a real machine or emulator. You can view the assembly code implementation of any called function by setting symbolic breakpoints or entering assembly debug mode and single-instruction jump.

An invalid address access exception occurred while reading the data member cache of the Class object pointer. However, the Class object of an object defines that the data is stored in the data segment of the process memory and exists with the entire application life cycle, which cannot be released or destroyed. Therefore, it is not possible to have illegal memory address access exceptions under normal circumstances. This problem occurs when the OC object that called the method is destroyed, and more specifically when an instance method is called on an OC object that has been freed. So when this type of crash occurs, the cause is the same regardless of whether there is a clear context. The following chart illustrates the reason clearly:


Memory layout comparison of objects before and after they are destroyed

Isa does not actually store the address of the Class object in arm64-bit systems. The above figure is intended to show the cause of the problem more visually.

The ISA pointer to an OC object, obj, points to the memory address of the correct Class object before it is destroyed. So calling the objc_msgSend method will work fine, and once the OBJ object is destroyed, the heap memory allocated for it will be reclaimed for other purposes, so it is possible that the data in this memory area will be overwritten. When an instance method is called on an already freed OC object, reading obj’s ISA pointer inside the objc_msgSend function will result in an unknown or possibly invalid pointer value. The above EXC_BAD_ACCESS exception crashes when accessing the memory that the unknown address points to.

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

You may be wondering why the crash occurred in the 5th instruction of the objc_msgSend function since the obJ object has been freed. The 3rd instruction of the objc_msgSend function accesses the isa data of the object. Why not crash there? 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, It is still accessible to the memory region to which the object points.

In addition to the function call stack providing a reference for analysis when an application crashes, you can also perform a step analysis from the values in the registers. According to the above function instruction implementation, it can be seen:

The x0 register holds the pointer to the destroyed object. The x1 register holds the address of the method name of the object that crashed. The X13 register holds the value of the isa pointer to the object. The x16 register holds the Class pointer object of the object.

The instructions at the function crash are:

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

LDP (x16) {LDP (x16) {LDP (x16) {LDP (x16) {LDP (x16) {LDP (x16) {LDP (x16) {LDP (x16) {LDP (x16) {

  Exception Codes: 0x00000000 at 0x00000005710bbeb8
Copy the code

The address value in register X16 also happens to be the same as the value in register X16. That is, the pointer to the Class object held in X16 is an invalid and invalid memory address.

In all OC methods if you set a symbolic breakpoint then x0 always stores the object of the execution of the method at the start of execution, which is also the argument to the first method; X1 always holds the name string of the executing method, which is also an argument to the second method; And then x2 through x15 might, in turn, be the other parameters of the method. So normally you can type Po $x0 in the debug console to display the object information and p (char*)$x1 to display the method name. For detailed introduction, please refer to my other article: Introduction to registers

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 track or find the cause of the problem, because at this time, there is no way to know which class object executed the method call and caused the crash, the only clue is the value in the X1 register. The value in this register holds the name of the called method, which is SEL data, so you can deduce the class name of the crashed object from the method name stored in X1.

The memory address of the method stored in register X1 is in the code segment of a 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 its loading starting and ending address and path name. From these interval lists, you can easily find out which library the method name referred to by register X1 belongs to. It is clear from the above example 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 crashes when it executes the method x1 points to. Once you have this further information, you can check the source code to see which part of the code calls the object defined in the library that caused the crash (although UIKit is not representative here, in practice the method name at the time of the crash may be in another library). This Narrows the scope of the problem to some extent.

Common crash exception analysis and location methods

When you have a crash exception call stack without context, you’re not helpless. In addition to analyzing by exception type (the type of signal), search engines and common question answering sites can be used to find answers, as well as several methods of locating and analyzing listed below:

1. Open source code

This method is actually very simple, apple actually open source a lot of the base library source code, so when the application crashes on these open source base library to download the corresponding source code to read. Then analyze the problem from the source code to find the cause of the abnormal crash. You can download the latest source code from openSource.apple.com. The downside of this approach is that not all of the code is open source, and the open source code doesn’t have to be the version of iOS running on your 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 with the same version of the operating system that caused the crash exception to facilitate online debugging and running. You can report in the crash exception:

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

The operating system version of the exception is displayed. For example, the operating system version of the exception is iOS 10.3.3. Because all libraries in the same operating system version number implement the same code. If there is no corresponding device version, try to find a device with the closest version. Check out the source code from the repository for the same version of the application as you did online after identifying the operating system version and the real device (this requirement is less stringent if the crash call stack does not contain any function code we wrote). And open the project, then add a symbolic breakpoint for the function or method name at the top 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 of the symbolic breakpoint can be selected as follows:

  1. Symbolic breakpoints can be set directly with the class name and method name if the method at the top of the stack causing the crash is an OC object.
  2. If the top of the stack causing the crash is a generic C function such as objc_msgSend, free, or objc_release, consider setting symbolic breakpoints with the names of the functions and methods at the second level of the function call stack. Such as the -[UIWebDocumentView _updateSubviewCaches] method in the text example.
  3. If the top of the function stack causing the crash is an unexposed C function, it is often considered to use the second layer of the function stack or the method name for the symbolic breakpoint because this function is more difficult to set up.

The purpose of symbolic breakpoints is to enable dynamic analysis at run time at breakpoints when the crash function call stack reappears. When you set a symbolic breakpoint, the system will stop at the first instruction of the set method or function if the program logic runs to that function or method. 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 sets the breakpoint is not necessarily called in only one place.


Symbol breakpoint setting

When the program stops at the starting address of the function or method that set the symbolic breakpoint, it then needs to set the second breakpoint in the method at the offset from the function calling the upper function in the crash stack. This can be seen in the crash report:

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

That is, 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, it can view the value of each register before the function calls the upper function so as to locate and analyze the problem.


The instruction that runs to produce the crash exception

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 the secondary breakpoint, the program runs to the breakpoint with the following instructions:

0x18c0248fc <+36>: bl 0x1893042dc ; 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 a method on an already freed object. 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 the release method. This further makes it clear that the exception crashed by calling release on an array object that had already been freed. As to what array x0 is and where it is stored, we can further analyze it by using the X0 register in the assembly instruction to look up the instruction backwards. A closer look at the problem shows that the thread that crashed was not in the main thread, but in a worker thread. The main thread is supposed to do all the view operations, so when some of the main thread’s child view array objects are released, the main thread is read again, and the above exception crash problem occurs.

After setting a breakpoint at a 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 parameter values 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 will not be able to reproduce, so you need to do something special: manually execute method calls. This is done by artificially calling the top of the stack in some demo code. As in the example above, you can manually create a UIWebDocumentView object when the [UIWebDocumentView _updateSubviewCaches] method is not executed. Manually call the corresponding _updateSubviewCaches method. 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 class information and create objects. For the second question, you can use some tools such as class-dump or other methods to confirm the number and type of method parameters. Anyway, the idea is to be able to break into a function, even if you don’t know how to pass arguments to zero or nil. The following is the simulated crash function call code:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Since class and method names are not public, there are techniques that allow a particular method to execute in order to gain access to 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 here it is added at the start of the program for convenience. Once the code is written, you can set symbolic breakpoints for the method. This way, when the program runs, it must be able to enter the inside of the function. Once a breakpoint occurs after the function is executed, you can perform crash analysis as described in the second method.

The principle of the third method is to try to run objects and methods by any means possible, as long as the method that produces the crash exception is called.

4. Third-party tools perform static analysis

Both methods are dynamic, and sometimes decompile tools can be used to analyze program code statically. Tools like Hopper or IDA. The downside is that these tools are fee-based and do not work as well as dynamic analysis. Personally, IDA analysis tools are friendlier and more powerful to use.

When using third-party tools, you need to find the library where the function that crashed is located, which is found in the list of function call stacks that crashed. If the crash function is defined in the application itself, unzip the ipa file uploaded to the appstore and extract the executable from it with a tool. If the crash function is defined in a system library, it can be in the following path:

~/Library/Developer/Xcode/iOS DeviceSupport/

The contents in the iOS DeviceSupport folder will show you a copy of all the libraries for various operating system versions that you have ever debugged online. If you have not debugged a crashed operating system version on a real device, please find a real device that has this operating system version installed and go online. In this way, you will have a copy of the system library for the corresponding operating system version in your folder.


Path to the UIKit library

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 corresponding library or executable with IDA you will see all the compiled code and data in the library. So you can use the search menu to find the function or method name that caused the crash. At this point you can take a closer look at 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 actual values of the individual registers at runtime, 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:


The IDA tool looks at the implementation of _updateSubviewCaches

When using IDA tools for analysis, you need to know things like library base address and code data offset address and address redirection. For security purposes, Apple uses ASLR to load each library, which means that the base address of the library is loaded randomly at run time, so that when a crash occurs, we need to convert the address that generated the crash into the address that we opened through IDA. The conversion formula is:

Converted address = original address value saved in register at crash time - base address value of library where address is located at crash time + base address set when tool opens library.Copy the code

For example, when we use IDA to see what the value in register X1 is, we simply subtract the value of x1 (0x018EB89B7b) from the base address of UIKit (0x18E03d000). Add the base address when the IDA tool opens the library (to see the base address scroll to the beginning of the IDA view, this time the open base address is 0x187769000). So the address value in register X1 should be converted to:

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

In IDA, jump to 0x1882B5B7B and you can see that the name of the method that generated the crash in this example is called Release:


The name of the method that caused the crash exception

Of course the IDA tool can customize the base address manually, so there is no need to calculate to align the base address with the online crash.

If you do not have third-party tools on hand, in fact, the system built-in OTools tools can also help us to locate the problem and assembly code view and analysis, specific methods we go to find the relevant oTools tutorial, here will not expand.

conclusion

All of the analysis methods listed above include static and dynamic analysis. When a crash occurs, in addition to analyzing the problem from the crash function call stack, you can also analyze and judge comprehensively from the registers, the loaded mirror list, and the assembly code of the function at the top of the crash stack. Of course, even this does not guarantee that all problems can be solved, the example listed in this article is only a very common crash exception in practice, I hope to use this example to play a role in the effect, after all, different crash exceptions are relatively different. Problems need specific analysis, into the internal implementation of the function will be able to find the root of the problem.

Please look forward to the next article: introduction to the implementation principles of common tools and commands in the iOS system.


Table of Contents 1. Assembly language in iOS 2. Instruction set in iOS 3. Deep into the iOS system bottom XCODE to assembly support introduction 4. Deep into the iOS system bottom CPU register introduction 5. Deep into the iOS system of the bottom of the assembly code program 6. Deep into the iOS system of the bottom of the assignment instruction 7. Deep into the bottom of iOS system deconstruction function 8. Deep into the bottom of iOS system common assembly code fragments 9. Deep into the ARC memory management of iOS system 10. Deep into the implementation and handling of iOS system exceptions 11. Introduction to the process of compiling links at the bottom of iOS system 13. Introduction to the mach-O file format in iOS Introduction to iOS image file manipulation API 16. Introduction to iOS static library 17. Deep into the iOS system bottom dynamic library 18. Deep into the iOS system bottom crash solution 19. An in-depth introduction to common iOS tools and commands


Welcome to visit myMaking the addressandJane’s address book