一个c语言动态库开发的惨痛教训,作为警示,希望大家不要碰到这类问题。
背景
交代下背景,最近的一个项目,apache module开发,使用的是c语言。这个其实比较无奈,apache mod_dav.h中声明的dav_error结构体,其中一个字段就是const char *namespace,这不是鄙视C++吗?
虽然有方法可以绕过,搞成C++的,最后还是决定用c语言开发。
悲剧
悲剧就是这样发生的。
调试发现模块crash频繁,用gdb调试,结果让人迷惑,在pool上分配的地址竟然out of bound,诸如如下面的代码。
ptr = apr_psprintf(r->pool, "<lp%d:href>%s</lp%d:href>", ns, ptr, ns);
resp = apr_pcalloc(pool, sizeof(*resp));
resp->href = apr_pstrdup(pool, uri);
...
而且有时候程序crash时,发现函数的入栈参数明显有问题,参数错位。折腾了两天也没查出是什么原因。怀疑是pool被程序破坏了,但该pool上分配的其他地址都很正常,检查n+1编程序也没发现什么问题。搞的相当于郁闷。本来感到apr的内存分配系统还是挺不错的,不过现在想想,pool要是真的不小心被程序破坏了,那也是相当难以查出来的。
发现原因
按照pool破坏的思路折腾了几天,实在是没找到头绪。这个问题原因的发现还是听偶然的,就是仔细检查编译警告时,发现编译器对于apr_xxx的string系列操作函数发出警告:
warning: assignment makes pointer from integer without a cast
我算是比较粗心的,对编译错误一般不仔细看,发现这个警告要归功于我的一位可爱的同事。
当时我看到这个警告就怀疑,是不是这个原因引起的crash。阴霍的天空无疑透出了一丝光亮。
相关.h文件没有包含导致编译器不认识这些库函数,如同编译warning所显示的那样:于是c语言就会把指针cast为int。由于编译机器是64位linux,如果把分配的地址空间cast到32位的int型数据,那么对于超过4G的地址空间来讲,这个结果就是一场灾难。
通常情况,如果函数没有定义,本来在运行时会报链接错误。但是这些是库函数,所以能够正常链接。
添加头文件,重新编译运行。程序不再crash。
十年被蛇咬。马上动手把所有的编译warning都修复掉。怕了...
教训
不要忽略编译warning
编译条件要严,像函数没有声明这种情况,应该设置为error,而非warning。我们的编译框架不够严格。