uncategorized

一次内存越界访问bug追踪记录

最近写毕业设计,想把结果简单的用OpenGL表现一下,结果程序总是莫名其妙的随机crash,在debug模式下,这一次crash在glfw的函数内部,下一次就crash在自己的代码里,再下一次又能好好运行(三次载入的是同一个版本的程序)。

刚开始我是懵逼的,因为有一段时间没写C++了,看到这样的崩溃不知道bug从何找起。而且这次遇到的错误信息貌似和以前都不一样,还有不少干扰信息

我决定先复制一下这个crash的信息,然后google一下看能不能找到原因,结果搜到的都是一些其他的问题产生的bug,和我的情况完全不同,只好作罢。

作为一个OpenGL/WebGL老手,我不是很敢相信是我调用OpenGL API出了问题,因为整个程序比较简单,,我出错的概率比较低。我对照官方的例子和我自己的旧代码检查了几遍,很快排除了opengl一些常见操作导致crash的可能,那么就只剩下自己的代码出问题了。

可是我怎么检查也发现不了问题在哪,于是我只好一个一个注释代码,直到不crash为止

这个工作相当烦,因为怀疑的点太多了!就在我快要抓狂的时候我默念一遍又一遍错误信息,突然灵光一闪:

每一次crash都有错误信息如下:

1
2
3
malloc: *** error for object 0x1001012f8: incorrect checksum for freed object
- object was probably modified after being freed.
*** set a breakpoint in malloc_error_break to debug

这里说已经释放的对象的校验和错误,也就是说已释放的对象可能已经被修改

那么什么时候才能修改到已经释放的对象?答案是野指针或者内存越界

想到这其实思路已经差不多出来了,首先我很确信程序中没有野指针,那么就只剩下内存越界了,检查几段数组操作的代码,发现是在操作Mesh的一段代码出了问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 这个函数的功能很简单,利用三角形顶点数据重新计算sharp surface的法线数据
void Mesh::generateSharpNormals() {
normals.resize(positions.size());
std::vector<glm::vec3> flat_positions;
std::vector<glm::vec3> flat_normals;
std::vector<unsigned int> flat_indices;
for (unsigned int i = 0; i < indices.size() / 3; ++i) {
// 这里直接把i当做index去访问顶点数组,因此导致越界
glm::vec3 u = positions[i * 3] - positions[i * 3 + 1];
glm::vec3 v = positions[i * 3 + 1] - positions[i * 3 + 2];
glm::vec3 normal = glm::normalize(glm::cross(v, u));
for (unsigned int j = 0; j < 3; ++j) {
flat_normals.push_back(normal);
flat_positions.push_back(positions[i * 3 + j]);
flat_indices.push_back(3 * i + j);
}
}
positions = flat_positions;
normals = flat_normals;
indices = flat_indices;
}

这段代码越界访问了堆区内存(访问的是vector内部的c array),导致后续的new校验失败,也就是说可能引起毫不相关代码的crash(但也不是完全不相关,被crash的代码应该紧接着这个越界操作不久,时间上局部性比较大)非常干扰程序员的判断

下面是正确的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void Mesh::generateSharpNormals() {
normals.resize(positions.size());
std::vector<glm::vec3> flat_positions;
std::vector<glm::vec3> flat_normals;
std::vector<unsigned int> flat_indices;
for (unsigned int i = 0; i < indices.size() / 3; ++i) {
glm::vec3 u = positions[indices[i * 3]] - positions[indices[i * 3 + 1]];
glm::vec3 v = positions[indices[i * 3 + 1]] - positions[indices[i * 3 + 2]];
glm::vec3 normal = glm::normalize(glm::cross(v, u));
for (unsigned int j = 0; j < 3; ++j) {
// 这里扩充了整个VBO
flat_normals.push_back(normal);
flat_positions.push_back(positions[indices[i * 3 + j]]);
flat_indices.push_back(3 * i + j);
}
}
positions = flat_positions;
normals = flat_normals;
indices = flat_indices;
}

这真的是非常低级的一个错误,不仅导致了程序崩溃,也会导致法线数据混乱。

需要注意的是,msvc 的debug mode会在vector的operator[] 检查越界,然而如果在mac使用的是libc++却不会

另外,以后再遇到incorrect checksum for freed object 这样的报错信息大概就知道是怎么回事了吧。。。不过出内存问题应该首先想到跑静态分析,能省下很多痛苦的debug时间

Share