strcpy在centos6.x,gcc4.4.7版本上会有bug,自我移动导致覆盖错误overlap

Gcc编译时无优化参数,以前曾经被-O坑过。

#include<stdio.h>
#include<string.h>

intmain()
{
charurl[512];
sprintf(url,"218.26.242.56/0/0/1023/6d6168bf1a7294ae0e1c071171adcd48.mp4");
printf("%s\n",url);
char*p=url;

strcpy(p+15,p+22);
printf("%s\n",url);
return0;
}

打印结果应该如下

218.26.242.56/0/0/1023/6d6168bf1a7294ae0e1c071171adcd48.mp4

218.26.242.56/06d6168bf1a7294ae0e1c071171adcd48.mp4

但是在centos6.3系统下,gcc4.4.7

打印结果会是

218.26.242.56/0/0/1023/6d6168bf1a7294ae0e1c071171adcd48.mp4

218.26.242.56/0/f1a7294a1a7294ae0e1c071171adcd48.mp4

目前实验redhat5.05.7centos7.2系统下都不会出现问题,唯有6.x(试了6.0、6.3、6.7)gcc4.4.7会有问题

下载4.4.7源码

wget http://ftp.gnu.org/gnu/gcc/gcc-4.4.7/gcc-4.4.7.tar.bz2

代码如下

externvoidabort(void);
externintinside_main;

char*
strcpy(char*d,constchar*s)
{
char*r=d;
#ifdefined__OPTIMIZE__&&!defined__OPTIMIZE_SIZE__
if(inside_main)
abort();
#endif
while((*d++=*s++));
returnr;
}

理论上不应该出现如此问题

Centos6.xstrcpy源码为汇编码

char*strcpy(char*dest,constchar*src)
{
return__kernel_strcpy(dest,src);
}
staticinlinechar*__kernel_strcpy(char*dest,constchar*src)
{
char*xdest=dest;

asmvolatile("\n"
"1:move.b(%1)+,(%0)+\n"
"jne1b"
:"+a"(dest),"+a"(src)
::"memory");
returnxdest;
}

同样看不出有什么问题。

将系统函数修改为自定义函数,使用一样的代码,结果均为正确。

网络上也找到过另外一种优化版本的strcpy代码,使用寄存器加速效果,在网上找到的号称gcc的优化代码也是类似

char*
strcpy(dest,src)
char*dest;
constchar*src;
{
registercharc;
char*__unboundeds=(char*__unbounded)CHECK_BOUNDS_LOW(src);
constptrdiff_toff=CHECK_BOUNDS_LOW(dest)-s-1;
size_tn;

do
{
c=*s++;
s[off]=c;
}
while(c!='\0');
n=s-src;
(void)CHECK_BOUNDS_HIGH(src+n);
(void)CHECK_BOUNDS_HIGH(dest+n);

returndest;
}


将之作为自定义函数使用后发现也没有问题。


继续发现strncpy和sprintf也会遇到同样的问题。

采用memcpy就没有问题了

memcpy(p+11,p+18,strlen(p+18)+1);


看了下源码,跟strcpy也没什么区别

void*
memcpy(void*dest,constvoid*src,size_tlen)
{
char*d=dest;
constchar*s=src;
while(len--)
*d++=*s++;
returndest;
}


暂时不明白为什么strcpy、strncpy、sprintf在gcc4.4.7下,自我移动会导致问题。

以前曾经在网上看见过strcpy的优化函数,在64位系统里,采用八字节长整形来进行复制,但是未在库中见过,只是作为优化的自定义代码推荐。

在这里例子中,如果我们将p+15改成p+16,就一切正常,把字符串总长度缩小到p+32(即*(p+32)=0),那么也一切正常。错误字段长度8字节,跟8都有关系,怀疑系统在什么地方做了优化,但是实在搞不清是谁在优化,优化后的代码是什么样子的。


所以建议如果要进行字符串自我移动,不要使用str,使用mem函数。


--------------------

同事提供了一个帖子,说的是内存重叠的问题

http://www.jb51.cc/article/p-smdmucpb-xt.html


但是这个例子的作者其实没有分析到点子上

charstr[]="123456789";
strcpy(str+2,str);

本身在代码逻辑上就是错的,从源码就能看出来,从前往后复制,会导致后面的内存覆盖。和我们这次遇到的不是一个情况。


按照源码应该结果是1212121212121212。。。。。。。。。。。一直到越界崩溃

但是实际结果是121234565678


在几个机器上试了下

在gcc4.1.1上,是12121212121。。。。。。 崩溃

Gcc4.4.7 显示121234565678

gcc4.8.5 显示12123456789


应该是在4.4.7上确实有优化,但是4.8.5应该是解决了,而且连这种逻辑异常的也一起支持了。

在网上找到了gcc4.7上strcpy的汇编bug,为什么是不是也有关系。

-----------------


网上查了一下,发现被误导了,这里应该研究libc.so的代码,而不是看gcc的代码


看了下libc的源码,define了不少实现,在不同设备上使用了不同优化的汇编码,应该是使用8字节复制代替单个字符串复制,在libc2.12有bug,libc2.17上应该是解决了。


查看他们的strcpy代码

libc2.12.1上

/*CopySRCtoDEST.*/
char*
strcpy(dest,src)
char*dest;
constchar*src;
{
reg_charc;
char*__unboundeds=(char*__unbounded)CHECK_BOUNDS_LOW(src);
constptrdiff_toff=CHECK_BOUNDS_LOW(dest)-s-1;
size_tn;
do
{
c=*s++;
s[off]=c;
}
while(c!='\0');
n=s-src;
(void)CHECK_BOUNDS_HIGH(src+n);
(void)CHECK_BOUNDS_HIGH(dest+n);
returndest;
}


libc2.17上

/*CopySRCtoDEST.*/
char*
strcpy(dest,src)
char*dest;
constchar*src;
{
charc;
char*__unboundeds=(char*__unbounded)CHECK_BOUNDS_LOW(src);
constptrdiff_toff=CHECK_BOUNDS_LOW(dest)-s-1;
size_tn;
do
{
c=*s++;
s[off]=c;
}
while(c!='\0');
n=s-src;
(void)CHECK_BOUNDS_HIGH(src+n);
(void)CHECK_BOUNDS_HIGH(dest+n);
returndest;
}

发现2.17相比2.12没什么改动,就是取消了寄存器(reg_char变成了char),在这个博客里面说过寄存器的重要性,可以提高copy速度,不明白为什么2.17取消了寄存器。

http://blog.csdn.net/astrotycoon/article/details/8114786


继续测试

发现在libc2.12上

(gdb)bt
#00x00000036a7532664in__strcpy_ssse3()from/lib64/libc.so.6
#10x0000000000400671inmain()attest.c:32

真正使用的是ssse3指令集下的strcpy.S实现


在glib2.17下

(gdb)bt
#0__strcpy_sse2_unaligned()at../sysdeps/x86_64/multiarch/strcpy-sse2-unaligned.S:232
#10x0000000000400655inmain()attest.c:9

直接进入.s汇编码,连libc.so都没有进入,不过这个汇编函数strcpy-sse2-unaligned也是glib里面的


对汇编实在无力,完全无从下手。

不过看来所谓的c源码对分析没有什么太大的帮助还容易引起误解,因为底层的库根本就不用c程序的源码啊。

相关文章

linux下开机自启: 在/etc/init.d目录下新建文件elasticsear...
1、因为在centos7中/etc/rc.d/rc.local的权限被降低了,所以...
最简单的查看方法可以使用ls -ll、ls-lh命令进行查看,当使用...
ASP.NET Core应用程序发布linux在shell中运行是正常的。可一...
设置时区(CentOS 7) 先执行命令timedatectl status|grep &...
vim&#160;/etc/sysconfig/network-scripts/ifcfg-eth0 B...