起因是给之前写的玩具ftp服务器加一个用户密码验证的功能,服务器本身用的是线程池的方案,密码验证需要用到Linux的crypt()这个函数。网上找了下示例程序,感觉挺简单的,照着写了下也一切正常,就老老实实的把这个功能加了进去,本以为这就完事了,只剩下编译执行就ok了。
这时出现了一个小插曲,编译的时候gcc给了一个warning:
我这时觉得warning算什么,又不是编译不成功,能执行就好嘛,根本就不需要放在心上,于是忽略后直接执行了,这时候出现了神奇的一幕:
竟然出现段错误core dump了!这是我万万没想到的,实在不明白怎么会出现访问非法地址的问题。不过既然问题出了,也只能慢慢找原因了。用gdb查出错误情况如下:
除了看出来系统发了一个段错误的信号外,其余的完全没看懂,也没看出到底是什么原因造成段错误的。但是能看出来程序问题出现在这一行
strcmp()这个函数我用了没有100次也有80次,从来没想到这里竟然会出现段错误。为了搞清楚到底是什么问题,单单调用了下这个函数,而没有将其的返回值再与0比较,如下
这时再编译执行:
竟然没问题了!然后我又试了用一个int型的变量接收strcmp的返回值,或者直接打印,都一样会出现core dump,这真是太奇怪了!
实在想不到办法了,就用了最老土的调试方式,打印,打印一切可以打印的值:
再执行,结果如下:
前三个都正常打印,到最后一个出现了core dump! 好消息是错误范围缩小了,坏消息是我依然不知道为什么crypt()函数会出现core dump, 明明实例程序运行了那么多次都一点问题没有。会不会是之前程序写的有问题,导致参数是错误的?于是干脆把参数写死,结果还是一样,core dump!同样的程序换到示例程序中,就一切正常。这时我基本上崩溃了,实在是想不到问题出现在哪里,同样的代码写在不同的文件里面,竟然结果完全不一样,我甚至怀疑是不是系统出问题了。叫了好几个人帮我一起看这个神奇的bug,都是毫无头绪。
实在没办法了,只能使用杀手锏,直接看手册,man crypt:
咦?crypt_r()是什么鬼,而且这个还需要crypt.h这个头文件。联想到我用的是线程池,难道跟线程完全相关?于是接着往下看
“crypt() is a reentrant version of crypt()”,reentrant是可重入的意思,果然跟线程有关!将头文件crypt.h包含进去,再编译运行,警告没了,core dump也消失了,问题解决!
总结一下:示例程序没任何问题估计是因为单线程,而我的ftp服务器采用的是线程池方案,gcc在多线程的情况下发现了crypt()这个函数不是线程安全的,给出了警告,我却没有理会。后来将crypt.h这个头文件包含进去之后,我猜想虽然没有直接在程序中写明使用crypt_r()这个线程安全的函数,但是链接的过程中由于源程序中包含了应有的头文件,链接的可能是crypt_r(),而不是crypt(),至此问题解决。
教训就是以后再也不敢不理会编译器的警告了。