上一节,我们已经知道split_config.arm64_v8a.apk
背后的含义,我们再整理下崩溃日志:
# GLThread 1371(29457) SIGSEGV(SEGV_MAPERR)
#00 pc 0000000000aa3f58 split_config.arm64_v8a.apk
#01 pc 0000000000aafb84 split_config.arm64_v8a.apk
GLThread 1371(29457)
表示这是一个OpenGL相关的线程,线程ID为1371
,进程ID为29457
,这里又牵扯到线程和进程的概念,涉及到fork
、thread
相关的编程知识,这里就不再展开了。
我们又发现一个知识盲区,就是这个SIGSEGV(SEGV_MAPERR)
,很有必要解读下这块的知识点。
SIGSEGV
当程序尝试访问超出其分配内存区域、未分配内存区域的地址时,操作系统会发送这个SIGSEGV(Segmentation Violation)信号给程序,以表明发生了访问错误。
而括号里面的SEGV_MAPERR
,准确说是一个错误码,告诉你是因为什么原因导致的异常,不过这个错误码非常晦涩难懂,只能给你一个排查方向。
接下来就详细介绍几个常见的错误码:
SIGABRT
产生这个错误的可能原因:
1. assert
在cocos2dx中有一个Marco:
CCASSERT(false, "xxx");
通常我们是用来检查某些情况是否符合预期,如果不符合,程序就会主动断言挂起,这个Marco的内部实现使用的就是assert
函数。
一般都是用在debug模式,在发布release版本的时候,Marco是一个空实现。
2. abort:
abort
函数会主动终止程序,同时发送信号SIGABRT
,cocos2dx中也有使用该函数。
3. 重复释放指针,或者释放野指针
这种指针的问题最难排查了。
SEGV_ACCERR
通常是因为程序试图访问一个无权访问的内存地址,例如尝试读取受保护的内存区域或未分配的内存区域。
这种错误通常发生在程序访问数组越界或使用已释放的指针时。
SEGV_MAPERR
通常是因为程序试图访问一个不合法的内存地址,在遇到该错误时,一般会提供相关的错误信息和堆栈跟踪,可以利用这些信息定位错误所在的代码行,并进行排查和修复。
这也是我们经常会遇到的崩溃错误码,排查方向为:
使用未初始化的变量
:这个属于c++编码习惯问题,成员变量一定要在构造函数进行初始化。野指针
:多个指针指向同一块内存,当这块内存释放时,需要同步给相关的指针,否则就会出现野指针。数组越界
:本质还是指针。堆栈溢出
:一般我们不会出现这种情况。
具体代码场景不是很好描述,我这里有一种情况可以参考理解下:
class A{
public:
void func(){}
};
class B{
public:
B(A* ptr){
this->_a = ptr;
}
void callA(){
this->_a->func();
}
private:
A* _a=nullptr;
};
B* b=nullptr;
void test()
{
A a;
b = new B(&a); // a是一个局部变量,test函数结束就会被回收释放,但是b还在持有
}
test();
b->callA(); // 此时a不为nullptr,但是调用a->func()就会crash
对象在使用时已经释放,通俗点说:对象A用局部变量初始化,分配在栈上,函数结束就提前释放了。赋值给其他对象B,B在其他函数引用A时,出现崩溃,这种问题排查起来也很坑。
小结
到这里我们对SIGSEGV
有了一个大概的认识,SIGSEGV(SEGV_MAPERR)
是我们遇到最多的崩溃信号量,当看到这个SEGV_MAPERR
,大部分情况下都是内存管理的问题,访问了无效的内存导致的。