iOS EXC_BAD_ACCESS的产生和调试

Crash大概可以分成两种:SIGABRTEXC_BAD_ACCESS

  • SIGABRT :是程序可以控制的崩溃,会因为应用做了系统不支持的事情而终断,简单来看,这种crash大半都是可追溯的,因为当crash发生的时候,会帮我们断点定位到可能存在问题的代码处
  • EXC_BAD_ACCESS :是全局堆栈crash,没有太多的信息可追溯,难以追踪、调试
实际场景有下面几种情况:

结论来自腾讯Bugly:

  1. 对象释放后内存没被改动过,原来的内存保存完好,可能不Crash或者出现逻辑错误(随机Crash)。
  2. 对象释放后内存没被改动过,但是它自己析构的时候已经删掉某些必要的东西,可能不Crash、Crash在访问依赖的对象比如类成员上、出现逻辑错误(随机Crash)。
  3. 对象释放后内存被改动过,写上了不可访问的数据,直接就出错了很可能Crash在objc_msgSend上面(必现Crash,常见)。
  4. 对象释放后内存被改动过,写上了可以访问的数据,可能不Crash、出现逻辑错误、间接访问到不可访问的数据(随机Crash)。
  5. 对象释放后内存被改动过,写上了可以访问的数据,但是再次访问的时候执行的代码把别的数据写坏了,遇到这种Crash只能哭了(随机Crash,难度大,概率低)!!
  6. 对象释放后再次release(几乎是必现Crash,但也有例外,很常见)。

参考下面的这张图:
iOS EXC_BAD_ACCESS的产生和调试_第1张图片
野指针表现形式.png

常见的调试方式:

1.NSZombie Objects --- 僵尸对象

原理:对于已经释放的对象,系统会把它标识为僵尸对象,当给这个僵尸对象发消息的时候,会出现Crash,通过系统打印的log信息我们就能够进行定位。

iOS EXC_BAD_ACCESS的产生和调试_第2张图片
开启Zombie Objects

像下面这样的:

static NSMutableArray *array;
- (void)viewDidLoad {
    [super viewDidLoad];
    array = [[NSMutableArray alloc] initWithCapacity:5];
    [array release];
}
- (void) viewWillAppear:(BOOL)animated {
    [array addObject:@"Hello"];
}

运行起来之后你就会收到一条像这样的消息:

-[__NSArrayM addObject:]: message sent to deallocated instance 0x6557370

从log中可以看到,给已经释放的数组发送了一条消息。有了这些信息我们就能够比较快的进行定位代码,解决问题。(如果发生了全系统栈Crash,很多时候这个就没什么用了)

2.Address Sanitizer-地址消毒器(翻译过来是这样的)

原理:当程序创建变量分配内存时,将此内存后面的一段内存也冻结住,标识为中毒内存。如图所示,黄色是变量所占内存,紫色是冻结的中毒内存。


内存示意图

当程序访问到中毒内存时(越界访问),就会抛出异常,并打印出相应的log信息。如果变量释放了,变量所占的内存也会标识为中毒内存,这时候访问这段内存同样会抛出异常(访问已经释放的对象)。

像这样的:

char *buffer;
- (void)viewDidLoad {
    [super viewDidLoad];
    unsigned size = 11;
    buffer = malloc(size);
    sprintf(buffer, "Hello World!");
    NSLog(@"%p, %s", buffer, buffer);
}

运行起来之后:


捕获到的内存越界

同样的代码,要是使用Zombie Objects选项来检测的话,是很难被发现的。因此对于 Zombie 来说 ,Address Sanitizer 拥有着更加强大的捕获能力,它们虽然功能相似,但是还是存在差异的:

iOS EXC_BAD_ACCESS的产生和调试_第3张图片
Zombie VS Sanitizer

从功能上看,貌似 Sanitizer 能干一些 Zombie 所不能干的事,但是 Sanitizer 还是存在弊端的:

  • 使用 Address Sanitizer 除了分配对象的内存之外,还需要额外的内存,这会导致App内存大量增加,用起来有可能会比较卡。(仅仅考虑Debug环境,线上环境你勾选这个?苹果大爷不会让你通过审核的)
  • Address Sanitizer 可能会没有log(官方的说法是显而易见的错误),不过会在访问中毒内存的代码处断住。

3.Hook对象的dealloc方法

iOS监控-野指针定位

该文章提出,我们可以通过Hook根类的dealloc方法将它重定位到我们Proxy对象的dealloc方法中来,这样当某个对象被释放的时候就会调用Proxy的dealloc方法,在该方法中让对象的isa指针指向Proxy对象,同时监听该对象消息转发的过程,如果接下来Proxy对象仍然能够收到消息的话,即抛出异常。同时为了避免内存泄露,在延时30s之后,将对象重定位回原来的类,并调用该类的dealloc方法。这样就完成了一次监听工作。


iOS EXC_BAD_ACCESS的产生和调试_第4张图片
image
  • 需要注意的是,我们选取的Proxy对象要和准备监听的对象结构是对齐的,这一个原则是我们所不能违背的。

你可能感兴趣的:(iOS EXC_BAD_ACCESS的产生和调试)