diary

home page  »  file  »  diary  »  Analysis of iOS Crash Capture and Stack Symbolization Ideas - iOS learning from beginner to proficient Ji Changxin

Analysis of iOS Crash Capture and Stack Symbolization Ideas - iOS learning from beginner to proficient Ji Changxin

Share the hottest information

 1.png

Recently, I was working on crash analysis, and found that although there are many references for crash capture and stack symbolization of iOS, there is no complete set of solutions, which leads to many pitfalls and delays in operation. So I want to make a summary to explain the overall idea of crash collection and analysis and the pit exit guide, and the specific details will be provided with relevant reference materials. With ideas, it is so easy to realize.

Crash capture

For crash capture, previously in Analysis of technical principles of mobile terminal monitoring system It is explained in detail in and the corresponding Demo is given. The crash is mainly caused by the Mach exception and Objective-C exception (NSException). At the same time, the Mach exception will be converted into the corresponding Signal signal when it reaches the BSD layer, so we can also capture the crash event by capturing the signal. For NSException, you can capture exception information by registering NSUncaughtExceptionHandler. The following figure is taken from Alibaba Baichuan, which shows the client crash analysis architecture concisely and clearly.

 2.png

conflict

Before we developed our own crash collection framework, we would definitely access Netease Cloud Capture, Tencent Bugly, Fabric and other third-party log frameworks to collect and analyze crashes. If multiple crash collection frameworks exist, conflicts often exist.

Whether for the Signal capture or the NSException capture, there will be the problem of handler coverage. The correct way is to first determine whether the former has registered a handler. If there is one, you should save the handler. After you have processed your own handler, you can throw the handler out for the previous registrar to handle. The corresponding Demo is given here. The Demo is provided by @ zerygao.

 typedef void (*SignalHandler)(int signo, siginfo_t *info, void *context); static SignalHandler previousSignalHandler = NULL; + (void)installSignalHandler {     struct sigaction old_action;     sigaction(SIGABRT, NULL, &old_action);     if (old_action.sa_flags & SA_SIGINFO) {         previousSignalHandler = old_action.sa_sigaction;     }     LDAPMSignalRegister(SIGABRT);     // ....... } static void LDAPMSignalRegister(int signal) {     struct sigaction action;     action.sa_sigaction = LDAPMSignalHandler;     action.sa_flags = SA_NODEFER | SA_SIGINFO;     sigemptyset(&action.sa_mask);     sigaction(signal, &action, 0); } static void LDAPMSignalHandler(int signal, siginfo_t* info, void* context) { //Get stack, collect stack     ........     LDAPMClearSignalRigister(); //Handle the handler registered by the former     if (previousSignalHandler) {         previousSignalHandler(signal, info, context);     } }

The above is a general code idea for handling the signal handler conflict, and the following is the NSException handler's processing idea. The two are similar with little difference.

 static NSUncaughtExceptionHandler *previousUncaughtExceptionHandler; static void LDAPMUncaughtExceptionHandler(NSException *exception) { //Get stack, collect stack     // ...... //Handle the handler registered by the former     if (previousUncaughtExceptionHandler) {         previousUncaughtExceptionHandler(exception);     } } + (void)installExceptionHandler {     previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();     NSSetUncaughtExceptionHandler(&LDAPMUncaughtExceptionHandler); }

Stack Collection

You can directly use the system method to obtain the current thread stack, or use PLCrashRepoter to obtain all thread stacks, or refer to BSBacktraceLogger Write a lightweight stack collection framework.

Stack symbol parsing

There are four common methods for symbolic stack restore:

  • symbolicatecrash

  • Atos tool under mac

  • An alternative to atos under linux atosl

  • Extract the correspondence between address and symbol through dSYM file, and restore the symbol

The above schemes have corresponding application scenarios. For online crash stack symbol restoration, the last three schemes are mainly used. The usage of atos and atosl is very similar. The following is an example of atos.

 atos -o MonitorExample 0x0000000100062ac4  ARM-64 -l 0x100058000 //Restore Results -[GYRootViewController tableView:cellForRowAtIndexPath:] (in GYMonitorExample) (GYRootViewController.m:41)

However, atos is a tool on the Mac and needs to use Mac or Black Apple for parsing. If the parsing is done by the background, a Linux based parsing scheme is often required. At this time, atosl can be selected, but this library has not been updated for many years. At the same time, based on our company's attempt, atosl does not seem to support the arm64 architecture, So we abandoned the scheme.

Finally, the fourth scheme is used to extract the symbol table of dSYM. You can develop your own tools, or you can directly use bugly and Tools provided by Netease Cloud Capture The following is the extracted symbol table. The first column is the starting memory address, the second column is the ending address, and the third column is the corresponding function name, file name, and line number.

 a840    a854    -[GYRootViewController tableView:cellForRowAtIndexPath:] GYRootViewController.m:41 a854    a858    -[GYRootViewController tableView:cellForRowAtIndexPath:] GYRootViewController.m:42 a858    a87c    -[GYRootViewController tableView:cellForRowAtIndexPath:] GYRootViewController.m:42 a87c    a894    -[GYRootViewController tableView:cellForRowAtIndexPath:] GYRootViewController.m:42 a894    a8a0    -[GYRootViewController tableView:cellForRowAtIndexPath:] GYRootViewController.m:42 aa3c    aa80    -[GYFilePreviewViewController initWithFilePath:] GYRootViewController.m:21 aa80    aaa8    -[GYFilePreviewViewController initWithFilePath:] GYFilePreviewViewController.m:23 aaa8    aab8    -[GYFilePreviewViewController initWithFilePath:] GYFilePreviewViewController.m:23 aab8    aabc    -[GYFilePreviewViewController initWithFilePath:] GYFilePreviewViewController.m:24 aabc    aac8    -[GYFilePreviewViewController initWithFilePath:] GYFilePreviewViewController.m:24

Because the program's starting base address changes every time, the address mentioned above is a relative offset address. After we get the crash stack address, we can match the address in the symbol table according to the offset address in the stack, and then find the function symbol corresponding to the stack. For example, in the fourth line below, when the offset is 43072, it is converted to hexadecimal, which is a840. If you use a840 to find the corresponding relationship in the symbol table above, you will find that it corresponds to - [GYRootViewController tableView: cellForRowAtIndexPath:]. Based on this method, you can completely return the stack address to the original function symbol.

 0   libsystem_kernel.dylib              0x0000000186cfd314 0x186cde000 + 127764 1   Foundation                          0x00000001887f5590 0x1886ec000 + 1086864 2   GYMonitorExample                    0x00000001000da4ac 0x1000d0000 + 42156 3   GYMonitorExample                    0x00000001000da840 0x1000d0000 + 43072

UUID

Our application has multiple versions and supports multiple different architectures. How can we find the symbol table corresponding to the crash log? It depends on the UUID. Only when the UUID of the crash log is consistent with the UUID of the dSYM, can the correct resolution result be obtained.

The method to obtain the UUID of the dSYM:

 xcrun dwarfdump --uuid 

Method of obtaining UUID in application:

 #import  NSString *executableUUID() {     const uint8_t *command = (const uint8_t *)(&_mh_execute_header + 1);     for (uint32_t idx = 0; idx < _mh_execute_header.ncmds; ++idx) {         if (((const struct load_command *)command)->cmd == LC_UUID) {             command += sizeof(struct load_command);             return [NSString stringWithFormat:@"XXXX-XX-XX-XX-XXXXXX",                     command[0], command[1], command[2], command[3],                     command[4], command[5],                     command[6], command[7],                     command[8], command[9],                     command[10], command[11], command[12], command[13], command[14], command[15]];         } else {             command += ((const struct load_command *)command)->cmdsize;         }     }     return nil; }

System library symbolization

The above only extracts the symbol table in dSYM in our application, which is still powerless for the system library. For example, UIKit has no way to symbolize its address. To symbolize the dynamic library, you need to first obtain the symbol file of the system library. The system symbol file can be extracted from the iOS firmware, or the symbol file of the corresponding system can be found from the open source project on Github.

Just now, I just mentioned an idea. For details, please refer to the following materials:

The copyright belongs to the author. For commercial reproduction, please contact the author for authorization, and for non-commercial reproduction, please indicate the source.

Ji Changxin is responsible for ios learning from beginner to proficient

fabulous ( zero )

This article is written by Ji Changxin Author, article address: https://blog.isoyu.com/archives/20342.html
use Knowledge Sharing Attribution 4.0 International License Agreement. Unless the reprint/source is indicated, they are all original or translated by this website. Please sign your name before reprinting. Last editing time: August 23, 2017 at 04:00 pm

Popular articles

Post reply

[Required]

I am a human?

Please wait three seconds after submission to avoid unsubmission and repetition