美文网首页
备忘录之-iOS Crash调试

备忘录之-iOS Crash调试

作者: 忧郁的小码仔 | 来源:发表于2019-08-13 19:22 被阅读0次

    1. 符号表

    符号表是内存地址和函数名、文件名以及行号等内容的映射表,一般线上出现崩溃的话,我们能看到的大约是一堆一堆的内存地址,单独看这些地址并不能帮助我们很快的定位问题,有了符号表,就能将这些地址映射为我们容易理解的方法名,文件名等信息,这样找起来就很方便了。(如果你用过一些第三方的bug收集框架,比如bugly,在查看崩溃日志的时候,一般会提示你上传符号表,否则你可能只能看到一堆让人头大的内存地址)

    要得到符号表,就要有.dSYM文件和crash文件,.dSYM文件是包含了调试信息的目标文件,一般打包或者调试的时候自动生成。
    另外要注意的是,我们要给每一个发布版本找到对应的.dSYM文件,因为不同的版本,不同的代码,.dSYM文件也不一样。

    下面,我们来找一找.dSYM文件

    1. 在打包过的历史中查找

    window -> Organizer打开我们曾打包过的文件目录:

    右键在文件中显示 找到对应文件,显示包内容 .dSYM文件
    1. 直接在Products下面生成的.app中也能找到对应的.dSYM文件
    屏幕快照 2019-08-14 上午10.42.54.png 屏幕快照 2019-08-14 上午10.43.00.png
    1. apple developer My APP已经构建过的,在构建详情里也有下载dSYM的链接。

    注意:
    如果是debug模式,或者有些情况下没有生成.dSYM文件的,我们需要检查下下面几个选项:

    屏幕快照 2019-08-14 上午10.41.09.png 屏幕快照 2019-08-14 上午10.41.30.png

    2. Crash文件

    1. 直接从手机导出crash文件
      将手机连到电脑上后,在XCode的Window->Devices and Simulators下,找到对应的设备,点右侧的View Device Logs打开下面的日志列表,在日志列表中找到自己app crash的日志,右键导出即可。
    屏幕快照 2019-08-14 上午10.57.57.png 屏幕快照 2019-08-14 上午10.58.07.png
    1. 手机 设置->隐私->分析->分析数据 ,找到我们的app对应的crash日志,点击进入详情后,右上角有分享按钮,分享即可。
    屏幕快照 2019-08-14 上午11.04.14.png
    1. 在apple developer My App那里获取苹果收集的crash.
    2. 利用第三方app crash分析平台,比如Bugly.

    3. 文件校验,以及crash文件符号化

    在符号化crash文件之前,我们要先校验crash文件,.dSYM文件的UUID,只有UUID一致才表明crash文件和.dSYM文件是匹配的,才能正确的符号化crash文件。

    比如,下面是我们的crash文件和.dSYM文件:


    屏幕快照 2019-08-14 下午1.10.46.png

    获取crash文件的UUID:

    grep "[AppName] arm64" t.crash
    

    比如,我的app叫PlayWithCrash:


    屏幕快照 2019-08-14 下午1.13.17.png

    获取.dSYM文件的UUID:

    dwarfdump --uuid [.dSYM文件名]
    

    比如:


    屏幕快照 2019-08-14 下午1.14.03.png

    这里crash文件的UUID是连续的小写字母,.dSYM文件的UUID是类似XXX-XXX-XXX这样的字符。

    确认两者的UUID一致之后,我们开始将crash文件符号化,这里需要用到XCode的一个脚本工具symbolicatecrash,我们要用它将crash文件符号化,执行下面命令来获取symbolicatecrash文件的目录:

    find /Applications/Xcode.app -name symbolicatecrash -type f
    
    屏幕快照 2019-08-14 下午1.18.21.png
    最后这条SharedFrameworks目录就是我们需要的路径,到该路径下将symbolicatecrash文件拷贝到.crash和.dSYM同一个文件夹下面,然后执行下面两条命令:
    export DEVELOPER_DIR=/Applications/XCode.app/Contents/Developer
    ./symbolicatecrash ./mycrash.crash ./PlayWithCrash.app.dSYM > symbolResult.crash
    

    这样,就生成了一个新的符号化后的crash文件,来对比下符号化前后的变化:


    result.png

    最大的变化是,符号化之前,报异常的地方是一堆内存地址,符号化之后,我们看到的不仅有内存地址,更有这些内存地址对应的方法。

    4. 崩溃日志上传

    关于崩溃日志收集,有很多第三方的服务可以集成使用,这里,我们选择自己将崩溃日志上传到自己的服务器。

    这里要用到下面这个方法:

    NSSetUncaughtExceptionHandler(...)
    

    这里我们传递一个C函数的地址进去,该方法用于设置最顶层的错误处理方法,可以用来在应用崩溃退出之前做一些处理。(我们可以在这里将崩溃的日志记录下来,下次应用登陆的时候将上次的日志上传给后台即可)

    void UncaughtExceptionHandler(NSException *exception) {
        NSArray *symbols = [exception callStackSymbols]; // 包含调用堆栈符号的数组
        NSString *reason = [exception reason];
        NSString *name = [exception name];
        
        LastException *lastException = [[LastException alloc] init];
        lastException.symbols = symbols;
        lastException.reason = reason;
        lastException.name = name;
    
        NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"lastException.archiver"];
        BOOL result = [NSKeyedArchiver archiveRootObject:lastException toFile:filePath];
        if (result) {
            NSLog(@"归档成功");
        } else {
            NSLog(@"归档失败");
        }
    }
    
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        
        NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"lastException.archiver"];
        LastException *lastException = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
        if (lastException) {
    
            NSString *pfxPath = [[NSBundle mainBundle] pathForResource:@"mymeizi" ofType:@"cer"];
            NSData *pfxData = [NSData dataWithContentsOfFile:pfxPath];
    
            // AFSSLPinningModeCertificate 和
            // AFSSLPinningModePublicKey 只能用于安全的访问链接,及浏览器地址栏https安全表示是绿色那种
            // 如果不是从可信任机构颁发的,而是自签名证书,就用AFSSLPinningModeNone模式
            AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
            // 默认不允许过期或者自签名证书的,如果是自签名证书,这里要设置为YES
            securityPolicy.allowInvalidCertificates = YES;
            // 默认是要验证请求的域名和证书中的域名是否完全一致,即使是子域名也不行,这里我们可以在证书中使用通配符域名
            // 这里,我们用于测试服务器,直接使用IP地址,所以把它关掉即可。
            securityPolicy.validatesDomainName = NO;
            if (pfxData) {
                securityPolicy.pinnedCertificates = [[NSSet alloc] initWithObjects:pfxData, nil];
            }
    
    
            AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
            [manager setSecurityPolicy:securityPolicy];
            manager.requestSerializer = [[AFJSONRequestSerializer alloc] init];
            manager.responseSerializer = [[AFJSONResponseSerializer alloc] init];
    
            NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
            [params setValue:lastException.symbols forKey:@"symbols"];
            [params setValue:lastException.reason forKey:@"reason"];
            [params setValue:lastException.name forKey:@"name"];
    
    
            [manager POST:@"https://192.168.1.57:443/uploadException" parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                
                NSFileManager *fileManager = [NSFileManager defaultManager];
                if ([fileManager fileExistsAtPath:filePath]) {
                    NSError *error;
                    [fileManager removeItemAtPath:filePath error:&error];
                    if (error) {
                        NSLog(@"删除失败");
                    } else {
                        NSLog(@"删除成功");
                    }
                }
                
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                NSLog(@"上传失败");
            }];
        }
    
    
        NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
        
        return YES;
    }
    

    这里,我们将最后截获的Exception先归档,UncaughtExceptionHandler函数这里不能发送网络请求,我们只能先归档,然后再app launch的时候检查有没有异常,有的话就上传,上传后删除即可。

    另外,如果使用第三方收集框架的话,最好只使用一个,如果NSSetUncaughtExceptionHandler被覆盖的话,就看不到准确的日志了。

    相关文章

      网友评论

          本文标题:备忘录之-iOS Crash调试

          本文链接:https://www.haomeiwen.com/subject/cqtijctx.html