1、chunk的结构
代码中使用malloc申请size内存,其实申请了一块size+8的chunk,如上图所示,chunk可以分为chunk头和chunk内存(malloc返回的地址,图中的mem),而chunk头就是用来排查堆越界的重点工具。
chunk头为8字节,前4个字节表示的是上一个chunk的大小(当前面一个chunk未使用时),当前面一个chunk使用时,这4个字节就会被前一个chunk使用,也就不会表示前一个chunk的大小了。
后四个字节可以分为两部分,低三位是PMA标志位:
P标记位表示前一个chunk是否使用,1为使用,0为未使用(前4个字节才有效);
M标记表示从哪个内存区域获得的虚拟内存,1表示chunk是从mmap映射区域分配的,0表示从heap区域分配的。
A表示该chunk属于主分配区或者非主分配区,1表示为主分配区,0表示非主分配区。
高29位表示当前chunk的大小(字节),这个大小包括chunk头。
注意:当前chunk的大小不一定等于size+8;由于c库维护chunk的原因或者页对齐等原因。
2、如何查找被踩的chunk
根据core文件,我们可以知道有问题的chunk的地址,然后通过readelf -l core可以查看core文件的segment,可以知道出问题的chunk地址处在哪个segment,如图所示:
图中offset是文件偏移量,VirtAddr是虚拟地址,PhysAddr是物理地址,FileSize是当前segment大小;
具体可以参考《程序员的自我修养》
假如有问题的堆再这个segment,我们可以获得以下信息,如图所示:
那我们就可以从core文件的offset偏移处读取堆上数据,可以使用以下条件判断chunk是否被踩:
1.如果上一个chunk未使用,校验size of previous chunk(当前chunk头的前4字节)和上一个chunk的size(上一个chunk头后4字节的29位)是否相等;
2.chunk的size一定大于16
通过这样,我们大概就知道被踩chunk的地址了。
3、如何找到罪魁祸首
我们找到被踩chunk后,我们怎么找到罪魁祸首呢?这有可能是上一个chunk踩的,也有可能是上上个chunk踩,我们如何判断踩其他chunk的chunk是谁申请的呢?
1.通过被踩chunk上的内容是否有特殊字段,从而判断出是谁踩的,然后再去代码里读取。
2.可以封装malloc函数,在chunk头后再设置一个头,用来保存lr(谁调用的malloc)寄存器,如果是必现的问题,那就可以找到是谁申请的踩人的chunk了。
3.可以查找其他symbol包含这个chunk的地址,symbol指的是bss段或者data段的全局变量、静态变量等。
第3种方法的原理是代码中有个全局变量或者静态变量,指向了踩人的chunk。
第三种方法该如何使用呢?
首先我们使用readelf -S a.out,读取可执行文件的section,找到对应的.data、.bss,然后读取core文件的相应的section,看看里面的数据是否有被踩chunk的地址,如果有的话,可以找到.data或者.bss对应的虚拟地址,在gdb下使用info symbol 虚拟地址,就可以找到对应的变量,就可以确认了。
tip1:为什么chunk最小为16字节?
因为C库使用双向链表对chunk进行管理,所以需要8字节保存指针(指向前一个chunk,指向后一个chunk),再加上chunk头,正好是16字节。