继续讲程序调试。这回比较跳跃,一下就到比较特殊的领域了。

通用调试器

这里指通常我们用的那种下断点,或者单步跟踪,然后打印表达式的指的调试器。一般来说就是gdb或者vs自带的调试功能吧。如果在非windows平台学会gdb是非常有用的,其功能相当给力,我在windows下也用mingw带的gdb调试。另外在windows下还可以使用一些诸如windbgollydbg这类比较精通汇编调试的工具。相信我,用C/C++牵扯到汇编的时候还是不少的,而且不少问题直接看汇编一目了然,看代码还要猜半天。

当然恰当的使用assertunit test,以及log,可以极大的减少你浪费在一步一步跟踪你程序上的时间,所以不要怕花时间在这些提高程序正确性的工作上。至少在发现bug之后,考虑补充想关的内容,对你调试也是有好处的。

内存调试器

我上一回介绍了一些程序的bug的分类。其中最容易调试的应该属于普通的逻辑错误带来的bug了,一般来说都是可以稳定的复现的。比较麻烦的往往就涉及到内存问题了,一般是类似内存泄露(或者别的资源泄露,比如fd),读写越界,多次析构,访问已析构的内容,使用未初始化的变量等等。再一种是加上并发的情况,有时仅仅是在极其微小的机会下(发生竞争)才会导致问题暴露,破坏内存,最后几乎什么信息都没留下的崩溃了,简直一点办法都没有。

这些各种异常的case如果没有工具进行辅助,简直无从下手啊,所以找一些工具帮助你是很好的。

Valgrind

valgrind是一个工具集,里面有不少好东西,其中就有用来检测内存和并发问题的工具。使用Linux开发的同学都应该看一看。

valgrind检查内存错误的原理分为两个部分:

  1. 替换malloc/realloc和free等一系列底层的内存分配函数。使得自己可以决定分配策略,可以记录每块内存是什么时候分配给谁的(通过展开堆栈)。而且自己控制分配策略可以使得一个内存地址归还之后不会马上又被分配出去,只样就更容易发现访问已释放地址等bug了(如果再分配出去了,就不是已释放地址了,虽然是访问到别人的内容)。通过这里的记录,在程序结束的时候,valgrind会检查全局变量区域,每个线程的栈的区域,那些内存还有指针指向(实际上类似引用的关系,构成一个可达区域的概念)。而没有不可达的地方就是泄露了的内存,因为之前的记录,也知道是谁分配的了。

  2. 在虚拟机中执行你的程序,因为是valgrind自己解释你的机器码,所以它可以做检查看这条指令是否读或者写了无效的内存,是否使用了未初始化的值等等。这是一个比较大的有点。当然这也会带来一些缺陷,我们下面再看。

因为上面两个方案,使得valgrind效果非常好基本上可以发现程序中的大部分内存问题(当然要你在会出错的路径上执行过)。而且退出的时候还是顺便检查哪些fd是开着的,可以帮助你检查fd的泄露问题。实在方便,所以程序写出来,肯定要放valgrind跑一下才是放心啊。

但因为解释执行的原因,也会带来一些缺陷:执行的比较慢,这可能导致有些问题就不出现了(竞争相关),解释器实现复杂,可以有bug。恩我遇到过一次在多线程环境下跑valgrind程序挂了,因为一个线程环境恢复后寄存器被改了,额……

Google’s Heap Checker / Heap Profilter

这个可以算作是tcmalloc的附带功能。Heap Checker检测内存泄露的原理和valgrind一样,记录mallocfree的情况,但我印象最后是不管全局指针什么的,申请了需要全部释放(当然这个功能我不怎么用,一般靠valgrind就好了,反正那个肯定要跑的。)

Heap Profiler用来检测另外一类内存泄露:你申请了之后保存下来了,之后就忘记把它清理了。比如说一个服务器不小心把所有请求都存到一个数组中,但是没有清理,就会占用越来越多的内存。但是按照一般的思路,这些地址还保存着,可能你还要用,人家也不好算你泄露了。但你还要排查怎么办: HeapProfiler可以每隔一段时间就把当前的分配情况记录下来,你1G的时候dump一次,2G的时候dump一次,用它带的工具一diff,就可以看出多出的空间是谁申请的了。嗯不错吧。

Electric Fence

这个玩意思路很简单:改变malloc的逻辑

  1. 每次分配的东西都不连在一块,分配的内存前后的内存我们也要过来,并且设置为不可读写。类似于一个电网,你一越界,程序就触电退出了。

  2. malloc申请到的内存给程序前,先填上随机值,看你还敢使用不初始化的变量么。

这个玩意优缺点明显:快,对程序本身执行速度没太大影响,方便检查出竞争条件下出现的问题。问题在于要消耗大量的内存,如果机器内存不足或者程序本身的分配非常多,就有点不好用了。

这些工具的好处是用起来成本不高(链接某个库,或者用特殊指令启动程序,然后看报告就好),所以出了问题都来一遍都可以,看看谁能解决就好。