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程序的源碼啊。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章