内存泄露和资源泄露是C\C++程序员不得不面对的一个问题,随着程序越来越大,稍不留神就可能在程序中留下了内存泄露的隐患,这个问题很多人可能觉得没什么,就泄露点内存而已,只要程序逻辑没问题,但是如果程序运行时间很长或者泄露的内存很大的话,会导致系统资源占用过多,严重的也可能使得程序崩溃。
    前段时间为了分析程序中是否存在内存泄露问题,使用了Devpartner工具来进行分析,但是该工具要对程序进行重新装载,插入很多的监测代码,导致程序运行速度相当慢,效率也很低,一次运行下来就得个几天时间,而且也只能对操作系统管理的内存来进行分析,但是如果是自己申请的一大块内存堆中发生了内存泄露的话,工具也没办法查出来了。
    于是准备自己弄一个内存监测的,其实蛮简单的东西,在封装的内存申请函数中插入代码来保存分配的指针和大小,在释放的地方则把这个指针删除掉,最后没有被删除掉的指针则是可能存在内存泄露的地方了,但是目前为止只能判断是否发生了内存泄露,接下来就是要把发生内存泄露时刻的调用堆栈打印出来,这样才能方便的跟踪问题到底发生在哪里。
    像VC、GDB,都提供调试过程中打印调用堆栈、变量值等功能,例如VC设置为debug模式的话,则会生成一个pdb的文件,这是程序数据库,保存着调试和项目状态信息,既然VC可以得到调试信息的话,那么从技术角度上来说的话,应该可以编程来实现得到调试信息的。
    接下来首先实现Windows下面的获取调用堆栈,查找MSDN,有一个StackWalk64函数可以获取堆栈的内容,具体的参数可以查看MSDN,里面有一个主要的输入输出参数就是LPSTACKFRAME64 StackFrame,STACKFRAME64结构表示了堆栈中的一个frame,首先需要对该参数进行初始化,也就是首先要获得当前函数堆栈的一些信息,我们知道函数调用的时候,会首先保存调用点的一些信息,从而在函数结束的时候可以返回到调用处继续执行,通过一小段汇编代码,我们可以很容易得到函数堆栈中的一些基本信息,利用这些信息初始化StackFrame参数,然后调用StackWalk64函数,该函数就会将函数地址的偏移量记录下来,接下来就是要将这些偏移量转换为实际的函数名、文件名、行号等信息,Windows API提供了另外两个函数SymGetLineFromAddr,SymGetSymFromAddr。前一个函数通过偏移量得到函数所在文件及行号,后面一个函数通过偏移量得到函数的实际名称,StackWalk64函数每次调用完后会自动的设置frame参数,下次再调用则得到的是上一层的函数信息,以此类推,通过判断frame的返回函数地址偏移量是否为0则可以用来判断堆栈结束。
    在Windows下我们可能需要手动添加某些代码到分配和释放内存的地方,感觉更加智能化一点的话应该可以使用钩子来实现。
    Linux下获取调用堆栈相比而言更加简单,有个backtrace函数和backtrace_symbols函数,一次就搞定了。