X86_64上没有写C函数声明导致的BUG(warning: initialization makes pointer from integer without a cast)
Posted sole_cc
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了X86_64上没有写C函数声明导致的BUG(warning: initialization makes pointer from integer without a cast)相关的知识,希望对你有一定的参考价值。
我的博客:http://blog.striveforfreedom.net
Table of Contents
1 简介
最近修改一个用C写的开源程序,需要加几个函数,因为偷懒没写函数声明,导致程序崩溃,最后花了很多时间才查明原因,原来是没有写函数声明惹的祸。感觉这个BUG在X86_64上还挺有代表性,因此这里把它记录下来。
2 导致崩溃的代码及解决思路
2.1 导致崩溃的代码
导致崩溃的代码简化后只大致这个样子的:
<span style="font-family:Microsoft YaHei;">//foo.c <strong>#include</strong> <stdlib.h> <strong>#include</strong> "bar.h" <strong>static const</strong> char* value = NULL; void <strong>set_value</strong>(<strong>const</strong> char* p) value = p; <strong>const</strong> char* <strong>get_value</strong>() <strong>return</strong> value; int <strong>main</strong>(int argc, char* argv[]) char p[] = "abcd"; set_value(p); failed_func(); <strong>return</strong> 0; //bar.c <strong>#include</strong> "bar.h" //简单起见,就没给出bar.h了,该文件包含函数failed_func的声明。 char <strong>failed_func</strong>(void) <strong>const</strong> char* p = get_value(); <strong>return</strong> *p; //进程崩溃 </span>
程序执行每次执行到failed_func函数,都会在注释的那一行崩溃。
2.2 解决思路
乍一看,这几个函数很简单,根本看不出有什么问题,为什么会导致崩溃呢?用gdb在函数failed_func上下一个断点,再step进函数get_value里,发现返回值就是是当初用set_value设置的值,然而等get_value函数返回再查看指针p的值时,发现指针p的值却不是当初设置的那个值了,这就很奇怪了,一个简单的函数调用却有如此怪异的结果,当时在C语言层面实在看不出有什么不对的地方,于是查看汇编代码,用set disassemble-next-line on,再一次进入函数get_value里,发现该函数设置好寄存器rax的值就直接返回了,rax的值就是当初用set_value设置的值,这个函数显然没有问题(该函数的返回值存在rax里)。回到函数failed_func里,紧接着调用函数get_value的callq指令之后的是一条cltq指令,该指令的作用是对eax的值进行符号扩展(sign-extend),结果存在rax里,这就导致rax的高32位值被设为全1或全0了(取决于eax最高位的值),再之后的指令是访问rax所指内存的指令,这条指令直接导致了崩溃,因为rax的值已经不是get_value所设置的值了(我们这个例子中rax的高32位全被置为1了)。这里的关键是cltq指令,为什么gcc会产生这么一条指令呢?原因在于C89有一个隐式声明规则(implicit declaration),当需要调用一个函数但找不到函数原型时,编译器会提供一个隐式声明,该隐式声明会假定函数返回值类型为int,C99已经去掉了这一规则,要求函数调用必须有函数声明,但gcc可能为了兼容老代码,并没有强制执行C99,只是给出了一个警告。在我们这个例子里,gcc找不到函数get_value的原型,于是假定函数get_value的返回值类型是int,因为X86_64上int是32位的而指针是64位的,于是把函数get_value返回值赋给指针p就相当于把一个32位的有符号数赋值给一个64位的无符号数(指针值是无符号的),C语言规定当赋值表达式两边类型不相同时,等号右边的类型会转成等号左边的类型(当然是在可转的前提下),于是32位的有符号int被转换转成64位的无符号数,于是编译器便生成了符号扩展指令cltq。这段代码在X86上不会崩溃,因为X86上int和指针都是32位的,编译器不会产生符号扩展指令。
设计上面这段示例代码的时候还有一个小小的trick,第一次设计这段代码的时候,在main函数里,传给函数set_value的参数我是这么定义的:
<span style="font-family:Microsoft YaHei;"><span style="color: rgb(0, 255, 255); "><strong>const</strong></span> <span style="color: rgb(0, 255, 0);">char</span>* <span style="color: rgb(255, 255, 0);">p</span> = <span style="color: rgb(0, 255, 0);">"abcd"</span>; </span>
但如果这样的话程序是不会崩溃的,原因在于字符串常量通常和代码一起放在代码段,而通常代码段一般会加载在较低的内存地址(通常会小于0x10000000),于是cltq指令执行之前rax值高32位就是0,执行之后rax的高32位还是0,rax值没有改变,程序也就不会崩溃,后来想到栈一般位于较高的内存地址,于是就将代码改成:
<span style="font-family:Microsoft YaHei;"><span style="color: rgb(0, 255, 0);">char</span> <span style="color: rgb(255, 255, 0);">p</span>[] = <span style="color: rgb(0, 255, 0);">"abcd"</span>; </span>
因为栈的地址通常会大于0x10000000,执行cltq指令之后rax值的高32位全为1,这时的rax值代表着一个很大的虚拟地址,访问便会导致段错误,原因请看我的另一篇文章:Linux & X86上Segmentation fault原因分析。
3 小结
其实这个BUG完全可以避免,编译时gcc给出了一条很明显的警告:warning: initialization makes pointer from integer without a cast,这条警告已经说明了问题的实质所在——用一个整数值来初始化指针。加上-Wall选项之后,还会一条函数没有声明的警告:warning: implicit declaration of function ‘get_value’,如果当时能看到这两条警告,问题立马就能得到解决。我平时写程序,编译选项都是加上-Wall, -Werror的,这次修改开源程序,偷懒没写函数声明,再加上这个开源程序本身产生的警告实在太多了,导致编译器给出的找不到函数声明的警告淹没在这一大堆警告里,根本没有察觉,最终花了很多时间才查明原因。这个事情给我的教训就是:无论如何都要坚持写函数声明,一定不能忽视警告,一定要从一开始就消灭警告,否则等警告多起来,就很难有意愿去消除警告了。
转自:http://blog.csdn.net/sheniudou/article/details/16824125
以上是关于X86_64上没有写C函数声明导致的BUG(warning: initialization makes pointer from integer without a cast)的主要内容,如果未能解决你的问题,请参考以下文章
x86_64-w64-mingw32-gcc.exe:错误:CreateProcess:没有这样的文件或目录
MATLAB:mex-compile 上缺少框架 – 架构 x86_64 的未定义符号
Bug Could not retrieve mirrorlist http://mirrorlist.centos.org/?release=7&arch=x86_64&repo=o