Recently, in my work on Crash analysis, I found that although there are many references for iOS Crash capture and stack symbolization, there is no complete set of solutions, which leads to many pits in operation and a lot of time is lost. Therefore, I want to make a summary to elaborate the overall idea of Crash collection and analysis and the guide to pit exit, and relevant reference materials will be provided for specific details. With that in mind, the implementation is So Easy.

Collapse of capture

As for crash capture, it has been described in detail in the technical principle analysis of mobile terminal monitoring system, and the corresponding Demo has been given. Crash is mainly caused by Mach exception, Objective-C exception (NSException), and for Mach exception, After the BSD layer converts to the corresponding Signal, we can also capture the Crash event by capturing the Signal. For NSException can NSUncaughtExceptionHandler capture exception information through registration. The following diagram, taken from Alibaichuan, shows the client crash analysis architecture succinctly.





conflict

Before we develop Crash collection framework by ourselves, we will definitely connect to third-party log frameworks such as NetEase Yunfu, Tencent Bugly and Fabric for Crash collection and analysis at the earliest. When multiple Crash collection frameworks exist, there are often conflicts.

Either Signal or NSException may have handler coverage. The correct way to do this is to check whether the handler has been registered and save the handler if it has. After you have finished processing your own handler, you throw that handler out for the previous registrant to handle. Here is the corresponding Demo, 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 the stack, collect the stack........ LDAPMClearSignalRigister(); If (previousSignalHandler) {previousSignalHandler(signal, info, context); }}Copy the code

Signal handler (NSException); Signal handler (NSException); Signal handler (NSException);

static NSUncaughtExceptionHandler *previousUncaughtExceptionHandler; The static void LDAPMUncaughtExceptionHandler (NSException * exception) {/ / get the stack, collects stack / /... / / the former register handler if (previousUncaughtExceptionHandler) {previousUncaughtExceptionHandler (exception); } } + (void)installExceptionHandler { previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler(); NSSetUncaughtExceptionHandler(&LDAPMUncaughtExceptionHandler); }Copy the code

Stack gather

You can get the current thread stack directly using the system method, or you can use PLCrashRepoter to get all thread stacks, or you can refer to BSBacktraceLogger to write a lightweight stack collection framework.

Stack symbol resolution

There are four common approaches to stack symbolization:

  • symbolicatecrash
  • MAC under the ATOS tool
  • Atosl, a replacement for ATOS under Linux
  • The corresponding relationship between address and symbol is extracted from dSYM file for symbol restoration

All the above schemes have corresponding application scenarios. For online Crash stack symbol restoration, the last three schemes are mainly adopted. Atos and ATOSL are used in a similar way. Here is an example of ATOS.

Atos -o MonitorExample 0x0000000100062AC4 ARM-64 -L 0x100058000 // Restore result -[GYRootViewController tableView:cellForRowAtIndexPath:] (in GYMonitorExample) (GYRootViewController.m:41)Copy the code

However, ATOS is a tool on Mac, which needs to use Mac or black Apple for parsing. If the background is used for parsing, a linux-based parsing scheme is often required. Atosl can be selected at this time, but this library has not been updated for many years. Atosl didn’t seem to support the ARM64 architecture, so we abandoned it.

Finally, the fourth scheme was used to extract the symbol table of dSYM. Tools could be developed by ourselves, or tools provided by Bugly and NetEase Yunfu could be directly used. The extracted symbol table is shown below. The first column is the start memory address, the second column is the end 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
Copy the code

Because the program starts each base address will change, so the address mentioned above is a relative offset address, after we get the crash stack address, we can match the offset address in the stack with the address in the symbol table, and then find the corresponding function symbol of the stack. Such as the fourth row, offset of 43072 is converted to hexadecimal a840, use a840 to the symbol table above to find corresponding relation, will find that corresponds to the – [GYRootViewController tableView: cellForRowAtIndexPath:]. In this way, the stack address is completely restored to the function symbol.

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

UUID

Our application exists in multiple versions and supports many different architectures, so how do we find the symbol table that corresponds to the crash log? Only when the UUID of the crash log is the same as the UUID of the dSYM, the correct resolution result can be obtained.

Obtain the UUID of dSYM:

Xcrun dwarfdump --uuid <dSYM file >Copy the code

Method for obtaining UUID in application:

#import <mach-o/ldsyms.h>

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:@"%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
                    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;
}
Copy the code

System library symbolization

The above is only extracted from the symbol table in dSYM in our application, but there is nothing that can be done for the system library. For example, THERE is no way for UIKit to symbolize its address. If you want to symbolize the dynamic library, you need to obtain the symbol file of the system library first. Extracting system symbol files can be obtained from the iOS firmware or the corresponding system symbol files can be found from the open source project on Github.

I just talked about an idea. For details, please refer to the following materials:

  • IOS Exception Catching
  • Ramble on the iOS Crash collection framework
  • IOS dSYM file structure Analysis
  • Talk about extracting system library symbols from iOS firmware
  • IOS Crash analysis prerequisite: symbolic system library method
  • Symbolic parsing of iOS crash stack information
  • 杨咏臻 | actual combat iOS collapse the stack symbolic interpretation
  • Handling unhandled exceptions and signals