使用 electric-fence 调试内存越界

时间:2021-09-13 10:59:58
使用 electric-fence 调试内存越界

2012-12-14 16:40:53| 分类: linux使用 |举报|字号 订阅
在嵌入式上开发底层软件经常会遇见莫名其妙的问题,尤其是在使用大量的第三方库时,这些问题大多跟内存相关。但受嵌入式的限制,valgrind等在pc上常用的工具不能在嵌入式下运行,如何调试内存越界等复杂问题呢? electric-fence 是个不错的工具。

最近项目调试时遇到一个很棘手的问题,想象描述如下:

程序莫名奇妙的crash,coredump的地方出现几处且都是不太可能出现的,例如dlopen函数中的malloc;但大都跟malloc相关;
调整下程序结构,例如将经常出现coredump函数的调用提前,偶尔不会coredump的;
有时在易于coredump代码前随机分配点内存可能会出现不coredump的奇迹
利用gdb跟踪,栈被破坏,看不出信息
由此想象,跟同事分析基本是内存使用出了问题而且基本可能是内存越界访问导致的。 但由于coredump的地方存在多处,不是第一现场,而且程序很复杂,使用了很多开源软件,且该程序只能在ARM上运行,不能使用valgrind等工具。

我们分析,解决该问题的首要目标是找到第一现场。electric-fence就是这样一个好帮手,下载地址:http://perens.com/FreeSoftware/ElectricFence/electric-fence_2.1.13-0.1.tar.gz 。 使用过程很简单,解压后make会libefence.a 静态库,然后将在自己的应用程序链接该库即可(也可以使用其动态库方式,参见其README.debian 文件)。

链接该库后程序一运行即崩溃,提示“Allocating 0 bytes, probably a bug.”。 查看代码,这是由于malloc(0)导致即malloc传递的size==0,可通过 export EF_ALLOW_MALLOC_0=1 解决。 再次运行程序,还是崩溃,这时如果有gdb可以使用gdb跟踪,也可运行:

ulimit –c unlimited

产生coredump文件来跟踪。 利用gdb,bt查看栈信息可以找到第一现场,即程序首先越界访问处。经过多次调查,我们程序的问题原因如下:

程序include了 第三方头文件(这个第三方头文件又引用了很多头文件 ), 在某个头文件中一个类包含的一个私有数据成员是struct类型, 但该类型定义被宏加以控制:如:

struct Condition {

//……

#ifdef HAVE_PHTHEADS

// define data

#else

//define data

#endif

}
显然是否定义HAVE_PHTHEADS影响Condition 结构的大小;而我们引用该头文件时没有定义HAVE_PHTHEADS 宏,而调用的第三方库在编译时定义了该宏,因此导致内存越界。

electric-fence 库中还可以设置如下宏:

EF_DISABLE_BANNER 是否显示electric-fence 欢迎信息
EF_ALLOW_MALLOC_0 是否允许malloc(0),即分配size=0的内存区
EF_PROTECT_FREE 控制被释放(free)的内存是否能被再次使用,若>0,则该片内存不可再次被分配
EF_ALIGNMENT,内存对齐, malloc() 返回地址对齐
EF_FREE_WIPES : 对于被释放(free)的内存是否wipes,实现为使用0xbd填充释放的内存, 设置它易于检测某片内存是否被释放
EF_PROTECT_BELOW 该标志很重要。内存越界包括两类,一类是高地址越界,即地址增加过程越界;另一类是低地址越界, void * p = 0x 123; 却访问了 0×122. 默认情况下测试时为高地址越界情况。 正常情况下两类都需要测试: 采用如下方法:
* gdb 调试程序, 解决所有coredump

* export EF_PROTECT_BELOW = 1

* gdb 调试程序, 解决所有coredump

下面谈谈为什么electric-fence 能获取第一现场,即一越界立即coredump。electric-fence 原理如下:

重写malloc、free、realloc、calloc、valloc 函数,对于C++ 应使用 extern “C”; C 使用 extern 来导出符号;以确保应用程序使用 electric-fence 的malloc等函数
系 统调用malloc会多分配一个page(注意是 虚存) 然后根据是否EF_PROTECT_BELOW 设置来决定返回新分配内存区的高地址(或低地址)给user;同时,调用 mprotect((caddr_t)address, size, PROT_NONE) 来设置多分配的page页为不可访问;这样一旦程序越界便会进入不可访问区间,因此coredump。同时,EF_ALIGNMENT 也会影响到该处。
electric-fence 内存管理的实现基本思路为:a. 每次向操作系统申请MEMORY_CREATION_SIZE(当前为1M)字节内存,使用mmap完成; b. 存在一片内存区 用于存放 所有 slot 信息, slot 用于管理被分配的内存信息,如返回给用户的地址以及其内部实际地址、size等; c. 分配时从当前slot 中查找是否有能满足分配需求的内存区,这里注意的是每次实际分配的内存区大小比用户请求的至少要大 一个page,大致公式为: real_size = user_size + (alignment – (user_size % alignment )) + page_size; 若存在则拆分内存区,修改相关slot信息 d. free 内存时不会将内存交给操作系统,而是设置free或者标志PROTECTED,PROTECTED意味着该片内存不会被再次分配,由 EF_PROTECT_FREE 控制。 若设置了EF_FREE_WIPES,还会用0xbd填充用户free的区域。