一、CppCrash 常见问题分类与原因
1、空指针解引用 NULL pointer dereference
形如 SIGSEGV(SEGV_MAPERR)@0x00000000 或 cppcrash日志的Register中打印的r0,r1 等传参寄存器的值为0时,应首先考虑调用时是否传入了空指针。
形如 SIGSEGV(SEGV_MAPERR)@0x0000000c 或 cppcrash日志Register中打印的r1 等传参寄存器的值为一个很小的值时应考虑调用入参的结构体成员是否包含空指针。
2、程序主动终止SIGABRT
一般为用户/框架/C库主动触发,大部分场景下跳过C库/abort发起的框架库的第一帧即为崩溃原因,这里主要检测的是资源使用类的问题,如线程创建,文件描述符使用,接口调用时序等。
3、SIGSEGV无效内存访问
(1)多线程操作集合,std库的集合为非线程安全,如果多线程添加删除,容易出现SIGSEGV类崩溃,如果使用 llvm-addr2line 后的代 码行与集合相关,可以考虑这个原因。
(2)不匹配的对象生命周期,比如使用裸指针(不含有封装、自动内存管理等特性的指针)保存sptr类型以及shared_ptr类型,会导致内存泄漏和悬空指针问题。裸指针是指不含有封装、自动内存管理等特性的指针。它只是一个指向内存地址的简单指针,没有对指针指向的内存进行保护或管理。裸指针可以直接访问指向的内存,但也容易出现内存泄漏、空指针引用等问题。因此,在使用裸指针时需要特别小心,避免出现潜在的安全问题;推荐使用智能指针来管理内存。
4、use after free问题
返回临时变量、野指针:比如返回栈变量的引用,释放后未置空继续访问。
# include <iostream>int& getStackReference() {int x = 5;return x; // 返回 x 的引用
}int main() {int& ref = getStackReference(); // 获取 x 的引用// x 在 getStackReference 函数返回后被释放// ref 现在是悬空引用,继续访问会导致未定义行为std::cout << ref << std::endl; // 试图输出 x 的值,这是未定义行为return 0;
}
5、栈溢出
如递归调用,析构函数相互调用,特殊的栈(信号栈)中使用大块栈内存。
# include <iostream>class RecursiveClass {
public:RecursiveClass() {std::cout << "Constructing RecursiveClass" << std::endl;}~RecursiveClass() {std::cout << "Destructing RecursiveClass" << std::endl;// 在析构函数中递归调用RecursiveClass obj;}
};int main() {RecursiveClass obj;return 0;
}
创建一个 RecursiveClass 对象时,它的构造函数被调用。销毁这个对象时,它的析构函数被调用。在析构函数中,创建了一个新的RecursiveClass对象,这会导致递归调用,直到栈溢出。递归调用导致了无限的函数调用,最终导致栈空间耗尽,程序崩溃。
5、二进制不匹配
通常由ABI(应用程序二进制接口)不匹配引起,如自己编译二进制与实际运行的二进制接口存在差异,数据结构定义存在差异,这种一般会产生随机的崩溃栈。
6、踩内存
使用有效的野指针,并修改了其中的内存为非法值,访问越界,覆盖了正常的数据这种一般会产生随机的崩溃栈。
7、SIGBUS
SIGBUS (Aligment)考虑对指针进行强转之后地址是否已经处于非对齐状态。