最近在编程时遇到一个bug,通过gdb定位到printf函数,却怎么也找不到原因。从网上这篇博客中发现了问题所在:http://blog.csdn.net/smstong/article/details/50523120
还有一篇,是作者给出的实例,和我遇到的问题一模一样:http://blog.csdn.net/smstong/article/details/50767380
0x00 遇到的问题
在C语言网络编程时,功能是打印IP地址和端口号,如下,乍一看并没有什么错误。
printf("SRC: %s:%d\n", inet_ntoa(ipheader->ip_src), ntohs(tcpheader->th_sport));
printf("DST: %s:%d\n", inet_ntoa(ipheader->ip_dst, ntohs(tcpheader->th_dport));
可是,程序一运行,就报segment fault,通过gdb调试发现ipheader也并没有是NULL或者不正确的值,这是为什么呢?
0x01 原因分析
通过上面那篇博客链接,发现是inet_ntoa()这个函数调用出了问题。那篇博客中讲到,C语言编译器对于没有声明原型的函数,通通作为返回类型为整数的函数来处理,参数的类型则由调用的实参自动提升后确定。例如:
non_exit_function(12, 'c');
在编译时,这个没有事先声明的函数将被当作如下形式:
int non_exist_function(int, int);
这里,'c'被提升为了int类型。
所以,在我的代码中,
inet_ntoa(ipheader->ip_src);
被当作这样的函数原型处理了:
int inet_ntoa(addr_in);
然而,实际上这个函数原型应该是:
extern char* inet_ntoa(struct in_addr __in);
这样一来,返回值本来是指针类型,却被截断成了int类型。对于32位系统来说,由于指针类型和int类型长度相同,都是32位,所以恰好不会出现截断的情况,编译运行也是正常的。
然而在64位系统下,char *是64位,int仍然是32位,所以就出现了截断问题。
由于printf(char *, ...)是个变参函数,所以调用它时,编译器不会检查可变参数的数据类型,而是按照实参类型进行准备参数入栈。相当于:
printf("from %s:%d", 123, 80);
指示符%s对应的第一个参数类型却是int,从而导致printf()函数内部在通过va_arg()提取参数时产生错误,最终导致段错误。
这种段错误非常非常隐蔽,编译时又不会报错。所以出现之后并不能很容易的找出来。如果我们把代码改成这样:
char* sip = inet_ntoa(ipheader->ip_src);
printf("SRC: %s:%d\n", inet_ntoa(sip), ntohs(tcpheader->th_sport));
编译器就会给出警告信息:
warning: initialization makes pointer from integer without a cast [enabled by default]
所以说,虽然printf函数内加上函数调用,代码看起来只有一行,却埋下了编译器不会发生警告的种子。所以,将这样的语句分开写,该函数调用就函数调用,该传参就传参,是种良好的编程习惯。
0x02 解决方法
GCC有个开关选项:-Wimplicit-function-declaration
。只要把这个选项加上,编译器就会对所有的隐式声明函数的调用发出警告,非常方便。
$ gcc -Wimplicit-function-declaration -o tcpsyndos tcpsyndos.c -lpcap
tcpsyndos.c: In function ‘main’:
tcpsyndos.c:96:9: warning: implicit declaration of function ‘inet_ntoa’ [-Wimplicit-function-declaration]
printf("DST IP: %s\n", inet_ntoa(iphdr->ip_dst.s_addr));
^
tcpsyndos.c: In function ‘TCP_RST_send’:
tcpsyndos.c:216:5: warning: implicit declaration of function ‘close’ [-Wimplicit-function-declaration]
close(rawsocket);
^
如何解决这种警告呢?将含有函数原型声明的头文件包含进来就可以了。从编译器发出的警告看来,我缺少了两种头文件(当然,本人忘记了加这两个头文件非常愚蠢0_0)。
#include <arpa/inet.h>
#include <unistd.h>
网友评论