您的位置:首页 > 其它

穷人版valgrind

2015-12-09 20:18 761 查看
http://www.douban.com/note/524982126/

valgrind是内存泄漏的万灵药,只要能用得上valgrind。但在实际当中不管是运行速度还是运行环境都受限太多。最近碰到一个第三方商业库的内存泄漏,既没有源代码,也没法在实验室的重现,更没法用valgrind,于是周末加班搞定了一个自制valgrind。

valgrind提供的信息其实就两条。对所有申请的内存,记录其 1. malloc的请求来自哪里;2. free的请求来自哪里。所谓的“哪里”其实就是backtrace。通过比对malloc跟free的统计,可以推断有哪些内存是应该被释放却没有被释放,再根据backtrace来判断是哪里的代码出了错。

在我们的产品里,虽然没有第三方库的代码,但是allocator是我们自己的。所以最原始的想法就是在allocator中,为每个malloc请求记录其backtrace。这个问题可以分解为两步:1. 如何捕捉backtrace 2.如何为每个malloc请求记录backtrace。

那么先来看怎么捕捉。在x86平台上这个问题相对简单,因为x86一般都用上了frame pointer,因此backtrace很简单,直接读stack即可。但MIPS(也就是我们的平台)相对麻烦一些,因为MIPS一般来说都不用frame pointer,也就意味着必须分析二进制代码才能找到backtrace。但如果要在运行时分析instruction则消耗太大,而且也存在着分析不精确的问题。

这个问题的解决办法是静态分析和运行时分析相结合。首先,做offline的静态分析。对整个.text段扫描,结合symbol table,分析所有的instruction,找到所有routine的entry, exit, stack size以及return address($ra)的相对位移(相对stack来说)。然后把这些信息记入一张表。在运行时,根据$pc值,可以迅速查到当前的routine的详细信息,从而可以直接找到return
address,进而分析上一个frame,直到遇到stack顶端。

那么第一个问题解决了。第二个问题是怎样为每个malloc请求记录backtrace。一个完整的backtrace是一个long []。一般来说深度在十几到二十几。也就是说一个完整的backtrace就要占用160bytes左右。这内存个开销实在太大,系统没法承受。注意到在一个特定的库当中,malloc的地方并不会很多,而且对malloc的call
chain也不会很多(约100多)。于是我们可以考虑用一个类似hashtable的数据结构来存贮所有的call chain,也即key为每个call chain的特征值,value为完整的call chain。这样一来,每个malloc请求只需要记录call chain的特征值即可,也就是4bytes。

总结一下:

1 静态分析生成所有routine的基本情况,包括entry, exit, stack size, $ra offset

2 运行时通过静态分析表和stack来找到backtrace

3 生成backtrace的特征值,将完整的call chain存入global的backtrace table,并将特征值存入每块chunk

这样一来,当怀疑内存有泄漏的时候,对arena做几次扫描,对比一下前后的chunk数量及其backtrace,就能找到内存泄漏的路径了 - 跟valgrind能提供的帮助一模一样。

从性能上来看,开销约在5%左右,完全可以接受。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: