副标题:根据指针获取malloc空间大小
前言:
有时候程序出现coredump时,需要知道是所申请的内存空间被程序自身越界访问导致,还是双次释放导致。但不太好排查,这里提供一种思路。
一. 为什么free/delete不需要知道所申请空间大小?
C或C++在申请空间时,实际上会申请比预期更多的空间,一部分是因为对齐需要,也可能是刚好有那么一块内存可以满足条件,还有另一个原因是在所申请内存首部存了额外的信息占据了一点空间,该首部其中一部分信息便是该指针对应申请空间的大小。
我们可以看下Glibc实现下该首部的组成结构,其中存储申请空间大小的信息便是mchunk_size变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
struct malloc_chunk { INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */ INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */ struct malloc_chunk* fd; /* double links -- used only if free. */ struct malloc_chunk* bk; /* Only used for large blocks: pointer to next larger size. */ struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ struct malloc_chunk* bk_nextsize; }; |
当然,不同的库实现方式可能不同。
二. 由于库版本存在差异,怎么确定所申请空间大小信息在首部前第几个字节
1.复制如下代码到 chunkSizeTst.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <iostream> #include <vector> #include <string> #include <algorithm> #include <map> #include <queue> using namespace std; int main() { char *buf = new char[1024]; char *p = buf; for( int i=0; i<32; i++ ) printf("index:%d \tsize: %ld\n", i, *(size_t*)(p-i)); delete [] buf; return 0; } |
2.编译
1 2 3 |
g++ chunkSizeTst.cpp -g -O2 |
3.运行
1 2 3 |
./a.out |
可能会发生segmentation fault,但无所谓,能拿到我们要的结果即可
4.分析输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
index:0 size: 0 index:1 size: 0 index:2 size: 0 index:3 size: 0 index:4 size: 0 index:5 size: 0 index:6 size: 0 index:7 size: 4 index:8 size: 1041 index:9 size: 266496 index:10 size: 68222976 index:11 size: 17465081856 index:12 size: 4471060955136 index:13 size: 1144591604514816 index:14 size: 293015450755792896 index:15 size: 1224979098644774912 index:16 size: 0 [1] 32742 segmentation fault ./a.out |
从中可以看到,当index=8时,size=1041最接近我们所申请的空间大小,所以在作者所使用的相关工具及库版本的环境下,所申请内存地址往前8个字节处,存储的便是所申请空间的大小信息
三. 排查首部空间大小信息是否被越界修改
从作者目前的经验看,释放某块内存导致的崩溃一般有两种情况:
- 内存块首部存储的空间大小信息被意外修改,free释放内存时检测到错误
- 重复释放同一块内存
这里要排除1的情况,可以通过步骤二来验证,先确定空间大小信息存储在内存块的前几个字节,再通过gdb调试coredump文件,打印出对应地址存储的数据,看打印出的空间大小跟我们申请的大小是否一致,如果基本一致,说明该地址的数据没有被覆盖. 这个排除后,接下来就要看代码排查出问题2了。
补充下gdb怎么打印某个地址下存储的数据值:
1 2 3 |
p *(size_t*)0x123456 |
这里需要转换一下地址类型, 目的是告诉gdb取值时只取size_t长度的数据即可, 最前面的*是指针解引用, 告诉gdb打印出对应地址下存储的值, 而不是打印地址本身.