美文网首页
记录一次启动crash排查

记录一次启动crash排查

作者: 站在下一刻 | 来源:发表于2018-11-28 15:15 被阅读16次
    • 背景
      最近遇到一个启动的crash,这个启动是app的通知拉起来的,每次都会crash,而正常的启动则不会发生,所以不好断点调试,也是费了几番周折才定位到原因,最后发现是个很常见的错误导致的,主要运用swizzle定位到了问题,大体路径如下。

    1. 尝试查看手机Crash Logs

    • 一般crash都会在设备上留下crash的日志,可以通过在Xcode的Devices And Simulators中查看。

      连上xcode,打开手机的Device Logs,然后查看当天的crash,如下: Xcode上的crash.png
      查看源码,发现APMAnalytics是第三服务方的库,工程中都搜不到这个类名,这下麻烦了,不能从代码上分析原因,继续查看调用栈,发现了如下调用
      +[NSJSonSerialization dataWithJSONObject:options:error:]
      

    当时就觉得应该是传入的参数不合法导致的,然后写了一个NSJSonSerialization的category,覆盖掉dataWithJSONObject,每次返回nil,发现就不crash了,定位到确实是这个方法导致。

    1. 尝试hook造成crash的系统方法

    • 从上面的步骤看来,应该是调用这个方法的参数不对导致的,得采用swizzle的方式,保留dataWithJSONObject的原本逻辑,同时对参数的输入做下安全判断,这样应该就可以。
      @implementation NSJSONSerialization (ABSExtension)
    
    + (BOOL)lh_swizzleClassMethod:(SEL)origSel withMethod:(SEL)altSel {
        return [object_getClass((id)self) lh_swizzleMethod:origSel withMethod:altSel];
    }
    
    + (BOOL)lh_swizzleMethod:(SEL)origSel withMethod:(SEL)altSel {
        Method origMethod = class_getInstanceMethod(self, origSel);
        Method altMethod = class_getInstanceMethod(self, altSel);
        if (!origMethod || !altMethod) {
            return NO;
        }
        class_addMethod(self,
                        origSel,
                        class_getMethodImplementation(self, origSel),
                        method_getTypeEncoding(origMethod));
        class_addMethod(self,
                        altSel,
                        class_getMethodImplementation(self, altSel),
                        method_getTypeEncoding(altMethod));
        method_exchangeImplementations(class_getInstanceMethod(self, origSel),
                                       class_getInstanceMethod(self, altSel));
        return YES;
    }
    
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            [self lh_swizzleClassMethod:@selector(dataWithJSONObject:options:error:) withMethod:@selector(lh_dataWithJSONObject:options:error:)];
        });
    }
    
    + (NSData *)lh_dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError * _Nullable __autoreleasing *)error{
       if (obj && ([obj isKindOfClass:[NSArray class]]||
                    [obj isKindOfClass:[NSDictionary class]]||
                    [obj isKindOfClass:[NSString class]])) {
            return [self lh_dataWithJSONObject:obj options:opt error:error];
        }else{
            return nil;
        }
    }
    @end
    

    添加了以上代码后,发现还是发生crash,所以传入的参数应该是合法的,问题还是没有定位到,接着添加NSLog打印所有启动的参数传入,在Devices And Simulators中点击Open Console,发现启动过程的所有调用传入的参数都是合法的,而且数量很多,不知道是哪一条的问题。

    3. 添加try catch代码块

    • try catch 可以抓到iOS系统方法执行的异常,并打印出异常信息
      修改代码如下:
    + (NSData *)lh_dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError * _Nullable __autoreleasing *)error{
       NSData *res = nil;
        @try {
            res = [self lh_dataWithJSONObject:obj options:opt error:error];
        } @catch (NSException *exception) {
            NSLog(@"lh -- exception is %@,%@",exception,obj);
        } @finally {
            return res;
        }
    }
    

    既然上面步骤已经跟踪到是这个系统方法的问题,就想到加上try catch,这样应该可以抓到异常,同时也可以避免crash,运行了一遍,发现确实走到catch代码块里面,也打印了异常信息和对应传入的参数,最后发现传入的参数是个字典,但是某个key对应的value是个NSNumber,由于通知拉起的跳转逻辑,导致它是NAN,所以不能解析就crash了,然后找到对应的位置,修复了这个问题,最后决定保留这块代码记录一个数据点,上传今后类似异常信息

    4.总结

    • 遇到启动crash的时候,常用Xcode自带的console去打日志定位代码问题,同时结合设备生成的.crash文件可以快速定位到业务代码上的问题,如果崩在是非开源库中,就可以看看调用栈是否崩在系统方法里面,然后通过swizzle的方式找出对应的输入参数,最后加上try catch去找到异常信息一般就可以定位到问题了
    • try catch能抓到的异常还是挺多的,如解档失败,集合类插入nil数据等系统方法,使用的好可以避免很多crash

    相关文章

      网友评论

          本文标题:记录一次启动crash排查

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