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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了strcpy在centos6.x,gcc4.4.7版本上会有bug,自我移动导致覆盖错误overlap相关的知识,希望对你有一定的参考价值。

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

 

#include <stdio.h>
#include <string.h>
 
int main()
{
       char url[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);
       return 0;
}

打印结果应该如下

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

 

代码如下

extern void abort (void);
extern int inside_main;
 
char *
strcpy (char *d, const char *s)
{
  char *r = d;
#if defined __OPTIMIZE__ &&!defined __OPTIMIZE_SIZE__
  if (inside_main)
    abort ();
#endif
  while ((*d++ = *s++));
  return r;
}

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

Centos6.xstrcpy源码为汇编码

char *strcpy(char *dest, const char *src)
{
        return __kernel_strcpy(dest, src);
}
static inline char *__kernel_strcpy(char*dest, const char *src)
{
        char *xdest = dest;
 
        asm volatile ("\n"
                  "1:    move.b     (%1)+,(%0)+\n"
                  "       jne    1b"
                  : "+a" (dest), "+a" (src)
                  : : "memory");
        return xdest;
}

同样看不出有什么问题。

 

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

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

char *  
strcpy (dest, src)  
     char *dest;  
     const char *src;  
{  
  register char c;  
  char *__unbounded s = (char *__unbounded) CHECK_BOUNDS_LOW (src);  
  const ptrdiff_t off = CHECK_BOUNDS_LOW (dest) - s - 1;  
  size_t n;  
  
  do  
    {  
      c = *s++;  
      s[off] = c;  
    }  
  while (c != ‘\0‘);    
  n = s - src;  
  (void) CHECK_BOUNDS_HIGH (src + n);  
  (void) CHECK_BOUNDS_HIGH (dest + n);  
  
  return dest;  
}


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


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

采用memcpy就没有问题了

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


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

void *
memcpy (void *dest, const void *src, size_t len)
{
  char *d = dest;
  const char *s = src;
  while (len--)
    *d++ = *s++;
  return dest;
}


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

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

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


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


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

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

http://blog.csdn.net/stpeace/article/details/39456645


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

char str []="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上

/* Copy SRC to DEST.  */
char *
strcpy (dest, src)
     char *dest;
     const char *src;
{
  reg_char c;
  char *__unbounded s = (char *__unbounded) CHECK_BOUNDS_LOW (src);
  const ptrdiff_t off = CHECK_BOUNDS_LOW (dest) - s - 1;
  size_t n;
  do
    {
      c = *s++;
      s[off] = c;
    }
  while (c != ‘\0‘);
  n = s - src;
  (void) CHECK_BOUNDS_HIGH (src + n);
  (void) CHECK_BOUNDS_HIGH (dest + n);
  return dest;
}


libc2.17上

/* Copy SRC to DEST.  */
char *
strcpy (dest, src)
     char *dest;
     const char *src;
{
  char c;
  char *__unbounded s = (char *__unbounded) CHECK_BOUNDS_LOW (src);
  const ptrdiff_t off = CHECK_BOUNDS_LOW (dest) - s - 1;
  size_t n;
  do
    {
      c = *s++;
      s[off] = c;
    }
  while (c != ‘\0‘);
  n = s - src;
  (void) CHECK_BOUNDS_HIGH (src + n);
  (void) CHECK_BOUNDS_HIGH (dest + n);
  return dest;
}

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

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


继续测试

发现在libc2.12上

(gdb) bt
#0  0x00000036a7532664 in __strcpy_ssse3 () from /lib64/libc.so.6
#1  0x0000000000400671 in main () at test.c:32

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


在glib2.17下

(gdb) bt
#0  __strcpy_sse2_unaligned () at ../sysdeps/x86_64/multiarch/strcpy-sse2-unaligned.S:232
#1  0x0000000000400655 in main () at test.c:9

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


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

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


本文出自 “飞翔正义的博客” 博客,请务必保留此出处http://xzq2000.blog.51cto.com/2487359/1915623

以上是关于strcpy在centos6.x,gcc4.4.7版本上会有bug,自我移动导致覆盖错误overlap的主要内容,如果未能解决你的问题,请参考以下文章

在centOS7.2上编译gcc4.4.7

Centos6.5升级系统自带gcc4.4.7到gcc4.8.0

带有 gcc 4.4.7 的 CentOS 5.8 链接到 libstdc++ 6.0.8。这怎么可能?

ubuntu-15.04-server-i386.iso 安装 Oracle 11gR2 数据库

如何配置 Qt Creator 以在 centos 6.7 上使用 RH 的 devtoolset-2?

为啥 stoi 函数在 Visual Studio 2010 中可用