Skip to content

常见运行时错误排查方式

目前运行时错误分几个大类情况,本教程会放几个常见案例来说明问题所在,并附上会触发代码模式

一.ObjC/Swift 对象的+load函数穿透问题

下面演示一些程序执行时序不对引起运行时错误的案例**

案例1. +load执行顺序引起字符串解密没有触发引起问题

Note

//MyAClass.m
@implementation MyAClass

+ (void)load {
    [MyBClass show];
}

@end

//MybClass.m
@implementation MyBClass

+ (void)show {
    NSString* msg = @"你好";
    NSLog(msg);
}

@end

上面这段代码如果这个MyAClass.m/MyBClass.m 文件的I标识打开,将会被翻译成下面的样子,一般没有问题

Note

//MyAClass.m
@implementation MyAClass

+ (void)load {
    [self performSelector:@selector(load_delay) withObject:self afterDelay:0 inModes:@[NSRunLoopCommonModes, NSDefaultRunLoopMode]];//延迟到main启动时候调用
}

+ (void)load_delay {
    [MyBClass show];
}

@end    
//MyBClass.m
char* decode_str1[] = {0x11,0x11,...};

__attribute__((constructor)) __decode_string()
{
    ...//完成decode_str1解码
}

@implementation MyBClass

+ (void)show {
    NSString* msg = [NSString stringWithCString:decode_str1];
    NSLog(msg);
}

@end

Warning

但是如果MyAClass.m上的I标识没有了,混淆工具就无法正确处理,改写+load,这就会导致load的执行时机比MyBClass的__decode_string 函数更早别调用,这时候NSLog打印的就是nil

案例2. +load执行顺序引起资源加载问题

Note

//MyAClass.m
@implementation MyAClass

+ (void)load {
    [MyBClass show];
}

@end

//MybClass.m
@implementation MyBClass

+ (void)show {
    UIImage* image = [UIImage imageNamed:@"nihao"];
    NSAssert(image != nil, @"not found nihao");
}

@end

上面这段代码如果这个MyAClass.m/MyBClass.m 文件的I标识打开,将会被翻译成下面的样子,一般没有问题

Note

//MyAClass.m
@implementation MyAClass

+ (void)load {
    [self performSelector:@selector(load_delay) withObject:self afterDelay:0 inModes:@[NSRunLoopCommonModes, NSDefaultRunLoopMode]];//延迟到main启动时候调用
}

+ (void)load_delay {
    [MyBClass show];
}

@end    
//MyBClass.m
@implementation MyBClass

+ (void)show {
    UIImage* image = [UIImage imageNamed:@"nihao"];
    NSAssert(image != nil, @"not found nihao");
}

@end

Warning

但是如果MyAClass.m上的I标识没有了,也是一样的问题,+load函数的执行时机可能比COSDK.framework启动时序更早UIImage.hook_imageNamed这个方法都还没有挂上去,这是时候一定引起运行时崩溃

二.函数拦截和API引起的问题

下面将会演示一些函数API没有被勾住的情况

案例1. 资源被改名加密,但是COSDK.framework运行时根本无法处理

Note

#import <SVGAImage.framework>

@implementation MyClass
-(void)getSVGAImage {
    SVGAImage* img = [SVGAImage imageWithName:@"hello"];
    NSAssert(img != nil, @"not found hello.svga");
}
@end

Warning

由于这个SVGAImage.framework用到的函数都不在拦截列表里面 API拦截列表,特特别是没有用ObjC运行时方法,所以也没法在COSDK.framework初始化的时候完成拦截操作。如果首先确认这个SVGAImage.framework时静态库架构,并且是用了C API还是可以通过修改这个framework完成拦截的混淆外部SDK存档

案例2. 第三方framework胡乱拦截导致YYModel解析失败

Note

#import <TencentApi.framework>

@implementation MyClass
-(void)getModel {
    UserModel* user = [UserModel yy_modelWithJson:@{@"user" : @"haha}];
    NSAssert(user != nil, @"not found user");
}
@end

Warning

当这个TencentApi.framework把YYModel相关代码打包进去了,就会引起解析失败,这个时候,我们只能通过排除解决问题 排除混淆设置

三.ObjC/Swift Metadata和资源映射机制引起的崩溃问题

这种就非常常见了,一般情况下当有外部资源文件的时候,会进行关联操作,如果ObjC/Swift对象被更换了,但是对应的资源文件没有被修改,会一定会出现问题

案例1. 关闭了xib、storyboard的C标识,引起运行时错误

一般情况下不要随便关闭xib、storyboard等外部文件上的C标识,资源文件的修改都是要做关联修改的

案例2. 自定义格式的资源引用了某些对象名字

Note

@implementation MyClass
-(void)getModel {
    NSString* name = [NSString stringWithContentOfFile:@"nihao.cfg"];
    Class cls = NSClassFromString(name);
    ...
}
@end

Warning

这种情况下最好的方法就是不去混淆这个nihao.cfg内部的类,可以在混淆配置中排除掉 排除混淆设置

四.ObjC/Swift 字符串编译期共用引起的问题

这类问题的处理关键就是要通过调试来确认问题所在,要么排除要么统一改名操作 案例1. 编译器对字符串做合并操作,共用了MyClass这个字符串引起的问题

Note

//MyAClass.m
@implementation MyClass
-(void)getModel {
    UIImage* img = [UIImage imageNamed:@"MyClass"];
    NSAssert(img != nil, @"not found MyClass");
}
@end

Warning

要重现这个错误,不能打开字符串加密功能,因为加密后MyClass就不会共用了, 同时也不要改名那个叫做MyClass.png的图片,如果同时开启了图片改名,那么这个问题就不存在了

五.Debug/Release行为不一致引起的问题

这类问题大部分都是某些工程有差异化引起的,这时候只能用日志来确认问题所在,然后缩小调试范围在Xcode中调试

还是无法确认问题所在

这时候最好方式就是提供一个案例给我们的技术支持人员,我们将能快速发现和修复问题,给你发一个临时定制版本!