iOS Crash 三

前两章说了crash文件解析的方法和crash文件中包含的内容,基于的条件是拿到当前奔溃的手机,再拿到手机中对应的日志。或者,用户打开了日志上报的功能,统计到的日志被apple收集了。这两种方式都比较被动,而且没有筛选的功能。对于监测app状态来说不是很准确,定位的过程也比较长。

自定义一个crash 收集工具

上面已经介绍了背景,接下来开始自定义工具,先画一个逻辑图来阐述工具具备的功能

一步一步来,首先在客户端收集crash,收集的方式包含两种:

  • NSSetUncaughtExceptionHandler
  • handleSignal

至于hook Unix信号还是 mac内核信号,这个地址中给出了详细的介绍,这里就不在赘述了。工程中的函数如下:

需要include对应的头文件
#include <signal.h>
#include <execinfo.h>

- (void)crashHandler {
    signal(SIGHUP, signalHandler);
    signal(SIGINT, signalHandler);
    signal(SIGQUIT, signalHandler);

    signal(SIGABRT, signalHandler);
    signal(SIGILL, signalHandler);
    signal(SIGSEGV, signalHandler);
    signal(SIGFPE, signalHandler);
    signal(SIGBUS, signalHandler);
    signal(SIGPIPE, signalHandler);

    //异常时调用的函数
    NSSetUncaughtExceptionHandler(&handleExceptions);
}

void handleExceptions(NSException *exception) {
    NSLog(@"exception = %@",exception);
    NSString *callStackString = [[exception callStackSymbols] componentsJoinedByString:@"\n"];
    NSLog(@"callStackSymbols = %@",callStackString);
    NSLog(@"path: %@", realPath);

    // 当前将这个数据做文件保存
    NSDictionary *dict = @{
                           @"name": exception.name ? : @"unknown",
                           @"reson": exception.reason ? : @"unknown",
                           @"userInfo": exception.userInfo ? : @"unknown",
                           @"callStackReturnAddresses": exception.callStackReturnAddresses ? : @"unknown",
                           @"callStackSymbols": callStackString ? : @"unknown"
                           };

    NSLog(@"dict: %@",dict);
    // 将文件写入对应的地址
    NSLog(@"%@", realPath);
    if ([dict writeToFile:realPath atomically:YES]) {
        NSLog(@"写入数据成功");
    }

    for (NSInteger i = 0; i < 100; i ++) {
        NSLog(@"index : %ld", (long)i);
    }
}

NSString *signalMessageBy(int sig) {
    switch (sig) {
        case SIGHUP:
            return @"SIGHUP (hangup)";
            break;
        case SIGINT:
            return @"SIGINT (interrupt)";
            break;
        case SIGQUIT:
            return @"SIGQUIT (quit)";
            break;
        case SIGABRT:
            return @"SIGABRT (abort())";
            break;
        case SIGILL:
            return @"SIGILL (illegal instruction (not reset when caught))";
            break;
        case SIGSEGV:
            return @"SIGSEGV (segmentation violation)";
            break;
        case SIGFPE:
            return @"SIGFPE (floating point exception)";
            break;
        case SIGBUS:
            return @"SIGBUS (bus error)";
            break;
        case SIGPIPE:
            return @"SIGPIPE (write on a pipe with no one to read it)";
            break;

        default:
            return @"unknown";
            break;
    }
}

void signalHandler(int sig) {
    NSMutableString *mstr = [[NSMutableString alloc] init];
    [mstr appendString:@"Stack:\n"];
    void* callstack[128];
    int i, frames = backtrace(callstack, 128);
    char** strs = backtrace_symbols(callstack, frames);
    for (i = 0; i <frames; ++i) {
        [mstr appendFormat:@"%s\n", strs[i]];
    }

    // 这里在测试的时候是不会断点到的,所以需要存储在文件中
    // 当前将这个数据做文件保存
    NSDictionary *dict = @{
                           @"name": @"UncaughtExceptionHandlerSignalExceptionName",
                           @"reson": [NSString stringWithFormat:@"Signal %@ was raised.", signalMessageBy(sig)],
                           @"userInfo": @{ @"UncaughtExceptionHandlerSignalKey" : @(sig) },
                           @"callStackReturnAddresses": @"unknown",
                           @"callStackSymbols": mstr ? : @"unknown"
                           };

    NSLog(@"dict: %@",dict);
    // 将文件写入对应的地址
    NSLog(@"%@", signCrashPath);
    if ([dict writeToFile:signCrashPath atomically:YES]) {
        NSLog(@"写入数据成功");
    }
}

根据不同的奔溃类型,手动写了两处奔溃的代码:

// 数组插入空值
- (void)function9 {
    NSMutableArray *array = [NSMutableArray array];
    [array addObject:nil];
}

// 访问野指针
struct Test *test = {1,2};
free(test);
test->a;
// struct 定义
struct Test {
    int a;
    int b;
};

代码介绍完了,采用release的方式build到真机,运行crash,再从xcode device中下载当前真机中的内容,查看当前文件中的数据:数据如下:

  • exception存储的文件信息

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>callStackReturnAddresses</key>
        <array>
            <integer>6493351308</integer>
            <integer>6478382572</integer>
            <integer>6492927824</integer>
            <integer>6492090460</integer>
            <integer>4333932636</integer>
            <integer>6658430540</integer>
            <integer>6659614832</integer>
            <integer>6658455296</integer>
            <integer>6659723688</integer>
            <integer>6658968032</integer>
            <integer>6658922640</integer>
            <integer>6658916816</integer>
            <integer>6667181340</integer>
            <integer>6667190984</integer>
            <integer>6667162472</integer>
            <integer>6492992516</integer>
            <integer>6492990508</integer>
            <integer>6492981148</integer>
            <integer>6492065192</integer>
            <integer>6525509664</integer>
            <integer>6659966808</integer>
            <integer>4333932852</integer>
            <integer>6486368192</integer>
        </array>
        <key>callStackSymbols</key>
        <string>0   CoreFoundation                      0x000000018308ada4 &lt;redacted&gt; + 252
    1   libobjc.A.dylib                     0x00000001822445ec objc_exception_throw + 56
    2   CoreFoundation                      0x0000000183023750 _CFArgv + 0
    3   CoreFoundation                      0x0000000182f5705c &lt;redacted&gt; + 1412
    4   TestCrash                           0x000000010252905c -[ViewController function9] + 60
    5   UIKit                               0x000000018cdf964c &lt;redacted&gt; + 96
    6   UIKit                               0x000000018cf1a870 &lt;redacted&gt; + 80
    7   UIKit                               0x000000018cdff700 &lt;redacted&gt; + 440
    8   UIKit                               0x000000018cf351a8 &lt;redacted&gt; + 572
    9   UIKit                               0x000000018ce7c9e0 &lt;redacted&gt; + 2428
    10  UIKit                               0x000000018ce71890 &lt;redacted&gt; + 3160
    11  UIKit                               0x000000018ce701d0 &lt;redacted&gt; + 340
    12  UIKit                               0x000000018d651d1c &lt;redacted&gt; + 2340
    13  UIKit                               0x000000018d6542c8 &lt;redacted&gt; + 4744
    14  UIKit                               0x000000018d64d368 &lt;redacted&gt; + 152
    15  CoreFoundation                      0x0000000183033404 &lt;redacted&gt; + 24
    16  CoreFoundation                      0x0000000183032c2c &lt;redacted&gt; + 276
    17  CoreFoundation                      0x000000018303079c &lt;redacted&gt; + 1204
    18  CoreFoundation                      0x0000000182f50da8 CFRunLoopRunSpecific + 552
    19  GraphicsServices                    0x0000000184f36020 GSEventRunModal + 100
    20  UIKit                               0x000000018cf70758 UIApplicationMain + 236
    21  TestCrash                           0x0000000102529134 main + 88
    22  libdyld.dylib                       0x00000001829e1fc0 &lt;redacted&gt; + 4</string>
        <key>name</key>
        <string>NSInvalidArgumentException</string>
        <key>reson</key>
        <string>*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil</string>
        <key>userInfo</key>
        <string>unknown</string>
    </dict>
    </plist>
  • signal 存储的文件信息

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>callStackReturnAddresses</key>
        <string>unknown</string>
        <key>callStackSymbols</key>
        <string>Stack:
    0   TestCrash                           0x00000001042f9264 signalHandler + 116
    1   libsystem_platform.dylib            0x0000000182cacb48 _sigtramp + 36
    2   libsystem_pthread.dylib             0x0000000182cb66a8 &lt;redacted&gt; + 360
    3   libsystem_c.dylib                   0x0000000182a7fd0c abort + 140
    4   libsystem_malloc.dylib              0x0000000182b49838 &lt;redacted&gt; + 0
    5   UIKit                               0x000000018cdf964c &lt;redacted&gt; + 96
    6   UIKit                               0x000000018cf1a870 &lt;redacted&gt; + 80
    7   UIKit                               0x000000018cdff700 &lt;redacted&gt; + 440
    8   UIKit                               0x000000018cf351a8 &lt;redacted&gt; + 572
    9   UIKit                               0x000000018ce7c9e0 &lt;redacted&gt; + 2428
    10  UIKit                               0x000000018ce71890 &lt;redacted&gt; + 3160
    11  UIKit                               0x000000018ce701d0 &lt;redacted&gt; + 340
    12  UIKit                               0x000000018d651d1c &lt;redacted&gt; + 2340
    13  UIKit                               0x000000018d6542c8 &lt;redacted&gt; + 4744
    14  UIKit                               0x000000018d64d368 &lt;redacted&gt; + 152
    15  CoreFoundation                      0x0000000183033404 &lt;redacted&gt; + 24
    16  CoreFoundation                      0x0000000183032c2c &lt;redacted&gt; + 276
    17  CoreFoundation                      0x000000018303079c &lt;redacted&gt; + 1204
    18  CoreFoundation                      0x0000000182f50da8 CFRunLoopRunSpecific + 552
    19  GraphicsServices                    0x0000000184f36020 GSEventRunModal + 100
    20  UIKit                               0x000000018cf70758 UIApplicationMain + 236
    21  TestCrash                           0x00000001042f9134 main + 88
    22  libdyld.dylib                       0x00000001829e1fc0 &lt;redacted&gt; + 4
    </string>
        <key>name</key>
        <string>UncaughtExceptionHandlerSignalExceptionName</string>
        <key>reson</key>
        <string>Signal SIGABRT (abort()) was raised.</string>
        <key>userInfo</key>
        <dict>
            <key>UncaughtExceptionHandlerSignalKey</key>
            <integer>6</integer>
        </dict>
    </dict>
    </plist>

这里先介绍一个符号化的原理,apple提供了一个atos命令行工具,来完成符号化的过程。在crash的堆栈中,我们可以看到这样的信息

针对一行一行的堆栈信息做如下操作:

atos -arch <Binary Architecture> -o <Path to dSYM file>/Contents/Resources/DWARF/<binary image name> -l <load address> <address to symbolicate>

这里存在几个参数:

  • 当前二进制的架构类型
  • dSYM文件
  • 二进制镜像加载地址
  • 堆栈报错的地址
  • 当前进程的UUID信息,用于匹配dSYM文件

如果上面的信息都有了,运行指令,例如apple给出的例子:

$ atos -arch arm64 -o TheElements.app.dSYM/Contents/Resources/DWARF/TheElements -l 0x1000e4000 0x00000001000effdc
-[AtomicElementViewController myTransitionDidStop:finished:context:]

问题来了,自定义收集到的信息中,缺少如下信息:

  • 当前二进制的架构类型
  • 二进制镜像加载的地址
  • 当前进程的UUID信息

这些信息在哪里能够获取到呢?这里需要用到Mach-o相关的知识,二进制最终在手机上的呈现是以page的方式。在Mach-o层看到的信息为

  • header
  • load commad
  • data

header部分就是协议部分,定义了当前二进制使用的框架类型等,在Header中可以拿到的信息有

  • 二进制的框架类型
  • 二进制中各个镜像的加载地址
  • uuid

使用的方法为

// 这里存储一张表
NSMutableArray *globalProgressInfo;
void getProgressBinaryImagesInfo()
{
    printf("Binary Images:\n");
    //Get count of all currently loaded DYLD
    uint32_t count = _dyld_image_count();

    if (count > 0) {
        globalProgressInfo = [NSMutableArray new];

        for(uint32_t i = 0; i < count; i++)
        {
            NSMutableDictionary *dict = [NSMutableDictionary new];
            //Name of image (includes full path)
            const char *dyld = _dyld_get_image_name(i);
            if (dyld && strlen(dyld) > 0) {
                //Get name of file
                int slength = strlen(dyld);
                int j;
                for(j = slength - 1; j>= 0; --j)
                    if(dyld[j] == '/') break;

                //strndup only available in iOS 4.3
                char *name = strndup(dyld + ++j, slength - j);
                dict[@"name"] = [NSString stringWithFormat:@"%s", name];
                free(name);
            }

            const struct mach_header *header = _dyld_get_image_header(i);
            if (header) {
                // image address
                dict[@"imageAddress"] = [NSString stringWithFormat:@"0x%lX",(uintptr_t)header];
                // arch
                const NXArchInfo *info = NXGetArchInfoFromCpuType(header->cputype, header->cpusubtype);
                if (info && info->name) {
                    dict[@"arch"] = [NSString stringWithFormat:@"%s", info->name];
                }

                // uuid
                BOOL is64bit = header->magic == MH_MAGIC_64 || header->magic == MH_CIGAM_64;
                uintptr_t cursor = (uintptr_t)header + (is64bit ? sizeof(struct mach_header_64) : sizeof(struct mach_header));
                const struct segment_command *segmentCommand = NULL;
                for (uint32_t i = 0; i < header->ncmds; i++, cursor += segmentCommand->cmdsize)
                {
                    segmentCommand = (struct segment_command *)cursor;
                    if (segmentCommand->cmd == LC_UUID)
                    {
                        const struct uuid_command *uuidCommand = (const struct uuid_command *)segmentCommand;
                        if (uuidCommand) {
                            dict[@"uuid"] = [[NSUUID alloc] initWithUUIDBytes:uuidCommand->uuid].UUIDString;
                        }
                    }
                }
            }

            [globalProgressInfo addObject:dict];
        }
    }
}

输出的结果太多了:小一百个之多,所以在crash的时候是需要过滤的

(
    {
    arch = arm64;
    imageAddress = 0x102544000;
    name = TestCrash;
    uuid = "812B393C-BAE2-3E1B-A14E-8593657A8935";
},
    {
    arch = arm64;
    imageAddress = 0x102560000;
    name = "libBacktraceRecording.dylib";
    uuid = "CC6A753E-76FF-324E-8314-B0BAD859C354";
},
    {
    arch = arm64;
    imageAddress = 0x102584000;
    name = "libMainThreadChecker.dylib";
    uuid = "3E93EDAA-8307-3BEA-8226-AEF09B0D193E";
},
    ......
)

经过排重之后上传的crash文件的内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>callStackReturnAddresses</key>
    <array>
        <integer>6493351308</integer>
        <integer>6478382572</integer>
        <integer>6492927824</integer>
        <integer>6492090460</integer>
        <integer>4300867372</integer>
        <integer>6658430540</integer>
        <integer>6659614832</integer>
        <integer>6658455296</integer>
        <integer>6659723688</integer>
        <integer>6658968032</integer>
        <integer>6658922640</integer>
        <integer>6658916816</integer>
        <integer>6667181340</integer>
        <integer>6667190984</integer>
        <integer>6667162472</integer>
        <integer>6492992516</integer>
        <integer>6492990508</integer>
        <integer>6492981148</integer>
        <integer>6492065192</integer>
        <integer>6525509664</integer>
        <integer>6659966808</integer>
        <integer>4300867588</integer>
        <integer>6486368192</integer>
    </array>
    <key>callStackSymbols</key>
    <string>0   CoreFoundation                      0x000000018308ada4 &lt;redacted&gt; + 252
1   libobjc.A.dylib                     0x00000001822445ec objc_exception_throw + 56
2   CoreFoundation                      0x0000000183023750 _CFArgv + 0
3   CoreFoundation                      0x0000000182f5705c &lt;redacted&gt; + 1412
4   TestCrash                           0x00000001005a072c TestCrash + 34604
5   UIKit                               0x000000018cdf964c &lt;redacted&gt; + 96
6   UIKit                               0x000000018cf1a870 &lt;redacted&gt; + 80
7   UIKit                               0x000000018cdff700 &lt;redacted&gt; + 440
8   UIKit                               0x000000018cf351a8 &lt;redacted&gt; + 572
9   UIKit                               0x000000018ce7c9e0 &lt;redacted&gt; + 2428
10  UIKit                               0x000000018ce71890 &lt;redacted&gt; + 3160
11  UIKit                               0x000000018ce701d0 &lt;redacted&gt; + 340
12  UIKit                               0x000000018d651d1c &lt;redacted&gt; + 2340
13  UIKit                               0x000000018d6542c8 &lt;redacted&gt; + 4744
14  UIKit                               0x000000018d64d368 &lt;redacted&gt; + 152
15  CoreFoundation                      0x0000000183033404 &lt;redacted&gt; + 24
16  CoreFoundation                      0x0000000183032c2c &lt;redacted&gt; + 276
17  CoreFoundation                      0x000000018303079c &lt;redacted&gt; + 1204
18  CoreFoundation                      0x0000000182f50da8 CFRunLoopRunSpecific + 552
19  GraphicsServices                    0x0000000184f36020 GSEventRunModal + 100
20  UIKit                               0x000000018cf70758 UIApplicationMain + 236
21  TestCrash                           0x00000001005a0804 TestCrash + 34820
22  libdyld.dylib                       0x00000001829e1fc0 &lt;redacted&gt; + 4</string>
    <key>imageInfo</key>
    <dict>
        <key>CoreFoundation</key>
        <dict>
            <key>arch</key>
            <string>arm64</string>
            <key>imageAddress</key>
            <string>0x182F45000</string>
            <key>name</key>
            <string>CoreFoundation</string>
            <key>uuid</key>
            <string>533C841E-D6E9-313D-8ADB-02388744E2EF</string>
        </dict>
        <key>GraphicsServices</key>
        <dict>
            <key>arch</key>
            <string>arm64</string>
            <key>imageAddress</key>
            <string>0x184F2B000</string>
            <key>name</key>
            <string>GraphicsServices</string>
            <key>uuid</key>
            <string>5011EC25-11D7-3A56-AF50-1E8207D54962</string>
        </dict>
        <key>TestCrash</key>
        <dict>
            <key>arch</key>
            <string>arm64</string>
            <key>imageAddress</key>
            <string>0x100598000</string>
            <key>name</key>
            <string>TestCrash</string>
            <key>uuid</key>
            <string>8C66D9F9-9023-3E5C-B4E6-9BF8772ED730</string>
        </dict>
        <key>UIKit</key>
        <dict>
            <key>arch</key>
            <string>arm64</string>
            <key>imageAddress</key>
            <string>0x18CC53000</string>
            <key>name</key>
            <string>UIKit</string>
            <key>uuid</key>
            <string>BE6EF020-3CAA-3939-86DA-6DD6737541D5</string>
        </dict>
        <key>libdyld.dylib</key>
        <dict>
            <key>arch</key>
            <string>arm64</string>
            <key>imageAddress</key>
            <string>0x1829E1000</string>
            <key>name</key>
            <string>libdyld.dylib</string>
            <key>uuid</key>
            <string>6225B1CD-3984-3071-A64A-DD8F31B09C36</string>
        </dict>
        <key>libobjc.A.dylib</key>
        <dict>
            <key>arch</key>
            <string>arm64</string>
            <key>imageAddress</key>
            <string>0x18223C000</string>
            <key>name</key>
            <string>libobjc.A.dylib</string>
            <key>uuid</key>
            <string>EB1135B2-BDE9-3B69-B96E-42CA98200183</string>
        </dict>
    </dict>
    <key>name</key>
    <string>NSInvalidArgumentException</string>
    <key>reson</key>
    <string>*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil</string>
    <key>userInfo</key>
    <string>unknown</string>
</dict>
</plist>

对于解析堆栈来说,这里的数据就已经足够了。

题外话

到目前为止,还没有介绍dSYM是什么,长什么样子。dSYM文件通过nm指令可以查看符号文件中的内容。例如:

nm TestCrash.app.dSYM/Contents/Resources/DWARF/TestCrash > a.txt

产出的文件如下:

0000000100007034 t +[LJCaughtException applicationDocumentsDirectory]
00000001000082e8 t +[LJCaughtException getExceptionFileNameWithPrefix:]
00000001000070c0 t +[LJCaughtException getHandler]
00000001000070c4 t +[LJCaughtException processException:]
000000010000708c t +[LJCaughtException setDefaultHandler]
0000000100009414 t -[AppDelegate .cxx_destruct]
0000000100009258 t -[AppDelegate application:didFinishLaunchingWithOptions:]
00000001000093e8 t -[AppDelegate applicationDidBecomeActive:]
00000001000093e0 t -[AppDelegate applicationDidEnterBackground:]
00000001000093e4 t -[AppDelegate applicationWillEnterForeground:]
00000001000093dc t -[AppDelegate applicationWillResignActive:]
00000001000093ec t -[AppDelegate applicationWillTerminate:]
000000010000882c t -[AppDelegate crashHandler]
0000000100009400 t -[AppDelegate setWindow:]
00000001000093f0 t -[AppDelegate window]
0000000100006fc4 t -[LJCrashBackTraceModel .cxx_destruct]
0000000100006d4c t -[LJCrashBackTraceModel encodeWithCoder:]
0000000100006e4c t -[LJCrashBackTraceModel initWithCoder:]
0000000100006f9c t -[LJCrashBackTraceModel setStrImageLoadAddress:]
0000000100006fb8 t -[LJCrashBackTraceModel setStrImageName:]
0000000100006f80 t -[LJCrashBackTraceModel setStrStackAddress:]
0000000100006f8c t -[LJCrashBackTraceModel strImageLoadAddress]
0000000100006fa8 t -[LJCrashBackTraceModel strImageName]
0000000100006f70 t -[LJCrashBackTraceModel strStackAddress]
0000000100006cd0 t -[LJCrashInfoModel .cxx_destruct]
0000000100006cac t -[LJCrashInfoModel aryCrashBackTrace]
0000000100006928 t -[LJCrashInfoModel encodeWithCoder:]
0000000100006aa0 t -[LJCrashInfoModel initWithCoder:]
0000000100006cbc t -[LJCrashInfoModel setAryCrashBackTrace:]
0000000100006c84 t -[LJCrashInfoModel setStrCrashArch:]
0000000100006c68 t -[LJCrashInfoModel setStrCrashName:]
0000000100006c4c t -[LJCrashInfoModel setStrCrashReason:]
0000000100006ca0 t -[LJCrashInfoModel setStrCrashSystemVersion:]
0000000100006c74 t -[LJCrashInfoModel strCrashArch]
0000000100006c58 t -[LJCrashInfoModel strCrashName]
0000000100006c3c t -[LJCrashInfoModel strCrashReason]
0000000100006c90 t -[LJCrashInfoModel strCrashSystemVersion]
0000000100008744 t -[ViewController didReceiveMemoryWarning]
0000000100008404 t -[ViewController exceptionAction:]
0000000100008410 t -[ViewController function1]
000000010000846c t -[ViewController function2]
00000001000084c8 t -[ViewController function3]
0000000100008524 t -[ViewController function4]
0000000100008580 t -[ViewController function5]
00000001000085dc t -[ViewController function6]
0000000100008638 t -[ViewController function7]
0000000100008694 t -[ViewController function8]
00000001000086f0 t -[ViewController function9]
000000010000873c t -[ViewController signalCrashAction:]
00000001000083d0 t -[ViewController viewDidLoad]
0000000100008778 t -[ViewController1 viewDidLoad]
000000010000e2b0 s _OBJC_CLASS_$_AppDelegate
000000010000e198 s _OBJC_CLASS_$_LJCaughtException
000000010000e148 s _OBJC_CLASS_$_LJCrashBackTraceModel
000000010000e0f8 s _OBJC_CLASS_$_LJCrashInfoModel
000000010000e1e8 s _OBJC_CLASS_$_ViewController
000000010000e238 s _OBJC_CLASS_$_ViewController1
000000010000e0f0 s _OBJC_IVAR_$_AppDelegate._window
000000010000e0e8 s _OBJC_IVAR_$_LJCrashBackTraceModel._strImageLoadAddress
000000010000e0ec s _OBJC_IVAR_$_LJCrashBackTraceModel._strImageName
000000010000e0e4 s _OBJC_IVAR_$_LJCrashBackTraceModel._strStackAddress
000000010000e0e0 s _OBJC_IVAR_$_LJCrashInfoModel._aryCrashBackTrace
000000010000e0d8 s _OBJC_IVAR_$_LJCrashInfoModel._strCrashArch
000000010000e0d4 s _OBJC_IVAR_$_LJCrashInfoModel._strCrashName
000000010000e0d0 s _OBJC_IVAR_$_LJCrashInfoModel._strCrashReason
000000010000e0dc s _OBJC_IVAR_$_LJCrashInfoModel._strCrashSystemVersion
000000010000e288 s _OBJC_METACLASS_$_AppDelegate
000000010000e1c0 s _OBJC_METACLASS_$_LJCaughtException
000000010000e170 s _OBJC_METACLASS_$_LJCrashBackTraceModel
000000010000e120 s _OBJC_METACLASS_$_LJCrashInfoModel
000000010000e210 s _OBJC_METACLASS_$_ViewController
000000010000e260 s _OBJC_METACLASS_$_ViewController1
0000000100007018 t _UncaughtExceptionHandler
0000000100000000 T __mh_execute_header
0000000100009428 t _getAppName
0000000100009ac4 t _getCodeArch
0000000100009908 t _getImageInfo
00000001000094fc t _getImageLoadAddress
00000001000095dc t _getProgressBinaryImagesInfo
000000010000e410 s _globalProgressInfo
0000000100008b6c t _handleExceptions
00000001000087ac t _main
000000010000e3f8 s _preHander
000000010000e400 s _realPath
000000010000e408 s _signCrashPath
00000001000088c0 t _signalHandler
000000010000916c t _signalMessageBy

符号文件一个记录文件,记录当前工程中的文件编译之后在不同架构上的偏移量。进程在加载的时候都会从一个随机地址开始加载,mach-o中的section也是相对于这个初始的加载地址开始累加的,所以在奔溃的时候,可以通过奔溃堆栈的地址 - 进程加载的地址匹配到出错的函数名称

函数在dSYM中的便宜地址的映射 = 堆栈奔溃地址 - 进程记载地址 + slide(一般————Text代码段的slide是0x00000001000000000)

也可以通过如下方法查看dSYM文件中的slide

otool -arch <arch> -l <path_to_dsym> | grep __TEXT -m 2 -A 1 | grep vmaddr

拿当前我们奔溃的堆栈来说

atos -arch arm64 -o TestCrash.app.dSYM/Contents/Resources/DWARF/TestCrash -l 0x100598000 0x00000001005a072c

// 得到的结果为:
-[ViewController function9] (in TestCrash) (ViewController.m:89)

堆栈地址 - 镜像加载的地址 + 0x0000000100000000 =  0x000000010000872c

当前奔溃的函数是function9,对应的堆栈地址是,和上面算出来的地址有出入

00000001000086f0

原因是function9对饮的地址只是function9函数开始的地址,并非是函数真正报错的地址

- (void)function9 {
    NSMutableArray *array = [NSMutableArray array];
    [array addObject:nil];
}

所以通过nm的方式没能找到对应的报错的函数,换一个命令:

dwarfdump -e --debug-line TestCrash.app.dSYM/Contents/Resources/DWARF/TestCrash > line.txt

出来的结果如下:

----------------------------------------------------------------------
 File: TestCrash.app.dSYM/Contents/Resources/DWARF/TestCrash (arm64)
----------------------------------------------------------------------
.debug_line contents:
----------------------------------------------------------------------
debug_line[0x00000000]
----------------------------------------------------------------------

Address            Line   File
------------------ ------ ------------------------------
0x0000000000000000      1 /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/9.1.0/include/__stddef_max_align_t.h

----------------------------------------------------------------------
debug_line[0x000000a5]
----------------------------------------------------------------------

Address            Line   File
------------------ ------ ------------------------------
0x0000000000000000      1 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/fenv.h

......

Address            Line   File
------------------ ------ ------------------------------
0x00000001000083d0     22 /****/TestCrash/ViewController.m
0x00000001000083dc     23
0x00000001000083f8     25
0x0000000100008404     26
0x0000000100008404     27
0x0000000100008410     30
0x0000000100008434     32
0x0000000100008440     31
0x0000000100008444     31
0x000000010000844c     34
0x000000010000846c     37
0x0000000100008490     39
0x000000010000849c     38
0x00000001000084a0     38
0x00000001000084a8     41
0x00000001000084c8     44
0x00000001000084ec     46
0x00000001000084f8     45
0x00000001000084fc     45
0x0000000100008504     48
0x0000000100008524     51
0x0000000100008548     53
0x0000000100008554     52
0x0000000100008558     52
0x0000000100008560     55
0x0000000100008580     58
0x00000001000085a4     60
0x00000001000085b0     59
0x00000001000085b4     59
0x00000001000085bc     62
0x00000001000085dc     65
0x0000000100008600     67
0x000000010000860c     66
0x0000000100008610     66
0x0000000100008618     69
0x0000000100008638     72
0x000000010000865c     74
0x0000000100008668     73
0x000000010000866c     73
0x0000000100008674     76
0x0000000100008694     79
0x00000001000086b8     81
0x00000001000086c4     80
0x00000001000086c8     80
0x00000001000086d0     83
0x00000001000086f0     86
0x00000001000086fc     87
0x000000010000871c     88
0x000000010000872c     89
0x000000010000873c     91
0x000000010000873c     94
0x0000000100008744     98
0x0000000100008750     99
0x000000010000876c    101
0x0000000100008778    112
0x0000000100008784    113
0x00000001000087a0    115
0x00000001000087ac    115

......

这里对应的地址就能找到了,对应的行数也找出来了。

参考

Understanding and Analyzing Application Crash Reports

结合dSYM文件分析crash日志